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

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

332182 次浏览

您需要动态内存管理,并使用 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 的答案。 如果只想从控制台读取一行:

#include <stdio.h>


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

是的,它是不安全的,你可以做缓冲区溢出,它不检查文件结束,它不支持编码和其他很多东西。 事实上,我根本没想过它会做这些事情。 我承认我搞砸了 But...when I see a question like "How to read a line from the console in C?", I assume a person needs something simple, like gets() and not 100 lines of code like above. 实际上,我认为,如果你在现实中尝试编写这100行代码,你会犯更多的错误,比你选择 gets 时会犯的错误要多得多;)

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

如果您正在使用 GNU C 库或其他与 POSIX 兼容的库,那么您可以使用 getline()并将 stdin传递给它作为文件流。

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

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

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

char line[1024];


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

一种更安全的实现(不存在缓冲区溢出的可能性,但可能无法读取整行代码)是:

char line[1024];


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

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

正如建议的那样,您可以使用 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,并且行缓冲区可能没有改变(这就是为什么将第一个 char 设置为’0’很方便)。

fgets will not overfill line[] and it will ensure that there is a null after the last-accepted character on a successful return.

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

如果在结尾“0”之前没有终止“ n”,可能是因为有更多的数据,或者下一个请求将报告文件结束。您必须执行另一个 fgets 来确定哪个是哪个。(在这方面,使用 getchar ()进行循环更容易。)

在上面的(更新的)示例代码中,如果行[ sizeof (line)-1] = =’0’在 fgets 成功之后,您就知道缓冲区已经完全被填满。如果那个位置是由一个“ n”前进的,你知道你是幸运的。否则,在标准输入中要么有更多的数据,要么有一个文件结束。(当缓冲区没有完全填满时,您可能仍然处于文件的末尾,而且当前行的末尾可能没有’n’。由于必须扫描字符串以查找和/或消除字符串末尾之前的任何’n’(缓冲区中的第一个’0’) ,因此我倾向于首先使用 getchar ()

做你需要做的,以处理仍然有更多的行比你读取的数量作为第一块。动态增长缓冲区的示例可以使用 getchar 或 fgets。有一些棘手的边缘情况需要注意(比如记住下一个输入开始存储在缓冲区扩展之前结束上一个输入的’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 操作之后变得无效。

getline可运行示例

提到了 getline,但这里有一个例子。

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

指针菜鸟,读这个: 为什么 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 (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

或者从管道到 stdin:

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

Tested on Ubuntu 20.04.

Glibc 实现

No POSIX? Maybe you want to look at the Glibc 2.23的实现.

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

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

这需要一些宏观扩张,但你不太可能做得更好。

如何从 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`
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);
    

Here's an 示例程序 using the scan_line() function :

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

就像这样:

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

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

}

这里有一个最小的实现,好处是它不会保留’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;
}

NB: You are limited to INT_MAX to find your line return, which is more than enough.

有一个类似正则表达式的简单语法,可以在 Scanf 内部使用,将整行作为输入

scanf("%[^\n]%*c", str); ^ n 告诉接受输入,直到不遇到换行符。然后,使用% * c,它读取换行符,这里使用的 * 表示这个换行符被丢弃。

Sample code

#include <stdio.h>
int main()
{
char S[101];
scanf("%[^\n]%*c", S);
printf("%s", S);
return 0;
}