4.1 基础
4.1.1 基本概念
函数调用也是一种运算符,它对运算对象的数量没有限制。
左值与右值
C++表达式要不是左值,要不是右值。当一个对象被用作右值时,用的是对象的值(内容);当对象被用作左值时,用的是对象的身份(在内存中的地址)。
在实际运算中有一个重要原则:在需要右值的地方可以用左值替代,但是不能把右值当作左值使用。当一个左值被当作右值使用时,实际使用的是它的值(内容)。
使用关键字decltype的时候,如果表达式结果是左值,那么decltype作用于此表达式将得到一个引用类型。
using p = int *
delctype(p) //解引用返回左值,所以此处得到的是int&引用类型
delctype(&p) //取址符得到右值,所以此处返回类型为int **类型
4.1.2 优先级与结合率
略
4.1.3 求值顺序
在c++中,有四种求值运算符规定了运算对象的求值顺序:&&,||,?: , 逗号操作符(后面详细介绍)
4.2 算数运算符
算数运算符中,一元运算符的优先级高于而二元运算符。算数运算符的运算对象和结果都是右值,其两端的运算对象最终会被转换为同一类型。
通常布尔类型虽然算是整形的一种,但是其不应该参加算数运算,比如布尔变量b的值为真,在运算时它将被提升为整数1,而-b为-1,-1转换为bool类型仍然为true,而不是false。
在除法中,c++早期版本允许结果为负值的商向上或向下取证,但是在c++11中规定商一律向0取整。
关于取余符号
参与取余运算的对象必须是整数类型,其两端对象如果不符合要求并不会进行类型转换,而是会直接报错。
如果m%n不等于0,则它的符号与m相同。c++早期版本允许m%n的符号匹配n的符号,而且商向负无穷一侧取整,这一方式在新标准中被移除。
4.3 逻辑与关系运算符
运算对象以及结果都是右值。
|| 、&&具有短路求值特性。
在进行比较运算时除非比较的对象时布尔类型,否则不要使用布尔字面量true和false座位比较对象。
int i = 3;
bool b = false;
int *p = nullptr;
if(i); //不需要与true或false比较
if(b == true);
if(p); //不需要与true或false比较
if(p != nullptr); //可以与空指针进行比较
4.4赋值运算符
赋值运算符的左侧运算对象必须是一个可修改的左值
赋值运算符满足右结合律。
赋值运算符的优先级较低,比关系运算符的优先级还低。
注意各种复合赋值运算符。
要特别区分赋值运算符与等于运算符
if(i= 1) //永远为true,通常来说没有意义
if(i == 1)
4.5递增与递减
通常来说应该使用递增递减符号的前置版本。
后置递增运算符的优先级高于解引用运算符,所以pbeg++等价于 (pbeg++)。我们应该熟悉 *itr++这样的写法,因为这样可以使代码更加简洁。
要特别注意运算对象的求值顺序
*beg = toupper(*beg ++); //存在歧义,因为左右对象的求值顺序未确定
4.6成员访问运算符
点运算符以及箭头运算符都可用于访问对象的成员。
因为解引用运算符的优先级低于点运算符,所以如下
string s;
string *ps = &s;
*s.size(); //错误,相当于*(s.size())
(*s).size(); //正确
s->size(); //正确,直接使用箭头运算符访问成员
为了省去上面加括号那样繁琐的算法,所以有了箭头访问符。
箭头访问符·的返回结果是一个左值。
4.7 条件运算符
cond ? expr1 : expr2;
cond为true则计算表达式1,否则计算表达式2。需要特别注意的是,不会同时计算两个表达式。
条件运算符可以嵌套使用,但是通常不应该嵌套多层,那样会降低代码的可读性。
条件运算符的优先级特别低。
4.8 位运算符
位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。
位运算对象同样能用于bitset类型。
一般来说,如果运算对象是“小整数”,则它的值会被自动提升为较大的整数类型。如果运算对象是带符号的且其值是负,则位运算符如何处理运算对象的符号位依赖于机器。而且此时左移操作可能会改变符号位,因此这是一种未定义行为,所以强烈建议仅将位运算符用于处理无符号整数。
“<<” 左移,在右侧插入0
“>>” 右移, 如果运算对象是无符号整数,则在左侧插入0,如果是带符号整数,则插入0或者符号位的拷贝,视环境而定。
~ 取反
& 相与
| 相或
^ 异或(理解为判断两个对象的相应位数是否不同,不同则结果为1,否则为0)
移位运算符的优先级低于算数运算符,但是高于关系运算符、赋值运算符、条件运算符。
4.9 sizeof运算符
返回一条表达式或一个类型名所占的字节数,其返回值是size_t类型。
sizeof (type);
size (expr);
sizeof满足右结合律,而且其与*运算符的优先级相同,所以有如下形式
sizeof *p //等价以sizeof(*p)
另外由于sizeof**并不会实际计算表达式的值**,所以在上述表达式中即使p为一个空指针,其仍是合法的。
c++新标准允许我们使用域运算符来获取类成员的大小,而不需要通过实际的对象。
sizeof Sales_data::revenue; //不需要有一个实际的Sale_data对象。
对数组执行sizeof运算得到的是整个数组所占的空间大小,sizeof不会吧数组转换成元素指针来处理。
对string vector等容器sizeof只返回该类型固定部分的大小,而不会计算对象中元素所占用的空间。
size_t sz = sizeof(ia) / sizeof(*ia); //ia是一个数组名。此语句用于求数组的size,此用法很常见
4.10 逗号运算符
逗号运算符按从左到右的顺序求值,并且去最右侧运算对象的结果作为求值结果。如果最右侧运算对象是左值,那么运算结果也是一个左值。
c++ 不会直接将两个不同类型的值进行运算,而是先根据类型转换规则设法将运算对象的类型同一后再求值,上述的类型转换时自动执行的,被称作隐式转换。
4.11 类型转换
4.11.1 算术转换
算术转换的一般性原则是是运算不损失精度,所以通常运算符的运算对象会被转换为最宽的类型。所以int与double类型相加时,int会被转换为double类型。
无符号类型的运算对象
如果一个无符号类型与一个带符号类型进行计算,而且其中的无符号类型不小于带符号类型,那么带符号类型会转换为无符号类型。此时如果带符号类型恰好是负数,则将产生出人意料的结果。
如果带符号类型大于无符号类型,此时转换的结果依赖于机器。如果无符号类型的所有值都能存在带符号类型中,那么无符号类型转换为带符号类型,否则,带符号类型将转换为无符号类型。
通常来说,我们应该避免让无符号类型与带符号类型一起参加运算。
4.11.2 其他隐式转换
数组转化为元素指针:在大多数用到数组的表达式中,数组会自动转换为指向数组首元素的指针。当数组作为取址符(&)、sizeof、decltype、typeid等运算符的运算对象时,上述转换不会发生。
0或者nullptr可以转换为任意指针类型;指向任意非常量的指针可以转换为void ,指向任意对象的指针可以转换为const void 。
指针可以转换为bool类型。
允许将指向非常量类型的指针转换为指向相应的常量的指针,对于引用也是如此,可以将任何非常量引用转换为常量引用,但是要注意,以上两种转换不可以逆向进行。
可以通过对类的一些处理,实现类类型定义的转换。但是编译器每次只能执行一种类类型的转换。
4.11.3 显示转换
显示转换又称为强制类型转换。
c++提供四种强制类型转换操作符static_cast,const_cast,dynamic_cast, reinterpret_cast.
static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_const进行类型转换。
当需要把一个较大的算数类型转换为较小的类型时,static_cast非常有用。一般编译器发现一个较大的算数类型试图赋值给一个较小的类型时,就会给出一个警告信息。但是如果使用static_cast进行显示类型转换之后,警告信息就会被关闭。
const_cast
const_cast只能改变对象的底层const。
对于将常量对象转换为非常量对象的行为,我们称为“去掉const性质”。一旦我们去掉了某个对象的const属性,编译器就不会再阻止我们队该对象进行写操作。如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义行为。
const char *pc;
char *p = const_cast<char *>(pc); //正确,但是通过p写值是未定义的行为
const_cast常常用于有重载函数的上下文中。
reinterpret_cast
reinterpret_cast通常为运算对象的位模式提供低层次上的重新解释。
int *pi;
char *pc = reinterpret_cast<char *>(pi); //我们必须牢记pc所指向的真实对象是一个int而非一个字符
string str(pc); //导致异常,因为pc实际指向的是一个int对象,而不是一个字符数组
使用reinterpret_cast是非常危险的,其本质上依赖于机器,要想安全的使用它必须对涉及的类型和编译器实现转换的过程都非常了解。通常,最好不要使用reinterpret_cast。
旧式的强制类型转换
type(expr) //函数形式的强制类型转换
(type)expr //c风格
通常来说应该避免在程序中使用强制类型转换,尤其是reinterpret_cast。如果要使用强制类型转换,也应该使用c++提供的另外三种显示转换操作符来进行显示类型转换。