[C++学习笔记] 表达式

优先级与结合律

重载运算符时可以更改运算对象的类型 返回值类型等 但不能更改运算对象个数 运算优先级和结合律

当一个对象被用作右值时 用的是对象的值(内容) 而被用作左值时 用的是对象的身份(即在内存中的位置)

优先级和结合律规定了运算对象的组合方式 但运算对象的求值顺序与优先级和结合律无关
例如,在一条形如f()+g()*h()+j()的表达式中:

  • 根据优先级规定了g()的返回值和h()的返回值相乘
  • 根据结合律规定了f()的返回值 先与g()h()的乘积相加 所得结果再与j()的返回值相加

但对于这些函数的调用顺序并没明确规定
如果这三者影响同一对象 则会产生未定义的行为

int i=0; 
cout<< i << " " << ++i <<endl; //未定义的行为 因为<<运算符未明确规定求值顺序
*begin = toupper(*begin++); //这也是未定义的 因为赋值运算=也未规定求值顺序

所以凡是未定义求值顺序的运算 都不能同时出现修改值和使用值
四种运算符明确规定了求值顺序:

  • &&(逻辑与)
  • ||(逻辑或)
  • ?:(条件运算符)
  • ,(逗号运算符)

它们的求值顺序都是先左后右
&&||当且仅当左侧值无法确定表达式结果时才会求右侧值 这种策略称之为短路求值

(-m)/nm/(-n)都等价于-(m/n)
然而 m%(-n)等价于 m%n
(-m)%n(-m)%(-n) 都等价于 -(m%n)
取模结果的符号只看分子的符号(余数和被除数同号 这也符合我们的理解)

对于赋值运算符而言 若右值与左值不同类型 则将右值转换成左值类型
赋值运算符优先级较低 低于关系运算和算数运算符 因此要注意使用时是否要添加括号

while(i = get_v() != 42) 
while((i = get_v()) != 42) 
//前者错误 事实上是先将函数返回值与42比较后的布尔值再赋值给i 因为赋值运算优先级低

赋值运算符满足右结合律 如 int i, j; i = j = 0; 正确 满足右结合律
但对于这种多重赋值语句中的每一个对象 它的类型必须与右侧对象保持一致或可类型转换得到

例如,int i, *j; i = j = 0; 就是错误的多重赋值
虽然ij都可以接受0作赋值
但是intint *类型不一致 不能在同一条多重赋值语句中
实际上 多重赋值相当于自右向左作赋值运算 上面语句相当于j=0; i=j; 故不成立

复合运算符(如+= -= *=等等)与普通运算符(+ - *)的区别: 左侧对象求值次数少,普通两次,复合一次

除非必须 尽量不使用后置版本的递增递减运算符
因为后置版是将对象原始值的副本返回 需要额外的储存 而前置版本返回的是对象本身

除非为了在一条表达式中 既修改变量又使用原始值的情况 才用后置版本
例如 cout<<*iter++;要比 cout<<*iter; ++iter; 更简洁也更不易出错

条件运算符的优先级也很低 所以需要通过加括号来保证完成效果

cout<< ((grade < 60) ? "fail" : "pass");

使用位运算符时会将小整数提升成较大整数类型 如short char 等会被转换成 int
在二进制中的表现就是高位添加24个0

移位运算符(同时也是IO运算符) 满足左结合律
优先级在中间位置 低于算术运算符 高于 关系运算 赋值运算和条件运算

cout<<10<42; //错误 试图比较`cout`对象与`42` 

因为关系运算<优先级低于移位运算<<
事实上是先输出了10以后返回的cout对象在比较

sizeof 并不求值 因此可以对无效指针求也不会出错

逗号运算符 先对逗号左边表达式求值后丢弃 其真正结果是右侧表达式结果

int a = (1, 3); //a的值为3 注意这里加括号是因为逗号运算符的优先级低于赋值运算

常用运算符优先级:

作用域符>成员运算符>后置递增递减=类型转换>前置递增递减=逻辑非=正负号>函数>算术运算>移位运算(IO运算)>关系运算>位运算>逻辑与或>条件>赋值>复合赋值>throw>逗号运算

完整表:
从上往下 优先级由高到低

运算符 描述
:: 作用域解析运算符
() 函数调用
[] 数组下标
. 成员选择
-> 成员选择
++ -- 前缀自增/自减
+ - 正负号
! ~ 逻辑非/按位取反
* / % 乘法/除法/取模
+ - 加法/减法
<< >> 左移/右移
< <= > >= 关系运算符
== != 相等性运算符
& 按位与
^ 按位异或
| 按位或
&& 逻辑与
|| 逻辑或
?: 三目运算符
= += -= *= /= %= &= ^= |= <<= >>= 赋值运算符
, 逗号运算符

类型转换

算数转换

算数转换步骤:

  1. 首先 小整型转大整型 即能容纳它值的最小类型
  2. 接着运算中尽可能以最高精度类型保存 即都转行成最高类型
  3. 无符号和有符号进行计算时 若无符号类型不小于有符号 则转换成无符号 若小于 则依赖于机器决定

例如,3.13L +'a''a'先被提升成int 然后该int转成long double

强制转换

强制转换分为:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast

static_cast 最通用的转换 可以转除了const以外的类型转换

static_cast<double>(j);

const_cast 只用于修改表达式的常量属性但不能修改类型

const char b; 
char c = const_cast<char> (b);

const_cast在重载函数时最有用

dynamic_cast 就是为解决虚基类到派生类的转换而设计的。在C++中,dynamic_cast是一种用于在运行时进行类型转换的操作符。它用于将一个指向基类的指针或引用转换为指向派生类的指针或引用dynamic_cast提供了一种安全的方式来进行类型转换,它会在运行时检查转换是否有效,如果转换不合法,则返回一个空指针(对于指针转换)或抛出一个std::bad_cast异常(对于引用转换)。

dynamic_cast的语法如下:

dynamic_cast<new_type>(expression)

其中,new_type是目标类型,expression是要转换的表达式。

以下是dynamic_cast的一些特点和用法:

  1. 用于多态类型:dynamic_cast主要用于多态类型的转换,即基类指针或引用转换为派生类指针或引用。它只能用于具有虚函数的类层次结构中。

  2. 安全性检查:dynamic_cast会在运行时检查转换是否合法。如果转换不合法,即基类指针或引用不指向派生类对象,dynamic_cast会返回一个空指针(对于指针转换)或抛出一个std::bad_cast异常(对于引用转换)。

  3. 向下转型:dynamic_cast可以将基类指针或引用向下转型为派生类指针或引用。这样可以访问派生类特有的成员函数和成员变量。

  4. 向上转型:dynamic_cast也可以将派生类指针或引用向上转型为基类指针或引用。这样可以将派生类对象当作基类对象来使用。

  5. 用于类层次结构的判断:dynamic_cast还可以用于判断两个类对象之间的关系。如果转换成功,表示两个类对象之间存在继承关系;如果转换失败,表示两个类对象之间不存在继承关系。

需要注意的是,dynamic_cast只能用于具有多态性的类层次结构,即基类必须至少有一个虚函数。此外,dynamic_cast的性能相对较低,因为它需要在运行时进行类型检查。因此,在使用dynamic_cast时应该谨慎使用,尽量避免频繁的类型转换操作。

reinterpret_cast 运算符并不会改变括号中运算对象的值,而是对该对象从位模式上进行重新解释:

  1. 不进行类型检查:reinterpret_cast不进行任何类型检查或转换,它只是将一个指针或引用的二进制表示重新解释为另一种类型的指针或引用。因此,使用reinterpret_cast需要非常小心,确保转换是安全的。

  2. 用于底层操作:reinterpret_cast主要用于进行底层操作,例如将一个指针转换为一个整数,或者将一个整数转换为一个指针。这些操作通常需要对内存布局进行精确控制,因此需要使用reinterpret_cast来进行类型转换。

  3. 用于类型擦除:reinterpret_cast还可以用于类型擦除,即将一个具有模板参数的类型转换为一个没有模板参数的类型。这样可以在运行时动态地创建一个模板类的实例。

形如int(a) (int) a等为旧式的强制类型转换