Làm thế nào để đọc một dòng từ bảng điều khiển trong C?


108

Cách đơn giản nhất để đọc toàn bộ dòng trong chương trình bảng điều khiển C Văn bản được nhập có thể có độ dài thay đổi và chúng tôi không thể đưa ra bất kỳ giả định nào về nội dung của nó.


bạn có thể làm rõ, xin vui lòng? như @Tim đã nói bên dưới, thật khó hiểu những gì bạn đang yêu cầu :)
warren

Câu trả lời:


81

Bạn cần quản lý bộ nhớ động và sử dụng fgetschức năng này để đọc dòng của bạn. Tuy nhiên, dường như không có cách nào để xem nó đọc được bao nhiêu ký tự. Vì vậy, bạn sử dụng 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;
}

Lưu ý : Không bao giờ sử dụng được! Nó không kiểm tra giới hạn và có thể làm tràn bộ đệm của bạn


Báo trước - cần kiểm tra kết quả của phân bổ lại ở đó. Nhưng nếu điều đó không thành công, thì rất có thể có những vấn đề tồi tệ hơn.
Tim

4
Bạn có thể cải thiện hiệu quả một chút bằng cách thực hiện các tiện ích với bộ đệm và kiểm tra xem bạn có ký tự dòng mới ở cuối hay không. Nếu bạn không, hãy phân bổ lại bộ đệm tích lũy của bạn, sao chép vào nó và sử dụng lại.
Paul Tomblin

3
Hàm này cần sửa lại: dòng "len = lenmax;" sau realloc phải đứng trước realloc hoặc phải là "len = lenmax >> 1;" - hoặc một số tương đương khác giải thích rằng một nửa chiều dài đã được sử dụng.
Matt Gallagher

1
@Johannes, trả lời cho câu hỏi của bạn, cách tiếp cận của @ Paul SẼ nhanh hơn trên hầu hết các triển khai libc (tức là reentrant), vì cách tiếp cận của bạn khóa stdin cho mọi ký tự trong khi cách tiếp cận của anh ấy khóa nó một lần trên mỗi bộ đệm. Bạn có thể sử dụng ít di động hơn fgetc_unlockednếu an toàn luồng không phải là mối quan tâm mà là hiệu suất.
vladr

3
Lưu ý rằng chức năng này getline()khác với getline()chức năng tiêu chuẩn POSIX .
Jonathan Leffler

28

Nếu bạn đang sử dụng thư viện GNU C hoặc một thư viện khác tương thích với POSIX, bạn có thể sử dụng getline()và chuyển stdintới nó cho luồng tệp.


16

Một triển khai rất đơn giản nhưng không an toàn để đọc dòng cho phân bổ tĩnh:

char line[1024];

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

Một cách triển khai an toàn hơn, không có khả năng bị tràn bộ đệm, nhưng với khả năng không đọc được toàn bộ dòng, là:

char line[1024];

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

Không phải là 'chênh lệch một' giữa độ dài được chỉ định khai báo biến và độ dài được chỉ định trong chuỗi định dạng. Nó là một đồ tạo tác lịch sử.


14
Điều này không an toàn chút nào. Nó bị chính xác cùng một vấn đề tại sao getsđã bị xóa khỏi tiêu chuẩn hoàn toàn
Antti Haapala

6
Lưu ý của người điều hành: Nhận xét trên đề cập đến bản sửa đổi trước đó của câu trả lời.
Robert Harvey

13

Vì vậy, nếu bạn đang tìm kiếm các đối số lệnh, hãy xem câu trả lời của Tim. Nếu bạn chỉ muốn đọc một dòng từ bảng điều khiển:

#include <stdio.h>

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

Có, nó không an toàn, bạn có thể chạy quá tải bộ đệm, nó không kiểm tra phần cuối của tệp, nó không hỗ trợ mã hóa và rất nhiều thứ khác. Trên thực tế, tôi thậm chí không nghĩ liệu nó có làm BẤT CỨ những thứ này hay không. Tôi đồng ý rằng tôi hơi khó chịu :) Nhưng ... khi tôi thấy một câu hỏi như "Làm thế nào để đọc một dòng từ bảng điều khiển trong C?", Tôi cho rằng một người cần một cái gì đó đơn giản, như get () chứ không phải 100 dòng mã như trên. Trên thực tế, tôi nghĩ, nếu bạn cố gắng viết 100 dòng mã đó trong thực tế, bạn sẽ mắc nhiều sai lầm hơn những gì bạn đã làm nếu bạn đã chọn;)


