2.2.1 概述
表达式是组成 C++ 程序的基本元素之一。表达式表示一个计算过程,可以用来计算一个值,以及产生其他影响程序状态的副作用。
在编译器处理表达式的时候,换行符如同空格一样,当做空白字符处理。因此,换行符不会影响表达式的语义。
操作数
表达式常常由数个表达式和一个运算符组合而成。为了方便理解,我们将这样由运算符和表达式组成的表达式,其中的表达式称为操作数。
对于操作数只有一个的表达式,有如下的形式:
+a // `+` 是运算符,`a` 是操作数
对于运算符位于两个操作数之间的表达式,有如下的形式:
a + b // `+` 是运算符,`a` 和 `b` 是操作数
其中,称运算符左边的操作数(上面的 a
)为左操作数,右边的操作数(上面的 b
)为右操作数。
结合性和优先级
TODO 补充内容
未定义行为
在 C++ 中,有一些编写符合语法,但并不“正确”的程序的行为,称为未定义行为。标准没有规定发生未定义行为的程序应该如何运行——这样的行为可能会导致程序崩溃、产生错误的结果、或者产生其他不可预测的行为。
例如,我们知道现实中的整数除法,除数不能为0。在 C++ 中,如果除数为0,这个表达式的行为就是未定义的。
未定义行为常常是一种在实现和程序编写之间的妥协,当然也有一部分是历史原因遗留的错误设计。
以现在的情况来看,程序员应该使用标准的操作,避免未定义行为。在后面的内容中,如果涉及比较常见的未定义行为,会特别提醒读者。
一种奇妙的模因
在程序员圈子常常会嘲笑 C++ 的未定义行为。例如,“在作业本里写下1/0,引发了未定义行为从而骗过了老师的检查”,“输入1/0,然后我的程序把我的系统盘格式化了”。
诚然,未定义行为对于程序设计无疑会带来不少的心智负担,但现实世界里的程序天然运行在一个充满无法预测行为的环境中,与其设计一种“不存在未定义行为”的编程语言,不如想办法让程序员更好地理解未定义行为,这也正是笔者认为 C++ 这种古董语言的最大问题。
整数提升
对于 bool
和 char
类型,一些运算符会将其转换为 int
类型。例如:
+true // 值为 int 类型 的 1
+false // 值为 int 类型 的 0
+'a' // 值为 int 类型 的 97
1 + 'a' // 值为 int 类型 的 98
这个转换被称为整数提升,类型大小比 int
小的整数类型在正运算符、负运算符的表达式中会被提升为 int
类型。
后面会介绍到其他的、会发生整数提升的表达式。它们遵循相同的规则,即将类型大小比 int
小的整数类型提升为 int
类型,然后进行计算。
为什么要整数提升?
在现代的 CPU 中,为了平衡硬件的复杂性和易用性,不会为所有的宽度配置计算相关的电路(算术单元)。
往往作为基础的整数类型是一个字长(word)的大小,例如 32 位或 64 位。其他的整数类型,例如 char
和 short
,会被提升为这个字长的大小,然后再进行计算。
此外,提升前的类型本来可能是紧密地排列的,例如两个 short
紧密排列在一起,把这样的类型提升到 int
显然需要进行额外的操作把它们分开放进两个 int
的空间里。
虽然这样的操作在现代 CPU 中几乎不会带来开销,这里希望读者能知道,抠这一两个字节并不能提升任何性能。