Come leggere una riga dalla console in C?


108

Qual è il modo più semplice per leggere una riga intera in un programma della console C Il testo inserito potrebbe avere una lunghezza variabile e non possiamo fare alcuna supposizione sul suo contenuto.


potresti chiarire, per favore? come ha detto @Tim di seguito, è confuso quello che stai chiedendo :)
warren

Risposte:


81

È necessaria la gestione dinamica della memoria e utilizzare la fgetsfunzione per leggere la riga. Tuttavia, sembra che non ci sia modo di vedere quanti caratteri legge. Quindi usi fgetc:

char * getline(void) {
    char * line = malloc(100), * linep = line;
    size_t lenmax = 100, len = lenmax;
    int c;

    if(line == NULL)
        return NULL;

    for(;;) {
        c = fgetc(stdin);
        if(c == EOF)
            break;

        if(--len == 0) {
            len = lenmax;
            char * linen = realloc(linep, lenmax *= 2);

            if(linen == NULL) {
                free(linep);
                return NULL;
            }
            line = linen + (line - linep);
            linep = linen;
        }

        if((*line++ = c) == '\n')
            break;
    }
    *line = '\0';
    return linep;
}

Nota : non usare mai gets! Non esegue il controllo dei limiti e può sovraccaricare il buffer


Avvertenza: è necessario controllare il risultato della riallocazione lì. Ma se fallisce, molto probabilmente ci sono problemi peggiori.
Tim

4
Probabilmente potresti migliorare un po 'l'efficienza eseguendo fgets con il buffer e controllando se hai il carattere di nuova riga alla fine. Se non lo fai, rialloca il tuo buffer di accumulazione, copialo e fgets di nuovo.
Paul Tomblin,

3
Questa funzione necessita di una correzione: la riga "len = lenmax;" dopo il realloc dovrebbe precedere il realloc o dovrebbe essere "len = lenmax >> 1;" - o qualche altro equivalente che tenga conto del fatto che metà della lunghezza è già utilizzata.
Matt Gallagher

1
@Johannes, in risposta alla tua domanda, l'approccio di @ Paul POTREBBE essere più veloce sulla maggior parte delle implementazioni libc (cioè rientranti), poiché il tuo approccio blocca implicitamente lo stdin per ogni carattere mentre il suo lo blocca una volta per buffer. È possibile utilizzare il meno portabile fgetc_unlockedse la sicurezza dei thread non è un problema ma le prestazioni lo sono.
vladr

3
Notare che questo getline()è diverso dalla getline()funzione standard POSIX .
Jonathan Leffler

28

Se stai usando la libreria GNU C o un'altra libreria conforme a POSIX, puoi usarla getline()e passarla stdinper il flusso di file.


16

Un'implementazione molto semplice ma non sicura per leggere la riga per l'allocazione statica:

char line[1024];

scanf("%[^\n]", line);

Un'implementazione più sicura, senza possibilità di buffer overflow, ma con la possibilità di non leggere l'intera riga, è:

char line[1024];

scanf("%1023[^\n]", line);

Non la "differenza di uno" tra la lunghezza specificata che dichiara la variabile e la lunghezza specificata nella stringa di formato. È un manufatto storico.


14
Questo non è affatto sicuro. Soffre esattamente dello stesso problema perché è getsstato rimosso dallo standard del tutto
Antti Haapala

6
Nota del moderatore: il commento sopra si riferisce a una precedente revisione della risposta.
Robert Harvey

13

Quindi, se stavi cercando argomenti di comando, dai un'occhiata alla risposta di Tim. Se vuoi solo leggere una riga dalla console:

#include <stdio.h>

int main()
{
  char string [256];
  printf ("Insert your full address: ");
  gets (string);
  printf ("Your address is: %s\n",string);
  return 0;
}

Sì, non è sicuro, puoi eseguire il sovraccarico del buffer, non controlla la fine del file, non supporta le codifiche e molte altre cose. In realtà non ho nemmeno pensato se facesse NESSUNA di queste cose. Sono d'accordo, ho un po 'sbagliato :) Ma ... quando vedo una domanda come "Come leggere una riga dalla console in C?", Presumo che una persona abbia bisogno di qualcosa di semplice, come gets () e non 100 righe di codice come sopra. In realtà, penso che se provassi a scrivere quelle 100 righe di codice nella realtà, faresti molti più errori di quelli che avresti fatto se avessi scelto di ottenere;)


