2008-10-02 10 views
8

Pracuję nad książką R K &. Czytałem dalej od moich ćwiczeń, głównie z braku czasu. Nadrabiam zaległości i wykonałem prawie wszystkie ćwiczenia z rozdziału 1, który jest tutorialem.Ćwiczenie K & R: Mój kod działa, ale czuje się śmierdzący; Porady dotyczące czyszczenia?

Moim problemem było ćwiczenie 1-18. Ćwiczenie ma na celu:

Napisz program do usuwania spacjami i kart z linii wejściowych i usunąć całkowicie puste wiersze

Mój kod (poniżej) robi, i działa. Moim problemem jest zastosowana metoda przycinania. Czuje się ... źle ... jakoś. Podobnie jak gdybym zobaczył podobny kod w C# w przeglądzie kodu, prawdopodobnie zwariowałbym. (C# jest jedną z moich specjalności.)

Czy ktoś może zaoferować porady dotyczące czyszczenia tego - z haczykiem, który powiedział, doradztwo musi używać tylko wiedzy z rozdziału 1 K & R. (Wiem, że istnieje milion sposoby czyszczenia tego przy użyciu pełnej biblioteki C, mówimy tylko o rozdziale 1 i podstawowym stdio.h tutaj.) Ponadto, udzielając porady, czy możesz wyjaśnić, dlaczego to pomoże? (Ja, mimo wszystko, starając się uczyć i kto lepiej niż uczyć się od ekspertów tutaj!)

#include <stdio.h> 

#define MAXLINE 1000 

int getline(char line[], int max); 
void trim(char line[], char ret[]); 

int main() 
{ 
    char line[MAXLINE]; 
    char out[MAXLINE]; 
    int length; 

    while ((length = getline(line, MAXLINE)) > 0) 
    { 
     trim(line, out); 
     printf("%s", out); 
    } 

    return 0; 
} 

int getline(char line[], int max) 
{ 
    int c, i; 

    for (i = 0; i < max - 1 && (c = getchar()) != EOF && c != '\n'; ++i) 
     line[i] = c; 

    if (c == '\n') 
    { 
     line[i] = c; 
     ++i; 
    } 

    line[i] = '\0'; 
    return i; 
} 

void trim(char line[], char ret[]) 
{ 
    int i = 0; 

    while ((ret[i] = line[i]) != '\0') 
     ++i; 

    if (i == 1) 
    { 
     // Special case to remove entirely blank line 
     ret[0] = '\0'; 
     return; 
    } 

    for ( ; i >= 0; --i) 
    { 
     if (ret[i] == ' ' || ret[i] == '\t') 
      ret[i] = '\0'; 
     else if (ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n') 
      break; 
    } 

    for (i = 0; i < MAXLINE; ++i) 
    { 
     if (ret[i] == '\n') 
     { 
      break; 
     } 
     else if (ret[i] == '\0') 
     { 
      ret[i] = '\n'; 
      ret[i + 1] = '\0'; 
      break; 
     } 
    } 
} 

EDIT: Doceniam wszystkie przydatne wskazówki widzę tutaj. Chciałbym przypomnieć ludziom, że wciąż jestem n00b z C, a konkretnie jeszcze nie doszedłem do wskaźników. (Pamiętaj, że nieco o Ch.1 z K & R - Ch.1 nie robi wskaźników.) Ja "trochę" dostaję niektóre z tych rozwiązań, ale wciąż są one dotknięte zaawansowaniem dla tego, gdzie jestem ..

I większość tego, czego szukam, to sama metoda przycinania - konkretnie fakt, że przepuszczam przez pętlę razy (co jest tak brudne). Czuję, że gdybym był tylko bardziej sprytny (nawet bez zaawansowanej znajomości C), mogło to być czystsze.

+0

nie można znaleźć żadnego problemu tutaj ... – Ilya

+0

Jest to oczywiście prawie 7 lat późno, więc niekoniecznie dla ciebie, ale raczej dla tych, którzy podążają - w tym przypadku K & R może być interpretowane jako oznaczające linie, które zawierają tylko "" "znaki, gdy mówią" usuń całkowicie puste linie "ponieważ pętla while w funkcji głównej zostanie zakończona, jeśli' getline' zwróci długość zero (tj. linię z dosłownie żadnymi znakami). Odnośnik: kod znajduje się na stronie 29. – chucksmash

Odpowiedz

5

Nie ma powodu, aby mieć dwa bufory, można przyciąć linię wejścia w miejscu

int trim(char line[]) 
{ 
    int len = 0; 
    for (len = 0; line[len] != 0; ++len) 
     ; 

    while (len > 0 && 
      line[len-1] == ' ' && line[len-1] == '\t' && line[len-1] == '\n') 
     line[--len] = 0; 

    return len; 
} 

odsyłając długości linii, można wyeliminować puste linie testując dla niezerowym linii długości

if (trim(line) != 0) 
    printf("%s\n", line); 

EDYCJA: Możesz uczynić pętlę while jeszcze prostszą, przyjmując kodowanie ASCII.

while (len > 0 && line[len-1] <= ' ') 
    line[--len] = 0; 
+0

Oto rodzaj pomysłu, którego szukam ...Ale podłączyłem go i bawiłem się nim trochę, a zamiast przycinać przestrzenie końcowe i puste linie, faktycznie wstawia się dodatkowe puste linie. :) –

