第四章 表达式
4.1 表达式基础
- 左值和右值:
- 一个对象被用作左值时,使用的是对象的身份(在内存中的地址,左值可以按名访问,而且其地址可以被赋值)
- 一个对象被用作右值时,使用的是对象的值(内容)
- (自己的理解)即使一个对象可以按名访问,但如果该对象的内容在只读数据段,该对象也是右值
- 需要右值的地方可以使用左值代替,但是反过来不行
decltype
作用于表达式(注意不是变量),推导出来是左值还是右值,与表达式返回值是左值还是右值相同
- 求值顺序:
- 如果改变了某个运算对象的值,在同一表达式中不要再使用该运算对象,参考
- 只有四种运算符明确规定了求值顺序:
&&
、||
、?:
、,
4.2 算术运算符
- 整数除法的结果向0舍入
- 取余运算m%n,结果符号与被除数m相同
4.3 逻辑和关系运算符
- 短路求值
4.4 赋值运算符
- C++11允许使用花括号括起来的初始值列表作为右侧运算对象,初始化列表为空时进行值初始化
- 赋值运算的返回结果是它的左侧运算对象,是一个左值
- 赋值运算符满足右结合律,这点和其他二元运算符不一样。
- 比如:
ival = jval = 0;
等价于jval = 0; ival = jval;
- 比如:
- 复合赋值运算符只求值一次,而普通运算符需要两次。
- 比如:a=a+1 要先求一次 a+1,再将结果赋值给 a
4.5 递增和递减运算符
- 前置版本
j = ++i
,先i加一,后给j赋值,优先使用 - 后置版本
j = i++
,先给j赋值,后i加一
- 混用解引用和递增运算符:
*p++
等价于*(p++)
- 首先进行自加,p指向下一个位置,返回原来对象的副本
- 将原来对象的副本进行解引用
1 2 3
auto iter = vi.begin(); while (iter!=vi.end()) cout<<*iter++<<endl; // 输出当前值,指针向前移1
4.6 成员访问运算符
ptr->mem
等价于(*ptr).mem
4.7 条件运算符
- 条件运算符(
?:
):cond? expr1: expr2
- 可以嵌套使用,右结合律,从右向左顺序组合
4.8 位运算符
- 位运算符是作用于整数类型的运算对象。
- 向左移(
<<
),向右移(>>
),位取反(~
)(逐位求反)、与(&
)、或(|
)、异或(^
) - 有符号数负值可能移位后变号,所以强烈建议位运算符仅用于无符号数。
4.9 sizeof运算符
- 语法:
sizeof (type)
:返回类型的大小sizeof expr
:返回表达式结果类型的大小- 返回类型是
size_t
的常量表达式
sizeof
并不实际计算其运算对象的值。- 对数组执行
sizeof
运算得到整个数组所占空间的大小。 - 对
string
或vector
对象执行sizeof
运算只返回该类型固定部分的大小,不会计算对象中元素所占空间的大小。
4.10 逗号运算符
从左向右依次求值,左侧求值结果丢弃,返回结果是右侧表达式的值。
4.11 类型转换
C++不会直接将两个不同类型的值相加,会先通过类型转换将运算对象的类型统一之后再求值
隐式类型转换
- 整型提升
- 数组转换成指针:大多数情况下,数组名字自动转换成指向数组首元素的指针
- decltype关键词参数、取地址符、sizeof、typeid不会发生这种转换
- 指针的转换:
- 常量整数值0或字面值nullptr能转换成任意指针类型
- 指向任意非常量的指针能转换成void*
- 指向任意对象的指针能转换成const void*
- 指向派生类的指针自动转换为指向基类的指针
- 转换成布尔类型
- 转换成常量:指向非常量类型的指针能转换成指向相应的常量类型的指针
1 2 3 4 5
int i = 0; const int *p = &i; const int &r = i; i = 9; // 可以通过i修改变量的值,但是不能通过p和r修改 cout<<i<<" "<<*p<<" "<<r;
- 类类型的转换
|
|
显式类型转换(即强制类型转换)
- 形式:
cast_name<type>(expression);
- type: 转换的目标类型,如果type是引用类型,则结果是左值
- cast_name: static_case, dynamic_cast, const_cast, reinterpret_cast中的一种
- static_cast:任何明确定义的类型转换,只要不包含底层const,都可以使用
- 把一个较大的算数类型赋值给较小的类型(可能有精度损失)
- 找回
void*
中的值
- const_cast:只能改变运算对象的底层const,一般用于去除const属性
- 只有const_cast可以改变表达式的const属性(顶层const或底层const都可以)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
int a = 2; const int *i = &a; // i是底层const int *j = const_cast<int*>(i); *j = 3; cout<<a<<" "<<*i<<" "<<*j; // 3 3 3 const int a = 2; const int *i = &a; // i是底层const int *j = const_cast<int*>(i); *j = 3; cout<<a<<" "<<*i<<" "<<*j; // 2 3 3 int a = 2; int *const i = &a; // i是顶层const int *j = const_cast<int*>(i); *j = 4; cout<<a<<" "<<*i<<" "<<*j; // 4 4 4
- const_cast常用于有函数重载的上下文中
- 只有const_cast可以改变表达式的const属性(顶层const或底层const都可以)
- reinterpret_cast:通常为运算对象的位模式提供低层次上的重新解释,慎重使用
|
|
- [[ch19-特殊工具与技术#
dynamic_cast
运算符|dynamic_cast]]:在运行时,将基类类型转换为派生类类型 - 旧式的强制类型转换本质上采用了static_cast, const_cast, reinterpret_cast中的一种,但是旧式的强制类型转化不够清晰,出了问题难以追踪