如何在格式错误的输入中正确使用带循环的 sscanf?

How to use sscanf with loops correctly with ill-formatted input?

我正在尝试从标准输入读取堆栈数据结构的命令,有效命令是 "push [number]" 和 "pop",这是我的代码:

while(getline(&input, &len, stdin) > 0){
while ((n = sscanf(input,"%64s%d%n",cmd,&num,&offset)) > 0){
    if (n == 1){
        if (!strcmp("pop",cmd)){
            pop(&head);
        } else if (!strcmp("push",cmd)){
            //Doing something
        } else if (isNumeric(cmd) && need_num){
            push(&head, num);
        } else{
            //error
        }
    }
    else if (n == 2){
        if (!strcmp("push",cmd)){
            push(&head, num);
        } else {
            //error
        }
    }
    else {
        //error
    }
    input += offset;
}
}

当然,这个程序有很多缺陷,因为我不熟悉带有循环的 sscanf。第一个问题是,如果我在一行中读入一系列命令,例如:

push 1 push 2 pop  push 3

实际上会报错,因为push后面有number,pop后面没有number,这样,这行代码对于"pop"命令是错误的:

input += offset;

但我不知道如何解决这个问题。 另一个问题是,如果我认为将 "push [number]" 命令分成两行是可以接受的:

push 5 pop push
4

我不知道是否有比我所做的更简单的方法来确定该行是否以 push 结束并且以下行以数字开头:

else if (!strcmp("push",cmd)){
        //Doing something
    } else if (isNumeric(cmd) && need_num){
        push(&head, num);
    }

如有任何帮助,我们将不胜感激!

如果大小不变,您可以在 char **char tab[][] 中使用 " " 作为分隔符拆分您的行。

然后你会检查你的 tab[i] 是否是一个数字,然后根据需要使用它,或者如果 (tab[i+1]) 后面跟着一个数字,则为 « push »,否则为 «弹出 ».

嗯,您在使用过程中没有任何明显的错误,但有一些细微的错误给您带来了问题。

您遇到的主要问题是在转换字符串和整数后只取一个偏移量。在 "pop" 的情况下, 匹配失败 发生在 %d 之前 你到达 %n您的 格式字符串 导致 n 保持未设置状态(保留最后一个设置值 - 在这种情况下导致 offset 的增量太大)

相反,您需要在格式字符串中使用两个这样的检查,例如:

    while ((rtn = sscanf (input, "%63s %n%d %n", 
                            cmd, &off1, &num, &off2)) > 0) {

这样,如果读取 "pop",则在 off1 中有适当的偏移量,如果读取 "push num",则在 off2 中有适当的偏移量。 (每个 %n 之前的附加空格是可选的(但建议)。使用 %s%d 前导空格被消耗——但最好养成考虑它的习惯,因为%[...]%c 不消耗前导空白。

将它放在一个简短的例子中,你可以做类似下面的事情:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXCMD 64   /* max characters for command */

int main (void) {

    char *buf = NULL;   /* buffer for getline */
    size_t n = 0;       /* alloc size (0 getline decides) */
    ssize_t nchr = 0;   /* getline returns (no. of chars read */

    while ((nchr = getline (&buf, &n, stdin)) > 0) {    /* read each line */
        char cmd[MAXCMD] = "",  /* buffer for command */
            *input = buf;       /* pointer to advance */
        int num = 0,            /* number to push */
            off1 = 0,           /* offset if single conversion */
            off2 = 0,           /* offset if double conversion */
            rtn = 0;            /* sscanf return */
        while ((rtn = sscanf (input, "%63s %n%d %n", 
                                cmd, &off1, &num, &off2)) > 0) {
            switch (rtn) {      /* switch on sscanf return */
                case 1:         /* handle "pop" case */
                    if (strcmp (cmd, "pop") == 0) {
                        printf ("pop\n");
                        input += off1;  /* set offset based on off1 */
                    }
                    else
                        fprintf (stderr, "error: invalid single cmd '%s'.\n",
                                        cmd);
                    break;
                case 2:         /* handle "push num" case */
                    if (strcmp (cmd, "push") == 0) {
                        printf ("push %d\n", num);
                        input += off2;  /* set offset based on off2 */
                    }
                    else
                        fprintf (stderr, "error: invalid single cmd '%s'.\n",
                                cmd);
                    break;
                default:
                    fprintf (stderr, "error: invalid input.\n");
                    break;
            }
        }
    }
    free (buf);     /* free memory allocated by getline */

    return 0;
}

(注:我使用了switch()语句,但如果你愿意,可以随意使用if, else if, else)

示例Use/Output

验证案例:

$ echo "pop pop push 1 push 2 pop push 3 pop" | ./bin/sscanfpushpop
pop
pop
push 1
push 2
pop
push 3
pop

$ echo "push 5 pop push 6 push 7 pop push 8" | ./bin/sscanfpushpop
push 5
pop
push 6
push 7
pop
push 8

检查一下,如果您有任何其他问题,请告诉我。