3.3 算术运算符与算术表达式
从本节开始逐一介绍C语言中经常用到的运算符以及由其构成的表达式。算术运算符是C语言中最常用到的一种运算符,在C语言中,除日常见到的基本算术运算符以外,还有两种特殊的运算符:自加和自减。本节先来介绍基本算术运算符,然后再来讨论自加和自减。
3.3.1 基本算术运算符
在C语言中,基本算术运算符共有5个,它们分别是:
+(加) -(减) *(乘) /(除) %(取模)
这5种运算符均是双目运算符,即在运算符的两边都有数据。传统意义上的算术运算就是加、减、乘、除运算以及求余数运算。双目算术运算符都是左结合的,其中左操作数与右操作数的类型可以不一致,运算时执行隐含类型转换,运算的结果是一个数值。
说明
与数学上的含义相同,+和-运算符都可以作为单目运算符出现,即运算符的右边有数据,例如+3、-5等,代表正数和负数。这都与日常数学运算表达式一致,比较容易理解。
对于普通算术运算符来说,要注意以下几点。
◆ C语言中的算术运算和日常的算术运算是有区别的。
● 没有乘方运算符,要计算a3应写作a*a*a的连乘,或用标准库函数pow(a,3)。
● “/”的运算对象可为各种类型数据。若参与运算量均为整型,结果也为整型,舍去小数;如果运算量中有一个是实型,则结果为双精度实型。例如要计算9/2,其结果为4,不是4.5。因此在程序设计中,有时可以利用整数除法获得所需的结果,但这也容易产生错误。如果两个操作数是整数,要获得实数除法,应当将两个或任意一个整型操作数强制转换为实型数,参见下面的代码:
int sum= 100; int count = 80; double unit = sum / (double) count; /*得出1.25*/
值得注意的是,根据编译系统的不同,有关负整数的除法也会得到不同结果。例如-5/3,有的系统的结果为-1,有的系统的结果为-2,如遇到此类问题应该实际编写程序测试结果。另外在执行除法运算时,如果除数为零,虽然编译时不会提示错误,但当程序运行时,会产生一个被零除的错误。
● “%”要求运算对象必须是整型数据,该运算符的功能是求两数相除的余数,余数的符号与被除数的符号相同。例如要计算2%10,其结果为2。或如计算8%5,其结果为3。当操作数中出现负数时,要注意结果的符号。例如计算-15%8,当求余数时,商为-1,余数为-7,所以其结果为-7;计算15%-8,当求余数时,商为-1,余数为7,所以其结果为7。由于该运算只能对整数进行,所以类似8.4%4这种写法是错误的。
◆ 算术运算符的优先级与结合性。运算符*,/和%的优先级高于+,-,此5种运算符的结合性是“自左至右”的;单目运算符正号(+)和负号(-)的优先级高于运算符*,/,%,这两种运算符的结合性是“自右至左”的。
◆ 如果算术运算的结果超过了某种类型的变量能够表示的最大范围,而不能存储在一个指定的变量中,这种情形称之为溢出(OverfIow)。例如这样的代码:
char k = 10*92; /*发生溢出*/
◆ 由算术运算符和运算分量组成的求值序列,称为基本的算术表达式。例如a-b、b*c和a%d+b/c等都是算术表达式。使用中要注意表达式值的计算顺序和值的数据类型。
代码3.2就是多个算术运算符组成的表达式求值的例子。
代码3.2 计算有多个算术运算符的算术表达式:fiIe2.c
#include <stdio.h> void main(){ int x,a=3; float y; x=20+25/5*2; /*变量x保存表达式的值,按照优先级计算结果*/ printf("(1)x=%d\n",x); x=-3*4%-6/5; /*表达式结果类型为整型*/ printf("(2)x=%d\n",x); x=(7+6)%5/2; /*利用()可以改变优先级*/ printf("(3)x=%d\n",x); y=25.0/2.0*2.0; /*运算符/和*优先级相同*/ printf("(4)y=%f\n",y); }
程序的运行结果为:
(1)x=30 (2)x=0 (3)x=1 (4)y=25.000000
从本例中可分析出普通算术运算符的运算特点和运算符的优先级与结合性。
3.3.2 特殊的算术运算符——自加和自减
C语言中提供了两个特殊的运算符,即自加运算符++和自减运算符--。它们都是单目运算符,运算对象可以位于运算符前面,也可以位于运算符后面。当运算符位于运算对象前面时,称为前缀运算符,如++i和--i;当运算符位于运算对象后面时,称为后缀运算符,如i++和i--。自加自减运算符的功能是将运算对象加1或减1后,再将结果保存到运算对象中去,如i++等同于i=i+1。自加自减运算符均为单目运算,只需一个运算量。
前缀运算符和后缀运算符两种形式虽然都是对运算对象完成加1或减1操作,但是它们之间还是有区别的。其主要的区别就是:
◆ 前缀形式:先对运算对象自增(自减),再引用运算对象的值作为表达式的值。
◆ 后缀形式:先引用运算对象的值作为表达式的值,再对运算对象自增(自减)。
前缀形式++a等价于a=a+1,--a等价于a=a-1 后缀形式a++等价于a=a+1,a--等价于a=a-1
可以看出,若单独使用,前后缀没有任何区别,每个表达式都可使内存中存储的变量a加1或减1。二者的根本区别是整个表达式的值不同。以自加为例,若每次运算前a的值都为5,则:
b=++a;等价于a=a+1;b=a;表达式的值为6,所以运算完毕后a=6,b=6 b=a++;等价于b=a;a=a+1;表达式的值为5,所以运算完毕后a=6,b=5
代码3.3表现了自加、自减运算符的前缀和后缀用法的区别。
代码3.3 自加、自减运算符的前缀和后缀用法的区别:fiIe3.c
#include "stdio.h" main() { int i=6,j=6,k=6, h=6,m,n,x,y; m=i++; /*后缀形式自加*/ n=++j; /*前缀形式自加*/ x=k--; /*后缀形式自减*/ y=--h; /*前缀形式自减*/ printf("\n i=%d, m=%d, j=%d, n=%d",i,m,j,n); printf("\n k=%d, x=%d, h=%d, y=%d",k,x,h,y); }
程序运行的结果为:
i=7,m=6,j=7,n=7 k=5,x=6,h=5,y=5
从中可以分析出,前缀形式n=++j;等价于j=j+1; n=j;也就是j先加1,后参与表达式的运算;后缀形式m=i++;等价于m=i; i=i+1;也就是i先参与表达式的运算,然后加1。简而言之,在表达式中出现前缀加的情况下,前缀加所作用的运算分量值先增1,然后进行其他级别较低的运算;而在表达式中出现后缀加的情况下,先以其运算分量的当前值参与其他级别较低的运算,最后该运算分量值才增1。
实际使用的过程中要注意:
◆ ++和--的运算对象只能是整型变量或运算结果是变量的表达式,不能是常量或运算结果是数值的表达式。
例如:5++、(a+2)++不合法。
◆ ++和--具有右结合性,结合方向为从右到左。
例如:-a++等价于-(a++)。
◆ 如果有多个运算符连续出现时,C系统会尽可能多地从左到右将字符组合成一个运算符。
代码3.4是一个很难分析的连续运算符组合的情况。
代码3.4 表达式中运算符的组合:fiIe4.c
main() {int a,b,s; a=5;b=5; s=a+b; printf("%d,%d,%d\n",a,b,s); s=a+++b; /*等价于(a++)+b */ printf("%d,%d,%d\n",a,b,s); s=++a+b; /*等价于(++a)+b */ printf("%d,%d,%d\n",a,b,s); s=--a+b; /*等价于(--a)+b */ printf("%d,%d,%d\n",a,b,s); s=a--+b; /*等价于(a--)+b */ printf("%d,%d,%d\n",a,b,s); s=(a++)+(a++)+(a++); /*a=3,a先参与运算,s=3+3+3,之后a进行3次自加 */ printf("%d,%d\n",a,s); s=-a+++-b; /*等价于 -(a++)+(-b) */ printf("%d,%d,%d\n",a,b,s); }
程序运行的结果为:
5,5,10 6,5,10 7,5,12 6,5,11 5,5,11 8,15 9,5,-13
通过这个程序可以分析出,如果在两个运算分量之间连续出现多个表示运算符的字符(中间没有空格),那么在保证有意义的条件下,就从左到右尽可能多地将若干个字符组成一个运算符。所以,表达式a+++b就解释为(a++)+b,而不是a+(++b)。建议在录入程序时,在各个运算符之间加入空格,或者使用圆括号,把有关部分括起来,使之作为整体处理,则可以避免不必要的错误。
说明
“++”和“--”是单个运算符,不是通常意义上的两个“加”号或者两个“减”号。
当一个表达式中包含多个算术运算符时,到底该如何计算呢?表达式的计算方法是,按优先级由高到低进行,相同优先级的运算符按结合方向计算。
算术运算符的优先级为先自加、自减、负号,而后乘、除、取模,最后加、减。其结合性是自加、自减、负号自右向左;其他的自左向右。
3.3.3 常见错误分析与解决方法
由运算符产生的错误一般属于编译性的错误,可以由编译系统直接捕捉到,并且给出错误提示信息。在有编译错误的前提下,不会产生可执行文件。
【例1】分析代码3.5中所包含的错误,编程验证表达式计算是不是正确。已知整数b初值为7,两个实数a和c初值分别为2.5和4.7,计算表达式:a+(int)(b/3*(int)(a+c)/2)*4。
代码3.5 忘记定义变量时的情况:fiIe5.c
#include "stdio.h" main() { int b=7; float a=2.5,c=4.7; d= a+(int)(b/3*(int)(a+c)/2)*4; /*错误代码,没有定义变量d*/ printf("%f\n",d); }
产生的错误信息为:
Undefined symbol 'd' in function main
分析错误信息(不同的编译系统产生的错误信息不一定相同)可看出由于运算过程中变量过多,所以忘记定义变量来存储表达式的值。改正的方法很简单,只要在变量的声明部分定义doubIe d;即可。
【例2】分析代码3.6所包含的错误,看将数据转成10以内的整数输出时出现什么错误。
代码3.6 参与运算的操作数类型不合法:fiIe6.c
#include "stdio.h" main() {float x; int n; scanf("%f",&n); n=x%10; /*错误代码,实型不允许进行%运算*/ printf("%d\n",n); }
产生的错误信息为:
Illegal use of floating point in function main
分析产生的信息,可看出错误产生的原因是由于数据参加了非法的运算。本代码忽略了取模运算所需操作数的类型,对于浮点数据,不可参加取模运算。所以在运算前,应将x强制转化成整型。
【例3】自加、自减是算术运算符中比较特殊的运算符,在使用中要注意其表达式的构成。通过代码3.7,分析系统提示的错误。
代码3.7 自加和自减时的错误:fiIe7.c
#include "stdio.h" main() { int m,n; m=0,n=1; m=(2+n)++ %4+n++; /*错误代码,表达式不允许自加运算*/ printf("%d,%d\n",m,n); }
产生的错误信息为:
Lvalue required in function main
在这个例子中可以看到,自加、自减运算要求操作数必须为整型变量,不可为常量和表达式。出错的原因是在对变量m第二次赋值时,表达式中出现了(2+n)++,表达式参加了自加运算,所以产生错误,改正时把括号去掉。
【例4】自加、自减运算使表达式变得简单、灵活。但在实际使用中其表达式具体的值依赖于系统。分析代码3.8,这是一个连续加号的例子。
代码3.8 连续加号的示例:fiIe8.c
#include "stdio.h" main() { int a=0; printf("%d",++a+a++); /*输出表达式++a+a++*/ }
在标准的Turbo C环境下,这个程序的编译将不会提示错误,并且能输出结果2。但是,不同的系统可能会产生不同的警告信息,一般来讲,系统认为++a+a++等类似的表达式有潜在的危险,一般不要使用,或分步使用。例如上例可改成:
main() {int a=0,m; m=++a; /*前缀自加,m保存自加表达式的值*/ m=m+a; /*对自加运算的分解*/ a++; /*后缀自加,变量值加1*/ printf("%d\n",m); }