+0

To się dzieje, gdy wpisujesz kod, rano, bez sprawdzania go :-) – Ferruccio

+0

Napisałem! = Zamiast == w oryginalnej pętli while. – Ferruccio

9

Jeśli trzymasz się rozdziału 1, wygląda mi to całkiem dobrze. Oto co polecam z punktu widzenia kodu test:

Podczas sprawdzania równości w C, zawsze szuka stałej pierwszy

if (1 == myvar) 

ten sposób nigdy nie przypadkowo zrobić coś takiego:

if (myvar = 1) 

Nie można uciec z tym w C#, ale kompiluje się dobrze w C i może być prawdziwym diabłem do debugowania.

+0

Whoa, wiele pochlebstw ... jeszcze nie wypiłeś dużo kawy, co w tym złego? –

+0

nie ma powodu, by zgodzić się na całkowicie słuszną radę, ja osobiście nie zawracam sobie głowy robieniem lochy, ale w jakikolwiek sposób perfekcyjnie poprawne głosowanie. – Ilya

+1

Chyba niektórzy ludzie nie lubią składni (1 == x). Jestem jednym z nich, ale nie uważam, że warto go zgodzić. – aib

1

Osobiście dla podczas konstruktów:

Wolę następujące:

while((ret[i] = line[i])) 
     i++; 

do:

while ((ret[i] = line[i]) != '\0') 
     ++i; 

Obaj sprawdzić przed = 0 ale pierwsze wygląda trochę odkurzacz. Jeśli znak jest cokolwiek innego niż 0, to ciało pętli wykona, jeśli nie zostanie przerwane z pętli.

także dla „dla” sprawozdania, będąc syntatically ważne, uważam, że:

for ( ; i >= 0; --i) 

właśnie wygląda „dziwnie” do mnie i rzeczywiście jest potencjalnym rozwiązaniem koszmar dla potencjalnych błędów. Gdybym sprawdzał ten kod, wyglądałby jak świecące czerwone ostrzeżenie. Zwykle chcesz używać pętli do iterowania znanej liczby razy, inaczej cosider pętli while. (jak zawsze są wyjątki od reguły, ale stwierdziłem, że to ogólnie jest prawdą). Powyższe oświadczenie może stać się dla:

