scanf与cout的误区:探究其内部的机制

目录

引入

误区

Ⅰ:scanf内的\n没有换行的作用

Ⅱ:空格也算一个字符

Ⅲ:按下enter后,会添加'\n'在末尾

Ⅳ:输入过后剩余的字符不会丢弃

Ⅴ:scanf内部的空白作用

Ⅵ:cin的输入也会跳过空白字符

Ⅶ:endl的作用

全程过程详解

总结

小测

答案

误区Ⅷ:缓冲区有可用的,不会再读取


引入

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),一直读到不是空白才停止

执行分步:

  1. 读到格式串第一个%c,准备读取字符。
  2. 遇见格式串里的空格 :程序去缓冲区,跳过所有空白。 缓冲区队首现在是'c'------'c'不是空白!停止跳过。
  3. 第一个%c读取字符'c',赋值给a;现在a = 'c'
  4. 继续往后走,格式串又遇到一个空格 ,再次触发:吃掉所有空白。 现在缓冲区队列剩下:['\n'](就是你敲的回车)。 '\n'属于空白,被空格规则消耗完毕,缓冲区空了。
  5. 来到第二个%c缓冲区已经空了!scanf 阻塞,等待你键盘输入新字符!

目前缓冲区域还有:**' '**, **'c'**, **'\\n**'**(enter产生)**


Ⅵ:cin的输入也会跳过空白字符

cin >> 对于 char:自动跳过开头所有空白字符 。 缓冲区队列现在:[' ', 'c', '\n']

  1. cin >> a:跳过开头空格,读取'c'存入a;队列剩余:['\n']
  2. cin >> b:缓冲区队首是'\n'属于空白,跳过;缓冲区空,阻塞等待键盘输入

Ⅶ:endl的作用

  1. '\n':输出 1 个换行
  2. 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" 中间没有空格:

  • 第一个 %ca
  • 第二个 %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

执行过程:

  1. 第一个 %c 直接读队首字符 c
  2. 所以 a = 'c'
  3. 缓冲区剩余:\n
  4. 格式串遇到中间的空格,开始清理空白
  5. \n 属于空白,被清理掉
  6. 缓冲区变空
  7. 第二个 %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

执行过程:

  1. cin >> a 先跳过空格
  2. 然后读取 c
  3. 所以 a = 'c'
  4. 缓冲区剩余:\n
  5. cin >> b 看到队首是 \n,也跳过
  6. 缓冲区空,程序等待输入

注意:

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,回车

请你作答:

  1. 第一次 printf 输出什么?变量 x、y 分别是什么?执行完第一个 scanf 之后,输入缓冲区剩余哪些字符?
  2. 第二个 scanf 内部每一步缓冲区如何变化?
  3. 第二次 printf 输出什么?

1.m+空格 m 空格 空格+空格+n+\n

2.先读走一个空格 再删去一个空格 再读走n 在最后面追加s+空格+t+\n

3.空+n

答案

输入:第一行 m n(m 后面 3 个空格)+ 回车 缓冲区初始:['m',' ',' ',' ','n','\n']

问题 1

  1. 第一次 printf 输出:m
  2. 变量:x='m'y=' '
  3. 第一个 scanf 执行完,缓冲区剩余:[' ',' ','n','\n']

误区Ⅷ:缓冲区有可用的,不会再读取

问题 2,第二个 scanf("%c %c", &x, &y); 完整过程

起始缓冲区:[' ',' ','n','\n']

  1. 第一个%c读取队首空格,x=' ';缓冲区变为:[' ','n','\n']
  2. 遇到格式串中间的空格:吞噬所有连续空白 ,吃掉剩下的空格;缓冲区变为:['n','\n']
  3. 第二个%c读取'n'y='n';缓冲区变为:['\n']

缓冲区里有n可用,程序不会阻塞等待输入,第二行输入s t完全不会被读取

//实测的时候不会让你再输入了


问题 3

第二次 printf("%c %c\n",x,y); x=' 'y='n' 输出: n

解释:打印 x (空格),printf 自带的分隔空格,再打印 n。肉眼看:先一个空格,再一个空格,再字母 n。

感谢观看

相关推荐
闻缺陷则喜何志丹1 小时前
【栈 运算系统】P3719 [AHOI2017初中组] rexp|普及+
c++·算法··洛谷·运算系统
bnmoel1 小时前
C++ 基础入门篇(一):命名空间,输入&输出,缺省参数,函数重载
c++·函数重载·语法·命名空间·缺省参数
Zixhy2 小时前
多点巡检机器人项目技术总结
linux·c++·机器人·自动驾驶
风云2 小时前
MiniLog 1.0 GA 发布:零分配、零依赖的 .NET 高性能日志框架
开源·c#·.net·日志·高性能·minilog
慧都小妮子3 小时前
C# 工程图纸数字化入库实战:Aspose.Imaging 自动抠图、多页 TIFF 合卷与坏页容错
图像处理·c#·.net·aspose·tiff·归档·文档管理
Pointer Pursuit4 小时前
C++11特性(二)
前端·c++·算法
tudousisi2225 小时前
CSP 第3题训练 Day 5|CSP202509C HTTP 头信息|Blog A
c++
闻缺陷则喜何志丹5 小时前
【动态规划】P10726 [GESP202406 八级] 空间跳跃|普及+
c++·算法·动态规划·洛谷
zyf1044166 小时前
暑期实践日志 Day48:复盘大纲与知识点对照表,系统汇总整理成果
学习·计算机网络·剪辑·暑期实践·课题任务