2.2.1 概述
This content is not available in your language yet.
表达式是组成 C++ 程序的基本元素之一。表达式表示一个计算过程,可以用来计算一个值,以及产生其他影响程序状态的副作用。
在编译器处理表达式的时候,换行符如同空格一样,当做空白字符处理。因此,换行符不会影响表达式的语义。
表达式常常由数个表达式和一个运算符组合而成。为了方便理解,我们将这样由运算符和表达式组成的表达式,其中的表达式称为操作数。
对于操作数只有一个的表达式,有如下的形式:
+a // `+` 是运算符,`a` 是操作数对于运算符位于两个操作数之间的表达式,有如下的形式:
a + b // `+` 是运算符,`a` 和 `b` 是操作数其中,称运算符左边的操作数(上面的 a)为左操作数,右边的操作数(上面的 b)为右操作数。
结合性和优先级
Section titled “结合性和优先级”这里相信读者已经在数学课上学过“先乘除,后加减”。C++ 沿用了这个规则:对于
a + b * c它的含义是 a + (b * c),即先计算 *,再计算 +。我们说,* 和 / 的优先级比 + 和 - 高;优先级高的运算符先和操作数结合、先被计算。括号可以改变这个顺序,(a + b) * c 就会先计算 a + b。
如果优先级相同,例如
a - b - c两个 - 的优先级一样,这时由结合性决定组合方式。- 是从左往右结合的,所以它的含义是 (a - b) - c,而不是 a - (b - c)。作为对比,后面会介绍到赋值表达式,其中有一个 = 运算符,例如 a = b = 42 的含义是 a = (b = 42),即从右往左计算。
使用优先级和结合性来理解表达式的计算顺序是可以的,但对于更复杂的表达式,以及更复杂的运算符组合,这种理解方式就显得有些难以掌握。这里推荐另一种理解方式:把表达式看成一层层组合出来的树状结构。每个表达式由若干个部分组成,这些组成部分可以继续拆分,直到无法拆分。例如,a + b * c 可以拆成这样的结构:
graph TB a-->r["a + b * c"] m["b * c"]-->r b-->m c-->m这里 a + b * c 的计算顺序可以通过树状结构直观地看出来:先计算 b * c,再将结果与 a 相加。
加减乘除的树状结构
Section titled “加减乘除的树状结构”那么,如何定义加减法和乘除法的组合方式,才能符合我们平时的计算习惯呢?
我们先定义乘除法算式,它有如下三种组成方式
乘除法算式 * 数乘除法算式 / 数数
然后定义加减法算式,它有如下三种组成方式
加减法算式 + 乘除法算式加减法算式 - 乘除法算式乘除法算式
这样,对于 a + b * c 这样的表达式,只有如下的唯一树状结构组合方式:
graph TB a["数(a)"] b["数(b)"] c["数(c)"] b-->b2["乘除法算式(b)"] b2-->y["乘除法算式(b * c)"] c-->y
a-->a2["乘除法算式(a)"] a2-->a3["加减法算式(a)"] a3-->x["加减法算式(a + b * c)"] y-->x容易观察到为什么只有这一种组合:如果先计算 a + b,结构就变成了 加减法算式(a + b) * 数(c),加减法算式 在左边的时候,中间的运算符不可能是 *,所以这种组合方式是不允许的。
继续考虑 a - b - c 这样的表达式,它的树状结构如下:
graph TB a["数(a)"] b["数(b)"] c["数(c)"] a-->a2["乘除法算式(a)"] a2-->a3["加减法算式(a)"] b-->b2["乘除法算式(b)"] c-->c2["乘除法算式(c)"] a3-->x1["加减法算式(a - b)"] b2-->x1 x1-->x2["加减法算式(a - b - c)"] c2-->x2同样的,如果先计算 b - c,结构就变成了 加减法算式(a) - 加减法算式(b - c),但在我们所有的定义里,加减法算式 不会出现在运算符 - 的右边,所以这种组合方式也是不允许的。
后文中会使用这种组合形式定义配合树状结构来讲解表达式的结构。笔者认为,对于 C++ 这样的、表达式与运算符众多的语言,这种方式能更清晰、更形象地体现“优先级”和“结合性”。
在 C++ 中,有一些编写符合语法,但并不“正确”的程序的行为,称为未定义行为(undefined behavior,简称 UB)。标准没有规定发生未定义行为的程序应该如何运行——这样的行为可能会导致程序崩溃、产生错误的结果、或者产生其他不可预测的行为。
例如,我们知道现实中的整数除法,除数不能为0。在 C++ 中,如果除数为0,这个表达式的行为就是未定义的。
未定义行为常常是一种在实现和程序编写之间的妥协,当然也有一部分是历史原因遗留的错误设计。
以现在的情况来看,程序员应该使用标准的操作,避免未定义行为。在后面的内容中,如果涉及比较常见的未定义行为,会特别提醒读者。
对于 bool 和 char 类型,一些运算符会将其转换为 int 类型。例如:
+true // 值为 int 类型 的 1+false // 值为 int 类型 的 0+'a' // 值为 int 类型 的 971 + 'a' // 值为 int 类型 的 98这个转换被称为整数提升,类型大小比 int 小的整数类型在正运算符、负运算符的表达式中会被提升为 int 类型。
后面会介绍到其他的、会发生整数提升的表达式。它们遵循相同的规则,即将类型大小比 int 小的整数类型提升为 int 类型,然后进行计算。