第5章 表达式、运算符和语句
通过第4章的学习,读者了解了C语言的基本数据及其存储方式。C语言中,通过运算符将常量、变量组成表达式,由表达式构成C语句。C程序是由这些C语句组成的。本章将介绍表达式、运算符和语句的相关内容。
5.1 表达式
表达式是由操作数和运算符共同组成的、能返回一个具体数值的式子。表达式中作为运算的数据称为操作数,操作数可以是常量、变量、函数或表达式;运算符是介于操作数间的运算符号,如+、-都是典型的运算符。下节将详细介绍C语言中的运算符。
5.1.1 简单表达式
C语言中,最简单的表达式就是单独的一个常量、变量。例如:
a (程序中定义的变量) 10 (字面整型常量) MAX (程序中定义的符号常量) 'h' (字面字符常量)
在简单表达式中,字面常量返回的值就是其本身,变量返回的值为该变量的值,符号常量返回为其定义的值。
在C语言中,更多的表达式是由一个或多个运算符连接的。例如:
3.14*2*2 PI*r*2
使用下节介绍的各种运算符,可构成不同类型的表达式,如算术表达、赋值表达式、关系表达式、逻辑表达式、条件表达式等。
5.1.2 逗号表达式
在C语言中,逗号既可作为分隔符,又可用在表达式中。逗号作为分隔符使用时,用于间隔说明语句中的变量或函数中的参数。例如,变量的声明语句或函数中作为参数的分隔符:
int a,b,c; printf("!f=%d\n",!f);
将逗号用在表达式中,可将若干个独立的表达式联结在一起,组成逗号表达式。逗号表达式的一般形式是:
表达式1,表达式2,……,表达式n
逗号表达式的运算过程是:先计算表达式1的值,然后计算表达式2的值,一直计算到最后的表达式n,并将表达式n的值作为逗号表达式的值返回。
逗号表达式典型的应用是在for循环语句中,有关for语句的内容,参见本书第8章的介绍。
例如,有以下嵌套逗号表达式:
(i=5*2,i*3),i*5
以上表达式首先计算圆括号内的逗号表达式的值。圆括号内又是一个逗号表达式,按从左到右的顺序计算。计算第一个表达式i=5*2的结果,使变量i=10。接着计算第2个表达式的值i*3=30,因此,括号内的逗号表达式返回值为30,接着计算表达式i*5的值,因为i=10,所以i*5=50,最后返回整个表达式的值为50。
为了检验计算的结果,可将以上表达式输入到C程序中,查看输出结果。
【程序5-1】输出逗号表达式的结果
1: #include <stdio.h> 2: 3: int main() 4: { 5: int i; 6: 7: printf("%d\n",((i=5*2,i*3),i*5)); 8: getch(); 9: return 0; 10: }
编译执行以上代码,屏幕上将输出50。
第7行程序如果写为如下形式(省略表达式最外层的圆括号):
printf("%d\n",(i=5*2,i*3),i*5);
这时,printf函数将(i=5*2,i*3)作为一个表达式,使用控制符%d输出其计算结果30,而后一个表达式i*5因为对应的输出控制符而被舍弃。这时,表达式i*5前面的逗号被作为printf函数的分隔符。
逗号运算符可以将多个表达式串联起来,从而构成语法上的“一个”表达式。这在语法上要求出现“一个”表达式时很有用(如 for 语句中的初始化、判断、改变循环变量这三部分都只能出现一个表达式,这时,就可使用逗号表达式来进行多个变量的赋值、运算操作)。但需要注意,一般不是特别需要,不建议读者滥用逗号表达式,因为对其最终结果初学者不好确认,使用程序容易出现不好查找的错误。
5.2 运算符
运算符是表示一个特定的数学或逻辑运算的符号。C语言提供了非常多的运算符,通过数量众多的运算符构造成丰富的表达式,使C语言功能十分完善,这是C语言的主要特点之一。
5.2.1 运算符概述
数学运算中,运算符是有优先级的,如在同一算式中先算乘除,再算加减。与此类似, C语言中的运算符也有优先级。C语言中除了算术运算符之外,还有很多其他类型的运算符,一些不同性质的运算采用了相同的运算符(如减法运算和取负都使用运算符“-”),这为掌握运算符的运算规律就带来了一定难度。
C语言的运算符不仅具有不同的优先级,而且还有一个特点,就是它的结合性。在表达式中,各运算量参与运算的先后顺序不仅要遵守运算符优先级别的规定,还要受运算符结合性的制约,以便确定是自左向右进行运算,还是自右向左进行运算。这种结合性是其他高级语言的运算符所没有的,因此也增加了C语言的复杂性。
C语言运算符按其功能分为:算术运算符、赋值运算符、逗号运算符、逻辑运算符、关系运算符、位逻辑运算符、条件运算符、括号运算符、地址运算符、成员访问运算符、sizeof()运算符等;按其要求参与运算的操作数的数目可分为:单目运算符、双目运算符和三目运算符(也称多目运算符)。
5.2.2 算术运算符
C语言的算术运算符十分丰富,除一般高级程序设计语言中的算术运算符外,还增加了自增自减运算符。
算术运算符分为单目运算符和双目运算符两类,如表5-1所示。单目运算符需要一个操作数,而双目运算符需要两个操作数。
表5-1 算术运算符
1.双目运算符
C语言的5个算术双目运算符,加(+)、减(-)运算符与数学中所学形式相同,很好理解。乘、除操作与数学中的含义也相同,只是因为ASCII符号中,没有与数学中相对应的符号,所以使用星号(*)表示乘号,使用斜线(/)表示除号,这种表示方法在所有程序设计语言中都是相同的。
C语言中增加了一个求模运算符(%)。所谓求模运算,是指将第1个操作数除以第2个操作数得到的余数。例如:
13%2=1
因为13除以2的商为6,余数为1。
在使用这些双目算术运算符时,需要注意以下几点。
求余运算符(%)的两个操作数必须是整型数据,不能是实型数据。
在除号(/)两侧的操作数都为整数时,其结果也是整型,若不能整除,余数将被舍弃。
若运算符两侧操作数的类型不同,系统自动进行类型转换,使操作数具有相同的类型,然后再运算。类型转换的规则参见上一章中的相关内容。
十进制整数转换为二进制数的方法是“除2取余法”,即将十进制数反复除以2,取其余数作为相应二进制数最低位,再除以2得到余数作为二进制数的第2位,循环相除即可将其转换为一个二进制数。下面的程序使用求模和整除的方法将十进制数转换为二进制数。
【程序5-2】十进制数转换为二进制数示例(不使用循环)
1: #include <stdio.h> 2: 3: int main() 4: { 5: int t,t1, b0,b1,b2,b3,b4,b5,b6,b7; 6: 7: printf("请输入0~255之间的整数:"); 8: scanf("%d",&t); 9: t1=t; 10: b0=t%2; /*第1位二进制位*/ 11: t=t/2; 12: b1=t%2; /*第2位二进制位*/ 13: t=t/2; 14: b2=t%2; /*第3位二进制位*/ 15: t=t/2; 16: b3=t%2; /*第4位二进制位*/ 17: t=t/2; 18: b4=t%2; /*第5位二进制位*/ 19: t=t/2; 20: b5=t%2; /*第6位二进制位*/ 21: t=t/2; 22: b6=t%2; /*第7位二进制位*/ 23: t=t/2; 24: b7=t%2; /*第8位二进制位*/ 25: printf("十进制数:%d ",t1); 26: printf("转换为二进制数为:%d%d%d%d%d%d%d%d\n",b7,b6,b5,b4,b3,b2,b1,b0); 27: getch(); 28: return 0; 29: }
编译执行以上代码,输入一个十进制数后得到转换的二进制数,如图5-1所示。
图5-1 【程序5-2】执行结果
以上程序中,使用b0~b7分别保存二进制位中从低向高位的1~8位。下面先简单介绍代码的含义。
第8行程序要求用户输入一个0~255的十进制整数。
第9行程序将用户输入的值保存到一个临时变量t1中,以便后面程序输出。
第10行程序使用求模运算得到余数,为二进制位的第1位。
第11行程序使整型变量t除以整数2,得到一个整数,若有余数,则被舍弃。
第12行至第24程序重复类似的操作,即可得到8位二进制的每一位数。
第25行程序输出第9行程序保存的变量t1的值。
第26行程序连续输出8位数字,代表二进制的8位数。
到目前为止,还未向读者介绍循环和数组的使用,本程序的代码看起来比较重复。使用循环和数组,可将【程序5-2】改写为以下形式:
【程序5-3】十进制数转换为二进制数示例(使用循环)
1: #include <stdio.h> 2: 3: int main() 4: { 5: int t,t1,i=0; 6: int b[8]; 7: 8: printf("请输入0~255之间的整数:"); 9: scanf("%d",&t); 10: t1=t; 11: while(b[i++]=t%2,t=t/2) ; 12: 13: printf("十进制数:%d ",t1); 14: printf("转换为二进制数为:"); 15: i=7; 16: while(i>=0) 17: printf("%d",b[i--]); 18: getch(); 19: return 0; 20: }
以上程序使用数组和循环,使程序的代码更精简。并且只需要对程序进行小的修改,即可对更大的十进制数进行转换。在读者学完本书的循环和数组相关章节后,即可读懂以上程序。
2.单目运算符
单目运算符只需要一个操作数。如取负运算符(-)将操作数设置为负数,如果操作数为负数,则得到的结果将为正数。
C 语言中更常用的两个单目运算符是其他高级语言中通常没有的,就是自增运算符(++)和自减运算符(--)。
自增运算符使变量的值增加1,自减运算符使变量的值减少1。例如:
i++; j--;
与下面的语句功能相同,使用变量i增加1,使用变量j的值减少1:
i=i+1; j=j-1;
由上可看出,使用自增自减运算符可使用代码更简单。使用自增自减运算符可节省代码行,例如:
j=i++;
相当于以下两条语句:
j=i; i=i+1;
注意
自增运算符和自减运算符的运算对象只能是变量,不能是常量或表达式。例如,5++或++(i+1)都是非法的表达式。
自增、自减运算符可使用两种方式来使用:一种是后缀模式,运算符出现在变量后面;另一种是前缀模式,运算符出现在变量的前面。这两种模式的区别在于,值的增加(或减少)的时间不同。
采用前缀模式时,先选择执行变量的自增(或自减)操作,再用计算的结果参与表达式的运算。
采用后缀模式时,先使用变量参与表达式的运算,再对变量执行自增(或自减)的操作。例如,以下语句:
i=5; j=i++;
执行以上两条语句后,变量j的值为5,变量i的值为6。程序首先将变量i的值(为5)赋值给变量j,再执行变量i的自增运算,得到6。而下面的语句:
i=5; j= ++ i;
执行以上两条语句后,变量j和i的值都是6。程序首先将变量i进行自增运算,使变量i的值为6,再将i的值赋值给变量j。
以下代码演示自增运算符的后缀模式和前缀模式的计算结果。
【程序5-4】自增运算符的后缀模式和前缀模式
1: #include <stdio.h> 2: 3: int main() 4: { 5: int i,j; 6: 7: i=j=0; 8: printf("变量i\t变量j\n"); 9: printf("%d\t%d\n",i++,++j); 10: printf("%d\t%d\n",i++,++j); 11: printf("%d\t%d\n",i++,++j); 12: printf("%d\t%d\n",i++,++j); 13: getch(); 14: return 0; 15: }
编译执行以上代码,将得到如图5-2所示的结果。
图5-2 【程序5-4】执行结果
在程序第7行中,将变量i和变量j都赋初值为0。
第8行代码中,变量i使用的是后缀模式,先将变量i的值输出(为0)再自增1,输出完成后,变量i的值为1。变量j使用的是前缀模式,先将变量j的值自增1得到1,再输出其结果(为1)。
第10行第12行代码也分别执行同样的操作,对变量i先输出,再进行自增运算;对变量j先进行自增运算,再输出变量j的值。
3.算术运算符的优先级
当一个表达式中,有多个运算符时,C语言先计算哪个运算符?例如:
3+5*4/2
在算术运算中,按“先乘除后加减”的运算规则确定计算顺序。在C语言中,也与此类似,因此以上表达式的计算顺序是先计算5*4,再将结果20除以2,最后将结果10和3相加,得到13。
在C语言中,确定运算符的计算顺序的规则称为计算符的优先级。各算术运算符的优先级如表5-2所示。
表5-2 算术运算符优先级
在表5-2 中,1 表示最高优先级。当表达式中有多个相同优先级的运算符时,将按从左到右的顺序运算。因此,在上例中,5*4/2 中的乘号和除号具有相同的优先级,将按从左到右的顺序计算。
根据规则,上例表达式中各运算符的运算顺序如下:
若需要改变表达式的运算顺序,例如先算3+5,再将和乘以4,最后除以2。这时,可使用圆括号。在C语言中,位于圆括号中的表达式将先进行运算。使用一个圆括号可修改上例的运算顺序,具体如下所示:
在算术运算时,如果有多个需要改变运算顺序的地方,可使用圆括号、方括号、大括号等,例如:
[(a+b)*c+d]/a
在C语言中,方括号和大括号有其他用途,若需要将以上算术式子改写为C语言的表达式,可使用嵌套的圆括号,具体如下:
((a+b)*c+d)/a
C语言总是先计算最内层圆括号中的部分。
5.2.3 赋值运算符
C语言将赋值作为一种运算,并定义了赋值运算符(=)。它的作用是把一个表达式的值赋予一个变量,在本书前面的例子中,已多处用到这种运算符了。
注意
赋值运算符的左边必须是一个变量。
C语言的赋值运算符可分为两类:
简单赋值运算符。
复合赋值运算符。
下面分别介绍这两类赋值运算符。
1.简单赋值运算符
简单赋值运算符(=)的语法格式如下:
变量 = 表达式
赋值运算符的作用是对右侧表达式进行计算,将计算结果保存到左侧指定的变量中。左侧必须是变量,不能是表达式或常量。
赋值运算符“=”与数学表达式中的符号“=”的意义是不同的,赋值运算表示的是内存空间的复制操作。例如,在排序程序中,经常要用到的交换两个变量的值,可以通过一个中间变量和下面一组赋值操作来完成:
temp=min; min=max max=temp;
赋值运算按照从右向左的顺序计算,所以下面语句的运算顺序如下:
a=b=c=10; a=(b=(c=10));
以上语句首先将常量10赋值给c,再将变量c的值赋值给b,再将变量b的值赋值给变量a。最后,变量a、b、c都具有相同的初始值。
另外,读者要注意的是,在C语言中,赋值符(=)为一个运算符,因此,可出现在表达式中。例如:
cout=(start=98)+(end=208);
首先,将常量98赋值给变量start,将常量208赋值给变量end,再将变量start和end相加的结果赋值给变量count,最后,count的值为306(=98+208)。
2.复合赋值运算符
C语言提供了很多精简代码的运算符,如前面介绍的自增、自减运算符。在赋值运算符中,又将其他运算符和赋值运算符组合,提供了复合赋值运算符。
复合赋值运算符是由各种常用运算符与赋值运算符组合而成的运算符,共有 10 种形式,如表5-3所示。
表5-3 复合赋值运算符
每个复合赋值运算符都由两个(或三个)字符组成,除赋值号(=)之外,前面的字符都是一个运算符。例如“+=”中的加号(+)、“-=”中的减号(-)等,前面的5个算术运算符已学过使用方法,后面几个运算符主要针对位操作,将在本章后面进行介绍。
复合赋值的运算规则是先用左侧的变量与右侧的操作数进行运算,再将运算结果存入左侧变量中。例如:
i+=8;
与下面的表达式相同:
i=i+8;
在使用复合赋值运算符时,首先,将右侧表达式计算得到一个确定的结果,再用这个结果与左侧变量中的值进行相关运算。下面的程序演示这种过程。
【程序5-5】使用复合赋值运算符
1: #include <stdio.h> 2: 3: int main() 4: { 5: int i,j; 6: 7: i=3; 8: j=2; 9: i*=j+2; 10: printf("%d\t%d\n",i,j); 11: getch(); 12: return 0; 13: }
编译执行以上程序,变量i的值将为12,变量j的值为2。
第9行程序先将变量j与常量2相加,然后再与变量i相乘。相当于以下语句:
i=i*(j+2);
初学者在学习使用复合赋值运算符时,可能有些不习惯,但是这种表达方式比较简练,易于维护。C编译器对这类代码进行优化,可生成高质量的目标代码。
5.2.4 关系运算符
关系运算符用来判断两个操作数的大小关系。例如,变量a和变量b是否相等?变量a是否大于变量b?这些都可通过关系运算符来表示。
在数学中,有下面 6 种关系运算符:小于(<)、小于或等于(≤)、等于(=)、大于或等于(≥)、大于(>)、不等于(≠)。
在C语言中,这6种关系运算符有所改变,如表5-4所示。
表5-4 关系运算符
通过关系运算符计算得到的结果只有两种情况:true或false。其中true表示指定的关系成立,而false则表示指定的关系不成立。例如:
10>5 (因为10比5大,该关系成立,所以其结果为true) 10<5 (因为10比5大,该关系不成立,结果为false) 10==5 (因为10和5不相等,该关系不成立,结果为false) 10!=5 (因为10和5不相等,该关系成立,结果为true) 10<=10 (因为10虽然不小于10,但等于10,该关系成立,结果为true)
在C语言中,true与yes或1相同,false与no或0相同。在以后的程序中,可以看到,只要是非0值,C语言都将其解释为true,为0则解释为false。
在使用关系运算符进行比较时,还需要注意等于(==)和不等于(!=)这两个运算符一般只应用到整型数据中,而不将其使用到实型数据中。这是因为实型数据保存的值存在误差,有可能使两个应该相等的数不相等。例如:
3*(1.0/3)==1
以上关系运算式按道理应该是成立的,结果应该返回true。但实际得到的结果却不为true。将以上关系式编写为C程序,观察输出的结果。具体代码如下:
【程序5-6】关系运算符示例
1: #include <stdio.h> 2: #include <stdio.h> 3: 4: int main() 5: { 6: float f; 7: 8: f=1.0/3; 9: printf("%d\n",(3*f==1)); 10: getch(); 11: return 0; 12: }
编译执行以上代码,屏幕上输出的结果为0。变量f只保存6位有效位,其值为0.333333,执行 3*f 时,得到的结果为 0.999999(使用 printf 函数输入 3*f 的值时,可能会显示为1.000000,但保存在内存中的值和整数1是不相等的)。
5.2.5 逻辑运算符
使用关系运算符可对两个操作数进行比较,如果需要将多个关系表达式的结果合并在一起,则需要使用逻辑运算符。例如,要判断变量i的值是否在0至10之间,需要使用两个关系表达式:i>=0和i<=10,不能使用0<=i<=10这种方式来表示。这时,就需要使用逻辑运算符来将两个关系表达式连接起来:
i>=0 && i<=10
如果变量i的值在0~10之间,则以上表达式的结果为true,否则为false。
C语言提供了3种逻辑运算符,如表5-5所示。与关系运算一样,逻辑运算的结果为true或false。
表5-5 逻辑运算符
在逻辑运算符中,逻辑与(&&)和逻辑或(||)都是双目运算符,逻辑非(!)是单目运算符。
设有两个变量a和b,用来保存逻辑量true或false,变量a和b可以进行表5-5所列的三种逻辑运算。因逻辑量只有两个值(true 或 false),所以 3 种逻辑运算的结果也能确定。将这些内容用一张表格表示,就是逻辑运算的“真值表”,该表反映了逻辑运算的规则,如表5-6所示。
表5-6 真值表
由上表可看出:
对于逻辑非运算,结果总是与操作数相反,即操作数为true,结果为false,操作数为false,结果为true。
对于逻辑与运算,只有当两个操作数都为true时,结果才为true,只要有一个操作数为false,结果就为false。如果第1个操作数为false,则不会再去判断第2个操作数的值,直接返回结果false。
对于逻辑或运算,只有当两个操作数都为 false,结果才为 false,只有一个操作数为true,结果都为true。如果第1个操作数为true时,则不会再去判断第2个操作数的值,直接返回结果true。
三个逻辑运算符的优先级如下:
逻辑非(!) > 逻辑与(&&) > 逻辑或(||)
逻辑运算对象是值为 true 或 false 的逻辑量,它可以是任何类型的数据,如整型、浮点型、字符型等,C编译系统以非0为true,0为false。在使用printf函数以整数形式输出逻辑量时,若为true,将输出1,若为false,将输出0。
下面的程序演示逻辑运算符的使用。
【程序5-7】逻辑运算符示例
1: #include <stdio.h> 2: 3: int main() 4: { 5: float f=6.18; 6: char c='h'; 7: int i=3,j=0; 8: 9: printf("i&&j=%d\n",i&&j); 10: printf("i||j+2&&c=%d\n",i||j+2&&c); 11: printf("!f=%d\n",!f); 12: getch(); 13: return 0; 14: }
编译执行以上程序,得到如图5-3所示的结果。
图5-3 【程序5-7】执行结果
第4~7行程序声明实型、整型、字符型变量,并给这些变量赋初值。
第9行输出整型变量i和j进行逻辑与运算的结果,因为是j=0,所以逻辑与运算结果为0。
第10行先运算j+2,得到整数2,然后计算两个逻辑运算符,先计算逻辑与运算,j+2的值不为0,字符变量c保存着字母“h”,其ASCII码也不为0,因此,j+2&&c的结果为true。变量i的值不为0,所以i||true的结果为true。所以第10行程序输出为1。
第11行程序对float类型的变量f进行逻辑非的运算,变量f的值不为0,所以返回值为false,输出0。
5.2.6 位运算符
与其他高级语言相比较,位运算是C语言中一个比较有特色的地方,利用位运算可以实现许多汇编语言才能实现的功能。
所谓位运算是指进行二进制位的运算。C语言指供的位运算符如表5-7所示。
表5-7 位运算符
位运算符中除“~”是单目运算符,其余均为双目运算符。
位运算符所操作的操作数只能是整型或字符型数据以及它们的变体。位运算不能用于float、double、long或其他更复杂的数据类型。
有关位运算符的使用,参见本书第14章中的相关介绍。
5.2.7 条件运算符
通过条件运算符将3个表达式连接在一起,组成条件表达式。条件表达式的语法格式如下:
表达式1?表达式2:表达式3
从以上格式可以看出,这是一个三目运算(C 语言中唯一的三目运算符)。通过问号(?)和冒号(:)将3个表达式分开。
条件表达式的运算过程是:先计算表达式1的值,如果它的值为非0(true),将表达式2的值作为条件表达式的值,若表达式1的值为0(false),则将表达式3的值作为条件表达式的值。
使用条件表达式可代替简单的if语句,if语句将在本书第7章中进行介绍。下面的程序让用户输入3个整数,求出这3个数的最大数。
【程序5-8】条件运算符示例
1: #include <stdio.h> 2: 3: int main() 4: { 5: int a,b,c; 6: int max; 7: 8: printf("请输入第1个整数:"); 9: scanf("%d",&a); 10: printf("请输入第2个整数:"); 11: scanf("%d",&b); 12: printf("请输入第3个整数:"); 13: scanf("%d",&c); 14: max=(a>b?a:b); 15: max=(max>c?max:c); 16: printf("最大数是:%d\n",max); 17: getch(); 18: return 0; 19: }
编译执行以上程序,在命令窗口,输入3个整数,可得到其最大数,如图5-4所示。
图5-4 【程序5-8】执行结果
第8~13行程序接收用户输入的3个整数,分别保存到变量a、b、c中。
第14行程序使用条件表达式得到变量a和b中最大的数,保存到变量max中。
第15行程序将a和b中的最大数max与c进行比较,得到max和c中的最大数,保存到max中。
最后,在第16行程序中,使用printf函数输出变量max即可。
在学习了第7章中的if语句后,可将程序中的第14行和第15行代码改写为如下形式:
if (a>b) max=a; else max=b; if (max<c) max=c;
使用if语句,可使程序的结构更清晰,而使用条件表达式可使代码更精简。
在条件表达式中,可能表达式2和表达式3结果值的类型不同,这些C编译器将其自动转换。例如:
a>0?6.18:3
以上条件表达式中,表达式2的类型为实数型,而表达式3的类型为整型。这时,C编译器将整数3转换为实数3.0,最后计算的结果返回值为实数型。
5.2.8 其他运算符
除了上面介绍的各种运算符外,C语言还有以下运算符:
sizeof:长度运算符(单目运算符)
[]:下标运算符
.:结构体成员运算符
->:指向结构体成员运算符
*:指针运算符(单目运算符)
&:取地址运算符(单目运算符)
1.sizeof运算符
其中sizeof运算符在前面章节中,曾多次使用,这是C语言中一个比较特殊的运算符。大部分程序设计语言中的运算符都是由一个或几个特殊符号构成的,看起来sizeof不像一个运算符,更像一个函数。但sizeof确实是C语言中的一个运算符。
简单地说,使用sizeof运算可返回一个数据类型所占的内存字节数。其操作数可以是数据类型、变量名、常量。例如,以下程序分别使用sizeof计算数据类型、常量、变量的字节长度。
【程序5-9】使用sizeof运算符
1: #include <stdio.h> 2: 3: int main() 4: { 5: float f=5; 6: 7: printf("%d\n",sizeof(float)); 8: printf("%d\n",sizeof(5.0f)); 9: printf("%d\n",sizeof(f)); 10: getch(); 11: return 0; 12: }
编译执行以上程序,可看到输出的数字都是4。
第7行程序输出float数据类型的字节长度。
第8行程序输出常量5.0f的字节长度,如果5.0不加后缀,C编译器将其按double类型存储,占用的字节数要增加。
第9行程序输变量f的字节长度。
2.[]下标运算符
下标运算符在数组中使用,将在本书第9章中进行介绍。
3.结构成员运算符
结构成员运算符(.)和指向结构成员运算符(->)在结构中使用,将在本书第 14 章中进行介绍。
4.*指针运算符
指针运算符(*)主要针对指针进行操作,在本书第12章中将进行详细介绍。
5.&取地址运算符
取地址运算符(&),在本书前面程序中,也曾多次用到,在使用scanf函数接收用户输入时,需要在变量名前,添加&运算符。
取地址符将得到一个变量在内存的地址,在 scanf 函数中,使用取地址运算符得到要保存用户输入数据的内存地址,将用户输入的数据,直接保存到该地址对应的内存空间。
5.3 表达式的运算顺序
在C语言中,表达式可以由各种类型的操作数、各种类型的运算符组成。对于这种由多个运算符连接的表达式,必须知道 C 对运算符操作的顺序(即表达式的运算顺序),才能确切地知道表达式最终的结果。
5.3.1 运算符优先级
上节介绍算术运算符时,提供了运算符的优先级,当表达式中有多种类型运算符混合在一起时,就需要对C语言的所有运算符统一规定优先级。
C语言中,运算符的运算优先级共分为15级。1级最高,15级最低。在表达式中,优先级高的运算符先运算。各运算符的优先级如表5-8所示。
表5-8 运算符的优先级和结合性
优先级为1的圆括号就是用来改变其他运算符的优先级的,即如果需要将具有较低优先级的运算符先运算,则可将该运算及操作数用圆括号括起来。
例如:
(a+b)*h/2
对于这种算术表达式,确定表达式的运算顺序比较简单,先进行圆括号中的加法运算,将得到的和与h相乘,再将积除以2即可。整个顺序是按从左向右执行的。
而对于下面的表达式:
++x||++y&&++z
该表达式包含了算术运算(自增)和逻辑运算,根据表5-8列出的各运算符的优先级,可确定其运算顺序如下:
算术运算符的优先级比逻辑运算符的优先级高,因此先计算算术运算符,再将得到的结果进行逻辑运算。而逻辑运算符中,逻辑与比逻辑或的优先级高,因此,上式添加圆括号可改写为如下表达式:
(++x)||((++y)&&(++z))
通过添加圆括号,可使用表达式的运算顺序更清晰,而不用再去查运算符的优先级来确定表达式的运算顺序。
5.3.2 运算符的结合性
在表达式中,当一个操作数两侧都有运算符,且两个运算符的优先级相同时,C会按什么顺序来运算表达式?例如:
a+b-c
其中操作数b的左侧为加号,右侧为减号,具有相同的优先级。对于这个表达式读者知道是先计算加法,再计算减法,即按从左向右的顺序计算的。这种称为左结合,大部分表达式都是采用这种方式。
而以下表达式:
a=b=c;
操作数b左右两侧都是赋值运算符,在C语言中规定将先运算右侧的赋值操作,即将变量c的值赋值给变量,再运算左侧的赋值操作,将变量b的值赋值给变量a。即按从右到左的顺序进行运算,称为右结合。
结合性是C语言的独有概念,单目运算符、赋值运算符和条件运算符是右结合,其他运算符都是左结合性。各运算符的结合性如表5-8所示。
5.3.3 自增自减运算符注意事项
在C语言中,自增、自减运算符使用得非常多,出问题的地方也很多。本节专门介绍一下这方面的注意事项。
1.操作数的类型
自增、自减运算符的操作数只能是变量,而不能是常量或表达式。因为自增、自减运算符具有对运算量重新赋值的功能。对于整型、实型、字符型、枚举型都可以作为这两个运算符的操作数。例如,以下表达式都是不合法的:
++8 (a+b)++ 'h'++
对于以上不合法的表达式,读者应该都能很好地理解。而下面的表达式:
++a+++b++
对初学者来说,将不好判断是否合法。
C编译器对程序编译时,将按从左到右的方式,尽可能多地将字符组合成一个运算符或标识符,因此上面的表达式相当于:
++(a++)+(b++)
这时,(a++)为一个表达式,给该表达式前面使用自增运算符,将是不合法的。
2.操作符产生的歧义
C语言中,操作符的使用相当灵活。但这种灵活性也可能使程序产生歧义,甚至出现在不同的C编译器中运算的结果不同的现象。
例如,有以下代码:
int a,b; a=4; b=a+++a+++a++;
执行以上程序后,变量a和变量b的值各为多少?
第一种情况:变量b的值为12(=4+4+4),变量a的值为7(进行3次自增)。
第二种情况:变量b的值为15(=4+5+6),变量a的值为7。
在本书的开发环境中,编译执行以上代码,得到的结果是第1种情况。
如果使用以下代码:
int a=4; printf("b=%d\ta=%d\n",(a+++a+++a++),a);
这时在本书的开发环境中,将输出b=21,a=7的结果。
使用自增或自减运算符,可以使C程序更精简,但也可能出现上面这种问题,对于这些代码很容易让人产生误解,从而导致编写的程序出错。因此,在程序中,应尽量避免出现这种有歧义的代码,使用一种更准确地描述代码要完成功能的写法。在计算机速度飞速发展的情况下,多写几行代码对程序的效率影响不大。
例如:在上面的示例中,若要使变量b的值为12,变量a的值为7,可将代码改写为以下形式:
int a,b; a=4; b=a+a+a; a=a+3; /*或写三个a++;语句*/
在程序中,类似a+++a+++ai++这类连续自增、自减的运算最好不要使用,以避免歧义出现,同时也可减少程序出错的可性能。
5.4 语句
语句是C程序的基本成分,一个语句是一条完整的计算机指令。在C语言中,每一个语句都必须使用一个分号作结束符。
5.4.1 语句书写方式
C语言的语句限制条件很少,最基本的一条就是每个语句必须以一个分号结束。用户可以很随意地、采用符合自己风格的方式,排列C语句。与其他程序设计语言不同,一条C语句可以排列在多行中。例如,有以下语句:
s=(a+b)*h/2;
可写为以下样式:
s= (a+b)*h/2;
甚至可以写成以下样式:
s= (a+b) *h/2;
在C语言中,通过分号标志一条语句的结束,所以上面的两行或三行没有分号,表示这两行或三行为一条语句。
将一条语句按多行书写不是一个好习惯,但这样的语句是合法的。
同样,因语句使用分号作为分隔符,将多条语句写在一行中也是合法的。例如:
temp=a[t];a[t]=a[I];a[I]=temp;
以上代码将三条语句写到一行中。
一般情况下,C编译器将忽略语句中的空格、制表符和空行。因此,为了使源程序排列更美观,更利于程序排错,一般可使用以下规则,输入源代码:
在一行内只写一条语句,并采用空格、空行保证清楚的视觉效果(如在运算符两侧添加空格)。
每一个复合语句使用一个TAB缩进(可以设定为4个空格),复合语句的大括号放在新的一行,单独成一行,便于检查源程序时配对。
例如,有如下的程序代码:
1: #include <stdio.h> 2: 3: int main() 4: {int i,j,t,n,temp,a[10]; 5: for(i=0;i<n-1;i++){ t=1; for(j=i+1;j<n;j++){ 6: if(a[j]<a[t] ) t=j; if(t!=i ){temp=a[t];a[t]=a[i];a[i]=temp;}}} 7: for(i=1;i<n;i++){printf("%d\n",a[i]);} 8: getch(); 9: return 0;}
以上程序代码,也可以顺利通过编译。但是,如果程序中有语句出错,用户要想查出错误是非常麻烦的,例如,编译器提示第5行有错,因为该行有多个语句,需要逐个去分析。为了使程序更方便阅读,使用以上介绍的规则,将源代码规范为以下形式:
1: #include <stdio.h> 2: 3: int main() 4: { 5: int i,j,t,n,temp,a[10]; 6: 7 for(i=0;i<n-1;i++) 8: { 9: t=1; 10: for(j=i+1;j<n;j++) 11: { 12: if(a[j]<a[t]) t=j; 13: if(t!=i) 14: { 15: temp=a[t]; 16: a[t]=a[i]; 17: a[i]=temp; 18: } 19: } 20: } 21: 22: for(i=1;i<n;i++) 23: { 24: printf("%d\n",a[i]); 25: } 26: getch(); 27: return 0; 28: }
以上代码占用了28行,程序结构看起来很清晰,如最外层的大括号为第4行和第28行,这对大括号处在同一列位置。第8行和第20行的大括号配对,第11行和第18行的大括号配对,第13行和第17行的大括号配对。层次非常清楚。
编译以上代码时,如果编译器提醒某行有错误,因为一行只有一条语句,所以可以很快找出错误的原因。
5.4.2 表达式语句
在大部分高级程序设计语言中,专门有赋值语句。在C语言中,将赋值作为一个操作符,因此只有赋值表达式。将赋值表达式后面添加分号,就成了独立的语句。
在表达式后面添加语句结束标志(分号),就构成了表达式语句。
以下内容可以作为表达式语句:
10; 3.14*2*2; 2*a*b+3;
以上表达式语句从语法上讲是正确的,能被C编译器顺利编译。但这些语句不对程序执行任何有用的操作。一般表达式语句应能完成一个相应的操作,如修改变量的值、给变量赋初值、调用某个函数等。如下面的语句:
a+=10; s=3.14*2*2; printf("%d\n",a);
执行以上表达式语句,将对表达式进行求值,将其赋值给某个变量。
5.4.3 空语句
在C语言中,如果一个语句只有一个分号“;”,则称该语句为空语句。
空语句是什么也不执行的语句。在程序中,空语句主要用来作空循环体。例如:
while(getchar()!='\n');
以上代码的功能是,只要从键盘输入的字符不是回车,则要求用户重新输入。即要求用户按回车,才继续后面的程序。在该部分代码中,接收用户按键,判断按键的内容都集中在while的判断中,因此,循环体中不再需要执行任何功能。就在循环体中,输入一个空语句作为循环体。
要注意的是,很多初学者会认为以上代码中的分号是作为while语句的结束。有关while语句的内容,参见本书第8章中的介绍。
5.4.4 复合语句
复合语句是由大括号中的0个或多个声明和语句共同构成。在C99中,语句和声明可以混合,在早期C语言中,则要求把声明放在语句之前。复合语句也称为代码块(或块语句)。
复合语句可以放在能够使用语句的任何地方。大部分时候用在if或while等程序控制结构中。例如:
if(a[i]<a[j]) { temp=a[i]; a[i]=a[j]; a[j]=temp; }
以上if语句对关系表达式进行运算,若返回值为true,则执行下方的复合语句,将a[i]和a[j]中的数据进行交换。如果不使用复合语句,直接写为如下形式:
if(a[i]<a[j]) temp=a[i]; a[i]=a[j]; a[j]=temp;
以上代码在if语句中的关系表达式返回值为true时,将只执行temp=a[i]语句。
有关if语句的使用,参见本书第7章中的内容。
在复合语句中,可以声明新的变量,其作用范围仅限于复合语句内。有关变量的作用范围的相关知识,参见本书第11章中的内容。
5.4.5 标号语句
C语言允许用户给每个语句前,添加一个标号,其格式如下:
标号: 语句;
标号为一个C语言的标识符,标号后面为一个冒号,冒号后面才是C语言的语句。
标号不能单独出现,而要和语句相联系。例如,以下的语句将标号和语句分为两行:
标号: 语句;
按照前面介绍的知识,以上两行也属于同一个语句。
可以使用goto语句或switch分支语句将程序控制转移到编有标号的语句。
标号语句有3种:
命名标号:可在任何语句前添加命名标号,使用goto语句,可跳转到命名标号处。
case标号:只能出现在switch语句体中,进行分支处理。
default标号:只能出现在switch语句体中,进行分支处理。
switch语句将在本书第7章中进行介绍,goto语句将在本书第8章中进行介绍。