04 表达式

字面值和变量是最简单的表达式,其结果就是字面值或变量的值

4.1 基础

基本概念

重载运算符后,运算对象的个数、优先级和结合顺序是不能改变的。

  • 左值:使用的是对象的身份(在内存中的位置)

  • 右值:使用的是对象的值

赋值运算的左边是一个左值。取地址运算符作用于一个左值对象。

求值顺序

注意求值顺序与优先级和结合律都无关。只有四种运算符明确规定了求值顺序:&& || ?: ,

下面的例子中,虽然优先级和结合律有规定。但四个函数的求值顺序是不确定的。如果这些函数会影响同一个对象,则这是条错误的表达式,将产生未定义的行为。

f() + g() * h() + j()

4.2 算数运算符

算数运算符的运算对象和结果都是右值。在表达式求值之前,小整型会被提升到较大的整数类型,所有运算对象最终会被转换成统一类型。

注意求模运算的符号

  • m%(-n) = m%n

  • (-m)%n = -(m%n)

4.3 逻辑和关系运算符

逻辑运算符作用于任意能转换为布尔值的类型。关系运算符作用于算数或指针类型。两者的返回值都是布尔类型。两者的运算对象和求值结果都是右值。

4.4 赋值运算符

赋值运算符左侧必须是一个可以修改的左值。若左侧对象是一个内置类型,用初始值列表只能包含一个值。

4.5 递增和递减运算符

如非必须,尽量使用前置版本。前置版本对一些迭代器类型有很高的性能。

注意后置递增运算符优先级高于解引用

// 这条语句等价于下面两条语句。建议使用上面这条语句
cout << *iter++ << endl;
cout << *iter << endl;
iter++;

4.6 成员访问运算符

ptr->item等价于(*ptr).item

注意点运算符优先级高于解引用,所以括号是必须的

4.7 条件运算符

4.8 位运算符

如果对象带符号,且值为负,则符号位的处理是没有明确规定的。因此强烈建议仅将位运算符作用于无符号对象。

位移运算符满足左结合律。

4.9 sizeof运算符

sizeof运算符返回一个表达式的值或者一个类型所占的字节数。

sizeof type;
sizeof expr;

在获取表达式值所占字节数的时候,并不会真的计算表达式,而是计算表达式结果类型的大小。

另外sizeof 数组名时,这里的数组名不会转化成指针,而是计算整个数组占用的字节数。

4.10 逗号运算符

逗号运算符有两个运算对象。从左到右依次计算。整个运算结果是逗号右边的值。

4.11 类型转换

算术转换

即把一种算术类型转换为另一种算术类型。

数组名转化成指针

大多数情况下数组名会自动转换为指向数组首元素的指针。但以下几种情况例外:

  • decltype(array)

  • &array

  • sizeof array

  • typeid array

指针的转换

  • 整数0或字面值nullptr能转换成任意类型的指针

  • 指向任意非常量的指针能转换为void *

  • 指向任意对象(包括常量,非常量)的指针能转换为const void *

显示转换

建议:尽量避免强制转换

旧式的显示转换

(type)expr; // c风格
type(expr); // 函数式

命名强制类型转换

cast-name<type>(expression);

参考这里