【问题标题】:How to get 3 chars entered with spaces in scanf into a char array?如何将scanf中带有空格的3个字符输入到字符数组中?
【发布时间】:2015-11-13 10:55:07
【问题描述】:

我以前是一名 java 程序员,但我现在正在大学学习 C 课程(计算机科学专业)。

我需要用户能够输入 3 个字符,前 2 个是数字,最后 1 个是 'v' 或 'h'。 例如“1 2 v”。

我需要用户能够在每个字符之间输入空格。

这是我当前的代码:

void manageInput(char box[][width]){
    char move[4];
    char input[16]; 
    while(1){

            scanf("%s", input);
            int i = 0;
            while(input[i] != 0){

                    if(input[i] != ' ' && input[i] != "\n"){
                            move[i] = input[i];
                    }
                    i++;
            }
            printf("%s\n", move);

            makeMove(box, move);
            printBox(box, height, width);
            // TODO
            if(move[0] == 'x'){
                    exit(0);
            }
    }

}

但是,如果我运行它,当我输入没有空格的字符(如“12v”)时它可以正常工作,但如果我输入“1 2 v”,它将打印出“1”,调用 printBox,然后打印出“ 2",然后再次打印出框,以此类推。

如果有人能解释我在这里做错了什么,我将不胜感激。

【问题讨论】:

  • 那么是数字还是char/字符?这在 Java 中也应该有所不同。并限制 scanf 输入的长度,否则会遇到 未定义的行为
  • char move[4] = {0};scanf(" %c %c %c", &move[0], &move[1],&move[2]);
  • "输入 3 个字符,前 2 个是数字,后 1 个是 'v' 或 'h'。例如 "1 2 v"。"是误导。 “1 2 v”是 5 个char,如果算上“输入”,还有 1 个。显然代码需要读取3个非空白char
  • 当使用带有 '%s' 格式说明符的 scanf()(和系列)时,为避免输入缓冲区溢出(如用户,在这种情况下,输入 16 个或更多字符),始终包含最大长度修饰符,它比输入缓冲区的长度小 1。并始终检查返回值(不是参数值)以确保操作成功

标签: c scanf


【解决方案1】:

如果有人能解释我在这里做错了什么,我将不胜感激。

简短的故事是:您的代码不符合您的要求。它根本不会做你想做的事。

您的要求是:

  • 所有字段都必须是一个字符。您的代码未满足此要求。您的代码将错误地接受每个字段的多个字符。
  • 字段之间必须有一个空格(正好是一个空格?)。您的代码未满足此要求。字段之间可能有多个空格,您的代码会错误地接受。

事实上,您的代码通过越界访问 move 数组来调用未定义的行为。 考虑到上述情况之一的结果,i 可能会变为高于 3 的某个值. 这段代码可能会发生什么:move[i] = input[i];?

您的代码也太复杂了。您的所有功能都可以由scanf 单独执行。这是一个非常强大的功能,当您知道如何正确使用它时...建议您有机会时多阅读和理解the manual。你会学到很多

我注意到您在所展示的逻辑中忽略了一些内容:预计第一个字段也可能是 'x',它对应于 exit 用例。这是一个糟糕的设计;来电者没有机会清理......但我会带着它跑。你真的应该使用return(并返回一个int值或其他东西,对应于错误/成功)。

让我们抛开最后一段,因为我们可以简单地将'x' 视为无效输入(并因此退出),我不想更改您的函数的合同;我会把它留给你。到目前为止描述的表达式似乎是int x = scanf("%1[0123456789]%*1[ ]%1[0123456789]%*1[ ]%1[vh]", a, b, c);

请注意,预计abc 将有足够的空间来存储一个字节长度的字符串。也就是说,他们的声明应该是这样的:char a[2], b[2], c[2];

确保检查返回值(在示例中为x)! 如果x 为3,则可以安全地假设三个变量abc 可以安全使用。如果x 为2,则可以安全地假设ab 可以安全使用,依此类推...如果xEOF0,则它们都不能安全使用.

通过检查返回值,您可以拒绝与该精确模式不匹配的输入,即:

  • 宽度不完全为一个字节的字段将被拒绝。
  • 太多或太少的空格都会被拒绝。

弹出了一些您忽略提及的内容,并且它也出现在您的代码中: Chux 提到您可能希望输入以 '\n'(换行符)字符终止。这也可以使用scanf以多种方式实现:

  • scanf("%1*[\n]"); 将尝试准确地读取并丢弃一个 '\n' 字符,但无法确保成功。 getchar 更适合此目的;类似if (getchar() != '\n') { exit(EXIT_FAILURE); } 的东西可能是有道理的,如果你想确保输入的行是完美的,并且当它们不是时被轰掉...#define BOMB_OUT?
  • scanf("%*[^\n]"); scanf("%*c"); 更有意义;如果您有兴趣阅读每行一个项目,那么丢弃该行中剩余的所有内容以及换行符本身是有意义的。请注意,您的程序应该在丢弃或截断输入时始终告诉用户。您也可以为此使用 getchar

