我在使用scanf和手动函数时遇到麻烦,无法在输入中输入字符串。
这是我的手动函数,用于在输入中获取一行字符串(我也得到[nl]字符):
void getln(char *a) {
int i,c;
i=0;
do {
c=getchar();
a[i]=(char)c;
i++;
} while(c!='\n');
}
然后,我这样使用它(char hs.school [40]; char hs.pc [20]; int hs.age;):
printf("Import age: ");
scanf("%d",&hs.age);
printf("Import personal code: ");
getln(hs.pc);
printf("Import school: ");
getln(hs.school);
输出:
Import age: 18
Import personal code: Import school: Vo Thi Sau
忽略 scanf 调用后 getln 调用的原因是什么? (但下一个 getln 效果很好) 你能解释一下细节,并建议我如何修复这个bug。谢谢!
编辑: 这是我的完整代码接受用户输入并将输入输出回屏幕,这在我做了一个小技巧后运行良好,但我决定提出一个问题,主要是为了扩展我的知识^ _ ^谢谢你的回答。
#include<stdio.h>
void getln(char *);
void putstr(char *);
int main(void) {
struct Student {
struct Fullname {
char first[10],middle[20],last[10];
}fu;
struct Native {
char social[30],district[30],province[30];
}na;
struct Score {
double maths,physics,chemistry;
}sc;
char pc[20],school[40];
int age;
}hs;
printf("Import stage:\n");
printf("- Import full name:\n");
printf("++ First name: ");
getln(hs.fu.first);
printf("++ Middle name: ");
getln(hs.fu.middle);
printf("++ Last name: ");
getln(hs.fu.last);
printf("- Import native living place:\n");
printf("++ Social: ");
getln(hs.na.social);
printf("++ District: ");
getln(hs.na.district);
printf("++ Province: ");
getln(hs.na.province);
printf("- Import school: ");
getln(hs.school);
printf("- Import personal code: "); // I have done a little trick
getln(hs.pc); // before I post the question,
printf("- Import age: "); // which swaped these two stage,
scanf("%d",&hs.age); // but it's works like a charm ^_^
printf("- Import scores:\n");
printf("++ Mathematics: ");
scanf("%lf",&hs.sc.maths);
printf("++ Physics: ");
scanf("%lf",&hs.sc.physics);
printf("++ Chemistry: ");
scanf("%lf",&hs.sc.chemistry);
printf("\nExport stage:\n");
printf("- Full name: ");
putstr(hs.fu.first);
printf(" ");
putstr(hs.fu.middle);
printf(" ");
putstr(hs.fu.last);
printf(".\n");
printf("- Native living place: ");
putstr(hs.na.social);
printf(", ");
putstr(hs.na.district);
printf(", ");
putstr(hs.na.province);
printf(".\n");
printf("- School: ");
putstr(hs.school);
printf(".\n");
printf("- Personal code: ");
putstr(hs.pc);
printf(".\n");
printf("- Age: %d.\n",hs.age);
printf("- Scores (Mathematics, Physics, Chemistry): %.2lf, %.2lf, %.2lf.\n",hs.sc.maths,hs.sc.physics,hs.sc.chemistry);
return 0;
}
void getln(char *a) {
int i,c;
i=0;
do {
c=getchar();
a[i]=(char)c;
i++;
} while(c!='\n');
}
void putstr(char *a) {
int i;
i=0;
while(a[i]!='\n') {
putchar(a[i]);
i++;
}
}
答案 0 :(得分:1)
您没有清除输入缓冲区。所以在这个换行符将放在给scanf
的第一个输入之后。所以getchar
会将新行作为输入。所以循环将退出。
在scanf
。
int c;
if ( scanf("%d",&hs.age) != 1 ) {
printf("Invalid Input\n");retrun 0; }
while((c=getchar()) != '\n' && c != EOF );
它将清除输入缓冲区。然后它将询问用户的第二个输入。
答案 1 :(得分:1)
在输入 hs.age 后,您按了输入,这是一个 \ n 字符。所以你的getln()被调用但是循环在一次迭代后被破坏,因为 c 包含&#39; \ n&#39; 。如果您打印 hs.pc ,输出屏幕中会有一个新行。
答案 2 :(得分:1)
未跳过getln
来电,它会将newline
(输入缓冲区)中的stdin
字符作为输入,并将'\n'
读为{ {1}},将其分配给c
,检查a[i]
是否为c
字符并退出。
要解决初始问题,您需要在调用'\n'
之前清除输入缓冲区。您可以使用其他答案中建议的getln
循环执行此操作,也可以为while
创建一个正确的格式字符串,该字符串将使用换行符,清空缓冲区。 (不是万无一失),但替代scanf
将是:
scanf
哪个会在数字前跳过所有空格(包括任何换行符),读取小数值,然后读取并丢弃换行符。 注意:这仅适用于没有尾随字符的数字。输入scanf(" %d%*c",&hs.age);
会在13abc
中留下bc\n
。在这种情况下,while循环更灵活,因为它会读取所有字符,直到遇到换行符并且可能是更好的选择:
stdin
至于您的scanf(" %d",&hs.age);
while ((c = getchar()) != '\n' && c != EOF);
功能,只需将每个字符读入getln
即可。并非真正需要a[i]
。您还需要对输入进行相同的检查,以免留下换行符。您还需要检查c
的最大长度i
减去1.我建议a
表示您输入的最大字符串长度。这将允许某些东西测试#define MAXS 128
以防止写入超出字符串的结尾。
以下是i
的替代方案。 注意:类型为getln
,允许它返回读取行的长度,以便您可以确定如果已达到int
该怎么做(因为仍然会有字符在那时MAXS
)。作为一般规则,如果您在可能存在错误的情况下在函数中执行某些操作,则最好返回指示成功/失败/问题的值:
stdin
答案 3 :(得分:0)
你能解释一下细节......
我会尽量不使用诸如 buffer 等令人困惑的术语。
您可能已经知道"%d"
对应于一组十进制数字字符,这些字符将转换为int
。当您按其他人的建议按“Enter”时,'\n'
字符将通过stdin
传输。 '\n'
不是十进制数字字符,因此它会被放回到您的getln
函数的流中,以便稍后发现...
实际上,您的“在scanf调用后立即调用getln”可能不会被忽略;只是阅读尾随的'\n'
并看到一个空行 。
这是假设另一个问题没有发挥作用。 getln
无法看到a
指向的字节数,因此它无法判断何时溢出,因此不会尝试阻止缓冲区溢出...您基本上已重写{ {1}}。如果您的输入足够长,那么我认为这也可能导致您的问题......缓冲区溢出是未定义的行为,使用未定义行为的后果是未定义。
关于未定义行为的主题,因为gets
在技术上并不产生字符串,所以我希望你以后不会将它用作标准字符串函数的输入...
同样关于未定义行为的主题,如果用户输入的内容不是一组十进制数字,您认为可能会发生什么? getln
通过返回值传达输入错误...因此永远不会忽略返回值。您可以(并且应该在某个时候)在the scanf manual中找到有关此内容的更多信息。
...并建议我如何修复此错误。
放弃用户输入并没有多大意义,但不幸的是,你不能指望一个能够带来更好的用户体验的解决方案,而不会使你的代码大小(以及这种解释)大大超出比例。
您可以使用scanf
来删除该行的其余部分(可能只是'\n'
),如下所示:scanf
...跟随{{ 1}} scanf("%*[^\n]"); getchar();
调用,当然......您甚至可以将两者合并在一起,如下所示:
"%d"
不幸的是,如果您的用户使用空格键而不是输入,他或她可能不会发现有关这方面的问题,直到它太晚了......
关于缓冲区溢出问题,我建议使用scanf
而不是if (scanf("%d%*[^\n]", &hs.age) != 1) {
puts("ERROR: EOF or file access error.");
exit(0);
}
getchar();
。 fgets
也有故障模式,它们通过返回值和数组内容传递。返回值用于传达gets
和文件访问错误,并且返回值中fgets
的存在(或缺少)用于表示输入行太大而无法存储在数组中。我们可以通知用户溢出(我相信他们会很感激)并使用先前使用的相同EOF
技巧丢弃多余的...
'\n'
我认为将这些解决方案包装到函数中是有意义的,除了这些函数会促进丢弃用户输入。尽管如此,后者足够冗长,你最有可能从抽象中获益......
scanf
...现在您可以这样使用:if (fgets(hs.pc, sizeof hs.pc, stdin) == NULL) {
puts("ERROR: EOF or file access error.");
exit(0);
}
size_t size = strcspn(hs.pc, "\n");
if (hs.pc[size] != '\n') {
printf("WARNING: MAXIMUM SIZE OF %zu EXCEEDED! LINE TRUNCATED.\n", sizeof hs.pc - 1);
scanf("%*[^\n]");
getchar();
}
hs.pc[size] = '\0';