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


108

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


你能澄清一下吗?就像@Tim在下面说的那样,这让您很困惑:)
沃伦

Answers:


81

您需要动态内存管理,并使用该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!它不执行边界检查,并且可能溢出缓冲区


警告-需要在那里检查重新分配的结果。但是,如果失败了,那么最有可能出现更严重的问题。
Tim

4
通过对缓冲区执行fgets并检查结尾是否有换行符,可能可以稍微提高效率。如果不这样做,请重新分配累积缓冲区,将其复制到其中,然后再次获取。
Paul Tomblin,

3
该函数需要更正:“ len = lenmax;”行 重新分配之后,应该在重新分配之前,或者应为“ len = lenmax >> 1;”。-或其他等效值,说明已经使用了一半的长度。
马特·加拉格尔

1
@Johannes,在回答您的问题时,@ Paul的方法在大多数(即可重入的)libc实现中可能会更快,因为您的方法隐式地为每个字符锁定了stdin,而他的每个缓冲区都将其锁定一次。fgetc_unlocked如果不是线程安全而是性能的考虑,则可以使用便携式性较差的方法。
vladr

3
请注意,这getline()与POSIX标准getline()功能不同。
乔纳森·莱夫勒


16

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

char line[1024];

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

一个比较安全的实现是:没有可能发生缓冲区溢出,但是有可能无法读取整行:

char line[1024];

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

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


14
这一点都不安全。为什么要gets完全从标准中删除,它也遭受了完全相同的问题
Antti Haapala'Apr 1'16

6
主持人注意:上面的评论指的是答案
罗伯特·哈维

13

因此,如果您要查找命令参数,请看一下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;
}

是的,它不安全,您可以执行缓冲区溢出操作,不检查文件结尾,不支持编码和许多其他功能。实际上,我什至都没有想过是否做过这些事情。我同意我有点搞砸了。像上面一样。实际上,我认为,如果您实际上尝试编写这100行代码,那么您犯的错误将比选择gets时要犯的错误还要多;)


1
这不允许长字符串...-我认为这是他的问题的症结所在。
Tim

2
-1,不使用gets(),因为它不进行边界检查。
放松

7
另一方面,如果您正在为自己编写程序,而只需要读取输入内容,那就太好了。程序需要多少安全性取决于规范-您不必每次都将其作为优先事项。
Martin Beckett

4
@Tim-我想保留所有历史记录:)
Paul Kapustin

4
不赞成投票。gets不再存在,因此在C11中不起作用。
Antti Haapala

11

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


9

getline 可运行的例子

提到了这个答案,但这是一个例子。

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

指针newbs,请阅读以下内容:为什么getline的第一个参数是指针“ char **”而不是“ 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;
}

glibc实施

没有POSIX?也许您想看看glibc 2.23的实现

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

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

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


这里的目的是什么len,当阅读时也提供了长度
Abdul

@阿卜杜勒见man getlinelen是现有缓冲区的长度,0是魔术,并告诉它进行分配。读取是读取的字符数。缓冲区大小可能大于read
西罗Santilli郝海东冠状病六四事件法轮功


5

如建议的那样,您可以使用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”,则可能是有更多数据,或者下一个请求将报告文件结束。您将必须执行另一个fget以确定哪个是哪个。(就此而言,使用getchar()循环更容易。)

在上面的(更新的)示例代码中,如果成功获取fget之后,line [sizeof(line)-1] =='\ 0',则说明缓冲区已完全填满。如果该职位以'\ n'开头,则说明您很幸运。否则,stdin中可能有更多数据或文件结尾。(当缓冲区未完全填满时,您可能仍位于文件末尾,并且当前行的末尾也可能没有'\ n'。由于您必须扫描字符串才能找到和/或删除字符串末尾(缓冲区中的第一个'\ 0')之前的任何'\ n',我倾向于首先使用getchar()。)

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


2

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

  • 建立自己的功能是帮助您从控制台读取一行的方法之一

  • 我正在使用动态内存分配来分配所需的所需内存量

  • 当我们将要耗尽分配的内存时,我们尝试将内存大小增加一倍

  • 在这里,我使用循环使用该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`
    }
    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!

0

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

/*
 * 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
如果您goto用来处理错误情况,您的代码将变得更加简单。但是,您是否不认为可以重用tmp_buf,而不是malloc在循环中一遍又一遍地使用相同的大小?
Shahbaz

使用单个全局变量has_err报告错误使此函数在线程上不安全,使用起来也不舒服。不要那样做。您已经通过返回NULL来指示错误。还有空间认为打印的错误消息在通用库函数中不是一个好主意。
乔纳森·莱夫勒

0

在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终止的字符串的警告足够大且存在问题,最好不使用它-这很危险。
乔纳森·莱夫勒

0

像这样:

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

从控制台读取行的最好,最简单的方法是使用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 );

}


-3

此函数应执行您想要的操作:

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

我希望这有帮助。


1
你应该做的fgets( buffer, sizeof(buffer), file );不是sizeof(buffer)-1fgets为终止null留出空间。
user102008 2010年

请注意,这while (!feof(file))总是错误的,这只是错误使用的又一个示例。
乔纳森·莱夫勒
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.