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.
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.
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);return0;}
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;)
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.
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 郝海东 冠状 病 六四 事件 法轮功
6
Banyak orang, seperti saya, datang ke posting ini dengan judul yang sesuai dengan yang dicari, meskipun uraiannya mengatakan tentang variabel panjang. Untuk kebanyakan kasus, kami mengetahui panjangnya terlebih dahulu.
Jika Anda tahu panjang sebelumnya, coba di bawah ini:
char str1[1001]={0};
fgets(str1,1001, stdin);// 1000 chars may be read
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).
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 lengthchar* scan_line(char*line){int ch;// as getchar() returns `int`long capacity =0;// capacity of the bufferlong length =0;// maintains the length of the stringchar*temp = NULL;// use additional pointer to perform allocations in order to avoid memory leakswhile(((ch = getchar())!='\n')&&(ch != EOF)){if((length +1)>= capacity){// resetting capacityif(capacity ==0)
capacity =2;// some initial fixed length else
capacity *=2;// double the size// try reallocating the memoryif((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 memoryif((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 functionschar* 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}
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
*/typedefenum{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;}
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:
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:
unsignedint getConsoleInput(char**pStrBfr)//pass in pointer to char pointer, returns size of buffer{char* strbfr;int c;unsignedint 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 consoleif(strbfr==NULL)goto error;}
strbfr[i]='\0';*pStrBfr = strbfr;//successfully returns pointer to NULL terminated bufferreturn i +1;
error:*pStrBfr = strbfr;return i +1;}
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);
Jawaban:
Anda membutuhkan manajemen memori dinamis, dan menggunakan
fgets
fungsi tersebut untuk membaca baris Anda. Namun, sepertinya tidak ada cara untuk melihat berapa banyak karakter yang dibaca. Jadi Anda menggunakan fgetc:Catatan : Jangan pernah gunakan mendapat! Itu tidak melakukan pemeriksaan batas dan dapat meluap buffer Anda
sumber
fgetc_unlocked
jika keamanan benang bukan masalah, tetapi kinerjanya lebih baik.getline()
berbeda darigetline()
fungsi standar POSIX .Jika Anda menggunakan pustaka GNU C atau pustaka lain yang mendukung POSIX, Anda dapat menggunakan
getline()
dan meneruskannyastdin
untuk aliran file.sumber
Penerapan yang sangat sederhana namun tidak aman untuk membaca baris untuk alokasi statis:
Implementasi yang lebih aman, tanpa kemungkinan buffer overflow, tetapi dengan kemungkinan tidak membaca keseluruhan baris, adalah:
Bukan 'perbedaan satu' antara panjang yang ditentukan yang menyatakan variabel dan panjang yang ditentukan dalam format string. Itu adalah artefak sejarah.
sumber
gets
dihapus dari standar sama sekaliJadi, jika Anda mencari argumen perintah, lihat jawaban Tim. Jika Anda hanya ingin membaca baris dari konsol:
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;)
sumber
gets
tidak ada lagi, jadi ini tidak berfungsi di C11.Anda mungkin perlu menggunakan perulangan karakter demi karakter (getc ()) untuk memastikan Anda tidak memiliki buffer overflows dan tidak memotong input.
sumber
getline
contoh yang dapat dijalankanDisebutkan 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 *"?
implementasi glibc
Tidak ada POSIX? Mungkin Anda ingin melihat implementasi glibc 2.23 .
Ini memutuskan
getdelim
, yang merupakan superset POSIX sederhanagetline
dengan 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.
sumber
len
di sini, ketika membaca memberikan panjangnya jugaman getline
.len
adalah panjang buffer yang ada,0
merupakan keajaiban dan memerintahkannya untuk mengalokasikan. Baca adalah jumlah karakter yang dibaca. Ukuran buffer bisa lebih besar dariread
.Banyak orang, seperti saya, datang ke posting ini dengan judul yang sesuai dengan yang dicari, meskipun uraiannya mengatakan tentang variabel panjang. Untuk kebanyakan kasus, kami mengetahui panjangnya terlebih dahulu.
Jika Anda tahu panjang sebelumnya, coba di bawah ini:
sumber: https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm
sumber
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:
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).
sumber
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'
atauEOF
karakterakhirnya kami menghapus memori tambahan yang dialokasikan sebelum mengembalikan baris
Sekarang Anda bisa membaca baris lengkapnya seperti ini:
Berikut contoh program yang menggunakan
scan_line()
fungsi tersebut:masukan sampel:
keluaran sampel:
sumber
Saya menemukan masalah yang sama beberapa waktu lalu, ini adalah solusi saya, semoga membantu.
sumber
goto
untuk menangani kasus kesalahan. Namun demikian, tidakkah Anda pikir Anda bisa menggunakan kembalitmp_buf
, alih-alihmalloc
menggunakannya dengan ukuran yang sama berulang kali dalam satu lingkaran?has_err
untuk 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.Di sistem BSD dan Android Anda juga dapat menggunakan
fgetln
:Seperti:
The
line
tidak nol dihentikan dan berisi\n
(atau apa pun platform Anda menggunakan) pada akhirnya. Ini menjadi tidak valid setelah operasi I / O berikutnya on stream.sumber
Sesuatu seperti ini:
sumber
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.
}
sumber
Fungsi ini harus melakukan apa yang Anda inginkan:
Saya harap ini membantu.
sumber
fgets( buffer, sizeof(buffer), file );
tidaksizeof(buffer)-1
.fgets
meninggalkan ruang untuk mengakhiri null.while (!feof(file))
itu selalu salah dan ini hanyalah satu lagi contoh penggunaan yang salah.