C++ Primer 读书笔记 – 第五章

 第5章 表达式

⒈ 表达式(expression_r)是一个C++程序中最低级的计算,由一或多个用一个操作符(operator)连接起来的操作数(operands)组成
⒉ 每个表达式都产生一个结果。表达式可以用作操作数,因此可用多个操作符编写复合表达式
⒊ 在求解表达式的过程中如果需要储存运算结果,编译器会自动创建没有名字的临时对象(temporary object),这些对象会在外围最大的表达式结束后释放
⒋ 表达式是否合法、合法表达式含义如何(执行什么操作、结果是什么类型)均取决于操作数的类型

⒌⒈ 算术操作符
⒈ 按优先级从高到低排列为:
一元正+, 一元负-; 乘法*, 除法/, 取模%; 加法+, 减法-
⒉ 关于除法/
⑴ 两整数相除结果仍为整数,商的小数部分被截去
⑵ 一正一负两整数相除,结果值向
0一侧还是向-∞一侧取整依赖于机器
⒊ 关于取模
⑴ 操作数只能为整型
⑵ 两操作数均为负时结果为负或0;
两操作数一正一负时,结果的符号随哪个操作数而定依赖于机器

⒌⒉ 关系操作符和逻辑操作符
⒈ 按优先级从高到低排列为:
逻辑非!; 小于<, 小于等于<=, 大于>, 大于等于>=; 相等==, 不等!=; 逻辑与&&; 逻辑或||
⒉ 关系操作符和逻辑操作符接受算术或指针型操作数,并返回 bool 型值
逻辑操作符视其操作数为条件表达式
⒊ 逻辑与&&和逻辑或||操作符支持短路求值(short-circuit evaluation)
⒋ 不应串接使用关系操作符
形如i

⒌⒊ 位操作符
⒈ 按优先级从高到低排列为:
位取反~; 左移<<, 右移>>; 位与&; 位异或^; 位或|
⒉ 位操作符使用整型操作数,将其视为二进制位的集合
由于负整数的符号位如何处理依赖于机器,因此应使用 unsigned 整型操作数

⒌⒋ 赋值操作符
⒈ 赋值操作符的左操作数须为非 const 左值
赋值表达式的结果即为其左操作数(左值)
⒉ 赋值操作符从右向左结合,因此当各操作数都有相同的通用类型时,允许在一个表达式中进行多次赋值,如:
i = j = k = 0;
⒊ 复合赋值操作符
对于任意二元算术操作符或二元位操作符 op
a op= b;
相当于
a = a op b;
二者显著的差别在于前者只计算了一次左操作数,后者则计算了两次

⒌⒌ 自增和自减操作符
⒈ 自增++和自减–操作符为对象加1或减1提供了方便简短的实现方式,有前置和后置两种使用形式
前置操作返回加(减)1后的对象(左值),后置操作返回操作数的原值(右值)
⒉ 出于性能考虑,应只在必要时才使用后置操作符
后置操作符需要先保存原值以便返回,而前置操作符只需加(减)1后直接返回对象即可

⒌⒍ 箭头操作符
箭头操作符用于获取指针指向类类型对象的成员。
下面两个表达式等价:
p->foo;
(*p).foo;

⒌⒎ 条件操作符
⒈ 条件操作符是C++中唯一的三元操作符,能将简短的 if-else 语句嵌入表达式
格式为 cond ? expr1 : expr2
首先计算 cond 值,若为 true 则计算并返回 expr1, 为 false 则计算并返回 expr2
⒉ 避免深度嵌套以保证可读性

⒌⒏ sizeof 操作符
sizeof 操作符用于返回一个对象或类型的大小(单位字节,类型 size_t)
形式为 sizeof expr
或  sizeof(type_name)
sizeof 表达式的结果是编译时常量
对表达式使用 sizeof 时该表达式的值并不会被计算
⒊ 对数组作 sizeof 将得到整个数组在内存中的长度

⒌⒐ 逗号操作符
逗号表达式是一组由逗号分隔的表达式,从左向右计算并返回最右边表达式的值(若该表达式为左值则返回左值)

