表达式是由一个或者多个运算对象组成,字面值和常量是最简单的表达式,其结果就是字面值和变量的值。把一个运算符和一个或多个运算对象组合起来可以生成较为复杂的表达式
4.1基础
4.1.1 基本概念
一元运算符,如取地址符和解引用,二元运算符如比较运算符和算术运算符
,有些符号在不同的上下文中的含义是不一样的。
组合运算符和运算对象
运算对象转换
当运算符作用的对象不一致的时候会发生类型的转换
重载运算符
运算符虽然可以重载,但是运算对象的个数,运算符的优先级和结合律是无法改变的。
左值和右值
c++表达式要不然是右值,要不然是左值,当一个对象被用作右值的时候,用的是对象的值,当对象被用作左值时候,用的是对象的身份。
在需要右值的地方可以用左值代替,但是不能吧右值当作左值用。
在使用delctype的时候左值引用和右值引用各不相同。假定p的类型是*p ,表达式的结果如果是左值,decltype(*p)将得到一个引用类型, int &,另一方面,取地址符生成的是右值, decltype(&p)得到的是一个 int **p;
4.1.2 优先级和结合律 略
4.1.3 求值顺序
优先级规定了对象的组合方式,但没有规定按照什么顺序求值,类似 int i = f1() * f2(),谁先调用是未知的。
对于那些没有指定的执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误活未定义的行为。例如
cout << i <<” “<
求值顺序,优先级,结合律
例子: f() + g()*h() + j() 如果函数相互独立还好,但如果修改同一对象造成的结果不可预测。
4.2 算术运算符 (略)
4.3 逻辑和关系运算符
短路求值
4.4 赋值运算符
赋值运算对象必须是一个可修改的左值
赋值运算结果是它的一个左值对象
如果运算符的左右两个运算符的对象类型不同,则左侧的运算对象将换成左侧运算对象的类型。
c++11标准允许使用花括号括起来的初始值列表作为赋值运算的右侧运算对象
对于类类型时,赋值运算的细节由类本身决定。当赋值发生时候。右侧的运算对象元素替换左侧的运算对象。初始值列表初始化过程,编译器创建一个临时的值初始化变量给左侧的运算对象。
赋值运算满足右结合律,这一点和其他的二元运算符不太一样
int ival , jval ;
ival = jval =0;
赋值运算对象返回左值对象,如果赋值号左右类型不一致会执行类型转换。
赋值运算符的运算优先级较低
不要混淆相等运算符和赋值运算符
4.5递增和递减运算符
除非必须否则不用递增递减运算符的后置版本。
混淆解引用和递增运算符
auto pbeg = v.begin();
while(pbeg!=v.end()&&*pbeg >=0)
cout <<*pbeg++<
运算对象可以按任意顺序求值
复合运算最好不要在等号的左右两边都改变表达式的值
4.6成员访问运算符
*p.size()是错误的
4.7条件运算符
嵌套的条件运算符
例如 finalgrade=(grade > 90) ? “high pass”: (grade < 60)? “fail” : “pass”;
在输出的表达式中使用条件运算符略
4.8 位运算符
位运算一般处理无符号类型,有符号类型的处理方式依赖于机器。
运算符 | 功能 |
---|---|
- | 位求反 |
<< | 左移 |
>> | 右移 |
& | 位与 |
^ | 位异或 |
| | 位或 |
移动运算符
bits << 8 左移 8位
bits >> 8 右移 8位
位求反运算符
将对象逐位求反后生成一个新值。
移位运算符(又叫IO运算符)满足左结合律
4.9 sizeof 运算符
sizeof运算符满足右结合律,所得的值是一个size_t类型
有两种形式:
sizeof(type)
sizeof expr
第二种返回的是表达式结果类型的大小。sizeof并不实际计算其运算对象的值。
Sales_data data , *p;
sizeof(Sales_data);存放Sales_data类型对象所占空间的大小
sizeof data ; // Sales_data大小
sizeof p; //指针的大小
sizeof *p; // Sales_data 大小, 注意是右结合的原则,是Sales_data 的大小。
sizeof data.revenue; //revenue成员的大小
sizeof Sales_data::revenue //revenue 成员的大小
由于sizeof不会求运算对象的值,所以解引用一个无效的指针仍然是一种无效的行为。
sizeof运算符无须我们提供一个具体的对象
总结起来以下几点:
对char或者类型为char的表达式执行sizeof运算,结果得1
对引用类型的执行sizeof运算得到被引用对象所占空间的大小
对指针执行sizeof运算得到指针本身所占空间的大小
对解引用指针执行sizeof运算符得到指针指向的对象所占空间的大小,指针不需要有效。
对数组执行sizeof运算得到的整个数组所占空间的大小。
对vector或者string对象执行sizeof运算符会得到该类型固定部分的大小,不会计算对象中的元素占据了多少空间。
4.10 逗号运算符
可以用在for循环中
4.11类型转换
如果两种类型能够相互转换,那么他们是相互关联的。
如果类型转换是自动的无须程序员的介入,是隐式转换。
何时发生类型转换:
在大多数表达式中,比int小的整数类型首先提升。
条件中非布尔表达式转成布尔表达式。
函数调用传参时候。
4.11.1算术类型转换
略,研究 primer P143的例子吧
4.11.2其他隐式类型转换。
数组转换成指针: 大多数情况下,数组自动转换位指向数组的指针。当数组被用作deltype参数,&,sizeof typeid等运算符的运算对象的时候,上述转换不会发生
指针的转换: 常量整数值或者字面值nullptr能转换成任意指针类型,指向任意非常量的指针能转换成void*指针,指向任意对象的指针能转换成const void *,继承关系的类里面还有另外一种指针转换的方式。
允许指向非常两的指针转换成指向常量类型的指针。
类类型能够定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换。如果同时提出多个转化请求,请求将被拒绝。
4.11.3显示转换
虽然有时候必须这样做,但是这本质上是危险的。
在这里的关键我们要去分static_cast , dynamic_cast, const_cast , reinterpret_cast
dynamic_cast 支持运行时类型的识别,我们将在后面左更加详细的介绍。 cast-name指定了执行哪种类型的转换。
static_cast
任何具有明确定义的类型转换,不包含底层const,都可以使用static_cast
例如
double slope = static(j)/i;
static_cast 对于编译器无法自动执行的类型转换也非常的适用。
void * p = &d ;
double dp = = static_cast
旧式的强制类型转换
能够使用旧式的类型转换的地方替换为const_cast 和 dynamic_cast也合法。,如果替换不合法,则旧的类型转换执行的是reinterpret的类型转换。比如:
char pc = (char )ip;// ip是指向整数的指针。
说白了新的标准将类型转换的控制更加的清晰化了。