第 4 章:表达式
表达式由一个或多个运算对象(operand)组成,对表达式求值将得到一个结果(result)。
4.1 基础
- 组合运算符和运算对象
- 运算对象转换
- 重载运算符(overloaded operator)
- IO 库的
>>
、<<
- string、vector、迭代器的运算符
- IO 库的
- 左值、右值
- 当对象用作右值,用的是对象的值(内容)
- 当对象用作左值,用的是对象的身份(内存位置)
4.2 算术运算符
优先级从高到低排列。
4.3 逻辑运算符和关系运算符
if (val)
会把 val 转成布尔值,val 任意非 0 为真
if (!val)
会把 val 转成布尔值,val 为 0 为真
if (val == true)
与布尔值比较时,只有当 val 为 1 时才是真
4.4 赋值运算
赋值运算符的左侧运算对象必须是一个可修改的左值。
赋值运算满足右结合律
int ival, jval;
ival = jval = 0;
可以看作是 ival = (jval = 0)
, jval = 0
该赋值运算返回左侧运算对象即 jval
,所以 jval
又被赋给了 ival
。
因为赋值运算符的优先级低于关系运算符的优先级,所以在条件语句中,赋值部分通常应该加上括号。
4.5 递增递减运算符
递增和递减运算符有两种形式:前置版本和后置版本。
int i = 0, j;
j = ++i; // j = 1, i = 1; 前置版本得到递增后的值
j = i++; // j = 1, i = 2; 后置版本得到递增前的值
前置版本:得到递增后的值,即对象本身的左值(避免了不必要的工作)
后置版本:得到递增前的值,即对象原始值的副本作为右值(如果不需要修改前的值,那后置操作比较浪费)
下面这种情况比较适合使用后置版本,可以理解为 *(iter+)
cout << *iter++ << endl;
4.6 成员访问运算符
点运算符((*ptr).men
)和箭头运算符(ptr->men
)都可用于访问成员。
箭头运算符作用于一个指针类型的运算对象,结果是一个左值。
4.7 条件运算符
cond ? expr1 : expr2
4.8 位运算符
关于符号位如何处理没有明确的规定,所以强烈建议仅将位运算符用于处理无符号类型。
4.9 sizeof 运算符
sizeof (type);
sizeof expr;
4.10 逗号运算符
都要运算符首先对左侧表达式求值,然后将结果丢掉,结果是右侧表达式的求值结果。
4.11 类型转换
有时候类型是自动执行的,被成为隐式转换 implicit conversion。
- 比 int 小的整型提升为较大整型
- 在条件中非布尔值转布尔类型
- 初始化时,初始值转为变量类型。赋值时,右侧对象转为左侧对象的类型
- 在算术或关系运算中有多种类型,需要转换成同一种类型
- 函数调用也会发生类型转换
算术转换
整型提升
- 小整数类型提升为 int
- 较大的 char 类型提升为 int、unsigned int、long、unsigned long、long long、unsigned long long 中最小的一种类型。
无符号类型
一般有符号类型提升为无符号类型
其他隐式类型转换
- 数组转指针:
int ia[10]; int *ip = ia;
- 指针的转换 :
0
、nullptr
能转换成任意指针,任意非常量指针能转换成void*
,任意指针能转换成const void*
- 转换成布尔类型:从算术类型或指针类型转为布尔类型
- 转换成常量:非常量指针转换为常量指针
- 类类型定义转换:
while (cin >> str)
io 库定义的 istream 向布尔值转换的规则
显式转换
有时候希望显式的将对象强制转换为另外一种类型。
cast-name<type>(expression)
cast-name 是 static_cast、dynamic_cast、const_cast、reinterpret_cast
static_cast
任何具有明确定义的类型转换,只要不包含底层 const,都可以用 static_cast<double>(ival)
const_cast
将常量对象转为非常量对象
reinterpret_cast
为运算对象的位模式提供较低层次上的重新解释。
reinterpret_cast 本质上依赖于机器。要想安全地使用reinterpret_cast必须对涉及的类型和编译器实现转换的过程都非常了解。
建议:避免强制类型转换
旧式的强制类型转换
type (expr); // 函数形式
(type) expr; // c 语言风格