c++定义了一元运算符(&,*)、二元运算符(==,+)、三元运算符、函数调用也是一种特殊的运算符(运算对象的数量没有限制)。有的运算符既可以作为一元运算符也可以作为二元运算符,如*,具体情况由上下文决定。对于含有多个运算符的复杂表达式来说,想要理解它的含义首先要理解运算符的优先级、结合律、和运算对象的求值顺序。
优先级:优先级决定了运算对象的组合方式,高优先级的运算对象比低优先级对象优先组合在一起。
结合律:对于优先级相同的运算符,其进一步组合由结合律确定。
求值顺序:运算对象的求值顺序。
大多数情况下,不会明确指定求值的顺序,也是为了代码的生成效率,对于没有指定执行顺序的运算符来说,如果表达式指向并修改了同一对象,将会产生未定义的行为:
int i = 0;
cout << i <<" "<<++i << endl;//按理说结果应该是0 1,在vs2013上运行的结果是1 1
有4中运算符明确规定了运算对象的求值顺序:
- 逻辑与(&&):先求左侧对象的值,左侧为真时才求右侧对象的值。
- 逻辑或(||):先求左侧对象值,左侧为假时才求右侧对象的值。
- 条件运算符(?:):
- 逗号运算符(,):
一、算数运算符
表达式求值之前,小整数运算对象会被提升成较大的整数类型,所有运算对象最终转换成同一类型。都满足左结合律,按照优先级从高到低顺序:
(1)一元正号(+)、一元负号(-):
+作用于一个指针或算术值时,返回运算对象的一个(提升后的)副本。
-对运算对象值取负后,返回其(提升的)副本:
bool b=true;
bool b2=-b;//b2是true,-1转换成bool值是1
(2)乘法(*)、除法(/)、求余(%):
/:整数相除结果还是整数,同号为正,异号为负。
%:参与取余运算的对象必须是整数。如果m%n的结果不为0,结果的符号与m相同。
最终:(m/n)*n+m%n=m
(3)加法(+)、减法(-)
二、逻辑和关系运算符
关系运算符:>、<、>=、<=、==、!=
逻辑运算符:&&、||、!
其中,逻辑非(!)优先级最高,其次是大小关系运算符(>、<、>=、<=),然后是相等关系运算符(==和!=),然后是逻辑与(&&),最后是逻辑或(||)。
(1)逻辑运算符
&&和||使用短路求值法:
- 逻辑与(&&):先求左侧对象的值,左侧为真时才求右侧对象的值。
- 逻辑或(||):先求左侧对象值,左侧为假时才求右侧对象的值。
逻辑与的左侧运算对象是为了确保右侧对象求值过程的正确性和安全性:
index!=s.size()&&!isspace(s[index]);//首先检查index是否到达对象末尾,只有index在合理范围才会计算右侧对象的值
(2)关系运算符
关系运算符都满足左结合律
由于关系运算符的结果是布尔值,所以将几个关系运算符俩捏在一起会产生意想不到的后果:
if(i<j<k)
if(i<j&&j<k)//关系运算符的优先级大于逻辑与
三、赋值运算符
- 赋值运算符的左侧对象必须是一个可修改的左值:
int i=0;
const int ci=i;//初始化而非赋值
1024=i;//错误,字面值是右值
i+ci=0;//错误,表达式是右值
- 如果赋值运算符左右两个运算对象类型不同,则右侧运算对象转换成左侧运算对象的类型:
int k=3.14;//k的值是3
- 赋值运算满足右结合律,赋值运算返回的是其左侧运算对象:
int i,j;
i=j=0;
while((i=get_value())!=0){};//赋值运算返回的是其左侧运算对象
- 赋值运算符的优先级比关系运算符低:
if(p=getPtr()!=0);
if((p=getPtr())!=0);//这样更符合本意
四、递增和递减运算符
++和--这两种运算符必须作用域左值运算对象,前置版本(++i)将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。前置版本的递增运算符避免了不必要的工作,它把值加1后直接返回改变了的运算对象,而后置版本需要将原始值存储下来以便返回其未修改的内容,对于不需要修改前的值,后置版本的操作就是一种浪费,建议养成前置版本的习惯,更能提高性能。
后置递增运算符的优先级要高于解引用运算符,注意括号的运用:
*p++其实等价于*(p++);//先对p解引用,再将指针向后移动一个单位
(*p)++;//先对p解引用,对解出来的对象值加1
五、成员访问运算符
点运算符(.)用于获取类对象的一个成员,箭头运算符(->)与点运算符有关,p->mem等价于(*p).mem。
string s("abc"),*p=&s;
s.size();
p->size();
(*p).size();//都是等价的
点运算符的优先级高于解引用运算符,一般需要注意在解引用的时候加上括号:
*p.size();//先调用p的size()函数,再解引用返回值
(*p).size();//先对指针解引用,再调用这个对象的size()函数
六、条件运算符
cond?expr1:expr2
先求cond的值,若干有条件为真对expr1求值,并返回该值,否则对expr2求值并返回。满足右结合律。
七、逗号运算符
expr1,expr2
首先对左侧表达式求值,然后将值丢弃,逗号运算符真正的结果是右侧表达式的值。