Skip to content

机试读入:字符串与混合格式

场景引入

题目要求先读一个整数 n,再读一个字符 c。你老老实实写了两行 scanf,结果 c 里存的既不是字母也不是数字,程序后面的分支全走偏了。

问题出在换行符上。你按下回车时,那个 \n 也进了输入缓冲区,scanf("%d") 读走数字后把它留在了原地,接着 scanf("%c") 就把它当成了要读的字符。

数字、字符、整行三种读入方式对空白字符的处理规则各不相同,混用时的边界就是机试里最容易踩空的地方。这一篇把规则说清楚。

三种读入方式,三种空白规则

先建立一张对照,后面所有的坑都是从这里推出来的。

读法开头的空白什么时候停换行符去哪了
scanf("%d") / cin >> x自动跳过遇到空白留在缓冲区
scanf("%c")不跳过读满一个字符可能就是它读到的
scanf("%s") / cin >> s自动跳过遇到空白留在缓冲区
fgets / getline不跳过遇到换行被读走

三种方式里,只有 %c 和整行读入会看见空白字符。串行问题就出在「前一个读法把换行留下了,后一个读法看得见它」的交界处。

一、数字后面读字符:加一个前导空格

c
int n;
char c;
scanf("%d", &n);
scanf(" %c", &c);   // 格式串里的空格:跳过任意多个空白

scanf 格式串里的一个空格,含义是「跳过任意数量的空白字符,包括空格、制表符和换行」。加上它,之前遗留的 \n 就被吃掉了。

这个写法的代价接近于零,所以读单个字符时默认写 " %c",不用每次去推有没有遗留换行。

C++ 侧对应的问题出现在 cin >> n 之后用 cin.get(c),解决办法一样是先跳空白,或者直接用 cin >> c>>char 时会跳过空白)。

二、读带空格的一整行

scanf("%s")cin >> s 遇到空格就停,读不了 hello world 这样的一行。

C 用 fgets

c
char line[1005];
fgets(line, sizeof(line), stdin);
line[strcspn(line, "\r\n")] = '\0';   // 去掉行尾的换行

两点说明:

  • fgets 会把换行符一起读进来。strcspn(line, "\r\n") 返回第一个 \r\n 的下标,把那里改成 \0 就截断了。同时写 \r\n 是为了兼容 Windows 风格的换行,测试数据用哪种都不影响。
  • gets 从 C11 起已经被移除,别再用。它无法限制读入长度,缓冲区多大都可能被写穿。

C++ 用 getline

cpp
string line;
getline(cin, line);

三、>>getline 混用:先把换行吞掉

这是 C++ 侧容易撞上的串行。

cpp
int n;
cin >> n;
string line;
getline(cin, line);   // line 是空的

cin >> n 把数字读走,换行留在缓冲区;getline 一上来就撞见换行,于是认为「这一行到此为止」,返回一个空串。

两种修法:

cpp
cin >> n;
cin.ignore();                 // 丢掉一个字符,多数情况够用
getline(cin, line);
cpp
#include <limits>
cin >> n;
cin.ignore(numeric_limits<streamsize>::max(), '\n');   // 丢到行尾,更稳
getline(cin, line);

第二种能处理「数字后面还跟着若干空格才换行」的情况。两种都行,第二种不用想边界。

四、字符矩阵:一行一个 %s

迷宫、棋盘、图形类的题,输入常是这样:

5 5
#####
#...#
#.#.#
#...#
#####

矩阵里没有空格,所以可以一行当一个字符串读%s 会自动跳过前面遗留的换行:

c
int n, m;
char g[105][105];
scanf("%d %d", &n, &m);
for (int i = 0; i < n; i++) {
    scanf("%s", g[i]);      // 第 i 行整行读进 g[i]
}
// 访问用 g[i][j],天然是二维的

这个写法把「跳过换行」的事情交给了 %s,省掉了所有 getchar 清缓冲的操作。数组第二维要开到 m + 1 以上,给结尾的 \0 留位置。

如果矩阵里空格(比如用空格表示空地),%s 就会在空格处断掉,这时候改用整行读:

c
for (int i = 0; i < n; i++) {
    fgets(g[i], sizeof(g[i]), stdin);
    g[i][strcspn(g[i], "\r\n")] = '\0';
}

注意这里有个交界:scanf("%d %d", &n, &m) 之后缓冲区里还留着换行,第一次 fgets 会读到一个空行。在循环前先补一次 getchar() 把它吃掉,或者干脆用 fgets 读第一行再 sscanf 解析出 n 和 m,全程只用一种读法。

全程只用一种读法是绕开这类问题最省心的策略:要么全 scanf,要么全整行读加 sscanf。混用才需要在每个交界处想清楚换行在谁手里。

五、跳过不需要的字段

题面给了五列数据,你只关心第二列和第五列。不用把它们都读进变量,%* 表示「按这个格式读,但不赋值」:

c
scanf("%*d %d %*d %*d %d", &b, &e);

C++ 里没有直接对应的写法,读进临时变量丢掉即可。

延伸阅读

面试算法可视化图解