📔 C++ Primer 0x04 学习笔记
4.1 基础
4.1.1 基本概念
- C++ 定义了一元运算符、二元运算符以及一个三元运算符,看作用于几个对象。函数调用也是特殊的运算符,它对运算对象数量没有限制。一个符号是几元运算符要看上下文
- 理解表达式前要知道运算符优先级、结合律,运算对象求值顺序
- 运算过程中常常发生类型转换
- 自定义事实上已存在的运算符赋予另外含义的操作称为重载运算符
- 使用重载运算符的时候,运算对象的类型和返回值类型都由运算符定义,但是运算对象的个数、优先级、结合律无法改变
- C++ 的表达式要么是右值、要么是左值
- 当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)
4.1.2 优先级与结合律
- 复合表达式:两个或多个运算符的表达式
- 括号无视优先级与结合律
4.1.3 求值顺序
- 优先级规定了运算对象的组合方式,但没有说明运算对象按照什么顺序求值。大多数情况下,不会明确指定求值的顺序。
- 有可能因为求值顺序没有明确规定,产生未定义行为
- 4种运算符明确规定了运算对象的求值顺序:逻辑与、逻辑或、条件运算符、逗号运算符
- 运算对象的求值顺序与优先级和结合律无关
- 复合表达式两条经验:拿不准的时候可以加括号;如果改变了某个运算对象的值,那么在表达式其他地方不要再使用这个运算对象(如果子表达式嵌套那ok)
4.2 算术运算符
- 除非特殊说明,算术运算符能作用于任意算术类型以及任意能转换为算术类型的类型。
- 算术运算符都满足左结合律,也就是说优先级相同的时候从左向右顺序结合
- 算术运算符的运算对象和求值结果都是右值
- 表达式求值之前,小整数类型会被提升为较大整数类型,所有运算对象最终会转化成同意类型
- 取余运算运算对象必须是整数
- C++11 新标准规定商一律向0取整(即直接切除小数部分)
4.3 逻辑和关系运算符
- 逻辑运算符作用于任意能转换成布尔类型值的类型
- 逻辑与和逻辑或都是短路求值
- 进行比较运算时,除非比较对象是布尔类型,否则不要用布尔字面值
true
和 false
作为比较对象,true
和 false
会被转为相应的类型 - 关系运算符都满足左结合律
4.4 赋值运算符
- 赋值运算符的左侧运算对象必须是一个可以修改的左值
- 多重赋值语句每个对象,它的类型或者与右边对象类型相同,或者可由右侧对象类型转换得到
- 赋值运算符优先级相对较低,条件语句中通常加上括号
- 不要混淆相等运算符和赋值运算符
- 复合运算符左侧对象只求值一次,普通运算符求值两次,不过这个没啥性能影响
4.5 递增和递减运算符
- 除非必须,否则不用递增递减运算符的后置版本。前置版本把值+1后直接返回了运算对象;后置版本需要把原始值存下来,以便返回未修改的内容
-
*iter++
很常用,使用当前值,并将iter
+1 - 运算对象可按任意顺序求值,递增递减运算符会改变运算对象的值,要提防在复合表达式中错用这两个运算符
4.6 成员访问运算符
-
ptr->mem
等价于(*ptr).mem
- 解引用运算符的优先级低于点运算符
- 箭头运算符作用于一个指针类型的运算对象,结果是一个左值
- 点运算符:如果成员所属的对象是左值,那么结果是左值;反之,如果成员所属的对象是右值,那么结果是右值
4.7 条件运算符
- 当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值;否则运算的结果是右值
- 调节运算符满足右结合律,意味着运算对象一般按照从右想做的顺序组合
- 条件运算的嵌套最好别超过两到三层,否则可读性太差了
- 在输出表达式中使用条件运算符要套一层括号
4.8 位运算符
- 位运算符可以用于带符号和无符号类型。如果运算对象是带符号类型且为负数,那么运算符处理符号位依赖于机器,是未定义行为。因此,强烈建议仅将位运算符用于处理无符号类型
- 一般来说如果运算对象是小整型,他的值会被自动提升成较大的整型
- 移位运算符按照右侧运算对象要求移动位数,然后把左侧对象的拷贝作为求值结果。右侧运算对象一定不为负,且值必须严格小于结果的位数,否则会产生未定义行为。移除边界直接舍弃掉了
- 区分
&
和&&
,|
和||
,~
和!
- 移位运算符(IO运算符)满足左结合律
-
bool status = quiz1 & (1UL << 27)
可以查看quiz1
第27位是否为1
4.9 sizeof 运算符
-
sizeof
运算符返回一条表达式或者一个类型名字所占的字节数 -
sizeof
满足右结合律,所得的值是一个size_t
的常量表达式,可以用sizeof
的结果声明数组的维度 -
sizeof
不会实际求运算对象的值,所以在sizeof
的运算对象中解引用一个无效指针也是安全的,sizeof
不需要真的解引用指针就可以知道它所指对象的类型 -
sizeof
无需一个具体的对象,就可以知道类成员的大小 - 对
char
或类型为char
的表达式执行sizeof
,结果为1 - 对引用类型执行
sizeof
得到被引用对象所占的空间大小 - 对解引用指针执行
sizeof
得到所指对象的空间大小,指针不需要有效 - 对数组执行
sizeof
得到整个数组所占的空间大小,等价与对每个元素做sizeof
然后求和,sizeof
不会自动将数组转换成指针来处理。 -
sizeof(ia)/sizeof(*ia)
,可以得到数组的元素个数 - 对
string
或者vector
对象执行sizeof
值返回该类型固定部分的大小,不会计算对象中的元素占了多少空间
4.10 逗号运算符
- 逗号运算符含有两个运算对象,从左忘右顺序依次求值
- 首先对左侧的表达式求值,然后将结果丢弃,逗号运算符真正的结果是右侧表达式的值
4.11 类型转换
- 隐式类型转换的情况
- 在大多数表达式中,比
int
小的整型会被首先爱你提升为较大的整数类型 - 在条件中,非布尔值转换成布尔类型
- 初始化过程中,初始值转换成变量类型;赋值中,右值运算对象转成左值运算对象类型
- 如果算术运算或关系运算的运算对象多种,需要转换成同一类型
- 函数调用时也会发生类型转换
4.11.1 算术转换
- 既有整型又有浮点型,整型转换为浮点类型
- 整型提升,小整数类型(
bool
、char
、signed char
、unsigned char
、short
、unsigned short
等)转为较大整数类型(int
能存存int
,存不了存unsigned int
),总之就是提升成能容纳原类型所有可能值的最小类型 - 无符号类型的运算对象:
- 首先整型提升,如果匹配无需进一步转换。(同为有符号/无符号类型)
- 如果一个运算对象为无符号类型,一个对象为有符号类型,且无符号类型不小于带符号类型,那么有符号类型变成无符号类型。如果有符号类型为负数,那么发生未定义行为。
- 如果带符号类型大于无符号类型,结果依赖于机器。如果无符号类型能存到带符号类型,那么无符号转带符号;否则带符号无符号
4.11.2 其他隐式类型转换
- 数组转换成指针
- 指针的转换
- 常量整数值
0
或者字面值nullptr
能转换成任意指针类型 - 指向任意非常量的指针能转换成
void*
- 指向任意对象的指针能转换成
const void*
- 转换成布尔类型
- 转换成常量
- 类类型定义的转换
4.11.3 显示转换
- 虽然有时候不得不使用强制类型转换(cast),但是这种方法本质上是非常危险的
- 命名的强制类型转换
cast-name<type>(expression)
,其中cast_name
指定了执行的是哪种转换,有下面这几个static_cast
、dynamic_cast
、const_cast
和reinterpret_cast
-
static_cast
,任何有明确定义的类型转换只要不包含底层const
都可以用static_cast
,可以配合void*
使用,强制还原成原来的类型 -
const_cast
只能改变运算对象的底层const
,就是去掉原来的底层const_cast
,但是如果原来不是底层const
,再使用const_cast
会产生未定义后果。常用于函数重载 -
reinterpret_cast
通常为运算对象的位模式提供较低层次的解释。会掩盖真实的对象类型,当作reinterpret_cast
指定的类型去处理。这个很危险,而且依赖机器,一般不用 - 应当尽量避免强制类型转换
- 旧式的强制类型转换:C语言风格
(type) expr
和函数形式type(expr)
,会先尝试类似static_cast
和const_cast
的操作,如果替换后合法那就ok;否则将执行类似reinterpret_cast
的行为。 - 旧式强制类型转换不是很清晰明了,容易看漏。一旦出现问题也很难追踪
4.12 运算符优先级表
看书就完事了