C++运算符

算术运算符

C++的算术运算符有一元正号或加法+、一元负号或减法-、乘法*、除法/、求余%

算术运算符中,一元运算符优先级最高,其次是乘法和除法,优先级最低的是加法和减法。C++的算术运算符都满足左结合律,意味着当优先级相同时,按照从左向右的顺序进行组合。
算术运算符的运算对象和求值结果都是右值。在表达式求值前,小整数类型的运算对象被提升成较大的整数类型,所有运算对象最终都会转换成同一类型。
一元正号运算符、加法运算符和减法运算符都能作用于指针。当一元运算符作用于一个指针或者算术值时,返回运算对象的一个(提升后的)副本。
一元负号运算符对运算对象取负后,返回其(提升后的)副本:

int i = 1024;
int k = -1;//k是-1024
bool b = true;
bool b2 = -b;//b2是true

对于布尔类型b2来说,它的值很有意思,并不是我们想象的false。原因是对于大多数运算符来说,布尔类型的运算对象将被提升为int类型。对于bool b2 = -b这条语句来说,因为b的值是true,参与运算时被提升为1,对其求负后的结果是-1,再将-1转换回布尔值并将其作为b2的初始值,显然这个初始值不等于0,转换成布尔值后应该为true。
当算术运算的计算结果超出该类型所能表示的范围时会产生溢出。溢出的结果是未定义的:

short i = 32767;//假设short占16位,则32767是其所能表示的最大值
i += 1;
std::cout << i << std::endl;

在一些机器中,上述程序的输出结果为-32768。原因是一个带符号数32768需要17位,但是short类型只有16位,溢出导致符号位由0被改成了1,于是结果变成一个负值。具体原因如下:
32767在计算机中表示为0111111111111111,最高位是符号位,0代表正数。0111111111111111+0000000000000001=1000000000000000,符号位改变。计算结果正好是-32768的补码(现代计算机存储负数的方式是存储其补码)。
当作用于算数类型的对象时,算术运算符+、-、*、/的含义分别是加法、减法、乘法和除法。整数相除的结果还是整数,C++标准规定如果商含有小数部分,直接弃除。

int val1 = 21/6;//val1是3
int val2 = -21/6;//val2是-3

运算符%俗称“取余”或“取模”运算符,负责计算两个整数相除得到的余数。参与取余运算的运算对象必须是整数类型。根据取余运算的定义,如果m和n是整数且非0,则表达式(m/n)*n+m%n的求值结果与m相等。隐含的意思是,如果m%n不等于0,则它的符号和m相同。C++最新标准(截止C++14)规定,除了-m导致溢出的特殊情况,其他时候(-m)/n和m/(-n)都等于-(m/n),m%(-n)等于m%n,(-m)%n等于-(m%n)。

逻辑和关系运算符

逻辑运算符有逻辑与&&、逻辑或||、逻辑非!三种,关系运算符有小于<、小于等于<=、大于>、大于等于>=、相等==和不相等!=
关系运算符作用于算数类型或指针类型,逻辑运算符作用于任意能转换成布尔值的类型。逻辑运算符和关系运算符的返回值都是布尔类型。值为0的运算对象(算数类型或指针类型)表示假,否则表示真。对于这两类运算符来说,运算对象和求值结果都是右值。

短路求值

逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧对象的值。这种策略称为短路求值(short-circuit evaluation)。因此对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值;对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值。

关系运算符

顾名思义,关系运算符比较运算对象的大小关系并返回布尔值。关系运算符都满足左结合律。
因为关系运算符的求值结果是布尔值,所以将几个关系运算符连写在一起会产生意想不到的结果:
if (i < j < k)//若k大于1则为真
上面的语句首先i与j进行比较得到一个布尔值,然后将这个布尔值转换为整型数值(true转换为1,false转换为0)与k进行比较得到最终结果。


赋值运算符

赋值和初始化

赋值是在两个已经存在的对象间进行(字面值属于已存在对象),且要求赋值运算符的左侧运算对象是一个可修改的左值。而初始化则是要创建一个新的对象,用另一个已存在的对象为其初始化。
int i = 0, j = 0;//初始化
int k = i+1;//初始化
j = i+1;//赋值
const int ci = i;//初始化

赋值运算

赋值运算符的左侧运算对象必须是一个可修改的左值。对于上面的i,j,k来说,下面的赋值语句是非法的:
1024 = k;//错误:字面值是右值
i+j = k;//错误:算术表达式是右值
ci = k;//错误:ci是常量(不可修改的)左值
从C++11开始允许使用花括号括起来的初始值列表作为赋值运算符的右侧运算对象:
k = {3.14};//错误:窄化转换不被允许
vector<int>vi;//默认初始化为空
vi = {0, 1, 2, 3};//赋值
如果左侧运算对象是内置类型,那么初始值列表最多只能包含一个值,而且该值即使转换的话其所占空间也不应该大于目标类型的空间。
对于类类型来说,赋值运算的细节由类本身决定。对于vector来说,vector模板重载了赋值运算符并且可以接收初始值列表,当赋值发生时用右侧运算对象的元素替换左侧运算对象的元素。
赋值运算满足右结合律
int i, j;
i = j = 0;
上述赋值运算先将0赋值给j,再将j的值赋值给i。
对于多重赋值语句中的每一个对象,它的类型或者与右边对象的类型相同、或者可由右边对象的类型转换得到(在编译器允许的转换范围内)。

