1.6 运算符与表达式
1.6.1 运算符及运算规则
运算符是一种表示对数据进行何种操作处理的符号,每种运算符代表某种操作功能。表达式就是用运算符把操作数连接起来所构成的式子。每种运算符有自己的运算规则,如:优先级、结合性、运算对象类型和个数、运算结果的数据类型等。
运算符的优先级是指同一表达式中多个运算符被执行的次序。在表达式求值时,按运算符的优先级别由高到低的次序执行,例如算术运算符中采用“先乘除后加减”。运算符的优先级共分为15级,1级最高,15级最低。
运算符的结合性是当同一表达式中多个运算符的优先级别相同时被执行的运算次序,要按规定的“结合方向”来处理。各运算符的结合性分为两种,即左结合性(自左至右)和右结合性(自右至左)。
若变a、b的值分别3、4,则执行语句k=a-5+b;后,k的值为2。表达k=a-5+b中,有三个运算符'='、'-'、'+' ,'='的结合性为从右至左,因此执行时首先要求出其右边表达式a-5+b的值;计算a-5+b时,先求出a-5的值,再计算-2+b。
1.6.2 算术运算符
(1)基本的算术运算符与表达式
C语言中允许的算术运算符及其相关说明如表1-8所示。
表1-8 算术运算符及相关说明
虽然算术运算符与数学中的数值运算很相似,但是还是有些差别,使用时需要注意以下几点:
①两个整数相除结果为整数,如5/2的结果值为2,15/34的结果是0。小数部分被舍去(即取整)。若除数和被除数中有一个为浮点数,结果是浮点数,例如9/4.0、9.0/4、9.0/4.0的结果均为2.25。
②%运算符号要求两边必须都为整数,否则编译会报错。如果参加运算的数不是整数,则可以先进行强制类型转换为整数,再进行求余,如:(int)(5.3)%3=2。
③如果参加运算的多个数中有一个数为double型实数,则此时所有的数都按double型计算,其结果为double型数据。
④+、-运算符既具有单目运算(只有一个操作数)的取正值和取负值的功能,又具有双目运算的功能。作为单目运算符使用时,其优先级别高于双目运算符。
算术表达式是指用算术运算符和括号将运算对象(也称操作数)连接起来的符合C语法规则的式子。运算对象包括常量、变量、函数等。下面是一些合法的算术表达式:
8+a/b*3+7%2+'a' X*2/(y+5)
(2)算术运算符的优先级及结合性
算术表达式中按算术运算符优先级别的高低计算表达式的值,即先乘除,后加减。取模运算的优先级与乘除相同。函数和圆括号的优先级最高。如表达式a+b*c,先计算的应是b*c。用圆括号可以改变表达式的优先级,运算时先计算内括号中表达式的值,再计算外括号中表达式的值。如表达式a+b*(c-d),先计算的应是c-d。
算术运算符优先级别相同,“结合性”是从左到右。如表达式a+b-c,变量b两侧的运算符优先级别相同,计算顺序是等价于(a+b)-c计算。
1.6.3 赋值运算符
(1)赋值运算符
赋值运算符“=”是一个双目运算符,其功能是先计算赋值运算符“=”右边表达式的值,并将其赋给“=”左边的变量。由赋值运算符“=”将一个变量和一个表达式连接起来的式子称为赋值表达式。它的一般形式为:
变量名=表达式
例如:
a=9.6; / * 将浮点数9.6赋给变量a* / z=x/y; / * 将变量x和变量y进行除法运算,所得到的结果赋给变量z* /
说明:
①赋值运算符“=”的左边必须是变量,而诸如“3=3”“a+b=5”之类的看似符合数学规则的表达式,在C语言中都是错误的。
②在C中,赋值运算符“=”所表达的意思并不是数学中的相等号,执行了表达式“a=2;”是将“=”右边表达式的值赋给左边的变量,不是判断变量a中的值与2相等。
(2)复合的赋值运算符
为了简化程序的书写并提高编译效率,在赋值运算符“=”之前加上其他运算符,即可构成复合的赋值运算符。
例如在“=”前加一个“+”运算符就构成一个复合赋值运算符“+=”,而“x+=3”等价于“x=x+3”。
同理,“x*=y+2”等价于“x=x*(y+2)”、“x%=2”等价于“x=x%2”等。
常用的复合赋值运算符有:+=、-=、*=、/=、%=、>>=、<<=、&=、 =和|=。其中后五种是有关位运算的,将在本章后面学习。
例如:设变量a的初值为3,求表达式a+=a-=a*a的值。
该表达式的求解步骤如下:
①先进行a-=a*a运算,它相当于a=a-a*a=3-3*3=-6。
②再进行a+=-6的运算,它相当于a=a+(-6)=-6+(-6)=-12。
1.6.4 自加自减运算符、负号运算符及其表达式
(1)自加自减运算符
自加运算符“++”和自减运算符“--”都是单目运算符,其功能是使变量的值增1或减1。其优先级高于所有双目运算符。自加自减运算符只作用于简单变量,有两种应用形式:
①前缀形式:即运算符置于变量的前面。如表达式++i、--i。它表示将变量的值先加1或减1,然后用新的值参与表达式的运算。
②后缀形式:即运算符置于变量的后面。如i++、i--,它表示先用变量的值参与表达式的运算,然后将变量的值加1或减1。
表面上看,单独的++i和i++表达式的作用相当于i=i+1。在复合表达式中++i和i++的不同之处在于:++i是先执行i=i+1运算后,再使用i的值;而i++是先使用i的值后,再执行i=i+1运算。
【例1-9】阅读程序,理解自加自减运算符的用法。
/ *源程序1-9.C* / #include <stdio.h> void main() { int k=0,i=3,j=0; printf("%d\t",k=++i); / * 执行后,k的值为4,i的值也为4* / k=0;i=3;printf("%d\t",k=--i); k=0;i=3;printf("%d\t",k=i++); / * 执行后,k的值为3,而i的值变为4* / k=0;i=3;printf("%d\t",k=i--); k=0;i=3;printf("%d\t",-i++); k=0;i=3;printf("%d\n",-i--); }
程序运行结果为:
4 2 3 3 -3 -3
说明:
①“++”和“--”只能用于变量,不能用于常量或表达式。例如3++、(a+b)++、(-i)++等都是不合法的表达式。
②“++”和“--”优于算术运算符,而与单目运算符“!”“+”(正号)、“-”(负号)属同一级别。因此(a+b)++不是合法的表达式,但(a+b++)则是合法的表达式。
③对于表达式a+++b,其等价于(a++)+b、还是等价于a+(++b)?C编译系统的处理原则是尽可能多地“从左向右”将若干个字符组成一个运算符。因此a+++b应等价于(a++)+b。但为避免误解,最好在书写程序时添加必要的括号。
(2)负号运算符
负号运算符“-”是单目运算符,任何操作数前放置负号运算符相当于用-1乘以该操作数。
-i++则是合法的表达式。因为在变量i的两边,一边是“-”,另一边是“++”,这两个运算符的优先级别是相同的。此时表达式的计算需要考查运算符的结合性。从附录可知“++”“--”和“-”的结合性为“自右向左”。因此,对于表达式-i++,系统解释为“-(i++)”,意为如果i的原值为3,那么-i++相当于-(i++),则该表达式的值为-3,而使用后i的值为4(先取值,后自加)。同理-i--相当于-(i--),则该表达式的值为-3,结果i的值为2。
1.6.5 关系运算符与关系表达式
关系运算符用来比较两个数据的大小关系,运算的结果为“真(1)”或“假(0)”。
(1)关系运算符及关系表达式
关系运算符中的“关系”两字指的是一个值与另一个值之间的大小关系,关系运算符及关系表达式如表1-9所示。
表1-9 关系运算符及关系表达式
(2)关系运算符优先级
关系运算符的优先级如下:
算术运算符 > 关系运算符 > 赋值运算符
其中,<、<=、>、>=的优先级相同,且高于==和!=。==和!=的优先级相同。优先级相同时,关系运算符的结合方向为“自左向右”。
例如:“x>y+z”等价于“x>(y+z)”;
“x<y==z”等价于“(x<y)==z”;
“x=y<=z”等价于“x=(y<=z)”。如:若“y=2”“z=4”,则满足“y<=z”的条件,“y<=z”的值为“真(1)”;再把比较结果“真(1)”赋给x,因此x的值为1。
【例1-10】阅读程序,理解关系运算符的用法。
/ *源程序1-10.C* / #include <stdio.h> void main() { char c='k'; int i=1,j=2,k=3; float x=3e+5,y=0.85; printf("%d,%d\n",'a'+5<c,-i-2*j>=k+1); printf("%d,%d\n",1<j<5,x-5.25<=x+y); printf("%d,%d\n",i+j+k==-2*j,k==j==i+5); }
程序运行结果为:
1,0 1,1 0,0
1.6.6 逻辑运算符与逻辑表达式
(1)逻辑运算符及逻辑表达式
C语言中为了表达出数学中的不等式“a>b>c”,引入了逻辑运算。由逻辑运算符连接表达式称逻辑表达式,运算的结果为“真(1)”或“假(0)”。
逻辑运算符及逻辑表达式如表1-10所示。
表1-10 逻辑运算符及逻辑表达式
数学中的不等式“a>b>c”,在C语言中表示为a > b && b > c;
(2)逻辑运算符优先级与结合性
关系运算符的优先级如下:
逻辑运算符中的“&&”和“||”低于关系运算符,“!”高于算术运算符,
例如:
(a==b)&&(c<d) 可以写成a==b&&c<d;
(a+b)||(!a) 可以写成a+b||!a;
(a<b)&&(c>=d) 可以写成a<b&&c>=d。
逻辑与运算符“&&”和逻辑或运算符“||”的结合性为“自左向右”,而逻辑非“!”运算符的结合性为“自右至左”。例如:a&&b||c,先进行“a&&b”运算,再用其值与c进行逻辑或运算;a&&!b,先对b进行逻辑非运算,再用“!b”的值与a进行逻辑与运算。
【例1-11】阅读程序,理解逻辑运算符的用法。
/ *源程序1-11.C* / #include <stdio.h> void main() { int a,b=0,c=0,i; a=1; i=a&&++b&&++c; printf("%2d,%2d,%2d,%2d\n",a,b,c,i); a=0; i=a&&++b&&++c; printf("%2d,%2d,%2d,%2d\n",a,b,c,i); a+=10; i=a||++b&&(c=a>b); printf("%2d,%2d,%2d,%2d\n",a,b,c,i); }
本程序的运算结果为:
1,1,l,l 0,1,1,0 10,l,1,1
1.6.7 位运算符与位运算表达式
位运算操作是指按二进制位逐位进行运算的。位操作通常用于设备驱动程序,例如调制解调器程序、磁盘文件管理程序和打印机驱动程序。这是因为位操作可屏蔽掉某些位,如奇偶校验位。C语言中提供的位运算符如表1-11所示。
表1-11 C语言中的位运算符及表达式
说明:
①除“~”外,其他按位运算符均为双目运算符,即要求运算符两边各有一个运算对象。
②运算对象只能是整型或字符型数据,不能是实型。
③不要将它们与前面讲的逻辑运算符搞混了,注意这些运算符都是按位操作的。
(1)“按位与”运算符(&)
运算规则:参加运算的两个对象,按二进制位进行“与”运算。如果对应的二进制位都为1,则该位的结果为1,否则为0。即:
0&0=0 0&1=0 1&0=0 1&1=1
例如,3&6的运算结果是2。
“按位与”在实际的应用中,通常用它来保持一个数的某些位,而将其他位置为0。例如,对于某个变量a,如果只想得到其低4位,则可将其与15(十六进制的0xf)进行按位与,即a&15。则运算过程如下:
注意:进行上述运算后,变量a的值并未改变。可以把表达式的值赋给某一个变量,从而将该结果保留下来,如b=a&15。
从上面的分析我们也可以看出:如果要取一个数的某些位,则可构造一个数,令其对应位置1,其余位为0,再和原数进行按位与即可。
(2)“按位或”运算符(|)
运算规则:两个相应的二进制位中只要有一个为1,则该位的运算结果为1;只有都为0时,结果才是0。即0|0=0、0|1=1、1|0=1、1|1=1。例如,12|5的运算过程如下:
即表达式的值为13。
“按位或”在实际的应用中通常用于将一个数的某些指定位置1。例如,对于某个变量a(仍设a为字符型),如果想使得其低4位全为1,而高4位不变,则可将其与15(十六进制的0xf)进行“按位或”,即a|15。则运算过程如下:
(3)“按位异或”运算符( )
运算规则:参加运算的两个数的二进制位的对应位相同时为0,不同时为1。即0 0=0, 0 1=1,1 0=1,1 1=0。
例如:
“按位异或”的主要应用是使一个数中的特定位翻转(即1变0,0变1)。例如对于01110101,若想使其低4位翻转,则可以将之与00001111(十进制的15)进行 运算,即:
(4)“按位取反”运算符(~)
按位取反运算符“~”是一个单目运算符,用以对一个二进制数按位取反,运算规则是0!=1,1!=0。例如,~13是对13按位取反,即:
(5)左移运算符(<<)
用于将一个数的各个二进制位全部左移若干位,移出的高位(包括符号位)舍弃(也即所谓的“溢出”),而低位补0。例如,对于表达式3<<2,其运算过程为:
观察上面的结果,可以看到结果刚好比左移前的数大了4倍。事实上这并非巧合。将一个数左移1位相当于该数乘以2,左移2位相当于乘以22=4,进而可以推得:将一个数左移n位相当于该数乘以2n。不过前提是在左移的过程中没有发生1被移出的情况。请参见表1-12对此进行更进一步的理解。
表1-12 左移运算示例
(6)右移运算符(>>)
用于将一个数的各个二进制位全部右移若干位。对于低位移出时直接舍弃,而对于高位部分,到底是补0还是补1,则有两种情况:
①如果该数是正数或无符号数,则高位补入0;
②如果该数是负数(此时最高位即符号位为1),则取决于所使用的系统。有些系统补入0,即简单右移,这种被称为“逻辑右移”;而另一些系统则在高位补入1,这种右移被称为“算术右移”。
TC和VC这两种常用的C系统都采用的是“算术右移”。在没有1被移出(溢出)的情况下,与左移相反,右移1位相当于原数除以2,右移n位则相当于原数除以2n。
表1-13中列出了某字符型变量在VC系统中进行的右移运算,请读者进行对照分析。
表1-13 右移运算示例
以上所有的位运算符,在进行位运算过程中,如果不是将运算结果再赋值给原变量,则不会改变原变量的值。如表达式a>>2,我们说该表达式的值相当于是变量a的值除以4。而对于变量a,其值并未发生任何变化,除非写成a=(a>>2)。
(7)复合的赋值位运算符
除按位取反“~”外,其他的位运算符均可与赋值运算符构成复合的赋值位运算符,分别是“&=”“|+”“ =”“<<=”和“>>=”,其功能读者可根据前面的知识总结分析。
1.6.8 其他运算符
(1)逗号运算符及逗号表达式
逗号运算符“,”是所有运算符中优先级别最低的。逗号表达式是由逗号运算符连接起来的式子,又称为“顺序求值表达式”。其一般形式为:
表达式1,表达式2,…,表达式 n
对逗号表达式的求解过程是:先求解表达式1的值,再求解表达式2的值……最后整个逗号表达式的值是表达式n的值。
例如,设a=3,则逗号表达式“a*=a+4,a*4”的求解过程为:
①先求解表达式1“a*=a+4”的值,计算得到其值为21(注意,此时a的值已经变成21);
②再求解表达式2“a*4”的值。因为在上一步中,a的值已经变成21,因此计算得到84;
③整个表达式的值取表达式2的值,即84。
(2)条件运算符与条件表达式
条件运算符是C语言中唯一的三目运算符,由“?”和“:”两个运算符组合而成。条件表达式由条件运算符连接表达式构成,其一般形式为:
表达式1? 表达式2:表达式3
条件表达式的运算顺序是:先计算表达式1的值,若为非0值(真),则计算表达式2的值,并将其作为整个表达式的值;若表达式1的值为0值(假),则计算表达式3,并将其作为整个表达式的值。例如:
max=( (a > b) ? a:b );
首先,上面的表达式是一个赋值表达式,先计算表达式a>b的值。如果为真则取变量a的值赋值给变量max;否则,取变量b的值赋值给max。等价于将变量a和b中的较大者的值选出赋给max。
条件运算符的优先级别高于赋值运算符,而其结合方向为“自右至左”。因此表达式x=a>b?a:b等价于x=(a>b?a:b);而表达式a>b?c:d>e?f:g等价于a>b?c:(d>e?f:g)。
(3)sizeof运算符
sizeof是一个比较特殊的单目运算符,也是一个非常有用的运算符,用来求得一种数据在内存中占多少个存储单元(字节)。其一般使用形式为:
sizeof(变量或数据类型)
【例1-12】阅读程序,理解运算符sizeof的用法。
/ *源程序1-12.C* / #include <stdio.h> void main() { int a=1000; printf("%d, %d, %d",sizeof(a),sizeof(char),sizeof("abc")); }
程序运行结果为:
2,1,4
说明:
①在TC和VC中,int型变量所占的内存空间大小不同。VC中输出是“4,1,4”。
②对于字符串,其在内存中所占的存储单元,除了串中的有效字符外,还应包括字符串结束标志'\0'所占的一个字节的空间。