4.1基础
基本概念
一元运算符:作用在一个运算对象(&,)
二元运算符:作用于两个运算对象(==,)
函数调用也是一种特殊的运算符,对运算对象的数量没有限制。
重载运算符:用户自定义作用于类类型的作用对象,比如IO库的>><<
左值与右值
当一个对象被用作右值, 用的是值(内容);被用作左值时,用的是对象的身份(内存中的位置)。
需要右值的地方可以用左值替代,但不能把右值当作左值使用。左值当被右值使用时,实际使用的是内容(值)。
1.复制运算符需要左值,结果也是左值
2.取地址符作用域左值,返回一个指针是右值
3.内置的解引用、下标u你算符、迭代器解引用、string和vector的下标运算符结果都是左值
4.内置类型和得带起的递增递减运算符,作用域左值对象,结果也是左值。
int *p; //p为指针
decltype(*p); //*生成左值,结果是int&,
decltype(&p); //&生成右值,结果是int**
优先级与结合律
根据优先级和结合律来决定运算对象组合的方式
对于没有执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将引发错误并产生未定义的行为。
int i = 0;
cout << i << "" << ++i << endl;//未定义的
4种运算符只有当左侧对象值为真才继续求右侧的值。
&&
||
?:
,
4.2算数运算符
优先级如下
+,- //一元正负号
*,/,% //乘法除法
+,- //加减法
注意:bool值运算是true是1false是0
bool b = true;
bool b1 = -b; //b1也是true,1取负为-1,-1还是true
算数表达式可能会有异常,如除数是0或者溢出。
取余操作的对象都必须是整数类型。
在C++11中,整数的商一律向0取整。
取余操作的时候,正负结果取决于m/n中m的符号
21 % -5 = 1;
-21 % -8 = -5;
4.3逻辑关系运算符
作用于算术类型或指针类型,作用结果能转换成布尔值,值为0的运算对象表示假。
运算对象和求值结果都是右值
! < <= > >= == != && ||
&&和||时,都会短路求值。仅当左侧表达式无法确定结果时,才计算右侧的值。
expr && expr 左侧为true右侧才执行
expr || expr 左侧为false右侧才执行
关系运算符不要连用
if(i < j < k) //若k大于1则为真
if(i < j && j < k) //正确使用
4.4 赋值运算符
赋值预算符左侧必须是可修改的左值
int i = 0; //是初始化不是赋值
const int ci = i; //是初始化不是赋值
1025 = i; //非法
i = 3.1415; //类型是int,结果为3
允许用花括号的初始值列表作为赋值的右侧运行对象
vector<int> vi;
vi = {0,1,2,3,4,5,6};
赋值满足右结合律
前提是类型必须相同
int a,b;
a = b = 0; //都是0
int ival,*pval;
ival = pval = 0; //错误,指针的值不能赋给int
赋值运算的优先级很低
复合赋值运算
+= -= *= /= %= //算术运算发
<<= >>= &= ^= |= //位运算符
都等价于
a = a op b;
唯一的区别是复合运算符只求值1次,普通运算符求值2次。
4.5递增递减
两种++i和i++;
前置版本把对象本身作为左值返回
后置版本把对象原始值的副本作为右值返回
除非必须,尽量用前置版本
auto pbeg = v.begin();
while (pbeg != v.end && *beg >= 0)
cout << *pbeg++ << endl;
结果是输出当前的值并将pbeg向前移动一个元素
后置递增运算符优先级高于解引用,等价于*(pbeg++);
先把pbeg的值加一,再返回初始值的副本,因此解引用的对象是pbeg未增加之前的值
c++混用解引用和递增可以更简洁
cout << *iter++ <<endl;
等效
cout << *iter <<endl;
iter++;
如果一条表达式改变了运算对象的值,另一个表达式又要使用,容易出现错误。
while(beg != s.end() && !isspace(*beg)))
*beg = toupper(*beg++); //错误,赋值语句未定义
4.6成员访问运算符
.(点)和->(箭头)都可用于访问。
点获取类对象的一个成员
箭头是点运算的一种简化
ptr->mem 等价与 (*prt).mem
解引用优先级低于点运算,因此必须要括号。
4.7条件运算符
cond ? expr1 :expr2
cond是判断条件的表达式,如果真执行expr1否则执行expr2.
string finalgrade = (grade < 60) ? "fail" : "pass";
//低于60是fail,否则就pass
条件运算符优先级很低,如果嵌套再其他语句,需要加括号
cout << ((grade < 60) ? "fail" : "pass");
4.8位运算
作用于整数类型,看成二进制位的集合。
提供检查和设置二进制位的功能。
` //位求反
<< //左移
>> //右移
& //位与
^ //位异或
| //位或
左移右移时,边界职位的都被舍弃
优先级介于中间,比算数低比关系高,因此要适当加括号
cout << 42 + 10; //正确,结果为10
cout << 10 < 42 //错误,试图比较cout和42
4.9sizeof运算符
返回表达式或类型名字所占的字节数,返回值时size_t的常量表达式。
sizeof (type) //返回类型大小
sizeof expr //返回所占空间大小
sizeof结果依赖于其作用的类型
1.char结果为1
2.引用类型的结果时被引用对象所占空间的大小
3.对指针运算得到指针本身所占空间大小
4.对解引用指针得到指针指向对象所在空间的大小,指针不需要有效
5.数组返回数组所占空间大小
6.string或vector结果返回该类型固定部分的大小,不会计算对象中元素占用了多少空间。
可以用数组大小除以单个元素大小得到数组元素个数
constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr2[sz];
4.10逗号运算符
含有两个运算对象,从左向右依次求值。
首先对左侧求值,将结果丢弃。真正结果是右侧表达式的值。
如果右侧运算对象是左值,最终结果也是左值。
常常用于for循环
vector<int> :: size_type cnt = ivec.size();
for(vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt)
ivec[ix] = cnt;
每次循环迭代ix和cnt相应改变。
4.11类型转换
如果两种类型有关联,可以相互转换。
隐式转换:根据类型转换规则将运算对象类型同意后进行算术求值。
隐式转换会尽可能避免损失精度,如果有浮点和整型,整型会变浮点型。
在以下情况下,编译器会自动转换
1.大多数表达式中,比int类型小的整型提升为较大的整型
2.在条件中,非布尔值转成布尔类型
3.初始化过程中,初始值转成遍历的类型;赋值语句中,右侧转换成左侧的类型
4.如果运算中有多种类型,都需要转成同一种。
算术转换
比如一个对象是long double,则另一个都会转成long double。
整型也会变浮点型
整数提升
对于bool、char、signed char、unsighed char、short、unsigned short等类型、只要它们的所有可能指都能存在int中,就会提升会int。否则提升为unsigned int。
较大的char类型(wchar_t,char16_t,char32_t)提升成int、unsigned int、long、unsigned long、long long 和unsigned longlong中最小的一种。
无符号类型的运算对象
如果一个运算对象无符号,一个有符号,而无符号的类型不小于带符号的类型,则带符号的会转换成无符号来的。
带符号的大于无符号的,则转换结果依赖于机器。如果无符号的所有制都能存放在带符号类型中,则无符号转换成带符号。否则就带符号类型的运算对象转换成无符号的。
例如:
long和unsigned int, int和long大小相同,则long转成unsigned int;如果long类型占用的空间比int多,则unsigned int转成long。
其他隐式类型的转换
数组转换指针
数值会自动转换成首元素的指针
int *ip = ia;
指针的转换
0或者nullptr可以转换成任意指针类型;
指向任意非常累的指针能转换成void;
指向任意对象的指针能转换成const void;
转成布尔类型
如果指针或算数类型为0,转成false。
转成常量
允许将指向非常量类型的指针转换成指向常量类型的指针,引用同理。
类类型定义的转换
有编译器自动执行,但是每次只执行一种
显式转换
也称强制类型转换(cast)
int j,j;
double slope = i / j;
命名的强制类型转换如下形式
cast-name<type>(exoression);
tpye是转换的目标类型
expression是要转换的值。
如果type是引用,则结果是左值。
cast-name是 static_cast,dynamic_cast,const_cast和reinterpret_cast
static_cast
任何明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
double slope = static_cast<double>(j) / i;
当需要把较大的算术类型赋值给小类型,很有用。
强制类型转换告诉读者和编译器,不在乎精度损失,因此警告会被关闭。
对于编译器无法自动执行的,static_cast也能做到。例:
//找回存在void*指针的值
void *p = &d;
double *dp = static_cast<double*>(p);
//把指针存放在void*中,并用static_cast强转回来是,应该确保指针值不变。
//即转换的结果和原始地址相等,因此要确保转换后所得类型就是指针所指类型。
const_cast
只能改变运算对象的底层const
用于去掉const性质。
const char *cp;
const_cast<char*>(cp);
//但是不用用来挟制,是未定义的行为
char *p = const_cast<char*>(pc);
static_cast<string>(cp); //正确,字符串字面值转换成string
const_cast<string>(cp); //错误,const_cast只能改变常量属性
reinterpret_cast
为运算对象的位模式提供较低层次上的重新解释
int num = 0x00636261;//用16进制表示32位int,0x61是字符'a'的ASCII码
int * pnum = #
char * pstr = reinterpret_cast<char *>(pnum);
cout<<"pnum指针的值: "<<pnum<<endl;
cout<<"pstr指针的值: "<<static_cast<void *>(pstr)<<endl;//直接输出pstr会输出其指向的字符串,这里的类型转换是为了保证输出pstr的值
cout<<"pnum指向的内容: "<<hex<<*pnum<<endl;
cout<<"pstr指向的内容: "<<pstr<<endl;
//结果为
0x7ffcfa7e861c
0x7ffcfa7e861c
636261
abc
两个指针的值是完全相同的,只是内容不一样。
字符串指针要遇到" "才表示结束。
如果num值为0x63006261,输出为ab
如果num值为0x64636261,输出就不确定,可能出错。
尽量少用。
dynamic_cast
支持运行时的类型识别