如何从 C 中的控制台读取一行?

Posted

技术标签:

【中文标题】如何从 C 中的控制台读取一行?【英文标题】:How to read a line from the console in C? 【发布时间】:2010-09-23 18:50:53 【问题描述】:

在 C 控制台程序中读取整行的最简单方法是什么 输入的文本可能具有可变长度,我们无法对其内容做出任何假设。

【问题讨论】:

【参考方案1】:

您需要动态内存管理,并使用fgets 函数来读取您的行。但是,似乎无法查看它读取了多少个字符。所以你使用 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;

注意:千万不要使用gets!它不做边界检查,并且会溢出你的缓冲区

【讨论】:

警告 - 需要在那里检查 realloc 的结果。但如果失败了,那么很可能会出现更严重的问题。 您可以通过使用缓冲区执行 fgets 并检查最后是否有换行符来提高效率。如果不这样做,请重新分配累积缓冲区,复制到其中,然后再次 fgets。 这个函数需要更正:行“len = lenmax;”在 realloc 之后应该在 realloc 之前或者应该是 "len = lenmax >> 1;" -- 或其他等价物,说明一半长度已被使用。 @Johannes,在回答您的问题时,@Paul 的方法在大多数(即可重入)libc 实现上可能会更快,因为您的方法为每个字符隐式锁定标准输入,而他为每个缓冲区锁定一次。如果线程安全不是问题但性能是问题,您可以使用不太便携的fgetc_unlocked 请注意,此getline() 与POSIX 标准getline() 函数不同。【参考方案2】:

因此,如果您正在寻找命令参数,请查看 Tim 的答案。 如果您只想从控制台读取一行:

#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 中从控制台读取一行?”之类的问题时,我认为一个人需要一些简单的东西,比如 gets(),而不是像上面那样的 100 行代码。 实际上,我认为,如果您尝试在现实中编写这 100 行代码,您会犯更多的错误,而不是选择gets ;)

【讨论】:

这不允许长字符串... - 我认为这是他问题的症结所在。 -1,gets() 不应该使用,因为它不做边界检查。 另一方面,如果您正在为自己编写程序并且只需要读取输入,这非常好。程序需要多少安全性符合规范 - 您不必每次都将其作为优先事项。 @Tim - 我想保留所有历史记录 :) 投反对票。 gets 不再存在,因此这在 C11 中不起作用。【参考方案3】:

您可能需要使用逐个字符 (getc()) 循环来确保没有缓冲区溢出并且不会截断输入。

【讨论】:

【参考方案4】:

如果您使用的是 GNU C 库或其他符合 POSIX 标准的库,则可以使用 getline() 并将 stdin 传递给它作为文件流。

【讨论】:

【参考方案5】:

前段时间我也遇到过同样的问题,这是我的解决方案,希望对您有所帮助。

