Czytanie danych wejściowych ze standardowego wejścia

Czytam dane wejściowe użytkownika z stdin w zwykłym C. Problem polega na tym, że chcę rozsądnej implementacji, która jest odporna na błędy i ogranicza użytkownika do określonych danych wejściowych i nie jest do niczego pod względem złożoności. Funkcja get_strings() odczytuje znak wejściowy przez znak, o ile nie ma nowej linii (\n), nie ma EOF i wszystkie znaki przechodzą przez isalpha(). Ale chcę zachować spacje.

Niektóre punkty, które (moim zdaniem) zasługują na szczególną uwagę podczas przeglądu:

    – Chciałbym pozbyć się zewnętrzna pętla while, która w zasadzie testuje, czy użytkownik właśnie nacisnął Enter bez żadnego znaczącego wejścia.
    – Czy muszę w ogóle używać ferror ()? fgetc zwraca już EOF, gdy coś poszło nie tak, ale po prostu przestań czytać ze strumienia, zamiast mówić użytkownikowi, że coś poszło nie tak.
    – Nie dzieje się to za każdym razem, ale zdarza się: A \ n pozostaje w strumieniu stdin i następnym razem chcę uzyskać znaczące dane wejściowe fgetc () po prostu zostaje pominięty. Nie ma to znaczenia tutaj, ale ma znaczenie, gdy zadaję jedno znakowe pytania Tak / Nie. Mogę pozbyć się tego problemu tylko za pomocą konstrukcji, która usuwa wszystkie poprzednie rzeczy, które pozostają w stdin. Zobacz drugi blok kodu. .

Czy podchodzę do tego całkowicie źle? Czy są lepsze praktyki do przyjęcia? Wydaje mi się to naprawdę niezgrabne, a niezgrabne jest zawsze złe.

/** @brief Contains the dictionary */ static char **strings = NULL; /** @brief Helps with containing the dicionary */ static char *string; /* Reads input char by char with fgetc() */ static char *get_strings() { char *string = NULL; char ch; size_t len = 0; while (string == NULL && ch != EOF) { while (EOF != (ch = fgetc(in_stream)) && ch != "\n") { if (ch != " " && isalpha((int)ch) == 0) { fprintf(stderr, "Only [a-z] is a valid input. | \t" "| Input another or end with CTRL+D: "); continue; } string = (char*) realloc(string, len+2); if (string == NULL) { bail_out(EXIT_FAILURE, "realloc(3) failed"); } string[len++] = toupper(ch); if (len >= MAX_DATA) { bail_out(EXIT_FAILURE, "Input too long\n"); } } if (ferror(in_stream)) { bail_out(EXIT_FAILURE, "Error while reading from stream"); } } if(string) { string[len] = "\0"; } else { printf("\nFinished dictionary...\n"); } printf("Added string: %s | Input another or end with CTRL+D: ", string); return string; } /* Saves the returned strings from get_strings() in a linked list */ static void read_dict() { int index; for (index = 0; (string = get_strings()); ++index) { if (string[0] == "\0") continue; strings = (char**) realloc(strings, (index+1)*sizeof(*strings)); if (strings == NULL) { bail_out(EXIT_FAILURE, "realloc(3) failed"); } strings[index] = string; } /* Take a note of how many entries we have yet. */ dict_size = index; } 

Drugi CodeBlock z prostszym przypadkiem:

while(1) { char tmp; printf("Please enter your guess [a-z]: "); guess = fgetc(stdin); /* Jump back to start of loop */ if (guess == "\n") { continue; } /* HERE IS THE CLEAR FOR STDIN This part really just eats all remaining \ns from the user, so that later inputs can start uninterrupted. Can I get rid of it in some better way? */ while((tmp = getchar()) != "\n" && tmp != EOF); if(!isalpha(guess)) { fprintf(stderr, "Enter a valid letter [a-z]!\n"); continue; } } 

Komentarze

  • Szybkie uwaga: wolisz używać boolens z < stdbool.h > lub zamiast 1 i 0. To ' jest bardziej jasne, co masz na myśli
  • @Zorgatone W połowie się z tobą zgadzam; zawsze używaj stdbool.h, ale nie ' nie próbuj tworzyć własnych wartości bools.

Odpowiedź

Architektura

stdin jest zwykle buforowane po linii . Więc nic nie jest przekazywane do fgetc(), dopóki użytkownik nie naciśnie Enter . Kod OP wyświetli wiele komunikatów o błędach z danymi wejściowymi typu „Hello 123”. Lepiej oddzielić dane wejściowe użytkownika od sprawdzania poprawności danych wejściowych. Przeczytaj wiersz wprowadzania danych użytkownika za pomocą fgets() lub jakiejś własnej wersji, ponieważ fgets() ma kilka słabych stron. Następnie sprawdź poprawność danych wejściowych.

