Bagaimana cara membaca baris dari konsol di C?


108

Apa cara termudah untuk membaca baris lengkap dalam program konsol C Teks yang dimasukkan mungkin memiliki panjang variabel dan kami tidak dapat membuat asumsi apa pun tentang isinya.


bisakah Anda menjelaskan, tolong? seperti yang dikatakan @Tim di bawah, itu membingungkan apa yang Anda minta :)
warren

Jawaban:


81

Anda membutuhkan manajemen memori dinamis, dan menggunakan fgetsfungsi tersebut untuk membaca baris Anda. Namun, sepertinya tidak ada cara untuk melihat berapa banyak karakter yang dibaca. Jadi Anda menggunakan 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;
}

Catatan : Jangan pernah gunakan mendapat! Itu tidak melakukan pemeriksaan batas dan dapat meluap buffer Anda


Peringatan - perlu memeriksa hasil realoc di sana. Tapi jika gagal, kemungkinan besar ada masalah yang lebih buruk.
Tim

4
Anda mungkin dapat meningkatkan efisiensi sedikit dengan melakukan fgets dengan buffer, dan memeriksa apakah Anda memiliki karakter baris baru di akhir. Jika tidak, alokasikan ulang buffer akumulasi Anda, salin ke dalamnya, dan buat widget lagi.
Paul Tomblin

3
Fungsi ini membutuhkan koreksi: baris "len = lenmax;" setelah realoc harus mendahului realoc atau harus "len = lenmax >> 1;" - atau padanan lain yang menjelaskan fakta bahwa setengah panjangnya sudah digunakan.
Matt Gallagher

1
@Johannes, sebagai jawaban atas pertanyaan Anda, pendekatan @ Paul BISA lebih cepat pada sebagian besar (yaitu reentrant) implementasi libc, karena pendekatan Anda secara implisit mengunci stdin untuk setiap karakter sedangkan dia menguncinya sekali per buffer. Anda dapat menggunakan perangkat yang kurang portabel fgetc_unlockedjika keamanan benang bukan masalah, tetapi kinerjanya lebih baik.
vladr

3
Perhatikan bahwa ini getline()berbeda dari getline()fungsi standar POSIX .
Jonathan Leffler

28

Jika Anda menggunakan pustaka GNU C atau pustaka lain yang mendukung POSIX, Anda dapat menggunakan getline()dan meneruskannya stdinuntuk aliran file.


16

Penerapan yang sangat sederhana namun tidak aman untuk membaca baris untuk alokasi statis:

char line[1024];

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

Implementasi yang lebih aman, tanpa kemungkinan buffer overflow, tetapi dengan kemungkinan tidak membaca keseluruhan baris, adalah:

char line[1024];

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

Bukan 'perbedaan satu' antara panjang yang ditentukan yang menyatakan variabel dan panjang yang ditentukan dalam format string. Itu adalah artefak sejarah.


14
Ini sama sekali tidak aman. Itu menderita masalah yang persis sama mengapa getsdihapus dari standar sama sekali
Antti Haapala

6
Catatan moderator: Komentar di atas mengacu pada revisi jawaban sebelumnya.
Robert Harvey

13

Jadi, jika Anda mencari argumen perintah, lihat jawaban Tim. Jika Anda hanya ingin membaca baris dari konsol:

#include <stdio.h>

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

Ya, ini tidak aman, Anda dapat melakukan buffer overrun, tidak memeriksa akhir file, tidak mendukung pengkodean dan banyak hal lainnya. Sebenarnya saya bahkan tidak berpikir apakah itu melakukan hal-hal ini. Saya setuju saya agak kacau :) Tapi ... ketika saya melihat pertanyaan seperti "Bagaimana membaca baris dari konsol di C?", Saya berasumsi seseorang membutuhkan sesuatu yang sederhana, seperti gets () dan bukan 100 baris kode seperti di atas. Sebenarnya, saya pikir, jika Anda mencoba menulis 100 baris kode itu dalam kenyataan, Anda akan melakukan lebih banyak kesalahan, daripada yang akan Anda lakukan jika Anda memilih mendapatkan;)