1
Ciò non consente lunghe stringhe ... - che penso sia il punto cruciale della sua domanda.
Tim

2
-1, gets () non dovrebbe essere usato poiché non esegue il controllo dei limiti.
rilassarsi il

7
D'altra parte, se stai scrivendo un programma per te stesso e hai solo bisogno di leggere un input, va benissimo. La sicurezza di cui un programma ha bisogno è parte delle specifiche: non DEVI metterla come priorità ogni volta.
Martin Beckett,

4
@Tim - Voglio mantenere tutta la cronologia :)
Paul Kapustin

4
Downvoted. getsnon esiste più, quindi questo non funziona in C11.
Antti Haapala

11

Potrebbe essere necessario utilizzare un ciclo carattere per carattere (getc ()) per assicurarsi di non avere overflow del buffer e non troncare l'input.


9

getline esempio eseguibile

Menzionato in questa risposta, ma ecco un esempio.

È POSIX 7 , alloca la memoria per noi e riutilizza bene il buffer allocato su un ciclo.

Puntatore newbs, leggi questo: Perché il primo argomento di getline è un puntatore al puntatore "char **" invece di "char *"?

#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *line = NULL;
    size_t len = 0;
    ssize_t read = 0;
    while (read != -1) {
        puts("enter a line");
        read = getline(&line, &len, stdin);
        printf("line = %s", line);
        printf("line length = %zu\n", read);
        puts("");
    }
    free(line);
    return 0;
}

implementazione glibc

Nessun POSIX? Forse vuoi dare un'occhiata all'implementazione di glibc 2.23 .

Risolve a getdelim, che è un semplice superset POSIX getlinecon un terminatore di riga arbitrario.

Raddoppia la memoria allocata ogni volta che è necessario un aumento e sembra thread-safe.

Richiede un po 'di espansione macro, ma è improbabile che tu faccia molto meglio.


Qual è lo scopo lenqui, quando la lettura fornisce anche la lunghezza
Abdul

@Abdul vedi man getline. lenè la lunghezza del buffer esistente, 0è magico e gli dice di allocare. Read è il numero di caratteri letti. La dimensione del buffer potrebbe essere maggiore di read.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功


5

Come suggerito, puoi usare getchar () per leggere dalla console fino a quando non viene restituito un end-of-line o un EOF, costruendo il tuo buffer. Può verificarsi un aumento dinamico del buffer se non si è in grado di impostare una dimensione di riga massima ragionevole.

Puoi anche usare fgets come un modo sicuro per ottenere una riga come stringa C con terminazione null:

#include <stdio.h>

char line[1024];  /* Generously large value for most situations */

char *eof;

line[0] = '\0'; /* Ensure empty line if no input delivered */
line[sizeof(line)-1] = ~'\0';  /* Ensure no false-null at end of buffer */

eof = fgets(line, sizeof(line), stdin);

Se hai esaurito l'input della console o se l'operazione non è riuscita per qualche motivo, viene restituito eof == NULL e il buffer di riga potrebbe essere invariato (motivo per cui è utile impostare il primo carattere su "\ 0").

fgets non riempirà la riga [] e assicurerà che ci sia un null dopo l'ultimo carattere accettato in caso di ritorno riuscito.

Se è stata raggiunta la fine della riga, il carattere che precede la terminazione "\ 0" sarà un "\ n".

Se non ci sono "\ n" finali prima della fine "\ 0", è possibile che siano presenti più dati o che la richiesta successiva riporterà la fine del file. Dovrai fare un altro fgets per determinare quale è quale. (A questo proposito, il looping con getchar () è più semplice.)

Nel codice di esempio (aggiornato) sopra, se la riga [sizeof (line) -1] == '\ 0' dopo fgets riusciti, sai che il buffer è stato riempito completamente. Se quella posizione è preceduta da un "\ n" sai di essere stato fortunato. Altrimenti, ci sono più dati o una fine del file più avanti in stdin. (Quando il buffer non è riempito completamente, potresti essere ancora alla fine del file e potrebbe anche non esserci un '\ n' alla fine della riga corrente. Dato che devi scansionare la stringa per trovare e / o eliminare qualsiasi "\ n" prima della fine della stringa (il primo "\ 0" nel buffer), sono propenso a preferire utilizzare getchar () in primo luogo.)

