本节书摘来自华章出版社《C++程序设计教程(第3版)》一书中的第2章,第2.4节基本运算符和表达式,作者张志航,更多章节内容可以访问云栖社区“华章计算机”公众号查看
2.4 基本运算符和表达式
2.4.1 C++运算符及表达式简介
在C++中,数据处理是通过运算来实现的。运算符又称为操作符。为表示一个计算过程,需要使用表达式,表达式是由运算符、运算量构成的一个计算序列。在C++中有很多运算符,如算术运算符(+、-、*、/、%)、关系运算符(>、>=、<、<=、==、!=)等。表2-4中列出了C++中各种运算符及其优先级和结合性。
表2-4 C++中的运算符及其优先级和结合性
若一个运算符只能对一个运算量进行,则称其为一元运算符或单目运算符,如表2-4中第2行的“–”(取负)运算符。若一个运算符需要两个运算量,则称其为二元运算符或双目运算符,如表2-4中第4行的“+”(加法)和“–”(减法)运算符。若一个运算符需要3个运算量,则称其为三元运算符或三目运算符,如表2-4中第13行的“?:”条件运算符,后续章节会介绍其意义。
2.4.2 算术运算符和算术表达式
C++提供了5个算术运算符,它们是+(加)、-(减)、*(乘)、/(除)、%(求余),都是二元运算符,其中+(正号)和-(负号)又可用作一元运算符。
值得注意的是,对于除法运算,当两个运算量均为整型量时为整除,整除的意义是取除法结果的整数部分,如5/2得到结果2。当至少有一个运算量为实型量时则为通常意义的除法,如5.0/2得到结果2.5。
对于求余运算(或称模运算),运算量必须为整型量。例如,8%3结果为2,而8.0%3为非法运算。
2.4.3 运算优先级和结合性
C++表达式中可以出现多个运算符和运算量,计算表达式时必须按照一定的次序,运算符的优先级和结合性规定了运算次序。
所谓优先级是指,若在同一个表达式中出现了不同级别的运算符,首先计算优先级较高的。不同的运算符具有不同的运算优先级。表2-4中所列出的运算符的优先级自上而下是递减的。例如,表达式d=a+b c中出现了3个运算符,即=(赋值)、+(加法)、(乘法),这3个运算符的优先级由高到低依次是*、+、=,所以先算乘法,再算加法,最后执行赋值运算,即将赋值运算符右边的表达式的计算结果赋给变量d。
括号可以改变运算的优先次序,如表达式d=(a+b) c的运算次序是+、、=。
所谓结合性是指,在表达式中多个优先级相同的运算符连续出现时,运算次序是“自左向右”还是“自右向左”。表2-4列出了各运算符的结合性。例如,表达式d=a+b-c中出现了3个运算符,即=(赋值)、+(加法)、-(减法),其中加法和减法的运算优先级相同,而赋值运算的优先级较低。从表2-4中看出,加法和减法运算的结合性是从“左向右的”,因此计算该表达式时,先计算加法,再计算减法,最后进行赋值。而表达式a=b=c中出现的两个运算符优先级相同,运算的结合性是“自右向左”,所以先进行最右侧的赋值,再进行左边的,详见2.4.8节。
2.4.4 关系运算符和关系表达式
“关系运算符”实际上就是“比较运算符”。关系运算符的意义及其运算优先次序如下:
关系表达式是由关系运算符连接两个表达式构成的,如a>3.0、a+b>b+c、(a=3)>(b=5)、 'a'<'b'等。
关系运算符的运算优先级比算术运算符的优先级低,但比赋值运算符的优先级高。参加关系运算的两个操作数可以是任意类型的量。当比较结果成立时,结果为1(表示“真”,即true),当比较结果不成立时,结果为0(表示“假”,即false)。例如,若有“int a=1,b=2,c=3;”,则表达式a>b的值为0;表达式b<a+c的值为1;表达式a==b-1的值为1;表达式c>b>a的值为0,因为按照运算符的结合性,首先计算c>b结果为1,再计算1>a,结果为0。
2.4.5 逻辑运算符和逻辑表达式
C++语言中提供了3个逻辑运算符,如下所示。
! 逻辑“非”(一元运算符)
&& 逻辑“与”(二元运算符)
|| 逻辑“或”(二元运算符)
若a和b是两个运算量,a&&b的意义是,当a和b均为真时,表达式的值为真。a || b的意义是,当a和b均为假时,表达式的值为假。!a的意义是,当a为真时,!a为假;当a为假时,!a为真。可以用如表2-5所示的“真值表”来概括。
表2-5 逻辑运算“真值表”
接两个表达式构成的,如a>b&&x>y、a==b || x==y、!a>b。3个逻辑运算符的优先级由高到低依次是!、&&、||。逻辑非运算符(!)的优先级比算术、关系运算符高,而“与”(&&)和“或”(||)的运算优先级比算术、关系运算符的优先级低,但比赋值运算符的优先级高。因此,上述3个逻辑表达式的意义等价于(a>b)&&(x>y)、(a==b)||(x==y)、(!a)>b。
参加逻辑运算的运算量可以是任意类型的数据。进行逻辑运算时,在判断运算量的逻辑真假性时,规定任何非0值表示逻辑真,0值表示逻辑假。例如,若a=-1、b=2.0,则a为真,b也为真,从而表达式a&&b的结果为真。
C++在给出关系表达式或逻辑表达式运算结果时,以数值1代表“真”,以数值0代表“假”。例如,若a=1、b=-2,则表达式a>b的值为1。
注意:在C++程序中,要表示数学关系0≤x≤10时,逻辑表达式必须写0<=x && x<=10,而不能写成0<=x<=10。因为C++语言中表达式的运算是按照优先级和结合性进行的,不能看成与数学意义相同。例如,当x=-1时,数学关系0≤x≤10显然是不成立的;而C++中表达式0<= x<=10的运算结果为真,与数学关系式矛盾。计算过程是:表达式0<= x<=10中两个运算符的优先级相同,按照结合性,自左向右运算,先计算0<=x,结果为0;再计算0<=10,结果为真。而对表达式0<=x &&x<=10,先计算0<=x,结果为0;再计算0&&x<=10,结果为0,与数学关系保持一致。注意在计算表达式0&&x<=10时,涉及逻辑运算优化问题,见2.4.11节的叙述。
2.4.6 位运算符和位运算表达式
位运算是对整型量的运算,并且规定符号位参与运算,主要用于编写系统软件,完成汇编语言能够完成的一些功能,如对机器字以及机器字中的二进制位进行操作。位运算符共有6个,它们是按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移(<<)和右移(>>)。下面逐一介绍。
1.按位与(&)
;再计算0运算符“&”将两个运算量的对应二进制位逐一按位进行逻辑与运算。每个二进制位(包括符号位)均参加运算。例如:
char a=3, b=-2, c; // 此时,可将a、b、c看成1字节长度的整型量
` c = a & b;
a 0000 0011
& b 1111 1110
c 0000 0010`
运算结果:变量c的值为2。
2.按位或(|)
运算符“|”将两个运算量的对应二进制位逐一按位进行逻辑或运算。每个二进制位(包括符号位)均参加运算。例如:
char a=18, b=3, c; // 此时,可将a、b、c看成1字节长度的整型量
` c = a | b ;
a 0001 0010
| b 0000 0011
c 0001 0011`
运算结果:变量c的值为19。
3.按位异或(^)
运算符“^”将两个运算量的对应二进制位逐一按位进行逻辑异或运算。每个二进制位(包括符号位)均参加运算。异或运算的含义是,若对应位相异,结果为1;若对应位相同,结果为0,例如:
char a=18, b=3, c; // 此时,可将a、b、c看成1字节长度的整型量
` c = a ^ b ;
a 0001 0010
^ b 0000 0011
c 0001 0001`
运算结果:变量c的值为17。
请读者思考:如下程序段执行后,a、b的值分别是多少?参见本章习题第12题。
`int a=5, b=9;
a = a^b;
b = a^b;
a = a^b;
4.按位取反(~)`
运算符“~”是一元运算符,作用是将运算量的每个二进制位逐一取反。例如:
`int a=18, b;
b = ~a;
~ a 0000 0000 0000 0000 0000 0000 0001 0010
b 1111 1111 1111 1111 1111 1111 1110 1101`
运算结果:变量b的值为-19,或记为十六进制数0xffffffed。
5.左移(<<)
设a、n是整型量,左移运算的一般格式为:a<例如,已知“int a=15, x;”,a的二进制值是0000 0000 0000 1111,做“x=a<<3;”运算后x的值是0000 0000 0111 1000,其十进制值是120。对一个量进行左移一个二进制位,相当于乘以2操作。左移n个二进制位,相当于乘以2n操作。程序运行时,左移n位比乘以2n操作速度快。
左移运算有溢出问题,因为若以补码表示,整型量的最高位是符号位,当左移一位时,若符号位不变,则相当于乘以2操作,但当符号位变化时,就发生了溢出。例如,若有“char a=127, x ;”即a的二进制值是0111 1111,做“x=a<<2;”运算后x的值为1111 1100,它的真值-4,此时即发生了溢出。原因是,8位二进制补码所能表示的数值的范围是-128~+127,a的初值是127,若将a的值乘以22,则超出了数值范围。溢出后,变量a的值是其在内存中实际存储的值1111 1100,它是一个在逻辑上不正确的值。因为127乘以22后,逻辑上应得到508,但实际上是-4,这就是“溢出”的后果。“溢出”是存储位的限制引起的。
6.右移(>>)
设a、n是整型量,右移运算的一般格式为:a>> n,其意义是将a按二进制位向右移动n位,移出的最低n位舍弃,高位补0还是1呢?这取决于a的数据类型,若a是有符号的整型量,则高位补符号位;若a是无符号的整型量,则高位补0。例如:
`char a = -4, b=4, x, y;
x = a >> 2;
y = b >> 2;
a: 1111 1100 → x: 1111 1111
b: 0000 0100 → y: 0000 0001
x的值是-1,y的值是1。右移一位相当于除以2操作。又例如:
unsigned char a = -4 , x ;
x = a >> 2;
a: 1111 1100 → x: 0011 1111`
x的值是63。此时,右移一位符号位发生了变化,称为溢出,就不表示除2操作了。
说明:上面的例子中对a进行左移或右移,a本身的值并没有发生变化。这类似于:
int a=1, b;
b = a+2;
执行后a本身的值并没有变化。
请思考:已知“unsigned a; int n=4;”,表达式“a=(a<>(16-n))”对a中存储的二进制值做了怎样的操作?
2.4.7 自增、自减运算符和表达式
C++中有两个可以改变变量自身值的运算符,它们是自增(++)和自减(--)运算符,作用是使变量自身的值加1或减1。这两个运算符均是一元运算符,而且只能对变量做运算。它们可以放在变量之前或之后,如++i、--i表示先将i的值加1或减1,然后再参加其他运算;而i++、i--表示先用i的值参加运算,然后再将变量i的值加1或减1。
例如:“int i=3, j; j=++i;”,则运算结束后,i 的值是4,j的值也是4。
又如:“int i=3, j; j=i++;”,则运算结束后,i 的值是4,j的值是3。
再如:“int i=3, j=4, x; x=(i++)+(j++);”,则运算结束后,i、j的值是4、5,而x的值是7。
注意:自增、自减运算符只能作用于变量,表达式3++或++(x+y)都是非法的,原因是在这两个表达式中,++作用在常量和表达式上,而常量和表达式是不能被修改的。
2.4.8 赋值运算符和赋值表达式
1.赋值运算符
在C++中,“=”是赋值运算符,赋值表达式是由赋值运算符连接一个变量和一个表达式构成的,其格式是:
<变量> <赋值运算符> <表达式>
如:a=8和a=b+c是两个合法的赋值表达式。
赋值表达式的求解过程是:求出<表达式>的值,赋给<变量>。
整个赋值表达式(带下划线的)的值是:<变量>获取的值。
注意:赋值表达式中<表达式>还可以是另一个赋值表达式,如a=b=5。赋值运算符“=”与数学上的等号意义不一样,赋值表示一种运算,如i=i+1,表示先计算等号右边的i+1,再把结果赋给变量i。赋值运算符的优先级很低,比到目前为止我们学习过的所有运算符的优先级都低,其结合性是自右向左的,如表达式b=c=d=a+5是一个合法表达式,若a的初值是3,则先计算表达式a+5,结果是8,将8赋给变量d,此时赋值表达式“d=a+5”的值是8,再自右向左依次计算c=8,b=8,结果整个表达式的值是8。又如表达式a=5+c=6是非法表达式,因为按照运算符的优先级应先计算5+c,再计算右边的赋值“=”,即将6赋给“5+c”,这是一个非法赋值。再如,表达式a=b=5的值是5;表达式a=5+(c=6)的值是11;表达式a=(b=4)+(c=6)的值是10。
2.复合赋值运算符
表达式a=a+3可简写成a+=3,表达式a=ab可简写成a=b。在C++中,所有的二元算术运算符和二元位运算符与赋值运算符都可以组合成一个复合的赋值运算符。它们是:
+= -= *= /= %= <<= >>= &= ^= |=
“复合赋值运算符”与“赋值运算符”的优先级和结合性是一样的。如y=x+8,等价于y=(x+8),也等价于y=y(x+8)。又如表达式a+=a-=aa,如果a的初值为2,则先计算a*a,值为4,再计算a-=4,结果a的值变成-2,同时表达式a-=4的值也是-2,再计算a+=(-2),结果a的值是-4,整个表达式的值也是-4。
2.4.9 逗号运算符和逗号表达式
逗号运算符即“,”。逗号表达式格式是:
<表达式1> ,<表达式2>,…,<表达式n>
逗号表达式的求解过程:依次计算<表达式1>,<表达式2>,…,<表达式n>的值。
整个逗号表达式(带下划线的)的值为<表达式n>的值。
如逗号表达式a=35,a4,a+5。依次计算表达式a=35、表达式a4、表达式a+5。运算结束后:变量a的值为15,整个表达式的值为20。
逗号运算符的优先级在C++所有的运算符中是最低的,见表2-4,它的结合性是自左向右的。
关于逗号表达式的计算,下面给出几个例子。请指出下面表达式从最高层面上说是属于逗号表达式还是属于赋值表达式?运算结束后,给出变量a和x的值,并给出整体表达式的值。
a=35, a4 //是逗号表达式,运算结束后a=15,整体表达式的值为60
x=(a=3, 6*3) //是赋值表达式,运算结束后a=3,x=18,整体表达式的值为18
x=a=3, 6*3 //是逗号表达式,运算结束后a=3,x=3,整体表达式的值为18
2.4.10 sizeof 运算符和表达式
sizeof运算符是一元运算符,它的作用是求一个变量或常量所占用的字节数。其格式为:
sizeof (<类型标识>/<变量名>)
例如,已知“int i; double x;”,则sizeof(int)和sizeof(i)均合法,结果均为4;sizeof(double)和sizeof(x)均合法,结果均为8。在2.3.1节中已指出常量的默认类型,因此sizeof(439)的值是4,而sizeof(56.8)的值是8。
2.4.11 逻辑运算优化的副作用
C++语言在计算逻辑表达式的值时,从左向右扫描表达式,表达式的值一旦确定后,就不再继续进行计算,这就是逻辑运算的优化。具体表现如下所示。
若求<表达式1> && <表达式2>的值,计算时,从左向右扫描,先计算<表达式1>,当<表达式1>为真时,继续计算<表达式2>;当<表达式1>为假时,即能确定整个表达式的值为假,则停止计算<表达式2>。
若求<表达式1> | | <表达式2>的值,计算时,从左向右扫描,先计算<表达式1>,当<表达式1>为假时,继续计算<表达式2>;当<表达式1>为真时,即能确定整个表达式的值为真,则停止计算<表达式2 >。
例如:
`int x, y, z, w;
x = y = z = 1 ;
w = ++x || ++y && ++z; // A`
计算结束后,变量x、y、z和w的值分别是2、1、1和1。因为在计算A行表达式时,先计算++x,结果是2,值为真,其后紧跟或运算符 ||,不继续往右计算了,y和z的值保持不变。赋值号右边逻辑表达式的值为“真”,此时变量w被赋值为1。尽管表达式中后一个逻辑与运算符&& 的优先级比逻辑或运算符 || 的优先级高,但&&不会先算,因为对 || 运算符来说,&&运算的结果(即表达式++y && ++z的值)是 || 运算的第二个运算量,第一个运算量的结果已经是“真”了,就不需要计算第二个运算量了。又如
`int x = -1, y = 2, z = 0, w;
w = ++x && ++y || ++z; // B`
计算结束后,变量x、y、z和w的值分别是0、2、1和1。因为在计算B行表达式时,先计算++x,结果是0,值为假,其后紧跟与运算符&&,不继续计算&&后的运算量++y了,y的值保持不变。但是还要继续计算++z,因为对于或运算符 || 来说,前面&&运算的结果是 || 运算的第一个运算量,其值为假,需要计算第二个运算量 ++z。