char *input; while ((input = my_gets()) != NULL) { if (valid_phrase(input)) { foo(input); } else { fprintf(stderr, "Invalid input\n"); } free(input); } 

Jeśli chodzi o „Chciałbym pozbyć się zewnętrznej pętli while”. Ta pętla istnieje po to, aby dyskretnie konsumować "\n". Jeśli chcesz, aby zrobiła to pętla, po prostu poprzedz pętlę wewnętrzną

int ch; while ((ch = fgetc()) == "\n") ; ungetc(ch, stdin); 

char ch

ch nie jest najlepszym typem. fgetc() zwraca zazwyczaj 257 różnych wartości [0-255] i EOF. Aby je odpowiednio rozróżnić, zapisz wynik w int.

// bad char ch; .. while (string == NULL && ch != EOF) { while (EOF != (ch = fgetc(in_stream)) && ch != "\n") { // better int ch; .. while (string == NULL && ch != EOF) { while (EOF != (ch = fgetc(in_stream)) && ch != "\n") { 

To samo dotyczy char tmp;

realloc()

Przesyłanie nie jest potrzebne.
Modyfikuj pod kątem braku pamięci, aby zwolnić string – niepotrzebne, jeśli kod po prostu się kończy, ale dobra praktyka przy umieszczaniu zabawek (kod „s pointer).

// string = (char*) realloc(string, len+2); char * new_string = realloc(string, len+2); if (new_string == NULL) { free(string); bail_out(EXIT_FAILURE, "Out of memory"); } string = new_string; 

Dobre wykorzystanie sizeof(*strings) poniżej. Zalecane uproszczenie.

strings = (char**) realloc(strings, (index+1)*sizeof(*strings)); strings = realloc(strings, sizeof *strings * (index+1)); 

size_t len

Dobre użycie size_t do przedstawienia rozmiaru tablicy. Co ciekawe, kod nie działa tak samo z int index;. Poleć size_t index;

is...()

Dzięki zastosowaniu int ch nie ma potrzeby przesyłania. Ponieważ th to test logiczny, zalecamy użycie ! zamiast arytmetycznego == 0.

// if (ch != " " && isalpha((int)ch) == 0) { if (ch != " " && !isalpha(ch)) { 

Poniższe mogą być łatwiejsze do zrozumienia – mniej negacji. (Problem ze stylem)

if (!(ch == " " || isalpha(ch))) { 

ferror()

Niezłe sprawdzenie if (ferror(in_stream))

Nazwy zmiennych

string, strings jest tak samo przydatne, jak wywołanie liczby całkowitej integer. Może zamiast tego phrase, dictionary.

// OK /** @brief Contains the dictionary */ static char **strings = NULL; // Better // comment not truly needed static char **dictionary = NULL; 

get_strings() ma błędną nazwę. Brzmi ogólnie, ale kod ogranicza wprowadzanie do liter i spacji.Może get_words()?

Komentarze

  • Mam wrażenie, że podałeś tę samą odpowiedź dwa razy? Tak czy inaczej, to jest odpowiedź, której szukałem! Byłem całkowicie skoncentrowany na używaniu fgetc sind fgets ' nt działało na początku (z powodu nieuczciwego \ ns w stdin). Wydaje się to o wiele lepsze i uwzględnię to w swoim kodzie. Dzięki!
  • @Haini wiesz, że oprócz akceptacji, możesz również zagłosować za odpowiedź, jeśli tak bardzo Ci się spodobała 😉

Odpowiedź

Zła strategia realloc

Obecnie wywołujesz realloc() po każdym przeczytanym znaku. Powoduje to \ $ O (n ^ 2) \ $ czas na odczytanie ciągu, ponieważ za każdym razem, gdy wywołujesz realloc(), może być konieczne skopiowanie bieżącej zawartości do nowego bufora . Powinieneś albo po prostu przydzielić bufor o rozmiarze MAX_DATA, a następnie użyć realloc, aby zmniejszyć przydział na końcu, lub zmienić strategię realokacji gdzie rozmiar realokacji jest zwiększany za każdym razem o mnożnik (na przykład 2x).

Dotyczy to również tablicy ciągów, w której robisz to samo.

Dziwne wcięcie

Wcięcie jest dziwne, ponieważ zagnieżdżona pętla while znajduje się na tym samym poziomie wcięcia, co zewnętrzna pętla while.

Używać fgets ()?

Osobiście użyłbym fgets() (lub innej funkcji bibliotecznej, takiej jak readline()), aby odczytać ciąg. fgets() robi prawie to samo, co twoja pętla bez całej logiki zakodowanej ręcznie.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *