《C++ Primer》读书笔记 第四章 表达式

第四章 表达式

4.1 基础

4.1.1 基本概念

  • 一元运算符、二元运算符。
  • 组合运算符、运算对象:注意优先级、结合律、求值顺序。
  • 运算对象转换。
  • 重载运算符。
  • 左值:求值结果是一个对象或函数,但以常量对象为代表的某些左值实际上不能放在赋值语句左侧。
  • 需要右值的地方可以用左值替代,反之则不行。

4.1.2 优先级与结合律

  • 优先级:相对不同运算符而言。
  • 结合律:相对相同运算符而言。
  • 规定了运算对象的组合方式。

4.1.3 求值顺序

1
int i = f1() * f2();
  • 我们只知道函数在乘法运算之前调用,但不知道谁先调用。
1
2
int i = 0;
cout << ++i << endl;
  • 对没有指定求值顺序的运算符来说,如果表达式指向并修改了对象,它的结果是未定义的。
  • 4种明确规定了求值顺序的运算:&&||?:,

4.2 算术运算符

  • C++11规定商值一律向0取整(即直接去掉小数部分)。即(-m)/nm/(-n)都等于-(m/n)
  • C++11规定取余的符号与被除数一样:m%(-n)等于m%n(-m)%n等于-(m%n)

4.3 逻辑与关系运算符

  • 注意短路现象。

4.4 赋值运算符

  • C++11允许使用花括号括起来的初始值列表作为赋值语句右侧运算对象。对内置类型,初始值列表最多只能包含一个值,且即使类型转化所占空间也不能大于目标类型的空间。对类类型,赋值运算细节由类本身决定。
1
2
3
4
int k;
k = {3.14} // 错误,窄化转换
vector<int> vi;
vi = {1, 2, 3};
  • 复合赋值运算符。

4.5 递增和递减运算符

  • 除非必须,否则不用后置版本,因为会带来消耗,尤其是复制的迭代器。
  • 后置常用在*p++上,让代码更简洁。
  • 避免在表达式其它地方用到递增或递减的变量。

4.6 成员访问运算符

  • 注意解引用运算符优先级低于点运算符,所以(*p).size()中括号不可省略。

4.7 条件运算符

  • 条件运算符优先级非常低,记得使用括号。
  • 可以嵌套使用,但会降低可读性。

4.8 位运算符

  • 用于整数类型,把对象看作二进制位的集合。标准库类型bitset可以表示任意大小的二进制位集合,也可以使用。
  • 一般小整型会被自动提升为较大整型,运算对象可以有符号,但对负数的运算依赖于机器,并且左移还可能改变符号位,所以推荐操作无符号数。

4.9 sizeof运算符

返回表达式或类型名所占的字节数,结果是size_t类型的常量表达式。有两种形式:

1
2
sizeof(type)
sizeof expr // 返回表达式结果类型的大小,并不计算运算对象的值。
  • 因为不计算,sizeof *p中,即便p是个无效的指针也没有问题。
  • C++11中允许使用作用域运算符获得类成员的大小,如sizeof Sales_data::revenue
  • sizeof不会吧把数组转化成指针进行处理,得到的是数组所占字节数。
  • 对容器使用只返回该类型固定部分的大小。

4.10 逗号表达式

  • 左右先后执行,返回右侧的表达式结果。
  • 常用在for循环。

4.11 类型转换

  • 如果两种类型可以相互转换,那么它们就是关联的。
  • 隐式转换。

4.11.1 算术转换

4.11.2 其它隐式类型转换

4.11.3 显式转换

1
cast-name<type> (expression);

cast-name:

  • dynamic_cast:支持运行时类型识别。19.2
  • static_cast:任何具有明确定义的类型转换,只要不包含底层const。
  • const_cast:只能改变运算对象的底层const。去掉其const性质,让编译器不再阻止我们进行写操作。但如果对象是常量,写操作会造成未定义的结果。常用在函数重载中。6.4
1
2
const char *pc;
char *p = const_cast<char*>(pc);
  • reinterpret_cast:从位的模式提供较低层次的重新解释。风险较大。

旧式转换:

1
2
type (expr);
(type) expr;

4.12 运算符优先级表


练习

  • 4.1 四则运算。
  • 4.2 运算顺序*vec.begin()+1
  • 4.3 是否应该允许编译器利用求值顺序进行程序优化。不能接受
  • 4.4 四则运算。
  • 4.5 四则运算。
  • 4.6 判断奇偶数。KEY: (i & 0x1)
  • 4.7 溢出的理解。
  • 4.8 运算顺序:&&||==KEY: == is undefined
  • 4.9 指针和字符参与的逻辑运算。
  • 4.10 while 循环。
  • 4.11 判断4个数的相对大小。
  • 4.12 运算顺序:i!=j<KKEY: i!=(j<k)
  • 4.13 连续赋值。
  • 4.14 ==误写为=
  • 4.15 不能将int*赋给int
  • 4.16 避免优先级和误写带来的问题。
  • 4.17 i++++i的区别。
  • 4.18 ++while
  • 4.19 ++<=KEY: vec[ival++] <= vec[ival] is undefined behavior.
  • 4.20 ++*.()->
  • 4.21 ?:的使用。
  • 4.22 多重判断时?:if的比较。
  • 4.23 使用?:注意优先级。KEY: + > ==
  • 4.24 如果?:是左结合律的,如何计算。
  • 4.25 左移运算。
  • 4.26 整型数范围。
  • 4.27 &&&|||
  • 4.28 sizeof
  • 4.29 sizeof
  • 4.30 sizeof
  • 4.31 递增前置改后置。
  • 4.32 逗号表达式的理解。
  • 4.33 ,?:的优先级。
  • 4.34 隐式类型转换。
  • 4.35 隐式类型转换。
  • 4.36 强制类型转换。
  • 4.37 强制类型转换。
  • 4.38 强制类型转换。