【C++基础】第二十课:表达式基础

表达式基本概念,重载运算符,左值,右值,优先级,求值顺序

Posted by x-jeff on October 24, 2020

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.基本概念

表达式(expression)由一个或多个运算对象(operand)组成,对表达式求值将得到一个结果(result)

⚠️字面值和变量是最简单的表达式,其结果就是字面值和变量的值。

把一个运算符(operator)和一个或多个运算对象组合起来可以生成较复杂的表达式。

C++定义了一元运算符(unary operator)二元运算符(binary operator)

  • 作用于一个运算对象的运算符是一元运算符,如取地址符(&)和解引用符(*)。
  • 作用于两个运算对象的运算符是二元运算符,如相等运算符(==)和乘法运算符(*)。
  • 除此之外,还有一个作用于三个运算对象的三元运算符。
  • 函数调用也是一种特殊的运算符,它对运算对象的数量没有限制。

一些符号既能作为一元运算符也能作为二元运算符。以符号*为例,作为一元运算符时执行解引用操作,作为二元运算符时执行乘法操作。它的两种用法互不相干,完全可以当成两个不同的符号。

1.1.运算对象转换

在表达式求值的过程中,运算对象常常由一种类型转换成另外一种类型。例如,尽管一般的二元运算符都要求两个运算对象的类型相同,但是很多时候即使运算对象的类型不相同也没有关系,只要它们能被转换成同一种类型即可。

1.2.重载运算符

当运算符作用于类类型的运算对象时,用户可以自行定义其含义。因为这种自定义的过程事实上是为已存在的运算符赋予了另外一层含义,所以称之为重载运算符(overloaded operator)。IO库的>><<运算符以及string对象、vector对象和迭代器使用的运算符都是重载的运算符。

1.3.左值和右值

C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue)

这两个名词是从C语言继承过来的:左值可以位于赋值语句的左侧,右值则不能。但是在C++中,二者的区别就没那么简单了:当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。

左值指的是可以取地址的变量,左值与右值的根本区别在于能否获取内存地址。

一个重要的原则:需要右值的地方可以用左值来代替,但是不能把右值当成左值使用。当一个左值被当作右值使用时,实际使用的是它的内容(值)。

2.优先级与结合律

下图罗列出了全部的运算符,并用双横线将它们分割成若干组。同一组内的运算符优先级相同,组的位置越靠前组内的运算符优先级越高。

3.求值顺序

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

1
int i=f1()*f2();

我们知道f1f2一定会在执行乘法之前被调用,因为毕竟相乘的是这两个函数的返回值。但是我们无法知道到底f1f2之前调用还是f2f1之前调用。

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

1
2
int i = 0;
cout << i << " " << ++i << endl;

因为程序是未定义的,所以我们无法推断它的行为。编译器可能先求++i的值再求i的值,此时输出结果是1 1;也可能先求i的值再求++i的值,输出结果是0 1;甚至编译器还可能做完全不同的操作。因此此表达式的行为不可预知,因此不论编译器生成什么样的代码程序都是错误的。

类似的有:f()+g()*h()+j()。如果f,g,h,j是无关函数,它们既不会改变同一对象也不执行IO任务,那么函数的调用顺序不受限制。反之,如果其中某几个函数影响同一对象,则它是一条错误的表达式,将产生未定义的行为。