1
Anh ấy không cho phép dây dài ... - mà tôi nghĩ đó là mấu chốt của câu hỏi của anh ấy.
Tim

2
-1, get () không nên được sử dụng vì nó không kiểm tra giới hạn.
thư giãn

7
Mặt khác, nếu bạn đang viết một chương trình cho chính mình và chỉ cần đọc một đầu vào thì điều này hoàn toàn ổn. Mức độ bảo mật mà một chương trình cần là ngang bằng với thông số kỹ thuật - bạn không thể luôn đặt nó làm ưu tiên.
Martin Beckett

4
@Tim - Tôi muốn lưu giữ tất cả lịch sử :)
Paul Kapustin,

4
Bị phản đối. getskhông tồn tại nữa, do đó điều này không hoạt động trong C11.
Antti Haapala

11

Bạn có thể cần sử dụng vòng lặp theo từng ký tự (getc ()) để đảm bảo bạn không bị tràn bộ đệm và không cắt bớt đầu vào.


9

getline ví dụ có thể chạy được

Đã đề cập đến câu trả lời này nhưng đây là một ví dụ.

Nó là POSIX 7 , cấp phát bộ nhớ cho chúng tôi và sử dụng lại bộ đệm được cấp phát trên một vòng lặp một cách độc đáo.

Con trỏ newbs, hãy đọc phần này: Tại sao đối số đầu tiên của getline lại là con trỏ trỏ tới con trỏ "char **" thay vì "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;
}

triển khai glibc

Không có POSIX? Có thể bạn muốn xem cách triển khai glibc 2.23 .

Nó giải quyết thành getdelim, đó là một tập hợp đơn giản của POSIX getlinevới một dấu chấm hết dòng tùy ý.

Nó tăng gấp đôi bộ nhớ được cấp phát bất cứ khi nào cần tăng và trông an toàn cho luồng.

Nó yêu cầu một số mở rộng macro, nhưng bạn không chắc sẽ làm tốt hơn nhiều.


Mục đích là gì lenở đây, khi đọc cung cấp độ dài quá
Abdul

@Abdul xem man getline. lenlà độ dài của bộ đệm hiện có, 0là phép thuật và yêu cầu nó cấp phát. Đọc là số ký tự được đọc. Kích thước bộ đệm có thể lớn hơn read.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

6

Nhiều người, giống như tôi, đến bài đăng này với tiêu đề phù hợp với những gì được tìm kiếm, mặc dù phần mô tả nói về độ dài thay đổi. Đối với hầu hết các trường hợp, chúng tôi biết trước độ dài.

Nếu bạn biết độ dài trước tay, hãy thử dưới đây:

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

nguồn: https://www.tutorialspoint.com/c_standard_library/c_ Chức năng_fgets.htm


5

Như được đề xuất, bạn có thể sử dụng getchar () để đọc từ bảng điều khiển cho đến khi trả về cuối dòng hoặc EOF, xây dựng bộ đệm của riêng bạn. Tăng bộ đệm động có thể xảy ra nếu bạn không thể đặt kích thước dòng tối đa hợp lý.

Bạn cũng có thể sử dụng fgets như một cách an toàn để lấy một dòng dưới dạng chuỗi C null kết thú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);

Nếu bạn đã sử dụng hết đầu vào bảng điều khiển hoặc nếu thao tác không thành công vì lý do nào đó, thì eof == NULL sẽ được trả về và bộ đệm dòng có thể không thay đổi (đó là lý do tại sao việc đặt ký tự đầu tiên thành '\ 0' là hữu ích).

fgets sẽ không điền quá dòng [] và nó sẽ đảm bảo rằng có một giá trị rỗng sau ký tự được chấp nhận cuối cùng khi trả về thành công.

Nếu đến cuối dòng, ký tự đứng trước '\ 0' kết thúc sẽ là '\ n'.

Nếu không có dấu chấm hết '\ n' trước phần cuối '\ 0' thì có thể có nhiều dữ liệu hơn hoặc yêu cầu tiếp theo sẽ báo cáo phần cuối của tệp. Bạn sẽ phải thực hiện một công cụ khác để xác định cái nào là cái nào. (Về vấn đề này, lặp với getchar () dễ dàng hơn.)