1
Ini tidak memungkinkan untuk string panjang ... - yang menurut saya inti dari pertanyaannya.
Tim

2
-1, gets () tidak boleh digunakan karena tidak melakukan pemeriksaan batas.
bersantai

7
Di sisi lain, jika Anda menulis program untuk diri sendiri dan hanya perlu membaca masukan, ini baik-baik saja. Seberapa besar keamanan yang dibutuhkan sebuah program adalah setara dengan spesifikasi - Anda tidak HARUS menempatkannya sebagai prioritas setiap saat.
Martin Beckett

4
@ Tim - Saya ingin menyimpan semua sejarah :)
Paul Kapustin

4
Suara negatif. getstidak ada lagi, jadi ini tidak berfungsi di C11.
Antti Haapala

11

Anda mungkin perlu menggunakan perulangan karakter demi karakter (getc ()) untuk memastikan Anda tidak memiliki buffer overflows dan tidak memotong input.


9

getline contoh yang dapat dijalankan

Disebutkan pada jawaban ini tetapi berikut adalah contohnya.

Ini adalah POSIX 7 , mengalokasikan memori untuk kita, dan menggunakan kembali buffer yang dialokasikan pada sebuah loop dengan baik.

Pointer newbs, baca ini: Mengapa argumen pertama getline adalah pointer ke pointer "char **" bukan "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;
}

implementasi glibc

Tidak ada POSIX? Mungkin Anda ingin melihat implementasi glibc 2.23 .

Ini memutuskan getdelim, yang merupakan superset POSIX sederhana getlinedengan terminator baris arbitrer.

Ini menggandakan memori yang dialokasikan setiap kali peningkatan diperlukan, dan terlihat aman untuk thread.

Ini membutuhkan beberapa ekspansi makro, tetapi Anda tidak mungkin melakukannya dengan lebih baik.


Apa tujuan lendi sini, ketika membaca memberikan panjangnya juga
Abdul

@Abdul lihat man getline. lenadalah panjang buffer yang ada, 0merupakan keajaiban dan memerintahkannya untuk mengalokasikan. Baca adalah jumlah karakter yang dibaca. Ukuran buffer bisa lebih besar dari read.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功


5

Seperti yang disarankan, Anda bisa menggunakan getchar () untuk membaca dari konsol sampai akhir baris atau EOF dikembalikan, membangun buffer Anda sendiri. Buffer yang berkembang secara dinamis dapat terjadi jika Anda tidak dapat menetapkan ukuran baris maksimum yang wajar.

Anda juga dapat menggunakan fgets sebagai cara yang aman untuk mendapatkan baris sebagai string C yang diakhiri dengan 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);

Jika Anda telah kehabisan input konsol atau jika operasi gagal karena beberapa alasan, eof == NULL dikembalikan dan buffer baris mungkin tidak berubah (itulah mengapa pengaturan karakter pertama ke '\ 0' berguna).

fgets tidak akan memenuhi baris [] dan ini akan memastikan bahwa ada null setelah karakter yang diterima terakhir pada pengembalian yang berhasil.

Jika akhir baris tercapai, karakter sebelum penghentian '\ 0' akan menjadi '\ n'.

Jika tidak ada penghentian '\ n' sebelum akhiran '\ 0' mungkin ada lebih banyak data atau permintaan berikutnya akan melaporkan akhir file. Anda harus membuat widget lain untuk menentukan yang mana. (Dalam hal ini, melakukan perulangan dengan getchar () lebih mudah.)

Dalam kode contoh (diperbarui) di atas, jika baris [sizeof (line) -1] == '\ 0' setelah berhasil membuat widget, Anda tahu bahwa buffer telah terisi penuh. Jika posisi itu dilanjutkan dengan '\ n' Anda tahu Anda beruntung. Jika tidak, ada lebih banyak data atau akhir file di depan di stdin. (Ketika buffer tidak terisi sepenuhnya, Anda mungkin masih berada di akhir file dan mungkin juga tidak ada '\ n' di akhir baris saat ini. Karena Anda harus memindai string untuk menemukan dan / atau menghilangkan '\ n' sebelum akhir string ('\ 0' pertama di buffer), saya cenderung lebih suka menggunakan getchar () di tempat pertama.)