/*
 * 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;

【讨论】:

如果您使用goto 来处理错误情况,您的代码会变得更加简单。尽管如此,你不认为你可以重复使用tmp_buf,而不是malloc在循环中一遍又一遍地使用相同的大小吗? 使用单个全局变量has_err 来报告错误会使这个函数线程不安全并且使用起来不太舒服。不要那样做。您已经通过返回 NULL 来指示错误。也有人认为打印的错误消息在通用库函数中不是一个好主意。【参考方案6】:

一个非常简单但不安全的读取静态分配行的实现:

char line[1024];

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

一个更安全的实现方式是:

char line[1024];

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

不是声明变量的指定长度与格式字符串中指定的长度之间的“差一”。这是一件历史文物。

【讨论】:

【参考方案7】:

按照建议,您可以使用 getchar() 从控制台读取,直到返回行尾或 EOF,从而构建自己的缓冲区。如果您无法设置合理的最大行大小,则可能会动态增加缓冲区。

您也可以使用 fgets 作为一种安全的方式来获取一行作为 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);

如果您已用尽控制台输入或操作由于某种原因失败,则返回 eof == NULL 并且行缓冲区可能未更改(这就是为什么将第一个字符设置为 '\0' 很方便)。

fgets 不会溢出 line[] 并且它会确保在成功返回时最后接受的字符之后有一个 null。

如果到达行尾,则终止“\0”之前的字符将是“\n”。

如果在结尾 '\0' 之前没有终止 '\n' 可能是有更多数据或下一个请求将报告文件结尾。你必须做另一个 fgets 来确定哪个是哪个。 (在这方面,使用 getchar() 循环更容易。)

在上面的(更新的)示例代码中,如果 line[sizeof(line)-1] == '\0' 在 fgets 成功后,你就知道缓冲区被完全填满了。如果该位置以 '\n' 开头,您就知道您很幸运。否则,stdin 中会出现更多数据或文件结尾。 (当缓冲区没有完全填满时,您可能仍然处于文件末尾,并且当前行的末尾也可能没有 '\n'。因为您必须扫描字符串以查找和/或消除字符串结尾之前的任何 '\n'(缓冲区中的第一个 '\0'),我倾向于首先使用 getchar()。)

做你需要做的事情来处理仍然比你作为第一个块读取的数量更多的行。动态增长缓冲区的示例可以使用 getchar 或 fgets。有一些棘手的边缘情况需要注意(比如记住让下一个输入开始存储在缓冲区扩展之前结束上一个输入的 '\0' 的位置)。

【讨论】:

【参考方案8】:

在 BSD 系统和 android 上,您还可以使用fgetln

#include <stdio.h>

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

像这样:

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

line 不是以 null 结尾的,最后包含 \n(或您的平台使用的任何内容)。在流上的下一次 I/O 操作后它变得无效。

【讨论】:

是的,该函数存在。它不提供以 null 结尾的字符串的警告足够大且存在问题,最好不要使用它——这很危险。【参考方案9】:

getline 可运行示例

getline 被提及 on this answer 但这里是一个例子。

它是POSIX 7,为我们分配内存,并在循环中很好地重用分配的缓冲区。

指针新手,请阅读:Why is the first argument of getline a pointer to pointer "char**" instead of "char*"?

main.c

#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 (1) 
        puts("enter a line");
        read = getline(&line, &len, stdin);
        if (read == -1)
            break;
        printf("line = %s", line);
        printf("line length = %zu\n", read);
        puts("");
    
    free(line);
    return 0;

编译运行:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out

结果:这显示在热仪上:

enter a line

如果你输入:

asdf

然后按回车,就会出现这个:

line = asdf
line length = 5

后面跟着另一个:

enter a line

或者从管道到标准输入:

printf 'asdf\nqwer\n' | ./main.out

给予:

enter a line
line = asdf
line length = 5

enter a line
line = qwer
line length = 5

enter a line

在 Ubuntu 20.04 上测试。

glibc 实现

没有 POSIX?也许你想看看glibc 2.23 implementation。

它解析为getdelim,这是getline 的简单POSIX 超集,带有任意行终止符。

每当需要增加时,它会加倍分配的内存,并且看起来是线程安全的。

它需要一些宏扩展,但你不可能做得更好。

【讨论】:

这里len的目的是什么,read时也提供了长度 @Abdul 见man getlinelen 是现有缓冲区的长度,0 是魔术并告诉它分配。读取是读取的字符数。缓冲区大小可能大于read【参考方案10】:

如何在 C 中从控制台读取一行?

构建你自己的函数,是帮助你实现从控制台读取一行的方法之一

我正在使用dynamic memory allocation 分配所需的所需内存量

当我们即将耗尽分配的内存时,我们尝试将内存大小加倍

在这里,我使用循环使用getchar() 函数逐个扫描字符串的每个字符,直到用户输入'\n'EOF 字符

最后,我们在返回该行之前删除所有额外分配的内存

//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`
        length++;
    
    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;

现在你可以这样读一整行了:

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

这是一个使用scan_line() 函数的示例程序

#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

示例输入:

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

样本输出:

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

【讨论】:

与其他答案不同,适用于 GCC ,windows 10,编译为 C11 ,64bit。【参考方案11】:

类似这样的:

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;

【讨论】:

【参考方案12】:

从控制台读取一行的最佳和最简单的方法是使用 getchar() 函数,您可以一次将一个字符存储在一个数组中。


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

【讨论】:

【参考方案13】:

这是一个最小的实现,好处是它不会保留'\n',但是为了安全起见,你必须给它一个读取的大小:

#include <stdio.h>
#include <errno.h>

int sc_gets(char *buf, int n)

    int count = 0;
    char c;

    if (__glibc_unlikely(n <= 0))
        return -1;

    while (--n && (c = fgetc(stdin)) != '\n')
        buf[count++] = c;
    buf[count] = '\0';

    return (count != 0 || errno != EAGAIN) ? count : -1;


测试:

#define BUFF_SIZE 10

int main (void) 
    char buff[BUFF_SIZE];

    sc_gets(buff, sizeof(buff));
    printf ("%s\n", buff);

    return 0;

注意:您只能在 INT_MAX 内找到您的线路返回,这绰绰有余。

【讨论】:

以上是关于如何从 C 中的控制台读取一行?的主要内容,如果未能解决你的问题,请参考以下文章

C ++:从文本文件中读取单行,按字母顺序对单词进行排序

将stdin设置为C中的文件时如何从键盘读取

如何使用 cin 将整数从控制台读取到向量中

如何从控制台应用程序中读取 web.config 的 <connectionstring>?

C语言 读取数字

csharp 如何从控制台读取多维数组的示例。从计算机编程基础知识到C#http://www.introprogramm