第1章 开始
C++ 核心语法本身不含 IO 语句,其输入输出功能完全由全面的标准库实现。
核心概念:什么是“流”
- 基础核心:C++ 使用
iostream库处理输入输出。 - 基础类型:包含
istream(输入流)和ostream(输出流)两个核心类型。 - 概念定义:流(stream)是一个随时间推移而顺序生成或消耗的字符序列。
- 数据流动:字符序列从 IO 设备读出,或写入到 IO 设备中。
标准库的 4 个 IO 对象
标准库一共定义了 4 个基础 IO 对象:
cin(发音 see-in):istream类型对象,被称为标准输入,用于处理输入。cout(发音 see-out):ostream类型对象,被称为标准输出,用于处理输出。cerr(发音 see-err):ostream类型对象,被称为标准错误,通常用于输出警告和错误消息。clog(发音 see-log):ostream类型对象,专门用于输出程序运行时的一般性日志信息。
向流写入数据
main 函数体执行的表达式使用了 输出运算符(<<) 在标准输出上打印消息:
std::cout << "Enter two numbers:" << std::endl;
输出运算符(<<)的运算法则
-
运算对象:接受两个运算对象。
- 左侧:必须是一个
ostream对象(例如std::cout)。 - 右侧:是要打印的值。
- 左侧:必须是一个
-
计算结果:向给定
ostream写入给定值,其计算结果是其左侧的ostream对象。 -
链式拼接:因为返回左侧对象,多个
<<可以连接起来。上述表达式等价于:(std::cout << "Enter two numbers:") << std::endl;链中每个运算符的左侧运算对象都是相同的(本例中是
std::cout)。它与分成两条语句生成的输出相同:std::cout << "Enter two numbers:"; std::cout << std::endl;
打印对象详解
- 字符串字面值常量 (string literal):即
"Enter two numbers:",是用一对双引号包围的字符序列,文本会被打印到标准输出。 - 操纵符 (manipulator):即
endl,是一个特殊值。- 核心效果:结束当前行(换行)。
- 关键作用:将与设备关联的缓冲区 (buffer) 中的内容刷新到设备中。
- 目的:保证到目前为止程序产生的所有输出都真正写入输出流中,而不是仅停留在内存中等待写入。
:warning: WARNING:调试提示
- 原则:调试用的打印语句应该保证“一直”刷新流。
- 风险:如果程序崩溃,输出可能还留在缓冲区中,从而导致关于程序崩溃位置的错误推断。
std::endl 和 '\n'
在 C++ 中,std::endl 和 '\n' 都能实现换行,但它们的底层机制、性能表现以及安全特性有着本质的区别。
- 核心区别:是否刷新缓冲区
'\n'(换行符):- 机制:单纯的字符常量。它只负责将光标移到下一行,并将数据写入内存缓冲区,通常不会立刻触发硬件设备的物理写入。
std::endl(操纵符):- 机制:换行并强行刷新缓冲区。它等价于先输出
'\n',然后立刻调用std::flush(),逼迫内存缓冲区中的数据实时写到屏幕或文件中。
- 机制:换行并强行刷新缓冲区。它等价于先输出
- 维度对比
| 维度 | '\n' |
std::endl |
|---|---|---|
| 性能表现 | :trophy: 极高(减少物理 I/O 操作) | 较低(频繁触发系统调用) |
| 数据安全 | 较低(程序异常崩溃时数据可能丢失) | :trophy: 极高(确保数据实时落盘) |
| 等价写法 | std::cout << '\n'; |
std::cout << '\n' << std::flush; |
- 开发中的选型建议
-
大批量数据输出、循环体内部:首选
'\n'
在需要打印大量文本(如文件写入或高频循环)时,频繁使用std::endl会因不断刷新硬件而造成严重的性能瓶颈。// :cross_mark: 低效示范:会严重拖慢运行速度 for (int i = 0; i < 100000; ++i) { std::cout << i << std::endl; } // :trophy: 高效示范:运行速度成倍提升 for (int i = 0; i < 100000; ++i) { std::cout << i << '\n'; } -
程序调试、错误记录、交互提示:首选
std::endl
正如你刚才阅读的课本警告,如果程序发生崩溃,留在缓冲区的数据会直接丢失。为了确保能看到最后的输出以定位 Bug,调试或输出关键日志时必须使用std::endl。std::cout << "正在读取数据库..." << std::endl; run_dangerous_code(); // 如果此处崩溃,上一句提示依然能成功显示在屏幕上
使用标准库中的名字
- 为什么使用
std::前缀?
- 前缀含义:
std::cout和std::endl中的前缀std::,指明了cout和endl这两个名字是定义在名为std的命名空间(namespace) 中的。 - 核心作用:命名空间可以帮助我们避免不经意的名字定义冲突,以及避免因使用库中相同名字而导致的冲突。
- 标准库规定:标准库定义的所有名字都在命名空间
std中。
- 显式说明的“副作用”与操作符
- 副作用:通过命名空间使用标准库时,每次使用库中的名字,都必须显式说明我们想使用来自命名空间
std中的名字(例如必须写成std::cout)。 - 作用域运算符(
::):我们通过使用 作用域运算符(::),来指出我们想使用定义在命名空间std中的名字cout。
:light_bulb: 提示:更简单的访问标准库中名字的方法(
using namespace std;声明)。
从流读取数据
在提示用户输入数据之后,接下来需要读入用户的输入。首先可以定义两个变量 v1 和 v2 来保存输入:
int v1 = 0, v2 = 0;
- 内置类型:
int是一种内置类型,用来表示整数。 - 初始化 (initialize):将这两个变量定义为
int类型并初始化为0。初始化一个变量,就是在变量创建的同时为其赋予一个值。
输入运算符(>>)的运算法则
下一条读入输入数据的语句是:
std::cin >> v1 >> v2;
-
运算对象:与输出运算符(
<<)类似,它接受两个运算对象。- 左侧:必须是一个
istream对象(例如std::cin)。 - 右侧:作为其右侧运算对象。它从给定的
istream读入数据,并存入给定对象中。
- 左侧:必须是一个
-
计算结果:输入运算符返回其左侧的
istream对象作为其计算结果。 -
链式拼接:由于运算符返回其左侧运算对象,因此可以将一系列输入请求合并到单一语句中。上述表达式等价于:
(std::cin >> v1) >> v2;本例中的输入操作从
std::cin读入两个值,并将第一个值存入v1,将第二个值存入v2。换句话说,它与下面两条语句的执行结果是一样的:std::cin >> v1; std::cin >> v2;