Scanf 和一个手动函数一起工作时获取字符串
Scanf and a manual function to get string when they work together
我在使用 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效果不错)
你能解释一下细节并建议我如何修复这个错误吗?谢谢!
已编辑:
这是我的完整代码,接受用户输入并将输入导出回屏幕,这是 运行 在我做了一些小技巧之后,但我决定做一个问题,主要是为了扩充自己的知识^_^ 谢谢大家的解答
#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++;
}
}
您没有清除输入缓冲区。所以在这个换行符将放在给定 scanf
的第一个输入之后。所以 getchar
将获取新行作为输入。所以循环将退出。
在scanf
之后使用这一行。
int c;
if ( scanf("%d",&hs.age) != 1 ) {
printf("Invalid Input\n");retrun 0; }
while((c=getchar()) != '\n' && c != EOF );
它将清除输入缓冲区。然后它会询问用户的第二个输入。
输入 hs.age 后,您按下了 Enter,这是一个 \n 字符。所以你的 getln() 被调用了,但是循环在一次迭代后就被打破了,因为 c 包含 '\n'。如果您打印 hs.pc,您的输出屏幕中将有一个新行。
你的 getln
调用没有被跳过,它把留在 stdin
(输入缓冲区)中的 newline
字符作为它的输入,它读取 '\n'
作为 c
,将其分配给 a[i]
,检查 c
是否为 '\n'
字符并退出。
要解决最初的问题,您需要在调用getln
之前清除输入缓冲区。您可以按照另一个答案中的建议使用 while
循环来执行此操作,或者您可以为 scanf
制作一个适当的格式字符串,它将消耗换行符,清空缓冲区。 (并非万无一失),但替代方案 scanf
是:
scanf(" %d%*c",&hs.age);
这将跳过数字前的所有空格(包括任何换行符),读取十进制值,然后读取并丢弃换行符。 注意:这仅适用于没有尾随字符的数字。输入 13abc
将在 stdin
中留下 bc\n
。这种情况下的 while 循环更灵活,因为它读取所有字符直到遇到换行符,这可能是更好的选择:
scanf(" %d",&hs.age);
while ((c = getchar()) != '\n' && c != EOF);
至于你的getln
函数,只需要将每个字符读入a[i]
即可。没有真正需要 c
。您还需要对您的输入进行相同的检查,以便不留下换行符。您还需要根据 a
减 1 的最大长度检查 i
。我建议您输入的最大字符串长度为 #define MAXS 128
。这将允许测试 i
以防止写入超出字符串末尾的内容。
这是您的 getln
的替代方案。 注意:它是 int
类型,允许它 return 读取的行的长度,因此您可以确定在达到 MAXS
时要做什么(因为那时 stdin
中仍然会有字符)。作为一般规则,如果您在可能出错的情况下在函数中执行某些操作,最好 return 一个指示 success/failure/problem:
的值
#define MAXS 128
...
int getln (char *a)
{
size_t i = 0;
while ((a[i] = getchar()) != '\n' && a[i] != EOF)
{
i++;
if (i == MAXS - 1)
{
a[i] = 0;
break;
}
}
return i;
}
Can you explain me the details ...
我会尽量不使用混淆术语,例如 buffer。
您可能已经知道 "%d"
对应于一组转换为 int
的十进制数字字符。当您按照其他人的建议按 'Enter' 时,'\n'
字符将通过 stdin
传输。 '\n'
不是十进制数字字符,因此它会放回流中供您的 getln
函数稍后发现...
实际上,您的 "getln call right after scanf call" 可能 没有被忽略; 可能 只是阅读尾随 '\n'
并看到一个空行。
这是假设其他问题没有出现。 getln
看不到 a
指向多少字节,所以它无法判断它何时会溢出,因此没有尝试防止缓冲区溢出......你基本上重写了 gets
。如果您的输入足够长,那么我想这也可能导致您的问题......缓冲区溢出是未定义的行为,使用未定义行为的后果是 undefined.
关于未定义行为的主题,由于 getln
在技术上不生成字符串,我希望您以后不要将其用作标准字符串函数的输入...
关于未定义行为的主题,如果用户输入的不是一组十进制数字,您认为会发生什么? scanf
通过 return 值传达输入错误...所以 永远不要忽略 return 值 。您可以(并且应该在某个时候)在 the scanf manual.
中找到更多相关信息
... and suggest me how to fix this bug.
丢弃用户输入没有多大意义,但不幸的是,您不能指望一种解决方案可以在不使代码大小(和此解释)超出比例的情况下带来更好的用户体验。
您可以使用 scanf
丢弃十进制数字集后面的行的其余部分(可能只是 '\n'
),如下所示: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
也有失败模式,通过 return 值和数组的内容传达。 return 值用于传达 EOF
和文件访问错误,return 值中是否存在 '\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] = '[=11=]';
我认为将这些解决方案包装到函数中是有意义的,只是这些函数随后会促进用户输入的丢弃。尽管如此,后一个足够长,您很可能会从抽象中受益...
void getln(char *a, size_t a_size) {
if (fgets(a, a_size, stdin) == NULL) {
puts("ERROR: EOF or file access error.");
exit(0);
}
size_t size = strcspn(a, "\n");
if (a[size] != '\n') {
printf("WARNING: MAXIMUM SIZE OF %zu EXCEEDED! LINE TRUNCATED.\n", a_size - 1);
scanf("%*[^\n]");
getchar();
}
a[size] = '[=12=]';
}
... 现在您可以像这样使用它:getln(hs.pc, sizeof hs.pc);
我在使用 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效果不错) 你能解释一下细节并建议我如何修复这个错误吗?谢谢!
已编辑: 这是我的完整代码,接受用户输入并将输入导出回屏幕,这是 运行 在我做了一些小技巧之后,但我决定做一个问题,主要是为了扩充自己的知识^_^ 谢谢大家的解答
#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++;
}
}
您没有清除输入缓冲区。所以在这个换行符将放在给定 scanf
的第一个输入之后。所以 getchar
将获取新行作为输入。所以循环将退出。
在scanf
之后使用这一行。
int c;
if ( scanf("%d",&hs.age) != 1 ) {
printf("Invalid Input\n");retrun 0; }
while((c=getchar()) != '\n' && c != EOF );
它将清除输入缓冲区。然后它会询问用户的第二个输入。
输入 hs.age 后,您按下了 Enter,这是一个 \n 字符。所以你的 getln() 被调用了,但是循环在一次迭代后就被打破了,因为 c 包含 '\n'。如果您打印 hs.pc,您的输出屏幕中将有一个新行。
你的 getln
调用没有被跳过,它把留在 stdin
(输入缓冲区)中的 newline
字符作为它的输入,它读取 '\n'
作为 c
,将其分配给 a[i]
,检查 c
是否为 '\n'
字符并退出。
要解决最初的问题,您需要在调用getln
之前清除输入缓冲区。您可以按照另一个答案中的建议使用 while
循环来执行此操作,或者您可以为 scanf
制作一个适当的格式字符串,它将消耗换行符,清空缓冲区。 (并非万无一失),但替代方案 scanf
是:
scanf(" %d%*c",&hs.age);
这将跳过数字前的所有空格(包括任何换行符),读取十进制值,然后读取并丢弃换行符。 注意:这仅适用于没有尾随字符的数字。输入 13abc
将在 stdin
中留下 bc\n
。这种情况下的 while 循环更灵活,因为它读取所有字符直到遇到换行符,这可能是更好的选择:
scanf(" %d",&hs.age);
while ((c = getchar()) != '\n' && c != EOF);
至于你的getln
函数,只需要将每个字符读入a[i]
即可。没有真正需要 c
。您还需要对您的输入进行相同的检查,以便不留下换行符。您还需要根据 a
减 1 的最大长度检查 i
。我建议您输入的最大字符串长度为 #define MAXS 128
。这将允许测试 i
以防止写入超出字符串末尾的内容。
这是您的 getln
的替代方案。 注意:它是 int
类型,允许它 return 读取的行的长度,因此您可以确定在达到 MAXS
时要做什么(因为那时 stdin
中仍然会有字符)。作为一般规则,如果您在可能出错的情况下在函数中执行某些操作,最好 return 一个指示 success/failure/problem:
#define MAXS 128
...
int getln (char *a)
{
size_t i = 0;
while ((a[i] = getchar()) != '\n' && a[i] != EOF)
{
i++;
if (i == MAXS - 1)
{
a[i] = 0;
break;
}
}
return i;
}
Can you explain me the details ...
我会尽量不使用混淆术语,例如 buffer。
您可能已经知道 "%d"
对应于一组转换为 int
的十进制数字字符。当您按照其他人的建议按 'Enter' 时,'\n'
字符将通过 stdin
传输。 '\n'
不是十进制数字字符,因此它会放回流中供您的 getln
函数稍后发现...
实际上,您的 "getln call right after scanf call" 可能 没有被忽略; 可能 只是阅读尾随 '\n'
并看到一个空行。
这是假设其他问题没有出现。 getln
看不到 a
指向多少字节,所以它无法判断它何时会溢出,因此没有尝试防止缓冲区溢出......你基本上重写了 gets
。如果您的输入足够长,那么我想这也可能导致您的问题......缓冲区溢出是未定义的行为,使用未定义行为的后果是 undefined.
关于未定义行为的主题,由于 getln
在技术上不生成字符串,我希望您以后不要将其用作标准字符串函数的输入...
关于未定义行为的主题,如果用户输入的不是一组十进制数字,您认为会发生什么? scanf
通过 return 值传达输入错误...所以 永远不要忽略 return 值 。您可以(并且应该在某个时候)在 the scanf manual.
... and suggest me how to fix this bug.
丢弃用户输入没有多大意义,但不幸的是,您不能指望一种解决方案可以在不使代码大小(和此解释)超出比例的情况下带来更好的用户体验。
您可以使用 scanf
丢弃十进制数字集后面的行的其余部分(可能只是 '\n'
),如下所示: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
也有失败模式,通过 return 值和数组的内容传达。 return 值用于传达 EOF
和文件访问错误,return 值中是否存在 '\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] = '[=11=]';
我认为将这些解决方案包装到函数中是有意义的,只是这些函数随后会促进用户输入的丢弃。尽管如此,后一个足够长,您很可能会从抽象中受益...
void getln(char *a, size_t a_size) {
if (fgets(a, a_size, stdin) == NULL) {
puts("ERROR: EOF or file access error.");
exit(0);
}
size_t size = strcspn(a, "\n");
if (a[size] != '\n') {
printf("WARNING: MAXIMUM SIZE OF %zu EXCEEDED! LINE TRUNCATED.\n", a_size - 1);
scanf("%*[^\n]");
getchar();
}
a[size] = '[=12=]';
}
... 现在您可以像这样使用它:getln(hs.pc, sizeof hs.pc);