参考资料:
- 《C++ Primer》第5版
- 《C++ Primer 习题集》第5版
4.6 成员访问运算符(P133)
点运算符和箭头运算符都可用于访问成员,ptr->mem
等价于 (*ptr).mem
。箭头作用于指针类型对象,结果为左值;点运算符作用域对象,如果对象为左值则结果为左值,否则为右值。
4.7 条件运算符(P134)
条件运算符的形式如下:
cond ? expr1 : expr2;
其中,当 cond
为条件判断的表达式, expr1
和 expr2
是两个类型相同或可转换为某个公共类型的表达式。条件运算符首先对 cond
求值,若结果为真,则对 expr1
求值并返回该值;反之,则对 expr2
求值并返回该值。当两个表达式都是左值或能转换成同一种左值类型时,运算的结果为左值,否则为右值。
嵌套条件运算符
string finalgrade = (graph > 90) ? "high pass"
: (grade < 60) ? "fail" : "pass";
条件运算符满足右结合律。
在输出表达式中使用条件运算符
条件运算符的优先级非常低,所以通常要在其两端加上括号。
4.8 位运算符(P135)
位运算符如何处理符号位依赖于机器,所以强烈建议将位运算符用于无符号类型。
移位运算符
移位运算符的右侧对象不能为负,且值必须严格小于结果的位数,否则将产生未定义行为。
需要注意的是,有符号类型在右移时插入符号位还是 0 由具体环境决定。
位求反运算符
位与、位或、位异或运算符
移位运算符满足左结合律
使用 cout
和 cin
进行 IO 操作时会用到重载的移位运算符,重载运算符的优先级和结合律不变。移位运算符的优先级比算术运算符低,比关系运算符、赋值运算符和条件运算符高。
4.9 sizeof
运算符(P139)
sizeof
运算符满足右结合律,结果为 size_t
类型的常量表达式:
sizeof(type);
sizeof expr;
sizeof
可以返回表达式结果类型的大小,但不实际计算运算对象的值:
sizeof(*p);
sizeof Sales_data::revenue;
正是由于上述特性,即使 p
是一个无效指针,此处的解引用操作也不会有负面影响。C++11 新标准还允许通过作用域运算符来获取类成员的大小。
sizeof
运算符需要注意的情形:
sizeof
运算符作用于数组时,不会把数组转换成指针,而是返回整个数组所占空间的大小,等价于对数组中所有元素各执行一次sizeof
并求和。sizeof
作用于vector
或string
对象时将将返回一个固定的值,这个值与对象所含的元素个数无关。
获取数组元素个数的常见写法:
sizeof(arr)/sizeof(*arr);
4.10 逗号运算符(P140)
逗号运算符含两个运算对象,从左向右依次求值。逗号运算符首先对左侧运算对象求值,然后将求值结果丢弃,再对右侧运算对象求值并将该值作为结果(如果右侧运算对象的结果为左值,那么逗号运算符的结果也为左值)。
4.11 类型转换(P141)
int ival = 3.14 + 3;
上述表达式发生了隐式转换。算术类型之间的隐式转换被设计得尽可能避免损失精度,所以整型一般会转换成浮点型。因此,上面的表达式中的 3
首先被转换成 double
型,然后再将浮点数加法的结果转换成 int
型。
何时发生隐式类型转换
- 在大多数表达式中,比
int
小的整型值会提升为较大的整数类型。 - 条件中,非布尔值转换成布尔值
4.11.1 算术转换(P142)
算术转换的规则:运算对象转换成最宽的类型、整型转换成相应的浮点型。
整型提升
对 bool
、char
、short
等小整型,只要它们所有可能的值能存储在 int
里,就会被提升成 int
,否则被提升成 unsigned int
。
较大的 char
类型 (如 wchar_t
)会被提升成最小能容纳其所有值的整型(至少为 int
)。
无符号类型的运算对象
表达式首先执行整型提升,若提升后的运算对象均为带符号或均为无符号,则将小类型转换为大类型即可。
如果一个运算对象是无符号类型,一个对象是有符号类型,且那个无符号类型不小于有符号类型,则将带符号对象转换成无符号对象(如 int
和 unsigned int
运算,要将 int
转换成 unsigned int
);如果带符号类型大于无符号类型,此时如果带符号类型能容纳无符号类型的所有值,则将无符号对象转换成有符号对象,否则将有符号对象转换成无符号类型(如 long
和 unsigned int
,转换的结果取决于 unsigned int
所占空间的大小)。
理解算术转换
3.14L + 'a'; // 'a'先被提升成int,再转换成long double
4.11.2 其他隐式类型转换(P143)
数组转换成指针:在大多数情况中,数组会自动转换成指向数组首元素的指针。当数组作为 decltype
关键字的参数,或 &
、sizeof
、typeid
等运算符的对象时,上述转换不会发生。
指针的转换:常量整数值 0
和字面量 nullptr
能转换成任意类型的指针;任意指向非常量的指针能转换成 void*
;任意指向对象的指针能转换成 const void*
。
转换成布尔类型:如果指针类型或算术类型的值为 0 ,则结果为 false
,否则为 true
。
转换成常量
类类型定义的转换:类类型能定义由编译器自动执行的转换。
4.11.3 显式转换(P144)
命名的强制类型转换
cast-name<type>(expression);
其中,type
是转换的目标类型,expression
是要转换的值,如果 type
是引用类型,则转换结果为左值。cast-name
是 static_cast
、dynamic_case
、const_cast
和 reinterpret_cast
中的一种
static_cast
任何具有明确定义,且不包含底层 const
的转换都可以使用 static_cast
。
int i = 1, j = 1;
double dval = static_cast<double>(j) / i;
当我们需要将大类型赋值给小类型而不计较精度损失时,使用 static_cast
可以避免编译器输出警告信息。
static_cast
常用于找回 void*
中的值:
double d = 3.14;
void *p = &d;
double *dp = static_cast<double*>(p);
const_cast
const_cast
只能改变对象的底层 const
:
const char *pc = nullptr;
char *p = const_cast<char*>(pc); // 正确
int *p = const_cast<int*>(pc); // 错误
如果对象本身不是一个常量,使用 const_cast
获得写权限是合法的,否则将产生未定义行为:
int i = 1;
const int ci = 1;
const int *p1 = &i, *p2 = &ci;
int *q1 = const_cast<int*>(p1); // 合法
int *q2 = const_cast<int*>(p2); // 利用q2对ci进行写操作将会产生未定义行为
reinterpret_cast
reinterpret_cast
可以从位存储的角度对对象进行重新解释:
int *ip = nullptr;
char *pc = reinterpret_cast<char*>(ip);
应该尽可能避免使用强制类型转换
旧式的强制类型转换
早期的 C++ 版本中,显示强制类型转换包括以下两种类型:
type(expr);
(type)expr;
旧式的强制类型转换具有与 static_cast
、const_cast
和 reinterpret_cast
相似的行为。