void manageInput(char box[][width]){
    for (;;) {
        char a[2], b[2], c[2];
        int x = scanf("%1[0123456789]%*1[ ]%1[0123456789]%*1[ ]%1[vh]", a, b, c);
        if (x != 3) {
            /* INVALID INPUT should cause an error value to be returned!
             * However, this function has no return value (which makes it
             * poorly designed)... Calling `exit` gives no opportunity for
             * calling code to clean up :(
             */
            exit(EXIT_FAILURE);
        }

        if (getchar() != '\n') {
#           ifdef BOMB_OUT
            exit(EXIT_FAILURE);
#           else
            scanf("%*[^\n]");
            getchar();
            puts("NOTE: Excess input has been discarded.");
#           endif
        }

        char move[4] = { a[0], b[0], c[0] };
        printf("%s\n", move);
        makeMove(box, move);
        printBox(box, height, width);
        // TODO
        if(move[0] == 'x'){
            exit(0);
        }
    }
}

【讨论】:

  • 虽然我理解这个问题,@chux,但在前面添加一个空格将违反 OP 指定的要求。修改后我会在一个小时左右 ping 你...
  • @chux 感谢您的意见...您觉得如何?
【解决方案2】:

这看起来像两个简单的错误。

你需要为 move[] 和 input[] 使用单独的索引

    int i = 0;
    while(input[i] != 0){

            if(input[i] != ' ' && input[i] != "\n"){
                    move[i] = input[i];
            }
            i++;
    }

想象一下 1 2 v 的输入

input[0] != 0,所以我们进入循环

也不是 ' ' 或 '\n',所以我们将 input[0] 复制到 move[0]

到目前为止一切顺利

你增加 i,然后发现 input[1] == ' '

但是你又增加了 i

您发现您对 input[2] (2) 感兴趣 - 因此您将其复制到 move[2],而不是 move[1]。糟糕!

然后更糟糕的是,您永远不会在 move[] 的最后一个有效字符之后放置字符串结束字符。

【讨论】:

  • 另外,如上所述,input[i] 与 "\n" 的比较是不正确的。它应该是'\n'
【解决方案3】:

%s 使用 scanf 读取一个以空格分隔的字符串,所以如果这不是您想要的,那就不要使用它。 %c 读取单个字符,但不跳过空格,因此您可能还希望格式中的 (空格)跳过空格:

char input[3];

scanf(" %c %c %c", intput, input+1, input+2);

将读取 3 个非空白字符并跳过它们之前或之间的任何空白。您还应该检查 scanf 的返回值以确保它为 3 - 如果不是,则在到达文件结尾之前输入中的字符少于 3 个。

【讨论】:

  • 这是一个很好的信息,但它需要工作......似乎你付出了最小的努力来获得最大的分数。正如您稍后所说,需要检查 scanf 的返回值;你目前的例子是一个糟糕的例子。此外,这仅回答了问题的一半,尽管这似乎是朝着正确方向迈出的良好一步,但如果我们准确地解释 OPs 要求,那么第一个字段不需要前导空格,并且每个其他字段都需要分隔符正好一个空格字符(不是换行符或制表符)。
  • @Freenode-newbostonSebivor:OP 没有列出任何要求,只列出了两个应该工作的示例输入(12v1 2 v)。获得一个字符之间恰好有一个空格的“要求”即使不是完全错误也是迟钝的。
【解决方案4】:

由于潜在的缓冲区溢出,通过scanf 读取字符串通常是个坏主意。考虑使用fscanf 或更好的fgets,如

fgets(input, 15, stdin);

注意 '\0' 的额外字节。


此外,您在此处将 char 与字符串进行比较:input[i] != "\n"。它应该是input[i] != '\n'


顺便说一句,你可以使用类似的东西

int x, y;
char d;
scanf("%d%d%c", &x, &y, &d);

【讨论】:

  • "注意 '\0' 的额外字节。" fgets() 不需要。推荐fgets(input, sizeof input, stdin);
  • scanf("%d%d%c", &x, &y, &d);在 OP 的 "1 2 v" 中正确扫描。
  • 不幸的是,建议不要使用scanf 是很常见的,因为它很容易被误用,即使它是工作的正确工具。按照这种逻辑,建议不要完全使用 C 是有道理的。这种逻辑不属于这个问题的答案。
  • 此外,以比fscanf 更不安全为幌子反对使用scanf 是没有根据的;对此没有合乎逻辑的解释。 xy 可能包含多个数字。如果我可以建议,从一个已经学会如何安全使用scanf 的人那里,将xy 声明为unsigned int 并使用%1u(这是一个一个,而不是一个); %1d 会导致 UB 关于负值。这些指令都没有强制执行“字段之间有一个空格”的要求;他们可能会丢弃多个空白字符('\n''\t' 等)...并且始终检查返回值!
猜你喜欢
  • 2012-11-11
  • 1970-01-01
  • 1970-01-01
  • 2021-10-23
  • 2017-09-06
  • 2016-02-23
  • 2011-09-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多