其他章节:C++ Primer
表达式由一个或多个运算对象组成,对表达式求值将得到一个结果。
字面值和变量是最简单的表达式,其结果就是字面值和变量的值。
把一个运算符和一个或多个运算对象组合起来可以生成较为复杂的表达式。
基本概念
C++定义了一元运算符和二元运算符。
- 一元运算符作用于一个运算对象,取地址符(
&
)和解引用符*
- 二元运算符作用于两个运算对象
- 存在一个作用于三个对象的三元运算符
- 函数调用也是一种特殊的运算符,对运算对象的数量没有限制。
一些符号既能作为一元运算符也能作为二元运算符,由上下文决定,两种用法互不相干,完全可以当作两个不同的符号。
组合运算符和运算对象
对于含有多个运算符的复杂表达式来说,要想理解它的含义首先要理解运算符的优先级、结合律以及运算对象的求值顺序。
运算对象转换
表达式求值的过程中,运算对象常由一种类型转换为另一种类型。
一般的二元运算符都要求两个运算对象的类型相同,但很多时候即使运算对象的类型不同也没有关系,只要它们能被转换成一种类型即可。
重载运算符
C++语言定义了运算符作用于内置类型和复合类型的运算对象时所执行的操作。
当运算符作用于类运算对象时,用户可以自行定义其含义。这种自定义的过程是为已存在的运算符赋予另外一层含义,称之为重载运算符。
重载运算符时,包括运算对象的类型和返回值的类型,由该运算符定义;运算对象的个数、运算符的优先级和结合律无法改变。
左值和右值
C++表达式只能是右值或左值中的一个。
当一个对象被用作右值时,用的是对象的值(内容);当对象被用作左值时,用的是对象的身份(在内存中的位置)。
- 需要右值的地方可以用左值来代替,不能把右值当成左值(位置)使用。
- 当一个左值被当成右值使用时,实际使用的是它的内容(值)。
几种用到左值的运算符: - 赋值运算符需要一个(非常量)左值作为其左侧运算对象,得到的结果也仍然是个左值
- 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。
- 内置解引用运算符、下标运算符、迭代器解引用运算符、
string
和vector
的下标运算符的求值结果都是左值。 - 内置类型和迭代器的递增递减运算符作用于左值运算对象,其前置版本所得的结果也是左值。
- 关键字
decltype
左值和右值有所不同,如果表达式的求值结果是左值,decltype
作用于该表达式得到一个引用类型。- 假定
p
的类型是int*
,解引用运算符生成左值,所以decltype(*p)
的结果是int&
。 - 另一方面,因为取地址运算符生成右值,所以
decltype(&p)
的结果是int**
,结果是一个指向整型指针的指针。
- 假定
优先级和结合律
复合表达式是指含有两个或多个运算符的表达式。
- 复合表达式求值需要将运算符和运算对象合理地组合到一起,优先级与结合律决定了运算对象的组合方式。
- 表达式中的括号无视上述规则,使用括号将表达式的某个局部括起使其优先计算。
表达式最终的值依赖于其子表达式的组合方式。
- 高优先级运算符的运算对象要比低优先级运算符的运算对象更为紧密地组合在一起。
- 如果优先级相同,则其组合规则由结合律确定。算术运算符满足左结合律,意味着如果运算符的优先级相同,将按照从左向右的顺序组合运算对象。
括号无视优先级与结合律
括号无视普通的组合规则,表达式中括号括起来的部分被当成一个单元来求值,然后再与其他部分一起按照优先级组合。
求值顺序
优先级规定了运算对象的组合方式,但没有说明运算对象按照什么顺序求值。
- 大多数情况不会明确指定求值的顺序。
- 对于没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将引发错误并产生未定义的行为。
4种运算符明确规定了运算对象的求值顺序: - 逻辑与(
&&
)运算符,规定了先求左侧运算对象的值,只有左侧运算对象的值为真才继续求右侧运算对象的值。 - 另外三种分别是逻辑或
||
、条件(? :
)和逗号(,
)运算符。
求值顺序、优先级、结合律
运算对象的求值顺序与优先级和结合律无关,在一条形如f()+g()*h()+j()
的表达式中:
- 优先级规定,
g()
的返回值和h()
的返回值相乘。 - 结合律规定,
f()
的返回值先与g()
和h()
的乘积相加,所得结果再与j()
的返回值相加。 - 对于这些函数的调用顺序没有明确规定。
如果f
、g
、h
和j
是无关函数,它们既不会改变同一对象的状态也不执行IO 任务,那么函数的调用顺序不受限制。反之,如果其中某几个函数影响同一对象,则它是一条错误的表达式,将产生未定义的行为。
最好用括号来强制让表达式的组合关系符合程序逻辑的要求。
如果改变了某个对象的值,在表达式的其他地方不要再使用这个运算对象。