递增和递减运算符

递增运算符(++)和递减运算符(--)为对象的加1和减1操作提供了一种简洁的书写形式。这两个运算符还可应用于迭代器,因为很多迭代器本身不支持算术运算,所以此时递增和递减运算符除了书写简洁外还是必须的。
递增和递减运算符有两种形式:前置版本和后置版本。前置版本首先将运算对象加1(或减1),然后将改变后的对象作为求值结果。后置版本也会将运算对象加1(或减1),但是求值结果是运算对象改变之前那个值的副本:
int i = 0, j;
j = ++i;//j = 1, i = 1前置版本得到递增之后的值
j = i++;//j = 1, i = 2后置版本得到递增之前的值
这两种运算符必须作用于左值运算对象。前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。
C++的一个代码编写准则是:除非必须,否则不用递增递减运算符的后置版本。因为前置版本避免了不必要的工作,它修改值后直接返回改变了的运算对象。与之相比,后置版本需要创建一个副本将原始值存储下来以便于返回这个未修改的内容。
对于整数和指针类型来说,编译器可能会对这种额外的工作进行一定的优化,但是对于复杂的迭代器类型,这种额外的工作十分消耗资源。
然而在一些情况下,后置版本又是必须的:
std::vector<int> vi = {0, 1, 2, -1, -8, 3, 24};
std::iterator<int>::iterator it = s.begin();
while (it != s.end() && *it >=0)
std::cout << *it++ << std::endl;//输出当前值并将it向前移动一个元素
由于后置递增运算符的优先级高于解引用运算符,因此*it++等价于*(it++)。即it向前移动一个元素,然后返回it的初始值的副本作为解引用的运算对象。最终,这条语句输入it开始时指向的元素,并将指针向前移动一个位置。


成员访问运算符

点运算符.和箭头运算符->都可用于访问成员。其中,前者获取类对象或者结构体、共用体等复合类型的一个成员;后者与前者有关,表达式ptr->mem等价于(*ptr).mem
需要注意的是,解引用运算符的优先级低于点运算符,所以执行解引用运算的子表达式两端必须加上括号。
箭头运算符作用于一个指针类型的运算对象,结果是一个左值。点运算符分成两种情况:如果成员所属的对象时左值,那么结果是左值;反之,如果成员所属的对象是右值,那么结果是右值。


条件运算符

条件运算符 ?: 允许我们把简单的if-else逻辑嵌入到单个表达式中,使用格式如下:
cond ? expr1 : expr2
cond是判断条件的表达式,而,expr1和expr2是两个类型相同或可能转换为某个公共类型的表达式。当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值,否则运算结果是右值。


位运算符

位运算符作用于整数类型(还有biset类型)的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能。位运算符有位求反~、左移<<、右移<<、位与&、位或|、位异或^几种。
位运算符的运算对象可以是带符号的也可以是无符号的,而且如果运算对象时“小整型”,则它的值会被自动提升。如果运算对象是带符号的且值为负,哪儿位运算符如何处理运算对象的“符号位”依赖于机器。而且,此时的左移操作可能会改变符号位的值,因此是一种未定义的行为。故在编码过程中应尽量避免将位运算符用于处理无符号类型。


sizeof运算符

sizeof运算符返回一条表达式或一个类型名字所占的字节数。sizeof运算符满足右结合律,其所得的值是一个size_t类型的常量表达式。运算符的运算对象有两种形式:
sizeof (type)
sizeof expr
在第二种形式中,sizeof返回的是表达式结果类型的大小。与众不同的一点是,sizeof并不实际计算其运算结果的值:
Sales_data data, *p;
sizeof (Sales_data);//存储Sales_data类型的对象所占的空间的大小
sizeof data;//data的类型的大小,即sizeof(Sales_data)
sizeof p;//指针所占的空间大小
sizeof *p;//指针p所指类型的空间大小,即sizeof(Sales_data)
对于sizeof *p 由于sizeof满足右结合律并且与解引用运算符的优先级相同,所以表达式按照从右到左的顺序组合,也即等价于sizeof (*p)。另外,因为sizeof不会实际求运算对象的值,所以即使p是一个无效(即未初始化)的指针也不会有什么影响。在sizeof的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。
sizeof运算符的结果部分地依赖于其作用的类型:
·对char或者类型为char的表达式执行sizeof运算,结果为1
·对引用类型执行sizeof运算符得到被引用对象所占的空间大小
·对指针执行sizeof运算得到指针本身所占空间的大小
·对解引用指针执行sizeof运算得到指针所指向的对象所占空间的大小,指针不需有效
·对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并将所得结果求和。也就是说,sizeof运算不会把数组转换成指针来处理
·对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间(这与类的具体实现有关,查看类的头文件定义即可知道)



本文内容摘自《C++ Primer(第5版)》

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值