使用 fgets 和 sscanf 的意外重复
Unexpected repitition using fgets and sscanf
这是我的部分代码。 gets
和 sscanf
的目的是扫描由一个 space 分隔的三个变量。如果通过,则再次输出指令。否则,输出错误并退出程序。
我想使用 7 长度的字符数组来限制行中的数量,只得到像 'g 3 3' 这样的格式。但是我的代码似乎有问题。
#include <stdio.h>
int main (void) {
char line[7];
char command;
int x, y;
while(1){
/* problem: g 4 4 or g 4 4 can also pass */
fgets(line, 7, stdin);
nargs = sscanf(line, "\n%c %d %d", &command, &x, &y);
if(nargs != 3){
printf("error\n");
return 0;
}
printf("%c %d %d\n", command, x, y);
}
}
意外:
g 4 4
g 4 4
error
预计:
g 4 4
g 4 4
// I can continue type
谁能告诉我为什么它仍然会重复指令?
A directive composed of white-space character(s) is executed by reading input up to the first non-white-space character (which remains unread), or until no more characters can be read.
这说明 \n
指令和两个 space 字符在功能上是相同的:它们将匹配尽可能多的连续白色-space (spaces 、制表符、换行符等),因为它们可以来自输入。
如果你想匹配单个 space(并且只匹配单个 space),我建议使用 %*1[ ]
而不是 white-space 指令。您可以使用 %*1[\n]
类似地丢弃换行符。例如,由于换行符出现在行尾 :
nargs = sscanf(line, "%c%*1[ ]%d%*1[ ]%d%*1[\n]", &command, &x, &y);
不幸的是,这并不能完全解决您的问题,因为 the %d
format specifier is also defined to discard white-space characters:
Input white-space characters (as specified by the isspace
function) are skipped, unless the specification includes a [
, c
, or n
specifier
通过一些聪明的 hack,您可以继续使用 sscanf
(或者更好的是,scanf
没有中间缓冲区),但是在比较了可维护性成本方面的备选方案之后,我们不妨只使用 getchar
,所以如果您正在寻找问题的解决方案而不是您提出的问题的答案,我建议使用 。
你那里的东西不会起作用,因为如果用户输入一两个空格,sscanf()
不会被打扰。
您可以通过利用 short circuiting and by using getchar() 的简单方式解决此问题,例如:
#include <stdio.h>
#include <ctype.h>
#define SIZE 100
int main(void) {
int c, i = 0;
char line[SIZE] = {0};
while ((c = getchar()) != EOF) {
// is the first char an actual character?
if(i == 0 && !isalpha(c)) {
printf("error\n");
return -1;
// do I have two whitespaces in 2nd and 4th position?
} else if((i == 1 || i == 3) && c != ' ') {
printf("error\n");
return -1;
// do I have digits in 3rd and 5th position?
} else if((i == 2 || i == 4) && !isdigit(c)) {
printf("error\n");
return -1;
// I expect that the user hits enter after inputing his command
} else if(i == 5 && c != '\n') {
printf("error\n");
return -1;
// everything went fine, I am done with the input, print it
} else if(i == 5) {
printf("%s\n", line);
}
line[i++] = c;
if(i == 6)
i = 0;
}
return 0;
}
输出:
gsamaras@gsamaras:~$ gcc -Wall px.c
gsamaras@gsamaras:~$ ./a.out
g 4 4
g 4 4
g 4 4
error
你的程序有问题吗? gdb 是你最好的朋友 =)
gcc -g yourProgram.c
gdb ./a.out
break fgets
run
finish
g 4 4
然后单步执行语句,无论何时遇到 scanf 或 printf 只需键入 finish,您会看到程序成功完成了这次迭代,但随后程序没有等待输入,只是打印了错误消息?为什么 ?好类型:
man fgets
fgets 最多读取比 size 少一个,所以在你的情况下,fgets 只允许读取 6 个字符,但你给了它 7 个!是的,换行符是一个类似于 space 的字符,那么第 7 个会发生什么?它将被缓冲,这意味着您的程序将看到缓冲区中有字符并使用它们(本例中为一个字符),而不是从键盘读取。
编辑:您可以通过以下方式使您的程序运行
您可以忽略空行,if ( strccmp(line, "\n") == 0 ) 然后跳转到下一次迭代,如果您不允许使用 strcmp,则解决方法是比较 line[0]=='\ n'.
Can anyone tell me why it will still repeat the instruction?
棘手的部分是 "%d"
消耗前导白色-space,因此代码需要先检测前导白色-space。
" "
消耗 0 个或更多 white-space 并且永不失败。
所以"\n%c %d %d"
没有很好地检测到中间spaces的数量。
如果 int
s 可以超过 1 个字符,使用这个,否则见下面的简化。
使用"%n
检测sscanf()
进度缓冲区中的位置。
它使用 sscanf()
完成工作,这显然是必需的。
// No need for a tiny buffer
char line[80];
if (fgets(line, sizeof line, stdin) == NULL) Handle_EOF();
int n[6];
n[5] = 0;
#define SPACE1 "%n%*1[ ] %n"
#define EOL1 "%n%*1[\n] %n"
// Return value not checked as following `if()` is sufficient to detect scan completion.
// See below comments for details
sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" EOL1,
&command, &n[0], &n[1],
&x, &n[2], &n[3],
&y, &n[4], &n[5]);
// If scan completed to the end with no extra
if (n[5] && line[n[5]] == '[=10=]') {
// Only 1 character between?
if ((n[1] - n[0]) == 1 && (n[3] - n[2]) == 1 && (n[5] - n[4]) == 1) {
Success(command, x, y);
}
}
也许添加测试以确保 command
不是白色的space,但我认为这无论如何都会在命令处理中发生。
如果 int
必须只有 1 个数字,并且 mod 将 答案与上述答案相结合,则可以进行简化。这是有效的,因为每个字段的长度在可接受的答案中是固定的。
// Scan 1 and only 1 space
#define SPACE1 "%*1[ ]"
int n = 0;
// Return value not checked as following `if()` is sufficient to detect scan completion.
sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" "%n", &command, &x, &y, &n);
// Adjust this to accept a final \n or not as desired.
if ((n == 5 && (line[n] == '\n' || line[n] == '[=11=]')) {
Success(command, x, y);
}
@Seb 和我深入研究了检查 sscanf()
的 return 值的需要。尽管 cnt == 3
测试是多余的,因为 n == 5
仅在扫描整行时才为真,并且 sscanf()
returns 3,许多代码检查器可能会举起一个标志,指出未检查 sscanf()
的结果。在使用保存的变量之前不限定 sscanf()
的结果不是健壮的代码。这种方法使用 n == 5
的简单而充分的检查。由于许多代码问题源于未执行任何 限定 ,因此缺少 sscanf()
检查可能会在代码检查器中产生误报。很容易添加冗余检查。
// sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" "%n", &command, &x, &y, &n);
// if (n == 5 && (line[n] == '\n' || line[n] == '[=12=]')) {
int cnt = sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" "%n", &command, &x, &y, &n);
if (cnt == 3 && n == 5 && (line[n] == '\n' || line[n] == '[=12=]')) {
这是我的部分代码。 gets
和 sscanf
的目的是扫描由一个 space 分隔的三个变量。如果通过,则再次输出指令。否则,输出错误并退出程序。
我想使用 7 长度的字符数组来限制行中的数量,只得到像 'g 3 3' 这样的格式。但是我的代码似乎有问题。
#include <stdio.h>
int main (void) {
char line[7];
char command;
int x, y;
while(1){
/* problem: g 4 4 or g 4 4 can also pass */
fgets(line, 7, stdin);
nargs = sscanf(line, "\n%c %d %d", &command, &x, &y);
if(nargs != 3){
printf("error\n");
return 0;
}
printf("%c %d %d\n", command, x, y);
}
}
意外:
g 4 4
g 4 4
error
预计:
g 4 4
g 4 4
// I can continue type
谁能告诉我为什么它仍然会重复指令?
A directive composed of white-space character(s) is executed by reading input up to the first non-white-space character (which remains unread), or until no more characters can be read.
这说明 \n
指令和两个 space 字符在功能上是相同的:它们将匹配尽可能多的连续白色-space (spaces 、制表符、换行符等),因为它们可以来自输入。
如果你想匹配单个 space(并且只匹配单个 space),我建议使用 %*1[ ]
而不是 white-space 指令。您可以使用 %*1[\n]
类似地丢弃换行符。例如,由于换行符出现在行尾 :
nargs = sscanf(line, "%c%*1[ ]%d%*1[ ]%d%*1[\n]", &command, &x, &y);
不幸的是,这并不能完全解决您的问题,因为 the %d
format specifier is also defined to discard white-space characters:
Input white-space characters (as specified by the
isspace
function) are skipped, unless the specification includes a[
,c
, orn
specifier
通过一些聪明的 hack,您可以继续使用 sscanf
(或者更好的是,scanf
没有中间缓冲区),但是在比较了可维护性成本方面的备选方案之后,我们不妨只使用 getchar
,所以如果您正在寻找问题的解决方案而不是您提出的问题的答案,我建议使用
你那里的东西不会起作用,因为如果用户输入一两个空格,sscanf()
不会被打扰。
您可以通过利用 short circuiting and by using getchar() 的简单方式解决此问题,例如:
#include <stdio.h>
#include <ctype.h>
#define SIZE 100
int main(void) {
int c, i = 0;
char line[SIZE] = {0};
while ((c = getchar()) != EOF) {
// is the first char an actual character?
if(i == 0 && !isalpha(c)) {
printf("error\n");
return -1;
// do I have two whitespaces in 2nd and 4th position?
} else if((i == 1 || i == 3) && c != ' ') {
printf("error\n");
return -1;
// do I have digits in 3rd and 5th position?
} else if((i == 2 || i == 4) && !isdigit(c)) {
printf("error\n");
return -1;
// I expect that the user hits enter after inputing his command
} else if(i == 5 && c != '\n') {
printf("error\n");
return -1;
// everything went fine, I am done with the input, print it
} else if(i == 5) {
printf("%s\n", line);
}
line[i++] = c;
if(i == 6)
i = 0;
}
return 0;
}
输出:
gsamaras@gsamaras:~$ gcc -Wall px.c
gsamaras@gsamaras:~$ ./a.out
g 4 4
g 4 4
g 4 4
error
你的程序有问题吗? gdb 是你最好的朋友 =)
gcc -g yourProgram.c
gdb ./a.out
break fgets
run
finish
g 4 4
然后单步执行语句,无论何时遇到 scanf 或 printf 只需键入 finish,您会看到程序成功完成了这次迭代,但随后程序没有等待输入,只是打印了错误消息?为什么 ?好类型:
man fgets
fgets 最多读取比 size 少一个,所以在你的情况下,fgets 只允许读取 6 个字符,但你给了它 7 个!是的,换行符是一个类似于 space 的字符,那么第 7 个会发生什么?它将被缓冲,这意味着您的程序将看到缓冲区中有字符并使用它们(本例中为一个字符),而不是从键盘读取。
编辑:您可以通过以下方式使您的程序运行
您可以忽略空行,if ( strccmp(line, "\n") == 0 ) 然后跳转到下一次迭代,如果您不允许使用 strcmp,则解决方法是比较 line[0]=='\ n'.
Can anyone tell me why it will still repeat the instruction?
棘手的部分是 "%d"
消耗前导白色-space,因此代码需要先检测前导白色-space。
" "
消耗 0 个或更多 white-space 并且永不失败。
所以"\n%c %d %d"
没有很好地检测到中间spaces的数量。
如果 int
s 可以超过 1 个字符,使用这个,否则见下面的简化。
使用"%n
检测sscanf()
进度缓冲区中的位置。
它使用 sscanf()
完成工作,这显然是必需的。
// No need for a tiny buffer
char line[80];
if (fgets(line, sizeof line, stdin) == NULL) Handle_EOF();
int n[6];
n[5] = 0;
#define SPACE1 "%n%*1[ ] %n"
#define EOL1 "%n%*1[\n] %n"
// Return value not checked as following `if()` is sufficient to detect scan completion.
// See below comments for details
sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" EOL1,
&command, &n[0], &n[1],
&x, &n[2], &n[3],
&y, &n[4], &n[5]);
// If scan completed to the end with no extra
if (n[5] && line[n[5]] == '[=10=]') {
// Only 1 character between?
if ((n[1] - n[0]) == 1 && (n[3] - n[2]) == 1 && (n[5] - n[4]) == 1) {
Success(command, x, y);
}
}
也许添加测试以确保 command
不是白色的space,但我认为这无论如何都会在命令处理中发生。
如果 int
必须只有 1 个数字,并且 mod 将
// Scan 1 and only 1 space
#define SPACE1 "%*1[ ]"
int n = 0;
// Return value not checked as following `if()` is sufficient to detect scan completion.
sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" "%n", &command, &x, &y, &n);
// Adjust this to accept a final \n or not as desired.
if ((n == 5 && (line[n] == '\n' || line[n] == '[=11=]')) {
Success(command, x, y);
}
@Seb 和我深入研究了检查 sscanf()
的 return 值的需要。尽管 cnt == 3
测试是多余的,因为 n == 5
仅在扫描整行时才为真,并且 sscanf()
returns 3,许多代码检查器可能会举起一个标志,指出未检查 sscanf()
的结果。在使用保存的变量之前不限定 sscanf()
的结果不是健壮的代码。这种方法使用 n == 5
的简单而充分的检查。由于许多代码问题源于未执行任何 限定 ,因此缺少 sscanf()
检查可能会在代码检查器中产生误报。很容易添加冗余检查。
// sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" "%n", &command, &x, &y, &n);
// if (n == 5 && (line[n] == '\n' || line[n] == '[=12=]')) {
int cnt = sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" "%n", &command, &x, &y, &n);
if (cnt == 3 && n == 5 && (line[n] == '\n' || line[n] == '[=12=]')) {