Jak odczytać wiersz z konsoli w C?

108

Jaki jest najprostszy sposób odczytania pełnego wiersza w programie konsoli C Wprowadzony tekst może mieć zmienną długość i nie możemy nic założyć co do jego zawartości.

pbreault
źródło
czy mógłbyś wyjaśnić, proszę? jak @Tim powiedział poniżej, to mylące, o co prosisz :)
warren

Odpowiedzi:

81

Potrzebujesz dynamicznego zarządzania pamięcią i użyj tej fgetsfunkcji do odczytania linii. Jednak wydaje się, że nie ma sposobu, aby sprawdzić, ile znaków przeczytał. Więc używasz 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;
}

Uwaga : nigdy nie używaj pobiera! Nie sprawdza granic i może przepełnić twój bufor

Johannes Schaub - litb
źródło
Uwaga - należy tam sprawdzić wynik ponownego przydzielenia. Ale jeśli to się nie powiedzie, najprawdopodobniej są gorsze problemy.
Tim
4
Prawdopodobnie mógłbyś nieco poprawić wydajność, wykonując fgets z buforem i sprawdzając, czy na końcu masz znak nowej linii. Jeśli tego nie zrobisz, przydziel ponownie bufor akumulacji, skopiuj do niego i ponownie wykonaj fgets.
Paul Tomblin
3
Ta funkcja wymaga korekty: wiersz "len = lenmax;" po ponownym przydzieleniu powinno albo poprzedzać ponowne przydzielenie, albo powinno być „len = lenmax >> 1;” - lub inny odpowiednik, który uwzględnia fakt, że połowa długości jest już wykorzystana.
Matt Gallagher
1
@Johannes, w odpowiedzi na twoje pytanie, podejście @ Paula MOŻE być szybsze w większości (tj. Ponownie wprowadzanych) implementacjach libc, ponieważ twoje podejście niejawnie blokuje stdin dla każdego znaku, podczas gdy jego blokuje je raz na bufor. Możesz użyć mniej przenośnego, fgetc_unlockedjeśli bezpieczeństwo wątków nie jest problemem, ale wydajność.
vladr
3
Zauważ, że getline()różni się to od standardowej getline()funkcji POSIX .
Jonathan Leffler,
28

Jeśli używasz biblioteki GNU C lub innej biblioteki zgodnej z POSIX, możesz użyć getline()i przekazać stdindo niej jako strumień plików.

dmityugov
źródło
16

Bardzo prosta, ale niebezpieczna implementacja do odczytu wiersza dla statycznej alokacji:

char line[1024];

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

Bezpieczniejszą implementacją, bez możliwości przepełnienia bufora, ale z możliwością nie odczytania całej linii, jest:

char line[1024];

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

Nie „różnica o jeden” między długością określoną w deklaracji zmiennej a długością określoną w ciągu formatu. To historyczny artefakt.

Jonathan Leffler
źródło
14
To wcale nie jest bezpieczne. Cierpi na dokładnie ten sam problem, dlaczego getszostał całkowicie usunięty ze standardu
Antti Haapala
6
Uwaga moderatora: powyższy komentarz odnosi się do poprzedniej wersji odpowiedzi.
Robert Harvey
13

Tak więc, jeśli szukałeś argumentów poleceń, spójrz na odpowiedź Tima. Jeśli chcesz tylko przeczytać wiersz z konsoli:

#include <stdio.h>

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

Tak, to nie jest bezpieczne, można zrobić przepełnienie bufora, nie sprawdza końca pliku, nie obsługuje kodowania i wielu innych rzeczy. Właściwie nawet nie pomyślałem, czy to zrobiło JAKIEKOLWIEK z tych rzeczy. Zgadzam się, trochę schrzaniłem :) Ale ... kiedy widzę pytanie typu "Jak odczytać wiersz z konsoli w C?", Zakładam, że dana osoba potrzebuje czegoś prostego, na przykład gets (), a nie 100 linii kodu jak wyżej. Właściwie myślę, że gdybyś spróbował napisać te 100 linijek kodu w rzeczywistości, zrobiłbyś o wiele więcej błędów, niż gdybyś wybrał opcję dostaje;)

