Citirea intrărilor din stdin (Revizuirea codului, C, Flux)

Haini a intrebat.

Citesc intrările utilizatorului din stdin în simplu C. Problema este că vreau o implementare sănătoasă care să fie robustă la erori și să restricționeze utilizatorul la un anumit input și să nu fie nașpa din punct de vedere al complexității. Funcția get_strings() citește intrarea char cu char atâta timp cât nu există o linie nouă (
), nu EOF și toate caracterele trec prin isalpha() test. Dar vreau să păstrez spațiile.

Câteva puncte care (cred că) merită o atenție specială în timpul revizuirii:

    – Mi-ar plăcea să scap de bucla while exterioară care, practic, testează dacă utilizatorul tocmai a apăsat Enter fără nicio introducere semnificativă.
    – Chiar trebuie să folosesc ferror()? fgetc returnează deja EOF atunci când ceva nu a mers bine, dar eu voi opri doar citirea din flux în loc să îi spun utilizatorului că ceva nu a mers bine.
    – Nu se întâmplă de fiecare dată, dar se întâmplă: Un
    rămâne în fluxul stdin, iar data viitoare când vreau să obțin o intrare semnificativă, fgetc() este pur și simplu sărit. Nu contează aici, dar contează atunci când pun întrebări de tip Da/Nu cu un singur caracter. Nu pot scăpa de această problemă decât cu o construcție care șterge toate lucrurile anterioare care rămân în stdin. Pentru aceasta, consultați al doilea bloc de cod.

Așadar, mă ocup în mod total greșit de această problemă? Există practici mai bune pe care să le îmbrățișez? Mie mi se pare foarte greșit, iar greșit 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 != '
') {
        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
");
        }
    }
    if (ferror(in_stream)) {
        bail_out(EXIT_FAILURE, "Error while reading from stream");
    }
    }

    if(string) {
        string[len] = '';
    } else {
        printf("
Finished dictionary...
");
    }
    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] == '') 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 == '
') {
        continue;
    }

    /* HERE IS THE CLEAR FOR STDIN
       This part really just eats all remaining 
s from the user,
       so that later inputs can start uninterrupted. Can I get rid of
       it in some better way? */
    while((tmp = getchar()) != '
' && tmp != EOF);

    if(!isalpha(guess)) {
        fprintf(stderr, "Enter a valid letter [a-z]!
");
        continue;
    }
}

Comentarii

  • Notă rapidă: preferați să folosiți boolens din <stdbool.h> sau definiții în loc de 1 și 0. Este mai clar ce vreți să spuneți…  > Por Zorgatone.
  • @Zorgatone Sunt pe jumătate de acord cu tine; folosiți întotdeauna stdbool.h, dar nu încercați să vă creați propriile bool-uri.  > Por SirPython.
2 răspunsuri
chux – Reinstaurați-o pe Monica

Arhitectură

stdin este, de obicei, cu buffer de linie. Deci nu se dă nimic la fgetc() până când utilizatorul apasă Enter. Codul OP va afișa mai multe mesaje de eroare în cazul unei intrări de tipul „Hello 123”. Este mai bine să separați intrarea utilizatorului de validarea intrării. Citiți linia de intrare a utilizatorului cu fgets() sau o versiune proprie ca 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
");
  }
  free(input);
}

În ceea ce privește „Mi-ar plăcea să scap de bucla while exterioară”. Această buclă există pentru a consuma în tăcere '
'
. Dacă doriți o buclă care să facă acest lucru, este suficient să precedați bucla interioară cu

int ch;
while ((ch = fgetc()) == '
')
  ;
ungetc(ch, stdin);

char ch

La ch nu este cel mai bun tip. fgetc() returnează de obicei 257 valori diferite [0-255] și EOF. Pentru a le distinge în mod corespunzător, salvați rezultatul într-un fișier int.

// bad
char ch;
..
while (string == NULL && ch != EOF) {    
while (EOF != (ch = fgetc(in_stream)) && ch != '
') {

// better
int ch;
..
while (string == NULL && ch != EOF) {
  while (EOF != (ch = fgetc(in_stream)) && ch != '
') {

Același lucru pentru char tmp;

realloc()

Cast nu este necesar.
Modificați pentru out of memory pentru a elibera string – nu este necesar dacă codul va ieși pur și simplu din memorie, dar este o bună practică pentru a vă pune jucăriile (pointerul codului) deoparte.

// 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 lui sizeof(*strings) de mai jos. Se recomandă simplificarea.

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

size_t len

Bună utilizare a size_t pentru a reprezenta dimensiunea unui array. În mod curios, codul nu face același lucru cu int index;. Recomandare size_t index;

is...()

Cu utilizarea int ch, nu este nevoie de cast. Deoarece acesta este un test logic, se recomandă utilizarea ! mai degrabă decât aritmetica == 0.

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

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

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

ferror()

Verificare plăcută a if (ferror(in_stream))

Numele variabilelor

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

// OK
/** @brief Contains the dictionary */
static char **strings = NULL;

// Better
// comment not truly needed
static char **dictionary = NULL;

get_strings() are un nume greșit. Sună general, dar codul limitează introducerea la litere și spațiu. Poate get_words()?

Comentarii

  • Am impresia că ai postat același răspuns de două ori? Oricum, acesta este răspunsul pe care îl căutam! Eram total concentrat pe utilizarea fgetc sind fgets did’nt work in the start (Din cauza
    s rogue în stdin). Acest lucru pare mult mai bun și îl voi încorpora în codul meu. Mulțumesc!  > Por Haini.
  • @Haini știi că pe lângă accept, poți să dai și upvote unui răspuns dacă ți-a plăcut atât de mult ;-)-.  > Por janos.
JS1

Strategie de realocare proastă

În prezent, apelați realloc() la fiecare caracter pe care îl citești. Acest lucru duce la un timp $O(n^2)$ de citire a unui șir de caractere, deoarece de fiecare dată când apelați realloc(), este posibil să fie nevoie să copiați conținutul curent în noul buffer. Ar trebui fie să alocați doar un buffer de dimensiune MAX_DATA și apoi să utilizați realloc pentru a micșora alocarea la sfârșit, fie să treceți la o strategie de realocare în care dimensiunea realocării este mărită de fiecare dată cu un factor multiplicativ (cum ar fi 2x).

Acest lucru este valabil și pentru matricea de șiruri de caractere, unde faceți același lucru.

Indentare ciudată

Indentarea dvs. este ciudată, deoarece fișierele imbricate while este la același nivel de indentare ca și bucla exterioară while bucla exterioară.

Folosiți fgets()?

Personal, aș folosi fgets() (sau o altă funcție de bibliotecă, cum ar fi readline()) pentru a citi un șir de caractere. fgets() face cam ceea ce face bucla dvs. fără toată logica codată manual.

Tags:,