基础
运算符根据运算对象的个数分为一元、二元、三元运算符,相同的符号可能表示不同的运算符,具体符号的含义根据上下文决定。一般运算对象可以自动转换为运算符所需的类型。根据运算对象的不同运算符可以表示不同的含义,称为运算符的重载。
左值表示能放在赋值运算符的左侧,右值则不能。当一个对象被用作右值时,用的是对象的内容,当一个对象被用作左值时,用的是对象的身份(内存位置)。某些运算符必须要求运算对象为左值,左值可以当成右值使用。
运算符拥有不同的优先级和结合律,括号拥有最高的优先级。
大部分运算符没有规定求值顺序,即运算符的多个运算数哪个先求值,如果表达式修改了同一个对象,将会引发未定义的错误行为。如:
int i = 0; cout << i << " " << ++i << endl;
这种情况是错误的,因为无法确定编译器先求i还是先求++i,返回的结果也是不确定的。有4种运算符明确规定了运算对象的求值顺序,&&, ||, ?:, 逗号运算符。
运算对象的求值顺序与优先级和结合律无关。
算数运算符
算数运算符的运算对象和求值结果都是右值,参与取余运算的运算对象必须是整数。
逻辑和关系运算符
值为0的运算对象表示假,否则表示真,对于这两类运算符,运算对象和求值结果都为右值。
赋值运算符
左侧必须是一个可修改的左值,运算结果为左侧对象,并且也是左值。赋值运算符满足右结合律,赋值运算符的优先级较低。使用复合赋值运算符时左侧的运算对象只求值一次,对性能有些许提升。
递增和递减运算符
分为前置版本和后置版本,前置版本将对象本身作为左值返回,后置版本将对象原始值的副本作为右值返回。除非必须,建议一直使用前置版本。
*pBegin++,这种写法是合适的,先对指针进行解引用操作,然后在对指针递增。
特别注意,运算对象可按任意顺序求值,*beg=toupper(*beg++),该条语句是错误的,将产生为定义的值,因为等号左侧和右侧的求值顺序是不确定的,如果左侧先求值,语句等价于*beg=toupper(*beg),如果右侧先求值,语句等价于*(beg+1)=toupper(*beg)。
成员访问运算符
点运算符用于访问对象的成员,如果对象是左值,则点运算符返回左值,如果对象是右值,则点运算符返回右值。箭头运算符作用于指针类型的对象,结果是一个左值。obj.member等价于(*pObj).member。
条件运算符
条件运算符只对两个表达式中的一个表达式求值。条件运算符的两个表达式都是左值或能转换成同一类型的左值时运算结果是左值,否则为右值。
条件运算符的优先级很低,因此使用时最好加括号,如cout<<(grade<60)?"fail":"pass";无法达到预想的结果,因为会把问好前面当做条件。
位运算符
位运算符作用于整数的运算对象,提供设置和检查二进制位的功能。位运算对于符号位的处理没有明确的规定,因此建议将位运算只用于无符号数。
左移位在右侧插入值为0的二进制,右移位根据左侧运算对象的不同插入的值不同,如果左侧为无符号整数,则插入0,如果为有符号整数,插入0或插入1具体由环境而定。
注意移位运算符将左侧对象的拷贝作为求值结果,移位过程可能会提升运算对象。
位求反运算符(~)把运算对象逐位求反,char类型的运算对象首先转换成int类型,然后求反。
位与、位或、位异或在两个运算对象上逐位执行相应的逻辑操作。
移位运算符满足左结合律。重载运算符的优先级与结合律都与它的内置版本一样。
sizeof运算符
返回一条表达式或类型名字所占的字节数,满足右结合律,其所得的值是一个类型为size_t的常量表达式。运算符使用的两种形式sizeof(type)或sizeof expr,返回表达式计算结果的大小,并不计算表达式。对引用类型执行sizeof得到被引用类型对象的大小,对数组执行sizeof得到整个数组所占空间大小。
逗号运算符
含有两个运算对象,按照从左向右的顺序依次求值,与三元运算符,逻辑与逻辑或运算符一样规定运算数的求值顺序。逗号运算符返回右侧表达式的值,如果右侧表达式为左值,则运算结果为左值。
类型转换
算数类型之间可以进行隐式类型转换,并且设计的尽量不损失精度。
无符号类型与带符号类型参与运算时,如果无符号类型不小于带符号类型,则带符号类型转换成无符号类型,这会产生副作用。如果无符号类型得所有值都能存储在带符号类型中,则无符号类型转换成带符号类型,否则带符号类型转换成无符号类型。
在大多数用到数组的表达式中,数组自动转换成指向第一个元素的指针。
字面值0和nullptr能转换成任意类型的指针。
如果指针或算数类型为0,则转换成false,否则转换成true。
指向非常量的指针或引用能转换成指向常量的指针或引用。
避免使用强制类型转换。