跳至内容
Topos Uranos

摘要

本课结束课程的第二单元,这一单元讲的是数据及其类型。本课重建一条概念链:从变量出发,经过整数的二进制表示及其两种溢出、近似的实数与容差、作为编码的字符以及丢失信息的转换,一直到编译器所监督的常量;本课把其中适用于任何语言的内容与 C++ 特有的内容区分开来;汇总本单元的未定义行为,指出编译器能发现什么、检测器(sanitizer)能发现什么、什么谁也发现不了;在结业测评中重复入门测评的技能;最后给出六道综合整个单元的挑战题。

学习目标

  1. 根据数据可能取的值、所要求的精度以及要对它进行的运算,为问题中的每个数据选择类型并说明理由。
  2. 预测表示的界限在给定程序中造成的后果:整数的溢出、浮点的舍入以及转换造成的信息丢失。
  3. 使用具名常量和显式转换,并说明编译器在每种情况下检查什么。
  4. 识别本单元的未定义行为,并针对每种情况说出什么能发现它、如何预防它。
  5. 在本单元所学的内容中,区分适用于任何语言的内容与 C++ 特有的内容。

本单元的收获

本单元的目标是让人懂得:每个数据都带着一个类型存放在内存中,类型规定它能取哪些值、占多大空间、允许哪些运算,而这些界限会产生可以观察到的后果。这一目标已经达成,所依靠的只是导言声明为已知的内容:用本课程的选项编译和运行的工作流程、格式化输出、用 std::cin 读取,以及阅读编译器的消息。导言用来提出问题的那个做四次计算的程序,现在可以逐行解释,并加以修正。

变量与内存

变量与内存一课确立了:变量是一个有名字、类型固定、值可变的存储单元,类型决定它的大小,而 sizeof 可以测出这个大小。它区分了初始化与赋值,说明花括号会拒绝有损失的转换,并教导赋值是复制而不是绑定,被替换的值会丢失;由此便需要交换时的辅助变量。它把程序的状态定义为程序中各变量的值的集合,并教会用跟踪表追踪状态。它规定了命名的约定,最后讲了本单元的第一个未定义行为:读取一个从未被赋值的变量。

整数及其表示

整数及其表示一课讲授了以 10、2、16 为基数的进位制。它从类型有 nn 位推出它的取值范围:共有 2n2^n 个位模式,无符号时表示从 00 到 2n−12^n - 1 的数,有符号时(采用二进制补码)表示从 −2n−1-2^{n-1} 到 2n−1−12^{n-1} - 1 的数,负数比正数多一个。它区分了两种溢出:无符号整数的溢出是模 2n2^n 运算,是有定义的(0u - 1 等于 4 294 967 2954\,294\,967\,295);有符号整数的溢出是未定义行为,检测器会让程序停下。它说明有符号整数与无符号整数比较时前者会被转换,因此 −1<0u-1 < 0u 为假;最后给出了按数据可预见的范围选择整数类型的准则。

浮点数

浮点数一课说明,实数以符号、指数和二进制尾数的形式存储,因此可表示的值是一个有限集合,由分母为 2 的幂的有理数组成。由此得出,0.10.1 在机器中并不存在:存储的是离它最近的 double 值,而 0.1 + 0.2 输出为 0.30000000000000004。这一课给出了每次舍入的误差界,说明误差会累积,加法不再满足结合律;由此推出,两个计算得到的实数不能用 == 比较,而要按它们的距离、用有充分理由的容差来比较。它把 double 定为默认的实数类型,并介绍了无穷大和非数 NaN。

字符、布尔值、类型转换与常量

字符、布尔值、类型转换与常量一课说明,类型是读取位的规则:char 存储一个字节,其值是该字符的 ASCII 编码值;由于数字和字母的编码是连续的,'7' - '0' 等于 7;bool 为真或假,任何非零的数都转换为真。它研究了隐式转换,确立了转换发生在运算之中、取决于操作数的类型,而不是在保存结果时发生:double r{7 / 2} 的值是 3。它教会用 static_cast 写出有意的转换,这种写法并不避免损失,而是声明损失;它明确了花括号会拒绝窄化,并展示了本单元的第三个未定义行为:把放不下的实数转换为整数。最后讲了常量(const、constexpr 和 static_assert)、字面量的类型和 auto。