Fai quello che devi fare per gestire il fatto che ci sono ancora più righe rispetto all'importo letto come primo blocco. Gli esempi di crescita dinamica di un buffer possono essere fatti funzionare con getchar o fgets. Ci sono alcuni casi limite difficili a cui prestare attenzione (come ricordare che l'input successivo inizi a memorizzare nella posizione di "\ 0" che ha terminato l'input precedente prima che il buffer fosse esteso).


2

Come leggere una riga dalla console in C?

  • Costruire la tua funzione è uno dei modi che ti aiuterebbe a leggere una riga dalla console

  • Sto usando l'allocazione dinamica della memoria per allocare la quantità di memoria richiesta

  • Quando stiamo per esaurire la memoria allocata, proviamo a raddoppiare la dimensione della memoria

  • E qui sto usando un ciclo per scansionare ogni carattere della stringa uno per uno usando la getchar()funzione finché l'utente non inserisce '\n'o EOFcarattere

  • infine rimuoviamo ogni ulteriore memoria allocata prima di restituire la riga

//the function to read lines of variable length

char* scan_line(char *line)
{
    int ch;             // as getchar() returns `int`
    long capacity = 0;  // capacity of the buffer
    long length = 0;    // maintains the length of the string
    char *temp = NULL;  // use additional pointer to perform allocations in order to avoid memory leaks

    while ( ((ch = getchar()) != '\n') && (ch != EOF) )
    {
        if((length + 1) >= capacity)
        {
            // resetting capacity
            if (capacity == 0)
                capacity = 2; // some initial fixed length 
            else
                capacity *= 2; // double the size

            // try reallocating the memory
            if( (temp = realloc(line, capacity * sizeof(char))) == NULL ) //allocating memory
            {
                printf("ERROR: unsuccessful allocation");
                // return line; or you can exit
                exit(1);
            }

            line = temp;
        }

        line[length] = (char) ch; //type casting `int` to `char`
    }
    line[length + 1] = '\0'; //inserting null character at the end

    // remove additionally allocated memory
    if( (temp = realloc(line, (length + 1) * sizeof(char))) == NULL )
    {
        printf("ERROR: unsuccessful allocation");
        // return line; or you can exit
        exit(1);
    }

    line = temp;
    return line;
}
  • Ora potresti leggere un'intera riga in questo modo:

    char *line = NULL;
    line = scan_line(line);

Ecco un esempio di programma che utilizza la scan_line()funzione:

#include <stdio.h>
#include <stdlib.h> //for dynamic allocation functions

char* scan_line(char *line)
{
    ..........
}

int main(void)
{
    char *a = NULL;

    a = scan_line(a); //function call to scan the line

    printf("%s\n",a); //printing the scanned line

    free(a); //don't forget to free the malloc'd pointer
}

input di esempio:

Twinkle Twinkle little star.... in the sky!

output di esempio:

Twinkle Twinkle little star.... in the sky!

0

Mi sono imbattuto nello stesso problema qualche tempo fa, questa era la mia soluzione, spero che aiuti.

/*
 * Initial size of the read buffer
 */
#define DEFAULT_BUFFER 1024

/*
 * Standard boolean type definition
 */
typedef enum{ false = 0, true = 1 }bool;

/*
 * Flags errors in pointer returning functions
 */
bool has_err = false;

/*
 * Reads the next line of text from file and returns it.
 * The line must be free()d afterwards.
 *
 * This function will segfault on binary data.
 */
