Citirea intrărilor de la stdin

Am citit intrările utilizatorului de la stdin simplu C. Problema este că vreau o implementare sănătoasă, care să fie robustă la erori și să restricționeze utilizatorul la o anumită intrare și care nu „suge din punct de vedere al complexității. Funcția get_strings() citește caracter caractere atâta timp cât nu există o nouă linie (\n), nu există EOF și toate caracterele trec isalpha() test. Dar vreau să păstrez spații.

Unele puncte care (cred) merită o atenție specială în timpul revizuirii:

    – Mi-ar plăcea să scap de bucla exterioară în timp ce testează practic dacă utilizatorul tocmai a apăsat Enter fără nicio intrare semnificativă.
    – Am nevoie chiar să folosesc ferror ()? fgetc returnează deja EOF când ceva nu a mers bine, dar opriți citirea din flux în loc să spuneți utilizatorului că a mers ceva.
    – Nu se întâmplă de fiecare dată, dar se întâmplă: A \ n rămâne în fluxul stdin și data viitoare când vreau să primesc informații semnificative fgetc () este omis. Nu contează aici, dar este valabil atunci când pun întrebări cu un singur caracter Da / Nu. Nu pot scăpa de această problemă decât cu o construcție care elimină toate lucrurile anterioare rămase în stdin. A se vedea al doilea bloc de cod pentru acest lucru. .

Așadar, mă descurc complet greșit? Există practici mai bune de îmbrățișat? Îmi pare foarte ciudat, iar ciudat este întotdeauna rău.

/** @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; } 

Al doilea CodeBlock cu un caz mai simplu:

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; } } 

Comentarii

  • Rapid notă: preferați să utilizați boolens de la < stdbool.h > sau definește mai degrabă decât 1 și 0. It ‘ e mai clar ce vrei să spui
  • @Zorgatone Sunt pe jumătate de acord cu tine; folosește întotdeauna stdbool.h, dar nu ‘ nu încercați să vă rulați propriile boole.

Răspundeți

Arhitectură

stdin este de obicei linie tamponată . Deci, nimic nu este dat fgetc() până când utilizatorul nu dă clic pe Enter . Codul OP va da mai multe mesaje de eroare cu intrare precum „Bună ziua 123”. Mai bine să separați intrarea utilizatorului de validarea intrărilor. Citiți linia de introducere a utilizatorului cu fgets() sau o versiune proprie, deoarece fgets() are unele puncte slabe. Apoi validați intrarea.

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

Referitor la „Mi-ar plăcea să scap de bucla exterioară while”. Acea buclă există pentru a consuma în tăcere "\n". Dacă doriți ca o buclă să facă asta, tocmai a precedat bucla interioară cu

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

char ch

ch nu este cel mai bun tip. fgetc() returnează de obicei 257 de valori diferite [0-255] și EOF. Pentru a le distinge în mod corespunzător, salvați rezultatul într-un 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") { 

La fel pentru char tmp;

realloc()

Distribuirea nu este necesară.
Modificați pentru ca memoria să fie liberă string – nu este necesară dacă codul va ieși pur și simplu, dar o bună practică pentru a vă pune jucăriile (cod „s pointer) away.

// 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; 

O bună utilizare a sizeof(*strings) de mai jos. Recomandați simplificarea.

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

size_t len

O bună utilizare a size_t pentru a reprezenta o dimensiune a matricei. Curios, codul nu face același lucru cu int index;. Recomanda size_t index;

is...()

Dacă utilizați int ch, nu este nevoie de distribuție. este este un test logic, recomandăm utilizarea ! mai degrabă decât aritmetică == 0.

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

Următoarele pot fi mai ușor de înțeles – mai puține negații. (Problemă de stil)

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

ferror()

Bună verificare a if (ferror(in_stream))

Numele variabilelor

string, strings este la fel de util ca apelarea unui număr întreg integer. Poate că phrase, dictionary în schimb.

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

get_strings() este denumit greșit. Sună general, dar codul restricționează intrarea la litere și spațiu.Poate get_words()?

Comentarii

  • Simt că ai postat același răspuns de două ori? Oricum, acesta este răspunsul pe care l-am căutat! M-am concentrat în totalitate pe utilizarea fgetc sind fgets did ‘ nt a lucrat la început (Din cauza rogue \ ns in stdin). Acest lucru pare mult mai bun și voi încorpora acest lucru în codul meu. Vă mulțumim!
  • @Haini știți că, pe lângă acceptare, puteți susține un răspuns dacă v-a plăcut atât de mult 😉

Răspuns

Strategia de reallocare greșită

În prezent, apelați realloc() pentru fiecare caracter pe care îl citiți. Acest lucru are ca rezultat un timp \ $ O (n ^ 2) \ $ pentru a citi un șir, deoarece de fiecare dată când apelați realloc(), poate fi necesar să copiați conținutul curent în noul tampon . Ar trebui fie să alocați un buffer de dimensiune MAX_DATA și apoi să utilizați realloc pentru a micșora alocarea la sfârșit sau să treceți la o strategie de realocare unde dimensiunea realocării este mărită de fiecare dată cu un factor multiplicativ (cum ar fi 2x).

Acest lucru se aplică și matricei dvs. de șiruri, unde faceți același lucru.

Indentare ciudată

Indentarea dvs. este ciudată, deoarece bucla while imbricată se află la același nivel de indentare ca bucla while exterioară.

Utilizați fgets ()?

Aș folosi personal fgets() (sau o altă funcție de bibliotecă, cum ar fi readline()) pentru a citi un șir. fgets() face destul de mult ceea ce face bucla dvs. fără toată logica codificată manual.

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *