第四章 表达式

4.1 表达式基础

  • 左值和右值
    • 一个对象被用作左值时,使用的是对象的身份(在内存中的地址,左值可以按名访问,而且其地址可以被赋值)
    • 一个对象被用作右值时,使用的是对象的值(内容)
      • (自己的理解)即使一个对象可以按名访问,但如果该对象的内容在只读数据段,该对象也是右值
    • 需要右值的地方可以使用左值代替,但是反过来不行
    • decltype作用于表达式(注意不是变量),推导出来是左值还是右值,与表达式返回值是左值还是右值相同
  • 求值顺序
    • 如果改变了某个运算对象的值,在同一表达式中不要再使用该运算对象,参考
    • 只有四种运算符明确规定了求值顺序:&&||?:,

4.2 算术运算符

  • 整数除法的结果向0舍入
  • 取余运算m%n,结果符号与被除数m相同

4.3 逻辑和关系运算符

  • 短路求值

4.4 赋值运算符

  • C++11允许使用花括号括起来的初始值列表作为右侧运算对象,初始化列表为空时进行值初始化
  • 赋值运算的返回结果是它的左侧运算对象,是一个左值
  • 赋值运算符满足右结合律,这点和其他二元运算符不一样。
    • 比如:ival = jval = 0;等价于jval = 0; ival = jval;
  • 复合赋值运算符只求值一次,而普通运算符需要两次。
    • 比如:a=a+1 要先求一次 a+1,再将结果赋值给 a

4.5 递增和递减运算符

  • 前置版本j = ++i,先i加一,后给j赋值,优先使用
  • 后置版本j = i++,先给j赋值,后i加一
  • 混用解引用和递增运算符:*p++ 等价于 *(p++)
    • 首先进行自加,p指向下一个位置,返回原来对象的副本
    • 将原来对象的副本进行解引用
      1
      2
      3
      
      auto iter = vi.begin();
      while (iter!=vi.end())
          cout<<*iter++<<endl;    // 输出当前值,指针向前移1
      

4.6 成员访问运算符

ptr->mem等价于(*ptr).mem

4.7 条件运算符

  • 条件运算符(?:):cond? expr1: expr2
  • 可以嵌套使用,右结合律,从右向左顺序组合

4.8 位运算符

  • 位运算符是作用于整数类型的运算对象。
  • 向左移(<<),向右移(>>),位取反(~)(逐位求反)、与(&)、或(|)、异或(^
  • 有符号数负值可能移位后变号,所以强烈建议位运算符仅用于无符号数。

4.9 sizeof运算符

  • 语法:
    • sizeof (type):返回类型的大小
    • sizeof expr:返回表达式结果类型的大小
    • 返回类型是 size_t的常量表达式
  • sizeof并不实际计算其运算对象的值。
  • 对数组执行sizeof运算得到整个数组所占空间的大小。
  • stringvector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中元素所占空间的大小。

4.10 逗号运算符

从左向右依次求值,左侧求值结果丢弃,返回结果是右侧表达式的值。

4.11 类型转换

C++不会直接将两个不同类型的值相加,会先通过类型转换将运算对象的类型统一之后再求值

隐式类型转换

  • 整型提升
  • 数组转换成指针:大多数情况下,数组名字自动转换成指向数组首元素的指针
    • decltype关键词参数、取地址符、sizeof、typeid不会发生这种转换
  • 指针的转换:
    • 常量整数值0或字面值nullptr能转换成任意指针类型
    • 指向任意非常量的指针能转换成void*
    • 指向任意对象的指针能转换成const void*
    • 指向派生类的指针自动转换为指向基类的指针
  • 转换成布尔类型
  • 转换成常量:指向非常量类型的指针能转换成指向相应的常量类型的指针
    1
    2
    3
    4
    5
    
    int i = 0;
    const int *p = &i;
    const int &r = i;
    i = 9; // 可以通过i修改变量的值,但是不能通过p和r修改
    cout<<i<<" "<<*p<<" "<<r;
    
  • 类类型的转换
1
2
    while(cin >> s); //将cin转换为bool
    string s = "value"; // 将字符数组转换为string

显式类型转换(即强制类型转换)

  • 形式:cast_name<type>(expression);
    • type: 转换的目标类型,如果type是引用类型,则结果是左值
    • cast_name: static_case, dynamic_cast, const_cast, reinterpret_cast中的一种
  • static_cast:任何明确定义的类型转换,只要不包含底层const,都可以使用
    • 把一个较大的算数类型赋值给较小的类型(可能有精度损失)
    • 找回void*中的值
  • const_cast:只能改变运算对象的底层const,一般用于去除const属性
    • 只有const_cast可以改变表达式的const属性(顶层const或底层const都可以)
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      
      int a = 2;
      const int *i = &a; // i是底层const
      int *j = const_cast<int*>(i);
      *j = 3;
      cout<<a<<" "<<*i<<" "<<*j; // 3 3 3
      
      const int a = 2;
      const int *i = &a; // i是底层const
      int *j = const_cast<int*>(i);
      *j = 3;
      cout<<a<<" "<<*i<<" "<<*j; // 2 3 3
      
      int a = 2;
      int *const i = &a; // i是顶层const
      int *j = const_cast<int*>(i);
      *j = 4;
      cout<<a<<" "<<*i<<" "<<*j; // 4 4 4
      
      • const_cast常用于有函数重载的上下文中
  • reinterpret_cast:通常为运算对象的位模式提供低层次上的重新解释,慎重使用
1
2
3
int *i;
char *c = reinterpret_cast<char*>(i); // 但是c所指的真实对象是一个int而非char
// char *c = (char*) i; // 旧式的强制类型转换,等价
  • [[ch19-特殊工具与技术#dynamic_cast运算符|dynamic_cast]]:在运行时,将基类类型转换为派生类类型
  • 旧式的强制类型转换本质上采用了static_cast, const_cast, reinterpret_cast中的一种,但是旧式的强制类型转化不够清晰,出了问题难以追踪

4.12 运算符优先级表

运算符优先级表