scanf 与空白字符

1 minute read

Published:

引入

在 C 语言的 printf 中,我们常常使用\n\t (空格) 等空白字符,而这些字符在 scanf 中如何处理一般没有提及(当然,在一般情况下我们也并不会这么写). 在 C 99 标准中,规定:

空白字符:任何格式字符串中的单个空白字符处理所有来自输入的可用连续空白字符(如同通过于循环中调用 isspace 确定). 注意,格式字符串中 \n \t\t 或其他空白无区别.1

在 C 99 中,标准空白字符包括:

  • 空格 (0x20)
  • 换页 (0x2c) \f
  • 换行 (0x0a) \n
  • 回车 (0x0d) \r
  • 水平制表符 (0x09) \t
  • 垂直制表符 (0x0b) \v

当然,我们一般只使用 \n\t 这三个. 因此在后文的例子中我们只使用这三个空白字符.

前文的介绍可能比较抽象,简而言之:在 scanf 中,\n\t 以及上述标准空白字符均等价,且均会匹配任意数量的空白字符。具体行为可看下面的具体例子:

例子

在下面所有例子中, 表示输入一个空格(单纯空格可能在网页中显示不够明显),\n 表示输入一个回车,\t 表示输入一个制表符(Tab),同时,可能会有编译器在退出前不会刷新 stdout 缓冲区,导致下面程序的结果无法正常显示,因此可以在下面程序的 return 0 前加上一行 printf("\n").

例一

#include <stdio.h>
int main() {
    int a,b;
    scanf("%d %d",&a,&b);
    printf("a=%d b=%d",a,b);
    return 0;
}

输入:123\n456\n

输出:a=123 b=456

这里的分析很简单,由于 \n\t 在 scanf 中等价,因此两个 %d 中的空格匹配了 \n.

例二

#include <stdio.h>
int main() {
    int a,b;
    scanf("%d %d",&a,&b);
    printf("a=%d b=%d",a,b);
    return 0;
}

输入:123\n\n空空456\n

输出:a=123 b=456

这个例子相比于上个例子的区别在于在两个数字的输入之中有多个空白字符. 由于 scanf 中的空白字符可以匹配任意数量的空白字符,因此两个 %d 中的空格匹配了 \n\n空空 四个空白字符.

例三

#include <stdio.h>
int main() {
    int a,b;
    scanf("%d\n\n%d",&a,&b);
    printf("a=%d b=%d",a,b);
    return 0;
}

输入:123空456\n

输出:a=123 b=456

在这个例子中,scanf 有两个 \n,但输入中只有一个空格,因此两个 \n 匹配了一个 ,换句话说,scanf 中的空白字符不一定匹配缓冲区中的空白字符. 另一个例子如下:

#include <stdio.h>
int main() {
    int a;
    char b;
    scanf("%d\n%c",&a,&b);
    printf("a=%d b=%c",a,b);
    return 0;
}

输入:123a\n

输出:a=123 b=a

在这里 scanf 中的 \n 不匹配任何空白字符

例四

#include <stdio.h>
int main() {
    int a;
    scanf("%d ",&a); // %d 后有一个空格
    printf("a=%d",a);
    return 0;
}

输入:123\n

这里我们可能会认为当输入 \n 后程序会马上显示输出. 但实际上我们会发现程序仍在等待我们的输入,即使我们在多输入几次 \n 或者 \t 或者 也仍在等待我们输入. 这是因为在 scanf 中最后的那一个空格会匹配任意数量的空白字符,而 scanf 并不知道要匹配多少个空白字符,因此一直在等待输入(注意 \t\n 均为空白字符)当我们在上述输入后再次输入带有非空白字符的内容:a\n

此时程序输出 a=123,缓冲区还剩下 a\n,可以用如下程序进行验证:

#include <stdio.h>
int main() {
    int a;
    char c;
    scanf("%d ",&a); // %d 后有一个空格
    printf("a=%d\n",a);
    // 打印缓冲区中所有内容的 ASCII 码
    do {
        c=getchar();
        printf("%d ",c);
    } while(c!='\n');
    return 0;
}

输入:123\n (此时无输出)

再次输入:a\n,输出:

a=123
97 10

其中 97a 的 ASCII 码,10\n 的 ASCII 码.

这个情况在代码编写中很容易出现,由于误在 scanf 的末尾多输入一个空格,导致程序并没有按照我们所设想的情况进行输出.

例五

#include <stdio.h>
int main() {
    int a;
    char b;
    scanf("%d ",&a); // %d 后有一个空格
    scanf("%c",&b);
    printf("a=%d b=%c",a,b);
    return 0;
}

输入:123\n\n空a\n

输出:a=123 b=a

在此时,第一个 scanf 中的空格匹配了 \n\n空(由于 也是空白字符,因此也被匹配),因此 %c 匹配 a.

这里需要和下面程序进行对比:

#include <stdio.h>
int main() {
    int a;
    char b;
    scanf("%d",&a);
    scanf("%c",&b);
    printf("a=%d b=%c",a,b);
    return 0;
}

输入:123空a\n

输出:a=123 b=

这里 %c 匹配了 ,此时缓冲区还剩下 a\n.