Paul Kapustin
źródło
1
To nie pozwala na długie struny ... - co moim zdaniem jest sednem jego pytania.
Tim
2
-1, nie należy używać metody gets (), ponieważ nie sprawdza ona granic.
zrelaksuj się
7
Z drugiej strony, jeśli piszesz program dla siebie i potrzebujesz tylko przeczytać dane wejściowe, jest to w porządku. To, ile bezpieczeństwa potrzebuje program, zależy od specyfikacji - NIE MUSISZ stawiać tego jako priorytetu za każdym razem.
Martin Beckett
4
@Tim - Chcę zachować całą historię :)
Paul Kapustin
4
Głosowano w dół. getsjuż nie istnieje, więc to nie działa w C11.
Antti Haapala
11

Może być konieczne użycie pętli znak po znaku (getc ()), aby upewnić się, że nie ma przepełnienia bufora i nie obcinamy danych wejściowych.

Tim
źródło
9

getline przykład do uruchomienia

Wspomniano o tej odpowiedzi, ale oto przykład.

Jest zgodny z POSIX 7 , alokuje nam pamięć i ładnie wykorzystuje przydzielony bufor w pętli.

Pointer newbs, przeczytaj to: Dlaczego pierwszy argument getline jest wskaźnikiem do wskaźnika „char **” zamiast „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;
}

implementacja glibc

Brak POSIX? Może chcesz przyjrzeć się implementacji glibc 2.23 .

Rozwiązuje się to getdelim, co jest prostym zestawem POSIX getlinez dowolnym terminatorem linii.

Podwaja przydzieloną pamięć, gdy jest potrzebne zwiększenie, i wygląda na bezpieczną wątkowo.

Wymaga rozszerzenia makr, ale jest mało prawdopodobne, abyś zrobił coś lepszego.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
Jaki jest cel lentutaj, kiedy czytamy, podajemy również długość
Abdul
@Abdul zobacz man getline. lenjest długością istniejącego bufora, 0jest magiczne i mówi mu, aby go przydzielił. Odczyt to liczba odczytanych znaków. Rozmiar bufora może być większy niż read.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
6

Wiele osób, tak jak ja, przychodzi do tego posta z tytułem pasującym do tego, czego szukamy, chociaż opis mówi o zmiennej długości. W większości przypadków długość znamy wcześniej.

Jeśli znasz długość przed ręką, spróbuj poniżej:

char str1[1001] = { 0 };
fgets(str1, 1001, stdin); // 1000 chars may be read

źródło: https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm

Manohar Reddy Poreddy
źródło
5

Jak sugerowano, możesz użyć getchar () do czytania z konsoli aż do zwrócenia końca linii lub EOF, budując swój własny bufor. Dynamiczne powiększanie bufora może nastąpić, jeśli nie możesz ustawić rozsądnego maksymalnego rozmiaru linii.

Możesz również użyć fgets jako bezpiecznego sposobu na uzyskanie wiersza jako łańcucha zakończonego znakiem null w języku C:

#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);

Jeśli wyczerpałeś dane wejściowe konsoli lub operacja się nie powiodła z jakiegoś powodu, zwracany jest eof == NULL i bufor linii może pozostać niezmieniony (dlatego ustawienie pierwszego znaku na „\ 0” jest przydatne).

fgets nie przepełni linii [] i zapewni, że po ostatnim zaakceptowanym znaku po pomyślnym powrocie będzie istniał znak null.

Jeśli osiągnięto koniec linii, znak poprzedzający kończący „\ 0” będzie „\ n”.

Jeśli nie ma końca „\ n” przed zakończeniem „\ 0”, może to oznaczać, że jest więcej danych lub że następne żądanie zgłosi koniec pliku. Będziesz musiał zrobić kolejne fgets, aby określić, który jest który. (Pod tym względem pętle z getchar () są łatwiejsze.)

