在 C 控制台程序中读取整行的最简单方法是什么输入的文本可能具有可变长度,我们不能对其内容做出任何假设。
13 回答
您需要动态内存管理,并使用该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!它不做边界检查并且可能溢出你的缓冲区
如果您使用的是 GNU C 库或其他符合 POSIX 的库,则可以使用getline()
并将其传递stdin
给文件流。
读取静态分配行的一个非常简单但不安全的实现:
char line[1024];
scanf("%[^\n]", line);
一个更安全的实现,没有缓冲区溢出的可能性,但有可能不读取整行,是:
char line[1024];
scanf("%1023[^\n]", line);
不是声明变量的指定长度与格式字符串中指定的长度之间的“差一”。这是一件历史文物。
因此,如果您正在寻找命令参数,请查看 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 ;)
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
或从管道到标准输入:
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 的实现。
它解析为getdelim
,这是一个getline
带有任意行终止符的简单 POSIX 超集。
每当需要增加时,它会将分配的内存加倍,并且看起来是线程安全的。
它需要一些宏扩展,但你不可能做得更好。
您可能需要使用逐字符 (getc()) 循环来确保没有缓冲区溢出并且不会截断输入。
如建议的那样,您可以使用 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[] 并且它将确保在成功返回时最后接受的字符之后有一个空值。
如果到达行尾,则终止 '\0' 之前的字符将是 '\n'。
如果在结尾 '\0' 之前没有终止 '\n' ,则可能是有更多数据或下一个请求将报告文件结尾。你必须做另一个 fgets 来确定哪个是哪个。(在这方面,使用 getchar() 循环更容易。)
在上面的(更新的)示例代码中,如果 line[sizeof(line)-1] == '\0' 在 fgets 成功后,您就知道缓冲区已被完全填满。如果该位置以 '\n' 开头,您就知道您很幸运。否则,stdin 中会出现更多数据或文件结尾。(当缓冲区没有完全填满时,您可能仍然处于文件末尾,并且当前行的末尾也可能没有 '\n'。因为您必须扫描字符串以查找和/或消除字符串结尾之前的任何 '\n'(缓冲区中的第一个 '\0'),我倾向于首先使用 getchar()。)
做你需要做的事情来处理仍然比你作为第一个块读取的数量更多的行。动态增长缓冲区的示例可以使用 getchar 或 fgets。有一些棘手的边缘情况需要注意(比如记住让下一个输入开始存储在缓冲区扩展之前结束前一个输入的 '\0' 的位置)。
如何从 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);
这是一个使用该函数的示例程序: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!
我前段时间遇到了同样的问题,这是我的解决方案,希望对您有所帮助。
/*
* 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;
}
在 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
不是空终止的,并且最终包含(\n
或您的平台正在使用的任何内容)。在流上的下一个 I/O 操作后它变得无效。
像这样的东西:
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;
}
注意:您仅限于 INT_MAX 才能找到您的线路返回,这绰绰有余。