⒌⒒ new delete 表达式
new delete 表达式分别用于动态创建和释放单个对象或一个数组
new 表达式
new 表达式动态创建单个对象或一个数组,并返回指向该对象或数组首元素的指针
① 动态数组维数为0值时也将返回有效的非零指针,但不能解引用
② 可以创建动态的 const 对象或数组,它们无法修改但可以释放
③ 如果无法获取需要的空间,系统将抛出 bad_alloc 异常
⑵ 分配与对象(或数组元素)初始化
① 默认内置类型不初始化,类类型调用默认构造函数(必须提供)
单个对象: new [const] 类型名
一个数组: new [const] 类型名[维数]
② 添加空括号()可执行值初始化(value initialization)
即内置类型对象置为0,类类型对象调用默认构造函数(必须提供)
单个对象: new [const] 类型名()
一个数组: new [const] 类型名[维数]()
注:对于后者,我使用的编译器中,VC初始化后会把元素置为0,但mingw不会
③ 执行直接初始化
单个对象: new [const] 类型名(初始化式)
动态数组元素不支持类似初始化方式
delete 表达式
⑴ 由 new 动态分配的对象或数组需要显式地使用 delete 表达式释放(否则会造成内存泄漏)
delete ptr 和 delete[] ptr 分别释放单个对象和动态数组
① 如果指针不指向由 new 分配的对象,则对其 delete 不合法
例外:对零指针 delete 是合法的
② 读写已释放的对象或对同一内存空间多次 delete 都可能导致错误
因此 delete 之后应立即重置该指针的值(一般重置为0)
③ 释放动态数组如果丢掉方括号[],将可能导致编译器无法发现的错误

⒌⒑ 复合表达式的求值
⒈ 含有两个或以上操作符的表达式称为复合表达式(compound expression_r)
操作数和操作符的结合方式决定了符合表达式的值,而前者取决于操作符的优先级和结合性:
操作数优先与优先级更高的操作符结合;操作符优先级相同时则由结合性决定结合方向
⒉ 圆括号()凌驾于优先级之上
若不确定结合方式,则使用圆括号()强制确定操作数的组合
⒊ 结合方式确定并不意味着操作数的计算顺序并不确定
若修改了操作数的值,则不要在同一语句的其它地方再使用它,除非操作数的计算次序不成问题
运算符优先级与结合性表

⒌⒓ 类型转换
⒈ 隐式类型转换
两个类型间存在可转换关系,则称两个类型相关
⑴ 何时发生
当编译器期望获得某种类型的数据却得到另一种类型的,就会尝试自动转换类型
具体情况包括:
① 表达式中不同类型的多个操作数被转换到同一类型
② 用作条件的表达式被转换为
bool
③ 用一表达式对某变量初始化或赋值时,前者转换为后者的类型
④ 给函数传递实参和返回值时可能发生隐式类型转换
⑵ 内置类型转换规则
① 算术类型间的转换
ⅰ 浮点型转换为整型时小数部分被抛弃(在赋值、初始化等情况下发生)
ⅱ 二元(算术、关系等)操作符表达式中,为试图保留精度会把较小的类型转换为较大的类型
a类型提升
· 将比 int 小的整型提升为 int 或(当 int 不足以容纳原类型的所有可能值时)unsigned int
· 将 float 提升为 double
signed unsigned 之间的转换
· 对于 signed 较小类型和 unsigned 较大或相同类型,前者转换为后者
· 对于 signed 较大类型和 unsigned 较小类型,如何操作依赖于机器:
前者若能表示后者所有可能值则后者转换为前者,否则都转换为前者的 unsigned 形式
以上两种情况,负值有可能会被转换为 unsigned 导致溢出,造成意料之外的结果
② 转换为指针
ⅰ 表达式中的数组名会自动转换为指向数组首元素的指针
但以下情况除外:
a数组作为取地址操作符&的操作数
b数组作为 sizeof 操作符的操作数
c使用数组对数组的引用初始化时
ⅱ 指向任意类型对象的指针都可转换为 void* 型
ⅲ 整型常量0可转换为任意指针类型
bool 类型转换
ⅰ 算术值和指针值转换为 bool
0false0true
bool 值转换为算术类型
true1 false0
④ 枚举成员转换为整型
枚举类型对象或枚举成员可自动转换为整型(具体类型依赖于机器和枚举成员最大值,但至少为int)
⑤ 转换为 const 对象
const 对象在初始化相关的 const 型引用时自动转换为 const
const 对象的地址或指向非 const 对象的指针也可转换为指向 const 对象的指针
⒉ 显式类型转换
⑴ 若可能,避免使用强制类型转换
确实需要使用时也应尽量小心
⑵ 命名的强制类型转换
dynamic_cast
用于运行时类型识别(RTTI)
const_cast
可以添加或去除指针、数据成员指针或引用的 const 特性
static_cast
ⅰ 可以显式完成编译器隐式执行的任何类型转换
因潜在精度损失产生的编译器警告会被关闭
· 可以用以避免不必要的隐式转换,如
已知 d 为 double, i 为 int, 将 i*=d; 写成 i*=static_cast<int>(d); 可省去将 i 转换为 double 这一非必要步骤
reinterpret_cast
为操作数的位模式提供较低层次的重新解释,结果依赖编译器和机器
任何不当使用都可能导致运行时错误
⑶ 旧式强制转换
在合法使用 static_cast const_cast 的地方,提供与命名强制转换一样的功能
若两种转换均不合法,就执行 reinterpret_cast
由于不易判别每个显式转换的潜在风险,不推荐使用

你可能感兴趣的:(Reading,notes)