C++Primer(第5版)

第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' 都能实现换行,但它们的底层机制、性能表现以及安全特性有着本质的区别。

  1. 核心区别:是否刷新缓冲区
  • '\n'(换行符)
    • 机制:单纯的字符常量。它只负责将光标移到下一行,并将数据写入内存缓冲区,通常不会立刻触发硬件设备的物理写入。
  • std::endl(操纵符)
    • 机制:换行并强行刷新缓冲区。它等价于先输出 '\n',然后立刻调用 std::flush(),逼迫内存缓冲区中的数据实时写到屏幕或文件中。

  1. 维度对比
维度 '\n' std::endl
性能表现 :trophy: 极高(减少物理 I/O 操作) 较低(频繁触发系统调用)
数据安全 较低(程序异常崩溃时数据可能丢失) :trophy: 极高(确保数据实时落盘)
等价写法 std::cout << '\n'; std::cout << '\n' << std::flush;

  1. 开发中的选型建议
  • 大批量数据输出、循环体内部:首选 '\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(); // 如果此处崩溃,上一句提示依然能成功显示在屏幕上
    

使用标准库中的名字

  1. 为什么使用 std:: 前缀?
  • 前缀含义std::coutstd::endl 中的前缀 std::,指明了 coutendl 这两个名字是定义在名为 std 的命名空间(namespace) 中的。
  • 核心作用:命名空间可以帮助我们避免不经意的名字定义冲突,以及避免因使用库中相同名字而导致的冲突。
  • 标准库规定:标准库定义的所有名字都在命名空间 std 中。

  1. 显式说明的“副作用”与操作符
  • 副作用:通过命名空间使用标准库时,每次使用库中的名字,都必须显式说明我们想使用来自命名空间 std 中的名字(例如必须写成 std::cout)。
  • 作用域运算符(:::我们通过使用 作用域运算符(::,来指出我们想使用定义在命名空间 std 中的名字 cout

:light_bulb: 提示:更简单的访问标准库中名字的方法( using namespace std; 声明)。

从流读取数据

在提示用户输入数据之后,接下来需要读入用户的输入。首先可以定义两个变量 v1v2 来保存输入:

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;
    

正文完
 0
评论(没有评论)