Lakukan apa yang perlu Anda lakukan untuk menghadapi masih ada lebih banyak baris daripada jumlah yang Anda baca sebagai bagian pertama. Contoh buffer yang tumbuh secara dinamis dapat dibuat untuk bekerja dengan getchar atau fgets. Ada beberapa kasus tepi yang rumit yang harus diperhatikan (seperti mengingat agar masukan berikutnya mulai disimpan pada posisi '\ 0' yang mengakhiri masukan sebelumnya sebelum buffer diperpanjang).


2

Bagaimana cara membaca baris dari konsol di C?

  • Membangun fungsi Anda sendiri, adalah salah satu cara yang akan membantu Anda mencapai pembacaan baris dari konsol

  • Saya menggunakan alokasi memori dinamis untuk mengalokasikan jumlah memori yang dibutuhkan

  • Saat kami akan menghabiskan memori yang dialokasikan, kami mencoba menggandakan ukuran memori

  • Dan di sini saya menggunakan loop untuk memindai setiap karakter dari string satu per satu menggunakan getchar()fungsi hingga pengguna memasukkan '\n'atau EOFkarakter

  • akhirnya kami menghapus memori tambahan yang dialokasikan sebelum mengembalikan baris

//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;
}
  • Sekarang Anda bisa membaca baris lengkapnya seperti ini:

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

Berikut contoh program yang menggunakan scan_line()fungsi tersebut:

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

masukan sampel:

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

keluaran sampel:

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

0

Saya menemukan masalah yang sama beberapa waktu lalu, ini adalah solusi saya, semoga membantu.

/*
 * 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
Kode Anda akan menjadi JAUH lebih sederhana jika Anda gunakan gotountuk menangani kasus kesalahan. Namun demikian, tidakkah Anda pikir Anda bisa menggunakan kembali tmp_buf, alih-alih mallocmenggunakannya dengan ukuran yang sama berulang kali dalam satu lingkaran?
Shahbaz

Menggunakan satu variabel global has_erruntuk melaporkan kesalahan membuat fungsi ini tidak aman untuk thread dan kurang nyaman digunakan. Jangan lakukan seperti itu. Anda sudah menunjukkan kesalahan dengan mengembalikan NULL. Ada juga ruang untuk berpikir bahwa pesan kesalahan yang dicetak bukanlah ide yang baik dalam fungsi perpustakaan serba guna.
Jonathan Leffler

0

Di sistem BSD dan Android Anda juga dapat menggunakan fgetln:

#include <stdio.h>

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

Seperti:

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

The linetidak nol dihentikan dan berisi \n(atau apa pun platform Anda menggunakan) pada akhirnya. Ini menjadi tidak valid setelah operasi I / O berikutnya on stream.


Ya, fungsinya ada. Peringatan bahwa itu tidak menyediakan string yang diakhiri null cukup besar dan bermasalah sehingga mungkin lebih baik untuk tidak menggunakannya - ini berbahaya.
Jonathan Leffler

0

Sesuatu seperti ini:

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

Cara terbaik dan termudah untuk membaca baris dari konsol adalah menggunakan fungsi getchar (), di mana Anda akan menyimpan satu karakter pada satu waktu dalam sebuah 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

Fungsi ini harus melakukan apa yang Anda inginkan:

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

Saya harap ini membantu.


1
Seharusnya fgets( buffer, sizeof(buffer), file );tidak sizeof(buffer)-1. fgetsmeninggalkan ruang untuk mengakhiri null.
user102008

Perhatikan bahwa while (!feof(file))itu selalu salah dan ini hanyalah satu lagi contoh penggunaan yang salah.
Jonathan Leffler
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.