Trong mã ví dụ (đã cập nhật) ở trên, nếu dòng [sizeof (line) -1] == '\ 0' sau khi tạo thành công, bạn biết rằng bộ đệm đã được lấp đầy hoàn toàn. Nếu vị trí đó được tiến hành bởi '\ n', bạn biết rằng mình đã may mắn. Nếu không, có thêm dữ liệu hoặc phần cuối của tệp ở phía trước trong stdin. (Khi bộ đệm không được lấp đầy hoàn toàn, bạn vẫn có thể ở cuối tệp và cũng có thể không có '\ n' ở cuối dòng hiện tại. Vì bạn phải quét chuỗi để tìm và / hoặc loại bỏ bất kỳ '\ n' nào trước khi kết thúc chuỗi ('\ 0' đầu tiên trong bộ đệm), tôi có xu hướng thích sử dụng getchar () ngay từ đầu.)

Làm những gì bạn cần làm để đối phó với việc vẫn còn nhiều dòng hơn số lượng bạn đọc dưới dạng đoạn đầu tiên. Các ví dụ về tăng trưởng động một bộ đệm có thể được thực hiện để hoạt động với getchar hoặc fgets. Có một số trường hợp cạnh phức tạp cần chú ý (như nhớ có đầu vào tiếp theo bắt đầu lưu trữ tại vị trí của '\ 0' đã kết thúc đầu vào trước đó trước khi bộ đệm được mở rộng).


2

Làm thế nào để đọc một dòng từ bảng điều khiển trong C?

  • Xây dựng chức năng của riêng bạn, là một trong những cách sẽ giúp bạn đọc được dòng từ bảng điều khiển

  • Tôi đang sử dụng phân bổ bộ nhớ động để cấp phát lượng bộ nhớ cần thiết

  • Khi sắp hết bộ nhớ được cấp phát, chúng tôi cố gắng tăng gấp đôi dung lượng bộ nhớ

  • Và ở đây tôi đang sử dụng một vòng lặp để quét từng ký tự của chuỗi một bằng cách sử dụng getchar()hàm cho đến khi người dùng nhập '\n'hoặc EOFký tự

  • cuối cùng chúng tôi xóa bất kỳ bộ nhớ được cấp bổ sung nào trước khi trả về dòng

//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;
}
  • Bây giờ bạn có thể đọc toàn bộ dòng theo cách này:

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

Đây là một chương trình ví dụ sử dụng scan_line()hàm:

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

đầu vào mẫu:

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

đầu ra mẫu:

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

0

Tôi đã gặp vấn đề tương tự cách đây một thời gian, đây là cách giải quyết của tôi, hy vọng nó sẽ hữu ích.

/*
 * 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
Mã của bạn sẽ trở nên đơn giản hơn rất nhiều nếu bạn sử dụng gotođể xử lý trường hợp lỗi. Tuy nhiên, bạn có nghĩ rằng bạn có thể sử dụng lại tmp_buf, thay vì nhập mallocnó với cùng một kích thước lặp đi lặp lại trong vòng lặp không?
Shahbaz

Việc sử dụng một biến toàn cục duy nhất has_errđể báo cáo lỗi làm cho chuỗi hàm này không an toàn và kém thoải mái khi sử dụng. Đừng làm theo cách đó. Bạn đã chỉ ra lỗi bằng cách trả về NULL. Cũng có thể nghĩ rằng các thông báo lỗi đã in không phải là một ý kiến ​​hay trong một chức năng thư viện có mục đích chung.
Jonathan Leffler

0

Trên hệ thống BSD và Android, bạn cũng có thể sử dụng fgetln:

#include <stdio.h>

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

Như vậy:

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

Cuối cùng linekhông phải là null chấm dứt và chứa \n(hoặc bất kỳ thứ gì nền tảng của bạn đang sử dụng). Nó trở nên không hợp lệ sau thao tác I / O tiếp theo trên luồng.


Có, chức năng tồn tại. Cảnh báo rằng nó không cung cấp một chuỗi kết thúc bằng null đủ lớn và có vấn đề là tốt hơn hết là không nên sử dụng nó - điều đó rất nguy hiểm.
Jonathan Leffler

0

Một cái gì đó như thế này:

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

Cách tốt nhất và đơn giản nhất để đọc một dòng từ bảng điều khiển là sử dụng hàm getchar (), theo đó bạn sẽ lưu trữ một ký tự tại một thời điểm trong một mảng.

{
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

Hàm này sẽ làm những gì bạn muốn:

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

Tôi hi vọng cái này giúp được.


1
Bạn nên làm fgets( buffer, sizeof(buffer), file );không sizeof(buffer)-1. fgetsđể lại khoảng trống cho giá trị kết thúc null.
user102008

Lưu ý rằng while (!feof(file))luôn luôn sai và đây chỉ là một ví dụ khác về việc sử dụng sai.
Jonathan Leffler,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.