我想使用C程序读取用户输入的名称。
为此,我编写了以下代码:
char name[20];
printf("Enter name: ");
gets(name);
但是使用 gets
不好,那么有什么更好的方法吗?
在编写代码时,我们绝对不应该使用gets
(或具有未限定字符串大小的scanf
),因为这会导致缓冲区溢出。相反,我们应该使用带有stdin
句柄的fgets
函数,它允许我们限制将放入缓冲区中的数据。
以下是我用于从用户获取输入行的小代码片段:
#include <stdio.h>
#include <string.h>
#define OK 0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
int ch, extra;
// Get line with buffer overrun protection.
if (prmpt != NULL) {
printf ("%s", prmpt);
fflush (stdout);
}
if (fgets (buff, sz, stdin) == NULL)
return NO_INPUT;
// If it was too long, there'll be no newline. In that case, we flush
// to end of line so that excess doesn't affect the next call.
if (buff[strlen(buff)-1] != '\n') {
extra = 0;
while (((ch = getchar()) != '\n') && (ch != EOF))
extra = 1;
return (extra == 1) ? TOO_LONG : OK;
}
// Otherwise remove newline and give string back to caller.
buff[strlen(buff)-1] = '\0';
return OK;
}
// Test program for getLine().
int main (void) {
int rc;
char buff[10];
rc = getLine ("Enter string> ", buff, sizeof(buff));
if (rc == NO_INPUT) {
// Extra NL since my system doesn't output that on EOF.
printf ("\nNo input\n");
return 1;
}
if (rc == TOO_LONG) {
printf ("Input too long [%s]\n", buff);
return 1;
}
printf ("OK [%s]\n", buff);
return 0;
}
我认为读取用户输入的字符串的最佳和最安全的方式是使用getline()
下面是一个如何实现的例子:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
char *buffer = NULL;
int read;
unsigned int len;
read = getline(&buffer, &len, stdin);
if (-1 != read)
puts(buffer);
else
printf("No line read...\n");
printf("Size read: %d\n Len: %d\n", read, len);
free(buffer);
return 0;
}
getline
。ggets
函数,它提供了更接近于gets
但没有问题的语法。(Chuck Falconer的网站不再可用,尽管archive.org有一份拷贝,我也制作了自己的ggets页面。)\n
。 - jamesdlinchar*string_acquire(char*s,int size,FILE*stream){
int i;
fgets(s,size,stream);
i=strlen(s)-1;
if(s[i]!='\n') while(getchar()!='\n');
if(s[i]=='\n') s[i]='\0';
return s;
}
基于fgets,但去除了'\n'和stdin的额外字符(替换fflush(stdin)不适用于所有操作系统,如果需要在此之后获取字符串,则非常有用)。
fgetc
而不是 getchar
,这样它就可以使用提供的 stream
而不是 stdin
。 - jamesdlinscanf
函数移除输入字符串前的任何空格并限制要读取的字符数:#define SIZE 100
....
char str[SIZE];
scanf(" %99[^\n]", str);
/* Or even you can do it like this */
scanf(" %99[a-zA-Z0-9 ]", str);
scanf
限制要读取的字符数量,它可能会像 gets
一样危险。scanf(" %99[^\n]", str);
中手动输入 99
吗? - undefinedfgetln
:#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操作后,它将变为无效。您可以修改返回的line
缓冲区。
ANSI C 未知最大长度的解决方案
直接从 Johannes Schaub 的https://dev59.com/pHRC5IYBdhLWcg3wYP-h#314422中复制即可。
使用完毕后不要忘记free
返回的指针。
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;
}
这段代码使用malloc
来分配100个字符的内存空间。然后从用户逐个获取字符。如果用户达到了101个字符,它会使用realloc
将缓冲区扩大到200个字符。当达到201时,它将再次加倍到400,以此类推,直到内存不足。
之所以选择加倍而不是每次只增加100大小,是因为使用realloc
增加缓冲区大小可能导致旧缓冲区的复制,这是一项潜在的昂贵操作。
数组必须在内存中是连续的,因为我们希望能够通过内存地址高效地随机访问它们。因此,如果我们在RAM中有:
content buffer[0] | buffer[1] | ... | buffer[99] | empty | empty | int i
RAM address 1000 | 1001 | | 1100 | 1101 | 1102 | 1103
我们不能只增加buffer
的大小,因为这会覆盖我们的int i
。所以realloc
需要在内存中找到另一个有200个空闲字节的位置,然后将旧的100个字节复制到那里,并释放旧的100个字节。
通过倍增而不是加法,我们很快就能达到当前字符串大小的数量级,因为指数增长非常快,所以只需要进行合理数量的复制。
scanf("%[^\n]",name);
我不知道其他更好的选项来接收字符串,
scanf
,因为它非常难以正确使用。这种用法特别危险,因为它不限制输入并且很容易溢出 name
缓冲区。 - jamesdlin
scanf("%s")
函数,那么你就会出现问题。scanf
函数的整个目的在于扫描格式化的内容,而用户输入很少有比它更 非格式化 的了 :-) - paxdiabloscanf
并不总是不好的,它可以用于一些有限的事情,比如数字输入(和家庭作业)等。但即使在这种情况下,它也不像生产质量应该具备的那样健壮。即使我需要使用类似scanf
的操作来解析输入时,我也会将其读入缓冲区中,然后再从那里使用sscanf
进行解析。 - paxdiablo