2.4 运算符及表达式
在C语言中,运算符和表达式的数量之多,在高级语言中是少见的。正是丰富的运算符和表达式使C语言的功能十分完善,这也是C语言的主要特点之一。
C语言运算符的类别见表2.5。
表2.5 C语言运算符的类别
将运算对象(常量、变量、函数等)用运算符连接起来的符合C语言语法规则的式子称为C语言表达式。为了正确求出表达式的值,C语言规定了进行表达式求值时,各运算符的优先级和结合性(参见附录A)。
● 优先级:在一个表达式中如果有多个运算符时,计算是有先后次序的,这种计算的先后次序称为相应运算符的优先级。
● 结合性:当一个运算对象两侧运算符的优先级别相同时,进行运算(处理)的结合方向称为运算符的结合性。按从右向左的顺序运算,称为右结合性;按从左向右的顺序运算,称为左结合性。
在表达式中,各运算对象参与运算的先后顺序不仅要遵守运算符优先级别的规定,还要受运算符结合性的制约,以便确定是自左向右进行运算,还是自右向左进行运算。 运算符的结合性也是C语言的特点之一。
本节仅介绍算术运算符、赋值运算符、自增、自减运算符、逗号运算符和求字节数运算符,以及相应的表达式。
2.4.1 算术运算
1.基本算术运算符
基本算术运算符包括1个单目运算符和5个双目运算符,其名称、示例及运算功能见表2.6。
表2.6 算术运算符
说明:
① 关于除法运算符/。不能用÷表示除。另外应注意,两个整数相除结果为整数,例如, 7/2的结果值为3,舍去小数部分。如果参加运算的两个数中有一个数为实数,则结果是double型的,因为所有实数都按double型进行运算。
② 关于取余运算符%。运算符两侧的数据必须为整型数据。结果按下式计算:余数=被除数 - 除数*商。例如,7%3的结果为1。
2.算术表达式
用算术运算符、圆括号将运算对象连接起来的符合C语法规则的表达式称为算术表达式。例如,下面是一个合法的C语言算术表达式:
(a-b)/c*2+'a'+-15%4
C 语言算术表达式的书写形式与数学表达式的书写形式是有区别的,在使用时要注意以下4点。
① C语言表达式中的乘号不能省略。在数学中,5a、5×a、5·a都是合法的,但在C语言中只能写成5*a,初学者应谨慎。
② C语言表达式中只能使用合法用户标识符。例如,数学表达式πr2相应的C语言表达式应写成:3.1415926*r*r。
③ C语言表达式中的所有内容必须书写在同一行上,不允许有分子分母、上下标等形式,必要时要利用圆括号保证运算的顺序。例如,数学表达式相应的C语言表达式应写成:
(a+b*b)/(c+d)。
④ C语言表达式中不允许使用方括号和花括号,只能使用圆括号帮助限定运算顺序。可以使用多层圆括号,但左右括号必须配对,运算时从内层圆括号开始,由内向外依次计算表达式的值。
3.算术运算符的优先级与结合性
表2.7所示为括号运算符和算术运算符的优先级和结合性。
表2.7 括号运算符和算术运算符的优先级和结合性
算术表达式的运算过程遵循数学的运算规则,先计算括号内,再计算括号外。同一层括号中,负号优先运算,接下来先乘除,后加减。同一优先级时则按“左结合性”处理。
4.基本类型数据间的混合运算
如果一个表达式中运算对象的数据类型不同,则应当首先将其转换为同一种类型,然后再进行运算。转换类型的方法有两种:一种是自动类型转换,另一种是强制类型转换。
(1)自动类型转换
前已述及,字符型数据可以与整型数据通用,因此,整型、实型(包括单、双精度数据)、字符型数据间可以混合运算。例如,以下表达式:
0.5+2*'a'+15-1.23456789
是合法的。在运算过程中,C 语言遇到两种不同数据类型的数值进行运算时,会将某个数值做适当的类型转换,然后再进行运算。类型转换的规则如图2.4所示。图中,横向箭头为必定的转换,即单精度数或字符型数或短整型数,都必须无条件地转换成横向箭头左侧的数据类型(char型转换为int型,short型转为int型,float型转换成double型),然后再进行运算。而图中纵向箭头(箭头方向表示数据类型级别的高低,由低向高转换)是当一个运算符两侧的数据类型不相同时,先将级别低的数据类型转换成它们之间级别高的数据类型,然后进行运算。例如,int型与long型数据进行运算,先将int型的数据转换成long型,然后两个同类型(long型)数据进行运算,结果为long型。假设已指定m为int型变量,x为float型变量,y为double型变量,有下面的表达式:
图2.4 自动类型转换规则
5+'b'-x/2+y*m
其运算过程为:
① 进行5+'b'的运算。先将'b'转换成整数98,运算结果为整数103。
② 进行x/2的运算。先将x与2都转换成double型,运算结果为double型。
③ 将①与②的结果相减。先将整数 103 转换成 double 型(小数点后加若干个 0,即103.000…00),然后再相减,结果为double型。
④ 进行y*m的运算。先将m转换成double型,运算结果为double型。
⑤ 将④与③的结果相加,结果为double型。
(2)强制类型转换
强制类型转换是通过类型转换运算符来实现的。其一般形式为:
(类型标识符) (表达式)
其功能是把表达式的运算结果强制转换成类型标识符所表示的类型。
例如:
(double)a /* 将a转换成double型 */ (int)(x+y) /* 将x+y的和转换成int型 */ (float)(10*5) /* 将10*5的积转换成float型 */
在使用强制转换时应注意以下问题。
① 类型标识符和表达式都必须加括号(单个变量可以不加括号),如果把(int)(x+y)写成(int)x+y,则成了把x转换成int型之后再与y相加。
② 无论强制转换或是自动转换,都只是为了本次运算的需要而对数据长度进行的临时性转换,并不改变数据定义时的类型。
③ 强制类型转换的优先级高于自动类型转换。
【例2.6】 强制类型转换示例。
程序代码如下:
#include "stdio.h" main( ) { int i=1,j=5,k1,k2,k3; float x=5.7,y=2.8,z1,z2,z3; k1=(int)x; /* 将x强制转换成整数5(小数部分被截去)赋给k1 */ k2=(int)(x)/y; /* 将x强制转换成整数5,再除以y,赋给k2 */ k3=(int)(x/y); /* 将x除以y的值强制转换成整数,再赋给k3 */ z1=(float)i; /* 将i强制转换成实数1.0赋给z1 */ z2=(float)i/j; /* 将i强制转换成实数1.0,再除以j,赋给z2 */ z3=(float)(i/j); /* 计算i/j的值为0,再强制转换成实数0.0赋给z3 */ printf("k1=%d\n",k1); printf("k2=%d\n",k2); printf("k3=%d\n",k3); printf("z1=%f\n",z1); printf("z2=%f\n",z2); printf("z3=%f\n",z3); }
程序运行结果如下:
k1=5 k2=1 k3=2 z1=1.000000 z2=0.200000 z3=0.000000
2.4.2 赋值运算
1.赋值运算符和赋值表达式
在C语言中,称“=”为赋值运算符,它不同于算术中的等号。相等在C语言中是用“==”表示的。用赋值运算符将一个变量和一个表达式连接起来的式子称为赋值表达式。其一般形式为:
变量=表达式
赋值表达式的功能是,将赋值运算符右边表达式的值存放到以左边变量名为标识的存储单元中。
赋值表达式的值就是被赋值后赋值号左边变量的值。例如,赋值表达式“b=5”运算后,有两层意思:一是使变量b的值为5,即将5放到变量b对应的存储单元中,不论变量b的原值是多少,执行上述赋值操作后(或称赋值运算),b的值更新为5;二是求得赋值表达式“b=5”的值是5。
说明:
① 赋值运算符“=”的左边必须是变量,右边的表达式可以是单一的常量、变量、函数调用或表达式。例如,下面都是合法的赋值表达式:
x=10 y=x+10 y=sqrt(2)
② 赋值运算符“=”不同于数学中使用的等号,它没有相等的含义。例如,x=x+1,其含义是取出变量x中的值加1后,再存入变量x中。
③ 赋值运算符的结合性为“右结合性”。例如:
x=y=z=8 等价于 x=(y=(z=8))
运算时,先求赋值表达式“z=8”的值(得8),其值再赋给变量y(得8),再把表达式“y=8”的值赋给x(得8)。
下面是一些赋值表达式的例子:
x=8+(y=5) /* 赋值表达式的值为13,x的值为13,y的值为5 */ x=(y=2)+(z=6) /* 赋值表达式的值为8,x的值为8,y的值为2,z的值为6 */ x=(y=6)/(z=2) /* 赋值表达式的值为3,x的值为3,y的值为6,z的值为2 */
将赋值表达式作为表达式的一种,使赋值操作不仅可以出现在赋值语句中,而且可以以表达式形式出现在其他语句(如循环语句)中,这是C语言灵活性的一种表现。
2.赋值运算中的类型转换
当赋值运算符“=”左边变量的类型和右边表达式值的类型不一致时,首先要把赋值运算符“=”右边表达式值的类型强制转换为左边变量的类型,然后再进行赋值。转换规则见表2.8。
表2.8 赋值运算中数据类型的转换规则
【例2.7】 将一个带符号的短整型数据赋给相同长度的无符号短整型变量示例。程序代码如下:
#include "stdio.h" main( ) { unsigned short i; short j=-6; i=j; printf("%u",i); }
程序运行结果为:
65530
赋值情况如图2.5所示。
图2.5 带符号的整型数据赋给的无符号整型变量
思考:如果j为6,则运行结果是什么?
3.复合赋值运算符
先看一段代码:
int a=10; a=a+10;
上面的代码执行后,变量a的值是20。可以这样理解,假设用a表示银行存款,原先有10万元,现在再存入10万元,那么新的存款就等于旧存款加上10万元,即20万元,用C编程语言表达就是“a=a+10;”。
在C语言中这样的自加操作可以用另一种方式表达,并且采用这种表达方式,计算机的运算速度比较快。例如,“a=a+10;”的另一种运算速度较快的表达方式为“a+=10;”。
在C语言中,“+=”被定义为一种新的自运算符(“+”与“=”要连着,中间不能有空格),它实现的操作就是将其左边的量在自身的基础上加上右边表达式的值。同样的减、乘、除、求余等双目运算符都可以与赋值运算符结合形成自运算符-=、*=、/=、%=等。
C 语言将这种在赋值运算符之前加上双目运算符构成的新的自运算符称为复合赋值运算符,共有10种,如表2.9所示。
表2.9 复合赋值运算符
赋值运算符“=”和复合赋值运算符的优先级很低,仅高于逗号运算符,它们属于同一级别,其结合性为“右结合性”。有关位运算符和位运算赋值运算符将在第11章介绍。
例如,“x*=y+8”中的“+”运算符优先级高于“*=”运算符,应先进行“+”运算,故等价于“x=x*(y+8)”,而不等价于“x=x*y+8”。
又如“k+=j+=i+8”表达式等价于“k=k+(j=j+(i+8))”,当 i=2,j=12,k=10 时,其计算过程如下:
因为表达式语句中“+”优先级高于“+=”,故先运算i+8值为10;同一级的两个“+=”因结合性为由右向左,先运算右面的“+=”(即j+=10 的值为22),再运算左面的“+=”(即k+=22的值为32)。
C 语言采用这种复合赋值运算符,一是为了简化程序,使程序精练,二是为了提高编译效率,因为从编译的角度来看,它可以生成更短小的汇编代码。
2.4.3 自增、自减运算
当复合赋值运算是自加1或自减1时,C语言提供了更为优化的运算符—自增(++)、自减(--)运算符。
设整型变量a,初值为10。要实现对其加1,已知可以有以下两种写法:
● 方法1:a=a+1;
● 方法2:a+=1;
现在还有方法3,并且是最好的方法,即“++a;”或者“a++;”。
也就是说,只有在自加1的情况下,代码++a或a++可以生成最优化的汇编代码。
同样,自减1操作也有对应的运算符:--a或a--。
设a原值为10,则执行--a或者a--后,a的值都为9。
所以,自增“++”和自减“--”运算符的作用是使运算对象的值增1或减1,它们是两个单目运算符,可置于运算对象的左侧或右侧,例如,++i、i++、--i、i--等都属于合法的表达式。参加自增、自减运算的运算对象只能是变量而不能是表达式或常量。表2.10列出了自增、自减运算符的种类和功能。
表2.10 自增、自减运算符
自增、自减运算符出现在变量之前(如++i和--i),称为前置运算(prefix),出现在变量之后(如++i和--i),称为后置运算(postfix)。对一个变量i实行前置运算或后置运算,其运算结果是一样的,即都使变量的值增1或减1,但作为表达式来说却有着不同的值。例如,假设i的初值等于3,则:
++i 1 /* 先使i的值加1,然后再使用i,表达式的值是i加1之后的值为4 */ i++ 1 /* 先使用i的值,然后再使i的值加1,表达式的值是i加1之前的值为3 */
所以赋值表达式j=++i相当于i=i+1和j=i,赋值表达式的值是4,变量i的值也是4。而
赋值表达式j=i++则相当于j=i和i=i+1,赋值表达式的值是3,变量i的值是4。
例如,已知定义语句:
int a=3;b=5,c;
则有:
(1)c=(++a)*b;
等价于:a=a+1;
c=a*b;
结果:c的值为20。
(2)c=(a++)*b;
等价于:c=a*b;
a=a+1;
结果:c的值为15。
【例2.8】 自增、自减运算符使用示例。
程序代码如下:
#include "stdio.h" main( ) { int i=5,j,k; j=++i; /* 先使i的值变为6,j的值为6 */ i=5; k=i++; /* k的值为5,然后i变为6 */ printf("i=%d,j=%d,k=%d\n",i,j,k); printf("j=%d,k=%d\n",++j,k++); /* 先输出k的值5和j加1以后的值7,再使k加1 */ }
程序运行结果如下:
i=6, j=6, k=5 j=7, k=5
说明:
① 自增、自减运算符(++、--),只能用于变量,不能用于常量和表达式,例如,2++或(x+y)++都是不合法的。
② ++和--的结合方向是“右结合性”,其优先级高于基本算术运算符,与负号运算符为同一优先级。例如-i++,因为“-”运算符和“++”运算符优先级相同,而结合方向为“自右至左”,即它相当于-(i++)。
2.4.4 逗号运算
逗号运算符即逗号“,”。在 C 语言中可以用逗号将若干表达式连接起来,构成一个逗号表达式。其一般形式为:
表达式1,表达式2,表达式3,……,表达式n
逗号表达式的运算过程是,依次求解表达式1的值,表达式2的值,……,最后求解表达式n的值。整个逗号表达式的值和类型是表达式n的值和类型。
逗号表达式的计算顺序是自左向右,因此,逗号运算符又称为“顺序求值运算符”。例如:
a=36/9,4*a
这是一个逗号表达式,由表达式a=36/9和4*a构成。求值过程先执行a=36/9=4,再执行4*a=16,而整个逗号表达式的值为16。
在有逗号运算符参与的表达式中,逗号运算符是所有运算符中优先级级别最低的。因此,下面两个表达式的作用是不同的:
a=(2+b,a*2,a*=5) /*是赋值表达式,将一个逗号表达式的值赋给a,a的值等于表达式a*=5的值。*/ a=2+b,a*2,a*=5 /*是逗号表达式,它包括两个赋值表达式和一个算术表达式。*/
其实,逗号表达式无非是把若干个表达式串联起来。在许多情况下,使用逗号表达式的目的只是想分别得到各个表达式的值,而并非一定需要得到和使用整个逗号表达式的值,逗号表达式通常用于循环语句(for语句)中,详见第5章。
请注意,并不是任何地方出现的逗号都可以作为逗号运算符。例如,在变量说明中的逗号只起间隔符的作用,不构成逗号表达式。
2.4.5 sizeof运算符
sizeof运算符是一个单目运算符,它返回变量或数据类型的字节长度。其一般形式为:
sizeof(类型标识符) 或 sizeof(变量名)
例如:
● sizeof(double)表达式的值为8;
● sizeof(int)表达式的值为4。
设有下列程序段:
float f; int i; i=sizeof(f);
则变量i的值为4。