目录
引入
误区
Ⅰ:scanf内的\n没有换行的作用
Ⅱ:空格也算一个字符
Ⅲ:按下enter后,会添加‘\n’在末尾
Ⅳ:输入过后剩余的字符不会丢弃
Ⅴ:scanf内部的空白作用
Ⅵ:cin的输入也会跳过空白字符
Ⅶ:endl的作用
全程过程详解
总结
小测
答案
误区Ⅷ:缓冲区有可用的,不会再读取
引入
#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”,结果是怎么样的?
①
a c//输入 a c a c//输入 a c a c//输入 a c②
a c//输入 a a c//输入 a c a c//输入 a c③
a c//输入 a a c//输入 c a a c//输入 a c④
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
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 \ncin >> 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
#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。
感谢观看