目录
引入
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
char a, b;
scanf("%c%c", &a, &b);
printf("%c %c\n", a, b);
scanf("%c %c", &a, &b);
printf("%c %c\n", a, b);
cin >> a >> b;
cout << '\n' << endl;
cout << a << ' '<< b << endl;
return 0;
}
如果每次输入,都输入"a c",结果是怎么样的?
①
cpp
a c//输入
a c
a c//输入
a c
a c//输入
a c
②
cs
a c//输入
a
a c//输入
a c
a c//输入
a c
③
cpp
a c//输入
a
a c//输入
c a
a c//输入
a c
④
cpp
a c//输入
a
a c//输入
c a
a c//输入
c a
误区
答案是④, 这个情景本来是我今天学c++测试不同的输入输出方式的区别 我一开始幻想的结果是②的类型,可以运行后发现差别有点大,而我又不懂其中的机制,干脆就了解一下。
Ⅰ:scanf内的\n没有换行的作用
这是我一开始犯的错误,我记录一下以免再犯错,这点本身没什么可讲的,了解就可以了。
Ⅱ:空格也算一个字符
在第一次输入"a c"时候,会依次读出'a',' ','c','\n'这四个字符(至于为什么最后会有一个'\n',后续会讲),所以第一次的输出会是 'a'+' '+'\n'(这个'\n'是printf里带有的);
只有 %c(还有 %[ ] 扫描集)会把 \n、空格、Tab 这类空白字符当成普通数据读入变量。
| 格式符 | 是否读取空白(空格 /\n) |
|---|---|
%c |
✅会读取,空白就是普通字符 |
%d/%f |
❌自动跳过空白,不读取空白 |
%s |
❌自动跳过开头空白,遇到空白结束,空白不读入 |
%[ ]扫描集 |
可以配置,能接收换行 / 空格 |
Ⅲ:按下enter后,会添加'\n'在末尾
对于操作系统控制台(黑框)
按下回车产生\n,是告诉操作系统:这一行输入完毕,可以把整行数据交给程序。
你敲一堆字符,不按回车,
scanf是拿不到任何字符的。 从控制台设备角度 :\n触发行输入提交。
但是❗\n本身并没有 "结束输入" 的语义,它只是一个普通字符留在缓冲区里面,不会自动消失。
举例子: 输入:ab 回车 缓冲区:'a' 'b' '\n' \n只是把这一行放行给程序,它自己也留在缓冲区队列里,作为一个实实在在的字符。
对于 scanf 函数本身
❌\n不代表 scanf 读取结束!
%d、%f:读到数字结束就完成读取,后面的\n仅仅留在缓冲区,不会终止函数。%c:把\n当成普通字符,直接读入变量。
c
cs
char a,b;
scanf("%c%c", &a,&b);
输入1然后回车: 缓冲区:'1' , '\n'
- a 拿到
'1' - b 拿到
'\n'scanf 直接执行完毕,不需要你继续输入。 👉这里\n被当成第二个有效字符,不是结束标记,是被读取的数据。
Ⅳ:输入过后剩余的字符不会丢弃
会进入缓冲区,用于后续的读取
第一次输入输出后,就还剩下**'c'+'\n'(enter产生的)** 留在了缓冲区
⚠️缓冲区就相当于一个队列,字符排队,先进先出。
Ⅴ:scanf内部的空白作用
scanf("%c %c")【中间带空格】:空格会吞噬空白;读完第一个字符之后,会把后面所有空白吃掉,再等待下一个有效字符。
第二步:scanf("%c %c", &a, &b);
格式字符串:"%c %c"
两个
%c中间写了一个空格 。 scanf 格式串内的空格规则:消耗任意数量空白字符(空格、Tab、\n),一直读到不是空白才停止。
执行分步:
- 读到格式串第一个
%c,准备读取字符。 - 遇见格式串里的空格 :程序去缓冲区,跳过所有空白。 缓冲区队首现在是
'c'------'c'不是空白!停止跳过。 - 第一个
%c读取字符'c',赋值给a;现在a = 'c'。 - 继续往后走,格式串又遇到一个空格 ,再次触发:吃掉所有空白。 现在缓冲区队列剩下:
['\n'](就是你敲的回车)。'\n'属于空白,被空格规则消耗完毕,缓冲区空了。 - 来到第二个
%c:缓冲区已经空了!scanf 阻塞,等待你键盘输入新字符!
目前缓冲区域还有:**' '**, **'c'**, **'\\n**'**(enter产生)**
Ⅵ:cin的输入也会跳过空白字符
cin >> 对于 char:自动跳过开头所有空白字符 。 缓冲区队列现在:[' ', 'c', '\n']
cin >> a:跳过开头空格,读取'c'存入a;队列剩余:['\n']cin >> b:缓冲区队首是'\n'属于空白,跳过;缓冲区空,阻塞等待键盘输入。
Ⅶ:endl的作用
'\n':输出 1 个换行endl:输出 1 个换行,并且强制刷新缓冲区
👉cout << '\n' << endl; 等于连续输出两次换行! 输出两个换行字符,中间就空出来一整行空白。
| 代码 | 输出行为 |
|---|---|
cout << '\n'; |
输出 1 次换行 |
cout << endl; |
输出 1 次换行 + 刷新 IO 缓冲区 |
所以:
'\n' + endl= 两个换行,这就是看见一大段空隙的根源。
全程过程详解
运行环境:VS Windows 控制台,文本模式,回车送入
\n。
第 1 段:第一次输入 a c 回车
键盘输入:
text
a c
缓冲区进入:
text
a 空格 c \n
第 2 段:执行第一个 scanf("%c%c", &a, &b);
"%c%c" 中间没有空格:
- 第一个
%c读a - 第二个
%c读后面的空格
所以:
cpp
a = 'a';
b = ' ';
缓冲区剩余:
text
c \n
执行:
cpp
printf("%c %c\n", a, b);
输出:
text
a
注意:这里是 a 加一个空格再换行。
第 3 段:执行第二个 scanf("%c %c", &a, &b);
格式串是:
c
"%c %c"
两个 %c 中间有一个空格。
这个空格的作用是:
在读取第二个
%c之前,先吃掉缓冲区里所有连续空白。
当前缓冲区:
text
c \n
执行过程:
- 第一个
%c直接读队首字符c - 所以
a = 'c' - 缓冲区剩余:
\n - 格式串遇到中间的空格,开始清理空白
\n属于空白,被清理掉- 缓冲区变空
- 第二个
%c需要新字符,程序等待输入
注意:
text
第二个 %c 不会读刚才剩下的那个 \n
因为格式串中间的空格已经先把 \n 吃掉了。
第 4 段:第二次输入 a c 回车
你输入:
text
a c
缓冲区进入新字符:
text
a 空格 c \n
第二个 %c 现在读取队首:
cpp
b = 'a';
所以当前变量是:
cpp
a = 'c';
b = 'a';
执行:
cpp
printf("%c %c\n", a, b);
输出:
text
c a
第 5 段:缓冲区现在还剩什么?
刚才第二次输入是:
text
a 空格 c \n
其中 a 已经被第二个 %c 读走,所以缓冲区剩余:
text
空格 c \n
第 6 段:执行 cin >> a >> b;
cin >> 读取 char 时,会自动跳过开头空白。
当前缓冲区:
text
空格 c \n
执行过程:
cin >> a先跳过空格- 然后读取
c - 所以
a = 'c' - 缓冲区剩余:
\n cin >> b看到队首是\n,也跳过- 缓冲区空,程序等待输入
注意:
text
这里 a 被更新成了 'c'
第 7 段:第三次输入 a c 回车
你输入:
text
a c
缓冲区进入:
text
a 空格 c \n
cin >> b 读取队首字符:
cpp
b = 'a';
所以现在变量变成:
cpp
a = 'c';
b = 'a';
不是 a c。
这里是之前文本最关键的错误。
第 8 段:执行输出
cpp
cout << '\n' << endl;
cout << a << ' ' << b << endl;
先看第一句:
cpp
cout << '\n' << endl;
它会输出:
text
一个 \n
再加一个 endl
也就是连续两次换行。
所以控制台会先空出一行。
然后执行:
cpp
cout << a << ' ' << b << endl;
当前:
cpp
a = 'c';
b = 'a';
所以输出:
text
c a
不是 a c。
最终控制台完整结果
text
a c
a
a c
c a
a c
c a
总结
| 图标 | 关键点 | 说明 |
|---|---|---|
| 🔑 | scanf("%c%c") |
无空格 :原样读取,空格、\n全部当作普通字符存入变量 |
| 🔑 | scanf("%c %c") |
中间带空格 :读完第一个字符,在读下一个 % c 之前吞噬全部空白 ,\n不会被读到变量 |
| 📦 | 输入缓冲区 | 先进先出队列,键盘输入回车,往队列存入'\n';%d、%f不会拿走\n,会遗留 |
| ⚠️ | 经典坑 | %d之后直接跟%c:%c会直接读取遗留的'\n',程序不等输入 |
| ✅ | 修复方案 | %c格式里前面加空格:scanf(" %c", &ch);,读取前自动吃掉空白 |
| 🚫 | cout << '\n' << endl; |
\n换行,endl也换行 + 刷新;两个一起写 = 两次换行,产生多余空白行 |
| ✨ | cin >> ch |
自动跳过所有空白(空格、\n、tab),不需要手动加空格 |
小测
阅读下面代码,假设运行在 Windows 环境,按下 Enter 会向输入缓冲区存入'\n',缓冲区先进先出。
c
cs
#include <stdio.h>
int main(void)
{
char x, y;
scanf("%c%c", &x, &y);
printf("%c %c\n", x, y);
scanf("%c %c", &x, &y);
printf("%c %c\n", x, y);
return 0;
}
用户键盘操作: 第一行输入:
m n(m 和 n 之间 3 个空格),回车 第二行输入:s t,回车
请你作答:
- 第一次 printf 输出什么?变量 x、y 分别是什么?执行完第一个 scanf 之后,输入缓冲区剩余哪些字符?
- 第二个 scanf 内部每一步缓冲区如何变化?
- 第二次 printf 输出什么?
:
1.m+空格 m 空格 空格+空格+n+\n
2.先读走一个空格 再删去一个空格 再读走n 在最后面追加s+空格+t+\n
3.空+n
:
答案
输入:第一行 m n(m 后面 3 个空格)+ 回车 缓冲区初始:['m',' ',' ',' ','n','\n']
问题 1
- 第一次
printf输出:m - 变量:
x='m',y=' ' - 第一个 scanf 执行完,缓冲区剩余:
[' ',' ','n','\n']
误区Ⅷ:缓冲区有可用的,不会再读取
问题 2,第二个 scanf("%c %c", &x, &y); 完整过程
起始缓冲区:[' ',' ','n','\n']
- 第一个
%c读取队首空格,x=' ';缓冲区变为:[' ','n','\n'] - 遇到格式串中间的空格:吞噬所有连续空白 ,吃掉剩下的空格;缓冲区变为:
['n','\n'] - 第二个
%c读取'n',y='n';缓冲区变为:['\n']
缓冲区里有
n可用,程序不会阻塞等待输入,第二行输入s t完全不会被读取。
//实测的时候不会让你再输入了
问题 3
第二次 printf("%c %c\n",x,y); x=' ',y='n' 输出: n
解释:打印 x (空格),printf 自带的分隔空格,再打印 n。肉眼看:先一个空格,再一个空格,再字母 n。
感谢观看