🍺04_表达式 - 图1

基础

左值和右值

C++的表达式要不然是右值( rvalue) ,要不然就是左值(lvalue) 。这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能。 :::info

  • 当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
  • 一个重要的原则是在需要右值的地方可以用左值来代替,但是不能把右值当成左值(也就是位置)使用。
  • 当一个左值被当成右值使用时,实际使用的是它的内容(值)。到目前为止,已经有几种我们熟悉的运算符是要用到左值的。(赋值运算符,取地址运算符,迭代器递增递减运算符) :::

    求值顺序

    优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值。在大多数情况下,不会明确指定求值的顺序。

对于那些没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为

  1. int i=0;
  2. cout<< i<<" "<< ++i<<endl//未定义顺序,取决于编译器or报错

有4 种运算符明确规定了运算对象的求值顺序。 :::info

  • 逻辑与(&&)运算符:先求左侧运算对象的值,只有当左侧运算对象的值为真时才继续求右侧运算对象的值。
  • 逻辑或(||) 运算符:
  • 条件(? :)运算符:
  • 逗号,: ::: :::tips 求值顺序和优先级结合律没有关系!
    逻辑与逻辑或执行短路求值,即若是左边为真,这不进行右边的计算;
    相等运算符则会计算等号两边的值 :::
    1. f()+g()*h()+j();
    优先级确定了先计算g()*h(),但是这些函数求值的顺序是不确定的;若是这些函数是相关的,那么这条语句是错误的,因为无法确定函数调用的顺序。

算术运算符

一元运算符的优先级最高,接下来是乘法和除法,优先级最低的是加法和减法。优先级高的运算符比优先级低的运算符组合得更紧密。上面的所有运算符都满足左结合律,意味着当优先级相同时按照从左向右的顺序进行组合。 :::info 溢出:
超过当前类型所表示的范围 :::

赋值运算

赋值运算的结果是它的左侧运算对象,并且是一个左值。相应的,结果的类型就是左侧运算对象的类型。如果赋值运算符的左右两个运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型
赋值运算符满足右结合律,这一点与其他二元运算符不太一样:

  1. int ival,jval;
  2. ival=jval=0;
  • 赋值运算返回的是其左侧运算对象,所以靠右的赋值运算的结果(即jval)被赋给了ival
  • 对于多重赋值语句中的每一个对象,它的类型或者与右边对象的类型相同、或者可由右边对象的类型转换得到
  • 赋值语句经常会出现在条件当中。因为赋值运算的优先级相对较低,所以通常需要给赋值部分加上括号使其符合我们的原意。

    递增和递减运算符

    递增和递减运算符有两种形式:前置版本和后置版本。到目前为止,本书使用的都是前置版本,这种形式的运算符首先将运算对象加1(或减1) ,然后将改变后的对象作为求值结果。后置版本也会将运算对象加1(或减1) ,但是求值结果是运算对象改变之前那个值的副本

    尽量使用前置版本的递增

    :::tips 前置版本的递增运算符避免了不必要的工作,它把值加1 后直接返回改变了的运算对象。与之相比,后置版本需要将原始值存储下来以便于返回这个未修改的内容。如果我们不需要修改前的值,那么后置版本的操作就是一种浪费。 :::

解引用与递增

后置递增运算符的优先级高千解引用运算符,因此*pbeg++等价千*(pbeg++)pbeg++pbeg的值加1, 然后返回pbeg的初始值的副本作为其求值结果,此时解引用运算符的运算对象是pbeg未增加之前的值。

:::danger 大多数运算符都没有规定运算对象的求值顺序,这在一般情况下不会有什么影响。然而,如果一条子表达式改变了某个运算对象的值,另一条子表达式又要使用该值的话,运算对象的求值顺序就很关键了。 :::

  1. while(beg!=s.end()&&!ispace(*beg)){
  2. *beg=toupper(*beg++)//wrong!
  3. //1. *beg=toupper(*beg);
  4. // beg=beg+1;
  5. // *(beg+1)=toupper(*beg);
  6. // 可能按照任意一个来编译!
  7. }

成员访问

点运算符和箭头运算符都可用于访问成员,其中,点运算符获取类对象的一个成员;箭头运算符与点运算符有关,表达ptr->mem等价于(*ptr).mem

移位运算符

移位运算符的优先级不高不低,介于中间:比算术运算符的优先级低,但比关系运算符、赋值运算符和条件运算符的优先级高。因此在一次使用多个运算符时,有必要在适业的地方加上括号使其满足我们的要求。
移位运算符也是对int型进行操作的;

逗号运算符

逗号运算符(comma_operator)含有两个运算对象,按照从左向右的顺序依次求值。和逻辑与、逻辑或以及条件运算符一样,逗号运算符也规定了运算对象求值的顺序。
对于逗号运算符来说,首先对左侧的表达式求值,然后将求值结果丢弃掉。逗号运算符真正的结果是右侧表达式的值。如果右侧运算对象是左值,那么最终的求值结果也是左值。

类型转换

:::info 当两种不同的类型相加减or进行算术运算时,C++一般会自动进行类型转换(按照一定的规则) ::: 除了一些默认的类型转换,还有一些其他的类型转换:

  • 数组转换成指针:在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针

    1. int ia[10];
    2. int *p=ia;

    此例中,数组名称被转换为指向数组的首元素的指针or数组首元素的地址;

  • 指针转换成bool类型:char* cp=get_string();if(cp)……

  • 转换成常量:将指向非常量类型的指针转换成指向常量类型的指针e.g.int i; const int *p=&i

    显示转换

    :::info 命名的强制类型转换:
    cast_name<type>(expressions);

  • type:转换的目标类型

  • cast_name:转换方式
  • expressions:要转换的值 :::

    cast_name:

  • static_cast

  • dynamic_cast
  • const_cast
  • reinterpret_cast