while (i) 
{ 
     if (ret[i] == ' ' || ret[i] == '\t') 
     { 
      ret[i--] = '\0'; 
     } 
     else if (ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n') 
     { 
      break; 
     } 
} 
+0

Erm, masz na myśli "while (ret [i] = line [i])" .. – aib

+0

Dzięki za porady na temat zamiany pętli for z chwilą skonstruować. –

0

Przede wszystkim:

int main (void)

Wiesz parametry do funkcji main(). Są niczym. (Lub argc & argv, ale nie sądzę, że jest to materiał z rozdziału 1).

Stylewise, możesz spróbować K & nawiasów w stylu R. Są one znacznie łatwiejsze na pionowej przestrzeni:

void trim(char line[], char ret[]) 
{ 
    int i = 0; 

    while ((ret[i] = line[i]) != '\0') 
     ++i; 

    if (i == 1) { // Special case to remove entirely blank line 
     ret[0] = '\0'; 
     return; 
    } 

    for (; i>=0; --i) { //continue backwards from the end of the line 
     if ((ret[i] == ' ') || (ret[i] == '\t')) //remove trailing whitespace 
      ret[i] = '\0'; 

     else if ((ret[i] != '\0') && (ret[i] != '\r') && (ret[i] != '\n')) //...until we hit a word character 
      break; 
    } 

    for (i=0; i<MAXLINE-1; ++i) { //-1 because we might need to add a character to the line 
     if (ret[i] == '\n') //break on newline 
      break; 

     if (ret[i] == '\0') { //line doesn't have a \n -- add it 
      ret[i] = '\n'; 
      ret[i+1] = '\0'; 
      break; 
     } 
    } 
} 

(Dodano również uwagi i stałej jeden bug).

Dużym problemem jest korzystanie z MaXLine stałej - main() używa go wyłącznie dla zmienne i; trim(), który działa tylko na nich, nie musi używać stałej. Powinieneś przekazać rozmiar (y) jako parametr, podobnie jak w getline().

1

Przycinanie() jest zbyt duże.

To, czego potrzebuję, to funkcja strlen-owska (napisz ją i wpisz jej długość łańcucha znaków (const char * s)).

Następnie potrzebna jest funkcja o nazwie int scanback (const char * s, const char * matches, int start), która rozpoczyna się przy starcie, przechodzi do z, o ile skanowana jest postać o identyfikatorze s zawartym w zapałkach, return ostatni indeks, w którym znaleziono dopasowanie.

Następnie potrzebna jest funkcja o nazwie int scanfront (const char * s, const char * matches), która zaczyna się od 0 i skanuje do przodu, o ile znak skanowany wg jest zawarty w dopasowaniach, zwracając ostatni indeks, w którym dopasowanie zostało znalezione.

Następnie potrzebujesz funkcji o nazwie int charinstring (char c, const char * s), która zwraca wartość niezerową, jeśli c jest zawarty w s, 0 w przeciwnym razie.

Powinieneś być w stanie napisać wykończenia w zakresie tych.

0

Oto moje ukłucie w ćwiczeniu, nie wiedząc, co jest w rozdziale 1 lub K & R. Zakładam wskaźniki?

#include "stdio.h" 

size_t StrLen(const char* s) 
{ 
    // this will crash if you pass NULL 
    size_t l = 0; 
    const char* p = s; 
    while(*p) 
    { 
     l++; 
     ++p; 
    } 
    return l; 
} 

const char* Trim(char* s) 
{ 
    size_t l = StrLen(s); 
    if(l < 1) 
     return 0; 

    char* end = s + l -1; 
    while(s < end && (*end == ' ' || *end == '\t')) 
    { 
     *end = 0; 
     --end; 
    } 

    return s; 
} 

int Getline(char* out, size_t max) 
{ 
    size_t l = 0; 
    char c; 
    while(c = getchar()) 
    { 
     ++l; 

     if(c == EOF) return 0; 
     if(c == '\n') break; 

     if(l < max-1) 
     { 
      out[l-1] = c; 
      out[l] = 0; 
     } 
    } 

    return l; 
} 

#define MAXLINE 1024 

int main (int argc, char * const argv[]) 
{ 
    char line[MAXLINE]; 
    while (Getline(line, MAXLINE) > 0) 
    { 
     const char* trimmed = Trim(line); 
     if(trimmed) 
      printf("|%s|\n", trimmed); 

     line[0] = 0; 
    } 

    return 0; 
} 
+0

uh, to wygląda niebezpiecznie. Co się stanie, jeśli ktoś zadzwoni Trim (""); Będziesz czytał pamięć znajdującą się poza ciągiem znaków. I przy odrobinie szczęścia napiszecie też o tej pamięci. – quinmars

+0

Mogą występować błędy w tym kodzie. Nie testowałem tego bardzo dokładnie. Masz rację. Warunek pętli while w Trim() powinien również testować, że koniec jest większy niż s. Zakładając, że w adresach pamięci dorastają. – orj

0

osobiście by umieścić kodu:

ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n' 

w oddzieleniu (lub nawet zdefiniować makro)

0
  1. wykończenia powinien rzeczywiście stosować tylko buforze 1 (a @ Ferruccio mówi).
  2. wykończenia musi być podzielone, jak @plinth mówi
  3. trymowania nie wymaga zwraca żadnej wartości (jeśli chcesz sprawdzić na pusty ciąg, linia testowa [0] == 0)
  4. dla dodatkowego smaku C, użyć wskaźników zamiast indeksy

-go do końca linii (kończące 0; -Podczas nie na początku linii i aktualny charakter jest przestrzeń, wymień go na 0. -back off jeden char

char *findEndOfString(char *string) { 
    while (*string) ++string; 
    return string; // string is now pointing to the terminating 0 
} 

void trim(char *line) { 
    char *end = findEndOfString(line); 
    // note that we start at the first real character, not at terminating 0 
    for (end = end-1; end >= line; end--) { 
     if (isWhitespace(*end)) *end = 0; 
     else return; 
    } 
} 
0

Kolejny przykład robienia tego samego. Zrobiłem drobne naruszenie przy użyciu specyficznych dla C99. że nie będzie można znaleźć w K & R. stosowany również assert() funkcji, która jest częścią starndard biblioteki, ale prawdopodobnie nie jest ujęte w jednym rozdziale K & R.

#include <stdbool.h> /* needed when using bool, false and true. C99 specific. */ 
#include <assert.h> /* needed for calling assert() */ 

typedef enum { 
    TAB = '\t', 
    BLANK = ' ' 
} WhiteSpace_e; 

typedef enum { 
    ENDOFLINE = '\n', 
    ENDOFSTRING = '\0' 
} EndofLine_e; 

bool isWhiteSpace(
    char character 
) { 
    if ((BLANK == character) || (TAB == character)) { 
    return true; 
    } else { 
    return false; 
    } 
} 

bool isEndOfLine( 
    char character 
) { 
if ((ENDOFLINE == character) || (ENDOFSTRING == character)) { 
    return true; 
    } else { 
    return false; 
    } 
} 

/* remove blanks and tabs (i.e. whitespace) from line-string */ 
void removeWhiteSpace(
    char string[] 
) { 
    int i; 
    int indexOutput; 

    /* copy all non-whitespace character in sequential order from the first to the last. 
    whitespace characters are not copied */ 
    i = 0; 
    indexOutput = 0; 
    while (false == isEndOfLine(string[i])) { 
    if (false == isWhiteSpace(string[i])) { 
     assert (indexOutput <= i); 
     string[ indexOutput ] = string[ i ]; 
     indexOutput++; 
    } 
    i++; /* proceed to next character in the input string */ 
    } 

    assert(isEndOfLine(string[ i ])); 
    string[ indexOutput ] = ENDOFSTRING; 

} 
Powiązane problemy