W (zaktualizowanym) przykładowym kodzie powyżej, jeśli linia [sizeof (linia) -1] == '\ 0' po udanych fgets, wiesz, że bufor został całkowicie zapełniony. Jeśli ta pozycja jest poprzedzona znakiem „\ n”, wiesz, że miałeś szczęście. W przeciwnym razie w stdin jest albo więcej danych, albo koniec pliku z przodu. (Gdy bufor nie jest całkowicie wypełniony, nadal możesz znajdować się na końcu pliku, a na końcu bieżącej linii może również nie być znaku „\ n”. Ponieważ musisz przeskanować ciąg, aby znaleźć i / lub wyeliminować jakiekolwiek „\ n” przed końcem ciągu (pierwsze „\ 0” w buforze), jestem skłonny w pierwszej kolejności używać getchar ().)

Zrób to, co musisz zrobić, aby poradzić sobie z tym, że nadal jest więcej linii niż ta, którą czytasz jako pierwszy fragment. Przykłady dynamicznie rosnącego bufora można wykorzystać do pracy z getchar lub fgets. Istnieje kilka trudnych przypadków brzegowych, na które należy uważać (np. Pamiętanie, aby następne wejście zaczęło zapisywać w pozycji '\ 0', która kończyła poprzednie wejście przed rozszerzeniem bufora).

orcmid
źródło
2

Jak odczytać wiersz z konsoli w C?

  • Budowanie własnej funkcji jest jednym ze sposobów, które pomogłyby w czytaniu wiersza z konsoli

  • Używam dynamicznej alokacji pamięci, aby przydzielić wymaganą ilość pamięci

  • Kiedy zbliżamy się do wyczerpania przydzielonej pamięci, staramy się podwoić jej rozmiar

  • I tutaj używam pętli do skanowania każdego znaku ciągu jeden po drugim, używając getchar()funkcji, aż użytkownik wprowadzi znak '\n'lubEOF

  • na koniec usuwamy wszelką dodatkowo przydzieloną pamięć przed zwróceniem wiersza

//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;
}
  • Teraz możesz przeczytać całą linię w ten sposób:

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

Oto przykładowy program korzystający z scan_line()funkcji:

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

przykładowe dane wejściowe:

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

przykładowe wyjście:

Twinkle Twinkle little star.... in the sky!
Cherubini
źródło
0

Z tym samym problemem natknąłem się jakiś czas temu, to było moje rozwiązanie, mam nadzieję, że pomoże.

/*
 * 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;
}
dsm
źródło
1
Twój kod stałby się DUŻO prostszy, jeśli użyjesz go gotodo obsługi przypadku błędu. Niemniej jednak, czy nie sądzisz, że mógłbyś użyć go ponownie tmp_buf, zamiast używać mallocgo w kółko w tym samym rozmiarze?
Shahbaz,
Użycie jednej zmiennej globalnej has_errdo zgłaszania błędów sprawia, że ​​ta funkcja jest niebezpieczna dla wątków i mniej niż wygodna w użyciu. Nie rób tego w ten sposób. Już wskazałeś błąd, zwracając NULL. Można również pomyśleć, że drukowane komunikaty o błędach nie są dobrym pomysłem w przypadku funkcji biblioteki ogólnego przeznaczenia.
Jonathan Leffler
0

W systemach BSD i Android możesz również użyć fgetln:

#include <stdio.h>

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

Tak jak to:

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

To linenie jest zerowe i zawiera \n(lub cokolwiek używa twoja platforma) na końcu. Staje się nieważny po następnej operacji we / wy w strumieniu.

cud. mice
źródło
Tak, funkcja istnieje. Zastrzeżenie, że nie dostarcza łańcucha zakończonego znakiem null, jest wystarczająco duże i problematyczne, że prawdopodobnie lepiej go nie używać - jest niebezpieczne.
Jonathan Leffler
0

Coś takiego:

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

źródło
0

Najlepszym i najprostszym sposobem odczytania wiersza z konsoli jest użycie funkcji getchar (), dzięki której będziesz przechowywać po jednym znaku w tablicy.

{
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 );

}

Aaron nevalinz
źródło
-3

Ta funkcja powinna robić to, co chcesz:

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

Mam nadzieję, że to pomoże.

David Allan Finch
źródło
1
Należy to zrobić fgets( buffer, sizeof(buffer), file );nie sizeof(buffer)-1. fgetspozostawia miejsce na kończącą wartość null.
user102008
Zauważ, że while (!feof(file))jest to zawsze błędne i jest to jeszcze jeden przykład błędnego użycia.
Jonathan Leffler