char *readLine(FILE *file){
    char *buffer   = NULL;
    char *tmp_buf  = NULL;
    bool line_read = false;
    int  iteration = 0;
    int  offset    = 0;

    if(file == NULL){
        fprintf(stderr, "readLine: NULL file pointer passed!\n");
        has_err = true;

        return NULL;
    }

    while(!line_read){
        if((tmp_buf = malloc(DEFAULT_BUFFER)) == NULL){
            fprintf(stderr, "readLine: Unable to allocate temporary buffer!\n");
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        if(fgets(tmp_buf, DEFAULT_BUFFER, file) == NULL){
            free(tmp_buf);

            break;
        }

        if(tmp_buf[strlen(tmp_buf) - 1] == '\n') /* we have an end of line */
            line_read = true;

        offset = DEFAULT_BUFFER * (iteration + 1);

        if((buffer = realloc(buffer, offset)) == NULL){
            fprintf(stderr, "readLine: Unable to reallocate buffer!\n");
            free(tmp_buf);
            has_err = true;

            return NULL;
        }

        offset = DEFAULT_BUFFER * iteration - iteration;

        if(memcpy(buffer + offset, tmp_buf, DEFAULT_BUFFER) == NULL){
            fprintf(stderr, "readLine: Cannot copy to buffer\n");
            free(tmp_buf);
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        free(tmp_buf);
        iteration++;
    }

    return buffer;
}

1
Il tuo codice diventerebbe MOLTO più semplice se lo usassi gotoper gestire il caso di errore. Tuttavia, non pensi di poterlo riutilizzare tmp_buf, invece di mallocinserirlo ripetutamente con la stessa dimensione?
Shahbaz

L'utilizzo di una singola variabile globale has_errper segnalare gli errori rende questa funzione non sicura per i thread e meno che comoda da usare. Non farlo in questo modo. Hai già indicato un errore restituendo NULL. C'è anche spazio per pensare che i messaggi di errore stampati non siano una buona idea in una funzione di libreria generica.
Jonathan Leffler

0

Su sistemi BSD e Android puoi anche utilizzare fgetln:

#include <stdio.h>

char *
fgetln(FILE *stream, size_t *len);

Così:

size_t line_len;
const char *line = fgetln(stdin, &line_len);

Il linenon è terminato da null e contiene \n(o qualunque cosa stia utilizzando la tua piattaforma) alla fine. Diventa non valido dopo la successiva operazione di I / O sullo stream.


Sì, la funzione esiste. L'avvertenza che non fornisce una stringa con terminazione null è sufficientemente grande e problematica che probabilmente è meglio non usarla: è pericoloso.
Jonathan Leffler

0

Qualcosa come questo:

unsigned int getConsoleInput(char **pStrBfr) //pass in pointer to char pointer, returns size of buffer
{
    char * strbfr;
    int c;
    unsigned int i;
    i = 0;
    strbfr = (char*)malloc(sizeof(char));
    if(strbfr==NULL) goto error;
    while( (c = getchar()) != '\n' && c != EOF )
    {
        strbfr[i] = (char)c;
        i++;
        strbfr = (void*)realloc((void*)strbfr,sizeof(char)*(i+1));
        //on realloc error, NULL is returned but original buffer is unchanged
        //NOTE: the buffer WILL NOT be NULL terminated since last
        //chracter came from console
        if(strbfr==NULL) goto error;
    }
    strbfr[i] = '\0';
    *pStrBfr = strbfr; //successfully returns pointer to NULL terminated buffer
    return i + 1; 
    error:
    *pStrBfr = strbfr;
    return i + 1;
}

0

Il modo migliore e più semplice per leggere una riga da una console è usare la funzione getchar (), per cui memorizzerai un carattere alla volta in un array.

{
char message[N];        /* character array for the message, you can always change the character length */
int i = 0;          /* loop counter */

printf( "Enter a message: " );
message[i] = getchar();    /* get the first character */
while( message[i] != '\n' ){
    message[++i] = getchar(); /* gets the next character */
}

printf( "Entered message is:" );
for( i = 0; i < N; i++ )
    printf( "%c", message[i] );

return ( 0 );

}


-3

Questa funzione dovrebbe fare quello che vuoi:

char* readLine( FILE* file )
 {
 char buffer[1024];
 char* result = 0;
 int length = 0;

 while( !feof(file) )
  {
  fgets( buffer, sizeof(buffer), file );
  int len = strlen(buffer);
  buffer[len] = 0;

  length += len;
  char* tmp = (char*)malloc(length+1);
  tmp[0] = 0;

  if( result )
   {
   strcpy( tmp, result );
   free( result );
   result = tmp;
   }

  strcat( result, buffer );

  if( strstr( buffer, "\n" ) break;
  }

 return result;
 }

char* line = readLine( stdin );
/* Use it */
free( line );

Spero che aiuti.


1
Si dovrebbe fare fgets( buffer, sizeof(buffer), file );non sizeof(buffer)-1. fgetslascia spazio per il null finale.
user102008

Nota che while (!feof(file))è sempre sbagliato e questo è solo un altro esempio di uso errato.
Jonathan Leffler
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.