使用fgets和sscanf进行意外重复

这是我的代码的一部分。 getssscanf的目的是扫描由一个空格分隔的三个变量。 如果通过,则再次输出指令。 否则,输出错误并退出程序。

我想使用7长度字符数组来限制行中的数字,只获得类似’g 3 3’的格式。 但是我的代码似乎有些不对劲。

 #include  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 

谁能告诉我它为什么还会重复这个指令?

根据C11标准,7.21.6.2p5 :

由白色空格字符组成的指令通过读取第一个非空白字符(仍然未读取)的输入来执行,或者直到不再能够读取字符为止。

这描述了\n指令和两个空格字符在function上是相同的:它们可以从输入中匹配尽可能多的连续空格(空格,制表符,换行符等)。

如果你想匹配一个空格(只有一个空格),我建议使用%*1[ ]而不是white-space指令。 您可以使用%*1[\n]来同样丢弃换行符。 例如,由于换行符出现在一行的末尾

 nargs = sscanf(line, "%c%*1[ ]%d%*1[ ]%d%*1[\n]", &command, &x, &y); 

遗憾的是,这并不能完全解决您的问题,因为%d格式说明符也被定义为丢弃空白字符 :

除非规范包含[cn说明符],否则将跳过输入空白字符(由isspace函数指定)

有了一些聪明的黑客,你可能能够继续使用sscanf (或者更好的是,没有中间缓冲区的scanf ),但在比较可维护性成本方面的替代方案后,我们不妨只使用getchar ,所以如果你是寻找问题的解决方案,而不是回答你提出的问题,我建议gsamaras回答 。

你所拥有的将无法工作,因为如果用户输入一个或两个空格, sscanf()将不会受到打扰。

你可以通过利用短路和使用getchar()来简单地解决这个问题,如下所示:

 #include  #include  #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 

谁能告诉我它为什么还会重复这个指令?

棘手的部分是"%d"消耗领先的空白区域,因此代码需要首先检测前导空白区域。

" "消耗0或更多的空白区域,永不失败。

因此, "\n%c %d %d"无法很好地检测到插入空间的数量。


如果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]] == '\0') { // 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不是空格,但我认为无论如何在命令处理中都会发生。


如果int s必须只有1位数并且使用mod将@Seb答案与上述结合,则可以进行简化。 这是有效的,因为每个字段的长度在可接受的答案中是固定的。

 // 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] == '\0')) { Success(command, x, y); } 

@Seb和我都需要检查sscanf()的返回值。 虽然cnt == 3测试是多余的,因为n == 5只有当扫描整行并且sscanf()返回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] == '\0')) { int cnt = sscanf(line, "%c" SPACE1 "%d" SPACE1 "%d" "%n", &command, &x, &y, &n); if (cnt == 3 && n == 5 && (line[n] == '\n' || line[n] == '\0')) { 

你有程序问题吗? gdb是你最好的朋友=)

 gcc -g yourProgram.c gdb ./a.out break fgets run finish g 4 4 

然后单步执行语句,每当遇到scanf或printf只输入完成时,你会看到程序是否成功完成了这个迭代但是程序没有等待输入而只是打印错误信息? 为什么? 井型:

 man fgets 

fgets读取最多只比大小少一个,所以在你的情况下,fgets只允许读取6个字符,但你给它7个! 是的,换行符就像空格一样,所以7号会发生什么? 它将被缓冲,这意味着您的程序将不会从键盘读取,而是会看到缓冲区中有字符并将使用它们(本例中为一个字符)。 编辑:您可以采取以下措施使您的计划正常运作
你可以忽略空行,if(strccmp(line,“\ n”)== 0)然后跳转到下一次迭代,如果不允许使用strcmp,则解决方法是比较line [0] ==’\ N”。