<< 和 >> 不是"语法符号",它们是标准库重载出来的运算符:
cout << x 本质是"调用 operator<<(cout, x)"(11.2 讲过"运算符就是函数")。
理解这一点的回报是:<< 能连着写一长串、能接任何类型、能对你的自定义类生效——全都因为它是函数。
本节还带实例01「字符相加并输出」,它把 2.5 的输入输出 和 第三章的类型转换 缝在一起,顺便复习"字符在内存里其实是数字"。
前面三节把"流家族"和"常量词汇表"讲完了,本节回到最日常的那两个符号:
- 回看 2.5:那时你已经在写 cin >> x,但没人告诉你 >> 是个函数——本节补上;
- 回看 11.2.3:<< >> 只能重载成非成员函数(因为左边是 ostream)——那是本章的伏笔回收;
- 回看 10.4.4:cout << a << b 能连起来,靠的是 return *this;
- 回看 2.3.5 / 2.4.2:实例01 的整个原理就是"char 就是个小整数"。
一句话:本节是"把第二章的日常操作,用第十章和第十一章的知识重新解释一遍"。
一、<< 和 >> 的真面目:两个被重载的运算符
这两个符号原本的意思是位移(3.1 学的:x << 2 是把二进制位左移两位)。
但标准库给它们加了新的重载版本:
cout << x; // 实际调用的是 operator<<(cout, x)
cin >> x; // 实际调用的是 operator>>(cin, x)
两个名字的来历(叫着舒服,好记):
| 符号 | 学名 | 为什么这么叫 |
|---|---|---|
<< |
插入运算符(insertion) | 把字符插入到输出流里 |
>> |
提取运算符(extraction) | 从输入流里提取字符 |
为什么必须是"非成员函数"? 因为 cout << 5 里,左边是 ostream 对象,不是你的类的对象——你没法跑到 ostream 类里去加成员函数。所以标准库把它写成全局函数(11.2.2 讲过这一条,当时说"<< >> 必须非成员",出处就是这里)。
你能自己做的是"给自己的类重载它":
struct Point { int x, y; };
ostream &operator<<(ostream &out, const Point &p) {
out << "(" << p.x << "," << p.y << ")";
return out; // ★ 必须返回 out,否则不能连写
}
// 之后就能:cout << p << endl;
二、为什么能"连着写一长串"
cout << "a" << 1 << endl; 这句里,<< 是左结合的,所以实际是:
((cout << "a") << 1) << endl;
要让这成立,必须满足两件事:
operator<<的返回值是ostream &(不是void);- 返回的是"同一个流对象" —— 所以函数最后要写
return out;(成员版本里写return *this;,10.4.4 讲过)。
一句话:每一节 << 都把"流对象"原样交还给下一节。
这也解释了为什么 endl 能塞在中间:endl 也是一个函数(更准确地说是"函数模板"),它接收流、换行、刷新、再把流还回来。
三、>> 的三条解析规则(2.5 详细讲过,这里复述要点)
输入流读取数据时不是"照单全收",而是按目标类型去解析:
- 先跳过前导空白(空格、制表符、换行)——默认行为;
- 按目标变量的类型去解析字符:读
int就找数字、读double就找小数、读string就取到下一个空白前; - 一旦遇到不匹配的字符就停下,把那个字符留在流里,并且把流置于失败状态(
failbit)。
三条例外/变体:
getline(in, s)不跳空白(它专门读整行,包括空格);in.get(ch)连空白也读(一个字符都不放过);noskipws可以让>>不跳空白。
int x = 99; cin >> x; 如果你输入 abc:
x 还是 99(一个字符都没读进去),流进入失败状态,之后所有 >> 全部无效(除非 clear(),15.4.1 讲)。
这也是"输入必须校验"的根因。
四、实例01:字符相加并输出
教材这道题的本意,是让你看清一件事:
char在做算术时,用的不是"它长什么样",而是它在内存里存的那个数(ASCII 码)。
#include <iostream>
using namespace std;
int main() {
char a, b;
cout << "输入两个字符(用空格分开): ";
cin >> a >> b;
cout << "a = " << a << " 它的编码 = " << (int)a << endl;
cout << "b = " << b << " 它的编码 = " << (int)b << endl;
int sum = a + b; // ★ 相加时自动转成整数:用的是编码
cout << "相加(按整数输出): " << sum << endl;
char c = a + b; // ★ 存回 char:按单字节截断
cout << "相加(按字符输出): " << c << " 编码 = " << (int)c << endl;
return 0;
}
看到什么算成功(输入 3 4):
输入两个字符(用空格分开): 3 4
a = 3 它的编码 = 51
b = 4 它的编码 = 52
相加(按整数输出): 103
相加(按字符输出): g 编码 = 103
数字怎么来的(都验算过):
'3'的 ASCII 码是 51,'4'是 52(注意不是 3 和 4 ——字符'3'和数字3是两回事,2.2.3 讲过);51 + 52 = 103;103对应的字符是'g'('a'是 97,往后数 6 个:97 + 6 = 103)。
读结果——这道题只教三句话:
cout << a输出的是"字符长相",cout << (int)a输出的才是"里面的数"。 加个(int)就能看见真相。char参与算术运算时,会先"提升"成int——所以a + b的结果是int(这里是 103),不是char。- 把它塞回
char才会"按一字节截断" ——103 恰好还在char的范围内,所以得到了'g'。
顺手警告一下:如果你输入 a b(97 + 98 = 195),195 超过了"有符号 char"的上限 127 ——存回 char 就会溢出(在常见平台上会变成负数)。这类例子请不要死记结果:char 到底是有符号还是无符号,由编译器实现决定(2.4.2 讲过)。记住"会溢出"这件事就够了。
五、动手 Demo:三种输入方式对照
>>、getline、get 三兄弟的差别,写一次印象最深。
#include <iostream>
#include <string>
using namespace std;
int main() {
cout << "输入: 12 34 abc def" << endl;
int m, n;
cin >> m >> n; // 按空白分词取两个整数
cout << "① >> : m=" << m << " n=" << n << endl;
string rest;
cin >> rest; // 再取一个词
cout << "② 下一个词 : " << rest << endl;
// 把这一行剩下的部分读掉(清干净)
string junk;
getline(cin, junk);
cout << "③ 吃掉剩余 : [" << junk << "]" << endl;
cout << "再输入一整行(可以有空格): ";
string line;
getline(cin, line); // 读整行(含空格)
cout << "④ getline : [" << line << "]" << endl;
cout << "输入一个字符: ";
char ch;
cin.get(ch); // 连空白也读
cin.get(ch); // 再读一个
cout << "⑤ get 两次后: [" << ch << "] (第二个字符)" << endl;
return 0;
}
看到什么算成功(输入按提示走:先打 12 34 abc def,然后打 hello world,然后打 xy):
输入: 12 34 abc def
① >> : m=12 n=34
② 下一个词 : abc
③ 吃掉剩余 : [ def]
再输入一整行(可以有空格): hello world
④ getline : [hello world]
输入一个字符: xy
⑤ get 两次后: [y] (第二个字符)
读结果:
- ①
>>一个词一个词地取,自动跳过空格; - ③
getline把>>剩下的半个行"吃干净"了 ——这是混用>>和getline时的必备动作(15.3.3 会专门讲这个坑); - ④
getline读到了含空格的一整行; - ⑤
get连空白都读 ——所以"输入xy"时,第一次get拿到了x,第二次拿到了y,打印第二个是y。
六、30 秒实验
- 实例01 里输入
a b→ 看相加(按字符输出)打出来的是什么(很可能是个怪符号或问号)。体会"溢出"和"实现定义"。 - 把
cout << a改成cout << a + 0→ 打出来的就变成数字。证明"字符在算术里就是整数"。 - 在 Demo 里把 ③ 那段(吃掉剩余)删掉,直接
getline→ ④ 那一行读到的是空串。这就是>>和getline混用的经典事故现场(原因:>>停在空格前,空格和换行还留在流里,getline一读就撞上了)。
七、常见误区
- "
<<和>>是语言的关键字/语法" ——它们是标准库重载的运算符(11.2)。所以你能给自定义类重载它们,也能把cin/cout换成文件流。 - "
cout << a << b是同时输出两个" ——是"依次输出":左结合,每次<<都返回同一个流,看起来像一条链。 - "
>>遇到空格会读进空格" ——不会,它跳过前导空白(这是默认行为,需要反着来才要noskipws)。 - "
cin >> x失败后重输一次就好了" ——不行,流卡在失败状态了,所有后续>>都失效,必须cin.clear()(15.4.1 讲)。 - "
char相加就是把两个字符接起来('3'+'4'得'34')" ——完全不对。+是算术运算,得到的是数字和(103);想把两个字符接成字符串要string或strcat(7.3 讲过)。 - "给自定义类重载
<<时返回值写void也行" ——不行,那就 不能连写cout << a << b。必须返回ostream &。
自测三问(点开对答案)
1. **`cout << x` 到底调用了什么?为什么能连着写?** ——**调用 `operator<<(cout, x)`**(标准库重载的函数)。**能连写是因为它返回 `ostream &`(同一个流对象)**,左结合之后就串成一条链(10.4.4)。 2. **实例01 里输入 `3 4`,为什么"相加按整数"是 103?再把它存进 `char` 会得到什么?** ——**因为 `'3'` 的编码是 51、`'4'` 是 52,51 + 52 = 103**(**不是 3 + 4 = 7**)。**103 对应字符 `'g'`**(`'a'` 是 97)。**要看清这一点,就把字符 `(int)` 一下。** 3. **`>>` 和 `getline` 混用时,为什么 `getline` 会读到空行?怎么解决?** ——**因为 `>>` 停在空白(空格/换行)之前,那个空白还留在流里**,**`getline` 一上来就撞上它,于是读到空串**。**解法:`>>` 之后先 `getline` 一次把这一行剩下的吃掉**(像 Demo 的 ③)。- ☐ 下一节(15.2 文件的打开)
相关笔记
- ios类中的枚举常量 —— 上一节:打开方式的常量
- 文件的打开 —— 下一节:终于开始碰文件
- C++语言中的流 ——
>>的三条解析规则原文 - 字符变量 —— 字符就是小整数(实例01 的原理)
- 重载运算符的形式与规则 ——
<<>>为什么必须非成员 - 隐藏的this指针 ——
return *this与链式调用 - 🔷 C++基础 · 索引 —— 全课路线图