文章目录
4.1 基础
4.1.1 基本概念
C++定义了一元运算符和二元运算符.
- 一元运算符作用于一个运算对象. 如取地址符(&)和解引用符(*)
- 二元运算符作用于两个运算对象. 如相等运算符(==)和乘法运算符(*)
除此之外, 还有一个作用于三个运算对象的三元运算符( ?: )
// ?: 条件表达式 ? 表达式1 : 表达式2;
//等价于以下表达
if (条件表达式)
{
result = 表达式1;
}
else
{
result = 表达式2;
}
函数调用也是一种特殊的运算符, 它对运算对象的数量没有限制
组合运算符和运算对象
对于含有多个运算符的复杂表达式来说, 要想理解它的含义首先要理解运算符的优先级, 结合律, 以及运算对象的求值顺序.
5 + 10 * 20/2;
运算对象转换
在表达式求值的过程中, 运算对象常常由一种类型转换成另外一种类型.
小整数类型(bool, char, short等)通常会被提升成较大的整数类型, 主要是int.
重载运算符
C++定义了运算符作用于内置类型和复合类型的运算对象时所执行的操作. 当运算符作用于类类型的运算对象时, 用户可以自行定义其含义. 因为这种自定义的过程事实上是为已存在的运算符赋予了另外一层含义, 所以称之为运算符重载.
我们使用运算符重载时, 其包括运算对象的类型和返回值的类型, 都是由该运算符定义的. 但是运算对象的个数, 运算符的优先级和结合律都是无法改变的
左值和右值
C++的表达式要不然时右值, 要不然就是左值.
在C++中, 一个左值表达式的求值结果是一个对象或者一个函数, 然而以常量对象为代表的某些左值实际上不能作为赋值语句的左侧运算对象. 此外, 虽然某些表达式的求值结果是对象, 但它们是右值而非左值.
当一个对象被用作右值的时候, 用的是对象的值(内容); 当对象被用作左值的时候, 用的是对象的身份(在内存中的位置)
需要右值的地方可以用左值来代替, 但是不能把右值当成左值(也就是位置)使用. 当一个左值被当成右值使用时, 实际使用的是它的内容(值).
赋值运算符需要一个(非常量)左值作为其左侧运算对象, 得到的结果也仍然是一个左值
取地址符作用于一个左值运算对象, 返回一个指向该运算对象的指针, 这个指针是一个右值
内置解引用运算符, 下标运算符, 迭代器解引用运算符, string和vector的下标运算符的求值结果都是左值
内置类型和迭代器的递增递减运算符作用于左值运算对象, 其前置版本所得的结果也是左值
使用关键字decltype的时候, 左值和右值也有所不同. 如果表达式的求值结果是左值, decltype作用于该表达式(不是变量)得到一个引用类型.
假定p的类型是int*, 因为解引用运算符生成左值, 所以decltype(*p)的结果是int&. 另一方面, 因为取地址运算符生成右值, 所以decltype(&p)的结果是int **, 也就是说, 结果是一个指向整形指针的指针.
4.1.2 优先级与结合律
复合表达式是指含有两个或多个运算符的表达式.
优先级和结合律决定了运算对象组合的方式. 表达式中的括号无视上述规则.
一般来说, 表达式最终的值依赖于其子表达式的组合方式. 高优先级运算符的运算对象要比低优先级运算符的运算对象更为紧密地组合在一起.
括号无视优先级和结合律
括号无视普通的组合规则, 表达式中括号括起来的部分被当成一个单元来求值, 然后再与其他部分一起按照优先级组合
优先级和结合律有何影响
优先级会影响程序的正确性
int ia[] = {0,1,2,3,4,5};
int last = *(ia+4);
last = *ia + 4;
如果想访问ia+4位置的元素, 那么加法运算两端的括号必不可少, 一旦去掉括号, *ia就会首先组合在一起, 然后4再与 *ia的值相加
结合律对表达式产生影响的一个典型示例是输入输出运算
cin >> v1 >> v2; //先读入v1,再读入v2
4.1.3 求值顺序
优先级规定了运算对象的组合方式, 但是没有说明运算对象按照什么顺序求值, 在大多数情况下, 不会明确指定求值的顺序
int i = f1() * f2();
我们知道f1和f2会在执行乘法前被调用, 但不知道到底f1在f2之前调用还是之后调用.
对于那些没有指定执行顺序的运算符来说, 如果表达式指向并修改了同一个对象, 将会引发错误并产生未定义的行为.
例:
<<运算符没有明确规定何时以及如何对运算对象求值, 因而下面的输出表达式是未定义的
int i = 0; cout<< i << " " << ++i <<endl;
因为程序是未定义的, 所以我们无法推断它的行为.
求值顺序, 优先级, 结合律
以下两条经验准则对书写复合表达式有益: