数据运算 计算机的基本功能是进行数据运算,而数据运算主要是通过对表达式的计算完成的。 表达式是将运算量用运算符连接起来组成的式子,其中运算量可以是常量、变量或函数。 由于运算量划分为不同的数据类型,每一种数据类型都规定了自己特有的运算或操作,这 就形成了对应于不同数据类型的运算符集合及其相应的求值规则。本章主要介绍C语 言运算符及各种表达式的构造与求值规则。 3.1 算术运算 算术运算又称数值运算,是程序设计中使用最多的一种数据运算。 3.1.1 算术运算符 算术运算符包括加(+)、减(-)、乘(*)、除(/)、求整数余数(%)、加1(++)、减1 (--)及正负号(+、-),其中,+、-、*、/、%运算符必须连接两个运算量,称为二元运 算符;++、--、正负号(+、-)运算符只能连接一个运算量,称为一元运算符,参见附 录A。 1.求余运算 求余运算又称求模运算,只能用来求两个整数(int型或char型)的余数。假设两个 整数分别为a 和b,a%b 表示求a 整除b 后的余数,基本规则如下: a %b =a -a /b *b 其中,a/b 得到a 除以b 的整数商,例如: 14%(-6)=14-14/(-6)*(-6)=14-(-2)*(-6)=2 -14%6=-14-(-14/6)*6=-14-(-2)*6=-2 2.加1和减1运算 加1运算又称自加运算,减1运算又称自减运算,是对变量自身增加1或减少1的运 ·35· 算。例如:++i表示i=i+1,--i表示i=i-1。 (1)++或--只能对变量施加运算,不能对常量或表达式施加运算。如++(25)、 ++(x+y)、--(x-3),++i--是错误的。 (2)++或--可以写在变量之前,称为前缀,也可以写在变量之后,称为后缀。它 们之间的区别在于:n++表示先取n的值,然后令n=n+1;++n表示先令n=n+1, 然后取n的值。例如,令i=5,j=5,则 x1=++i; //先计算i=i+1,得到i 的值是6,然后将6 赋给x1 x2=j++; //先将j 的值5 赋给x2,然后计算j=j+1,得到j 的值是6 【例3-1】 加1和减1运算。 由于加1和减1运算既有计算的功能,又有赋值的功能,因此将改变变量的值。程序 如下: #include void main() { int a=100; printf("%d\n",a); printf("%d\n",++a); //先计算a 的值为101,再输出a 的值 printf("%d\n",a--); //先输出a 的值101,再计算a=a-1 得a 的值为100 printf("%d\n",a); } 程序运行结果如下: 3.1.2 算术表达式 算术表达式是用算术运算符连接数值型的运算量构成的式子,用来完成数值计算的 功能。例如:-5*3+6%4/2-1、(a+b)/(a-b)、a+(b++)*c、(i++)+j和y+x++ -(++i)。 1.算术表达式的书写规则 正确书写算术表达式对正确计算该表达式的值非常重要,应注意以下几点。 (1)乘法运算符“*”在表达式中既不能省略,也不能用“·”或“×”代替;除法运算符 “/”也不能用其他符号代替。 (2)正确使用运算符的优先级和结合性来决定表达式的求值顺序。 优先级指的是当几个不同的运算符同时出现在表达式中时,各个运算符计算的优先 次序。例如,在表达式a/b+c中,由于除法的优先级高于加法,因此应先计算a/b然后与 c相加。 ·36· 结合性指的是当同一个优先级别的运算符同时出现在表达式中时,其运算的优先次 序。例如,在表达式a/b*c中,由于乘除法处于同一优先级,它们的优先次序由结合性确 定。乘除法的结合性为自左至右,因此,应先计算a/b然后再乘c;又如,在表达式-++i 中,取负号和减1运算处于同一优先级,根据它们自右至左的结合性,应先计算++i然后 再取负。 (3)恰当地使用括号可以改变优先次序或防止出现二义性。例如,表达式a/(b*c) 中,使用“()”可将乘法运算提前,从而改变了运算顺序,因此该表达式与a/b/c是等价的。 又如,对诸如---i这样的式子,其中连续3个减号的含义可以有多种解释,为防止二义 性,应使用括号将后“--”作为减1运算符,才能形成正确的表达式-(--i);否则,编 译系统就会报错。 (4)在表达式中只允许使用“()”改变运算优先顺序,不允许使用“[]”或“{}”,多重括 号可以嵌套配对使用,如((a+b)-(c+d))/(b+d)。 (5)C语言不含乘方运算,当需要进行乘方运算时,可以通过连乘的方式实现乘方运 算,也可以使用C编译系统提供的数学函数,如pow(x,y)表示xy ,pow10(n)表示10n 。 2.算术表达式的求值规则 (1)优先级和结合性规则。计算机在进行表达式的计算时,通常是严格按运算符的 优先级和结合性进行的,就算术表达式而言,“()”最优先,其次是一元运算符,然后是乘、 除和求模,最后是加、减;当同一优先级的运算符同时出现时,按它们的结合性确定其优先 次序。 【例3-2】 算术表达式计算中的优先级和结合性规则。程序如下: #include void main() { int a=3,b=5,c=15,d=2; printf("%d\n",6-a*b/c%d); printf("%d\n",++a-a++); printf("%d\n",a); } 在第一个printf()语句中,由于乘、除、求模的优先级高于减法,因此先计算a*b/c%d, 在这一步计算中,根据从左到右的原则依次进行乘、除和求模的运算,得到中间结果1,然 后再计算6-1,结果为5。在第二个printf()语句中,由于++优先于-,因此,先计算第 一个++a,得a=4(注意前后两个a是同一个变量,它们具有相等的值),然后计算4-4, 输出0,输出之后还要进行a++运算,得a的值为5,并在第三个printf()语句中输出5。 该程序的输出如下: (2)自动类型转换规则。算术表达式中可能含有不同类型数值量的混合运算,除了 ·37· 遵循优先级和结合性规则外,还要遵循如下自动类型转换规则。 ① 如果表达式中含char型,全部转换成int型。例如,表达式a' '+3' '+15中,字符常 量a' '和3' '将被转换成int型的97和51,然后再进行计算,其结果为163。 ② 如果表达式中含float型,全部转换成double型。 ③ 同类型的运算量运算后,结果仍为同类型。例如,表达式3/2的值是1而不是 1.5,因为其中的两个运算量均为int型,计算结果也应是int型。 ④ 不同类型的运算量运算前,先将低精度类型转换成高精度类型,运算结果为高精 度类型。这种类型转换称为自动类型转换。 类型精度的高低取决于两点:一是看类型的长度,长度越长,精度越高,例如,double 型的长度为8,而float型的长度为4,因此double型的精度高于float;二是看该类型所能 表示的数的范围,能表示的数越大,精度越高。例如,int型能表示的最大数为32767,而 unsignedint型能表示的最大数为65535,因此unsignedint的精度高于int。 【例3-3】 混合运算的自动类型转换求值规则。程序如下: #include void main() { char ch='a'; int i=5; unsigned int j=6; long int k=12; float f=3.0; double d=6.0; printf("%lf\n",ch/i+i*k-(j+k)*(f*d)/(f+i)); } 其中,表达式ch/i+i*k-(j+k)*(f*d)/(f+i)中含有多种类型的变量,根据优先级和 结合性规则及自动类型转换规则进行计算,计算过程如图3-1所示。 图3-1 表达式ch/i+i*k-(j+k)*(f*d)/(f+i)的求值过程 图中凡标有类型关键字的,表示对应的数据进行了自动类型转换,带圆圈的数字表示 计算的顺序号。 ·38· 和变量一样,C语言的表达式不仅有值,也分为不同的类型。表达式值的类型就是表 达式的类型。本例中,由于表达式ch/i+i*k-(j+k)*(f*d)/(f+i)的值是double型, 所以该表达式也是double型。程序输出结果如下: 38.500000 自动类型转换总是将低精度数转换为高精度数,目的是在转换过程中不损失数的精 度。注意,当负整数转换成无符号整数时,由于符号位变成了数值位,其值将会发生变化。 例如,shortinta=-1转换成unsignedshort型时将变成65535。 3.强制类型转换规则 在算术表达式的计算中,如果要违反自动类型转换规则,可以采用强制类型转换。例 如,根据自动类型转换规则,表达式3/2的值是1而不是1.5。如果将整数3强制转换成 float型,写成(float)3/2,其值就是1.5而不是1。 强制类型转换的一般格式如下: (类型关键字)(表达式) 强制也是一种一元运算符,其与其他一元运算符具有相同的优先级和结合性,它的功 能是将指定的表达式的值强制转换成指定的类型。例如: (double)a //将变量a 的值强制转换成double 型 (int)(x+y) //将表达式(x+y)的值强制转换成int 型 (int)x+y //将变量x 的值强制转换成int 型,然后与y 相加 【例3-4】 强制类型转换。程序如下: #include void main() { int a=7,x=3,y=2; float b=2.5f,c=4.7f,z=3.5f; printf("%f\n",b+a%3*(int)(b+c)%2/4); rintf("%d\n",(x+y)%2+(int)b/(int)z); } 程序运行结果如下: 2.500000 1 程序运行时,在第一个printf()语句中,b+c的值为7.2,强制转换成int型后变成7, 整个表达式变成2.5+7%3*7%2/4,最后计算结果为2.5,float型,因此要用"%f"控制 输出;第二个printf()语句中,变量b和z的值分别被强制转换成int型的2和3,整个表 达式变成(3+2)%2+2/3,最后计算结果为1(注意,在计算2/3时,其值是0而不是 0.67),int型,因此要用"%d"控制输出。 在程序设计实践中,强制类型转换有时非常有用。例如,运算符“%”要求连接两个整 ·39· 型量,设x是float型变量,则表达式x%3就是错误的,用(int)x%3就允许了。第5章将 要介绍的数组,当用float型或double型变量作下标时,也需要使用强制类型转换。此 外,C编译系统提供的数学函数大多数是double型的,通过强制类型转换可将它们转换 成需要的类型。 使用强制类型转换应注意以下几点: (1)在对一个表达式进行强制类型转换时,整个表达式应该用“()”括住。例如, (float)(a+b)若写成(float)a+b就只对变量a进行了强制类型转换。 (2)在对变量或表达式进行了强制类型转换后,并不改变变量或表达式原来的类型。 例如,设x为float型,y为double型,则(int)(x+y)是将x+y的值强制转换成int型,而 表达式x+y本身仍为double型。 (3)高精度整数转换成低精度整数时,由于会丢掉一些数位,因而会造成值的改变。 例如,int型整数65536转换成shortint型整数时会变成0;int型整数65535转换成short int型整数时会变成-1。 3.2 赋值运算 在C语言中,赋值运算与赋值语句紧密相关,赋值表达式的末尾加上分号就是赋值 语句。通过赋值表达式有助于揭示C语言特有的一些语言规则。 形如: v=e 的表达式称为赋值表达式。其中,“=”称为赋值号或赋值运算符,“=”右边的e是一个表 达式,左边的v是一个变量或数组元素。赋值表达式的功能是计算e的值并存放在v中。 (1)赋值运算符。 ① 赋值运算符“=”是一种二元运算符,必须连接两个运算量,其左边只能是变量或 数组元素,右边则可以是任何表达式。例如,x=y+2和x=x+1是允许的,而y+2=x 和5=x是错误的。 ② 赋值运算符除了“=”,还有另外10种复合赋值运算符,它们由赋值号“=”和另外 一个二元运算符组成,具有计算和赋值双重功能,这10种复合赋值运算符是+=、-=、 *=、/=、%=、&=、|=、^=、<<=和>>=。其中,前5种复合赋值运算符具有算术 运算和赋值的双重功能: x+=e等价于x=x+e; x-=e等价于x=x-e; x*=e等价于x=x*e; x/=e等价于x=x/e; x%=e等价于x=x%e。 后5种复合赋值运算符则具有位运算和赋值的双重功能。 ③ 赋值运算符的优先级在所有的C运算符集中处于倒数第二,参见附录A,结合性 ·40· 均为从右到左。 ④ 在使用复合赋值运算符时,要将右边的表达式作为一个整体与左边的变量进行运 算,例如,x*=y+3表示x=x*(y+3)而不是x=x*y+3。 (2)赋值表达式。 ① 作为一种表达式,赋值表达式同样也有值和类型的问题。C语言规定,被赋值的 变量或数组元素v的值和类型就是赋值表达式的值和类型。在赋值过程中,当赋值号两 边的v和e类型相同时,直接赋值;当v和e类型不一致时,C编译系统会自动将e的值转 换成与v相同的类型后再赋给变量v。赋值表达式中的类型转换既有可能要将e的值由 低精度转换成高精度,也有可能要由高精度转换成低精度,以满足与v的类型一致。例 如,赋值表达式x=10.8-4.3执行后,假设x是int型的变量,则根据类型转换规则,变量 x得到的值是6,那么,该赋值表达式的值为6,类型为int。 ② 上节介绍的++和--运算施加于变量后也构成赋值表达式,例如,i++表示 i=i+1,所以i++也是赋值表达式。但要特别注意++或--只能施加于变量,而不能 施加于表达式。例如,++i++就不是正确的赋值表达式,因为根据++运算符从右到 左的结合性,它表示++(i=i+1),++被施加在赋值表达式i=i+1上是不允许的。 ③ 赋值表达式本身也可以作为一个表达式赋给另一个变量。例如,b-=b*b是一 个正确的赋值表达式,可以将它赋给变量a,写成a=b-=b*b,也构成一个正确的赋值 表达式。这时,由于赋值运算符从右向左的结合性,先计算b-=b*b,然后再将这个值 赋给a。但是,要特别注意赋值号右边必须是变量或数组元素,诸如a=b+c=d、x=i+ +=y、u=2=v这样的表达式就不是正确的赋值表达式。 【例3-5】 赋值表达式中的类型转换。程序如下: #include void main() { int a; char b; float c; c=2.5; b=c; a=b; printf("%d,%d,%.1f",a,b,c); } 在计算数值型的赋值表达式时,要将赋值号右边的表达式转换成与左边变量或数组 元素相同的类型。这种转换既可能将高精度类型转换成低精度类型,也可能相反。在本 例中,计算赋值表达式c=2.5时,先要将双精度型常数2.5转换成float型,再赋给变量c; 在计算赋值表达式b=c时,先要将c中的float型数转换成char型,然后赋给变量b;在 计算赋值表达式a=b时,先要将b中的char型值转换成int型,然后赋给变量a。前两个 赋值是将高精度转换成低精度,第三个赋值则将低精度转换为高精度。程序运行结果 如下: 2,2,2.5 ·41· 【例3-6】 赋值表达式中类型转换引起的数值变化。程序如下: #include void main() { short int a=-1; unsigned short b; b=a; printf("%d %u\n",a,b); } 在VisualC++2010中,数值是按补码形式存储的,int型的-1的二进制存储格式为 16个“1”,在进行b=a的赋值时,要将-1转换成unsignedshort型。这时,最高位的符 号位也被视为数值位,16个“1”代表的数值是65535。因此,程序输出如下: -1 65535 【例3-7】 复合赋值运算符和变量的连续赋值。程序如下: #include void main() { int a=12; //a=12 a+=a-=a*a; //该表达式可拆分为a=a-a*a; a=a+a; printf("%d\n",a); } 在计算赋值表达式a+=a-=a*a时,按从右到左的顺序先计算a*a,得144,然后 进行a-=144的赋值运算,得到a=12-144= -132,该值作为赋值表达式a- =a*a 的值,参加下一个赋值表达式的计算,得a=a+(-132)=-264。因此,当同一变量被连 续赋值时,要注意其值在不断变化。本例程序的运行结果如下: -264 3.3 逗号运算(顺序运算) 逗号运算符的作用是连接多个数据项,从而将它们能作为一个整体来处理。前面介 绍的用一个类型关键字定义多个变量时,各变量之间要用逗号隔开,在printf()函数中多 个输出项之间用逗号隔开,在scanf()函数中多个输入项之间用逗号隔开等,目的都是将 多个变量、输出项或输入项视为一个整体。本节讨论用逗号运算符将多个表达式作为一 个整体时的处理原则。 1.逗号表达式 用逗号运算符将几个表达式连接在一起就构成逗号表达式。例如, a=3*5,a*4,a+5 ·42· 通过使用两个逗号运算符将一个赋值表达式和两个算术表达式连接起来,构成一个逗号 表达式。 (1)逗号运算符的优先级和结合性在所有的运算符中是最低的,处于赋值运算符之 后。其结合性为从左到右。 (2)逗号运算符的功能是从左到右依次计算各个表达式的值,因此逗号运算符也称 顺序运算符。 (3)逗号表达式的值是逗号表达式中最右边的一个表达式的值。例如,设a是int型 变量,其初值为5,则 a=3*5,a*4,a+5 中第一个表达式的值是15,第二个表达式的值是60,第三个表达式的值是20,那么整个 逗号表达式的值就是第三个表达式的值,即20,而a的值为15。 如果用括号改变计算顺序,把上式改写成 a=(3*5,a*4,a+5) 就要先计算逗号表达式(3*5,a*4,a+5)的值,再将该逗号表达式的值赋给变量a,结果 得到a=10。 【例3-8】 逗号运算符及逗号表达式。程序如下: #include int main() { int a=2,b=4,c=6,x,y; y=(x=a+b), (b+c); printf("y=%d,x=%d",y,x); return 0; } 读者可结合前面所学的知识,分析该程序的运行结果。在不同的C编译环境下,其 运行结果有所差异,在VisualC++ 2010中的运行结果如下: 2.逗号表达式的应用 (1)用一个逗号表达式语句可代替多个赋值语句,如 a=0,b=1,c=2; 在语法上作为一条语句,它相当于下面的3条语句: a=0; b=1; c=2; (2)用一个逗号表达式语句可得到多个计算结果,如 ·43· y=10,x=(y=y-5,60/y); 执行后,可同时得到x的值为12,y的值为5。 (3)当某些语法位置只允许出现一个表达式时,用逗号表达式可实现多个表达式的 运算。例如,后面要介绍的for循环: for(i=0,j=0;i<8,j<10;i++,j++) 中的3个语法位置:循环变量赋初值、循环终止条件判断和循环变量增值都只允许一个 表达式,用逗号表达式可实现两个或多个表达式的运算。 3.4 关系运算和逻辑运算 在程序设计中,关系运算和逻辑运算通常用在程序流程控制中的if、switch、for或 while语句中,目的是进行条件判断,以便程序能按照指定的流程运行。 1.关系运算符 (1)关系运算符有6种,即>、<、>=、<=、==和!=,分别表示大于、小于、大于 或等于、小于或等于、等于和不等于。其中,表示大于或等于、小于或等于、等于和不等于 的4种符号在书写形式上与人们日常使用的数学比较符略有不同。 (2)关系运算符分为两个优先级,>、<、>=和<=处于同一优先级,==和!=处 于同一优先级,前者的优先级高于后者,它们在整个C语言运算符集合中的运算优先顺 序参见附录A,结合性都是从左到右。 (3)关系运算符主要用来对两个算术表达式或赋值表达式进行比较运算。 2.关系表达式 (1)用6个关系运算符中的一个连接两个算术表达式或赋值表达式,就构成了关系 表达式。例如: a+b>=c-d x==y score>90 用“==”或“!=”连接两个算术表达式或关系表达式就构成相等(不等)表达式, 例如: (b*b>=4*a*c)==(a!=0) (b*b-4*a*c)!=0 (2)关系表达式的值表示比较的结果,是一个逻辑值:当比较结果成立时,为逻辑 真;否则,为逻辑假。C语言不提供逻辑型数据,而是用整数1表示逻辑真,用整数0表示 逻辑假。例如: ·44· 0>9 //比较不成立,其值为0 100!=20 //比较成立,其值为1 注意:数值型数据转化为逻辑型数据时,非0数据表示逻辑真,0表示逻辑假。 (3)在相等表达式中,先计算两个算术表达式(或关系表达式)的值,然后对这两个值 进行相等性比较,其结果也是一个逻辑值。例如,计算相等表达式(25>5)!=(6>=7) 时,先计算25>5,其值为1;再计算6>=7,其值为0;然后比较1!=0,显然是成立的,因 此该表达式的值为1。 【例3-9】 关系表达式的计算。程序如下: #include void main() { int x=8,y,z; y=z=x++; printf("%d ",(x>y)==(z=x-1)); x=y==z; printf("%d ",x); printf("%d\n",x++>=++y-z--); } 程序执行赋值语句y=z=x++后,得到z=8,y=8,x=9。第一个printf()输出的是 关系表达式(x>y)==(z=x-1)的值,该表达式中x>y的计算结果是1,z=x-1的计 算结果是8(注意z=x-1是赋值表达式),经过1==8的比较后可知该关系表达式的值 是0。执行赋值语句x=y==z时,由于“==”的优先级高于“=”,故先进行y==z比 较,然后将比较的结果赋给变量x。由于y和z的值均为8,故y==z的值为1,从而得到 x=1。第三个printf()中的输出项是关系表达式x++>++y-z--,即比较1>= 9-8,得到结果为1。程序运行后输出结果如下: 0 1 1 思考:当x分别为8和-8时,关系表达式0<=x<=10的值分别是多少? 3.逻辑运算符 逻辑运算可以实现更复杂的比较。例如,数学不等式0≤x≤10表示的条件是,若x 在闭区间[0,10]中,不等式成立;否则,不成立。将该数学不等式写成关系表达式0<= x<=10是不正确的。因为根据关系运算符从左到右的结合性,无论x取何值,关系表达 式0<=x<=10的值恒为1。所以,为了得到满足0<=x和x<=10同时成立的条件, 必须用逻辑表达式0<=x&&x<=10来表示。 (1)逻辑运算符有“!”“&&”和“||”3种,分别表示逻辑非、逻辑与和逻辑或。其中, “!”为一元运算符,“&&”和“||”为二元运算符。 逻辑运算符必须连接逻辑量,运算的结果也是逻辑量,即只能取0或1,因此逻辑运 算的规则就比较简单,通常用真值表表示,见表3-1,表中0表示逻辑假、1表示逻辑真。 ·45· 表3-1 逻辑运算真值表 p q p&&q p||q !p 0 0 0 0 1 0 1 0 1 1 1 0 0 1 0 1 1 1 1 0 从真值表可以看出,只有两个运算量都是逻辑真时,它们的逻辑与运算结果才是逻辑 真;两个运算量中只要有一个为逻辑真,它们的逻辑或运算结果就为逻辑真;而一个逻辑 量经逻辑非运算后,总是取其相反的值。逻辑与也叫逻辑乘,逻辑或也叫逻辑和,逻辑非 也叫逻辑反,因为它们的运算规则与数学中的乘法、加法和取负号相似。 (2)逻辑运算符处于不同的优先级,其中逻辑非“!”和其他一元运算符具有相同的优 先级和结合性;逻辑与“&&”的优先级低于算术运算符及关系运算符而高于逻辑或“||”, 逻辑或“||”的优先级高于赋值运算符和逗号运算符。“&&”和“||”的结合性均为从左 到右。 (3)通常逻辑运算符连接的是关系表达式。但由于C语言规定任何非0值都被视为 逻辑真,而0则被视为逻辑假,因此逻辑运算符也可以连接任何数值型的表达式。 4.逻辑表达式 (1)用逻辑运算符连接关系表达式或其他任意数值型表达式就构成逻辑表达式。例 如,为了判断变量ch的值是不是字母,可用下面的逻辑表达式表示: ch>='A'&&ch<='Z'||ch>='a'&&ch<='z' (2)当用逻辑运算符连接数值表达式时,运算结果也是非0即1。例如: 25&&-3 //其值为1 0||356 //其值为1 !(-23) //其值为0 (3)一个逻辑表达式中可能含有算术运算符、关系运算符、赋值运算符等多种运算 符,计算逻辑表达式的值时,应按各种运算符的优先级和结合性一步一步进行计算。 【例3-10】 逻辑表达式的计算。程序如下: #include #define EOF 1 void main() { int a=3,b=4,c=5; printf("%d\n",3*(a+b)>c&&a++||c!=0&&!EOF); } 该逻辑表达式包含多种运算符,其计算过程如图3-2所示。 ·46· 图3-2 逻辑表达式3*(a+b)>c&&a++||c!=0&&!EOF的计算过程 其中,方框内的数字是各步计算的结果,圆圈内的数字是计算的顺序编号。程序运行 结果为1。 (4)在计算形如(表达式1)&&(表达式2)&&…的逻辑与表达式时,只要有一个 表达式的值为“假”,那么整个逻辑表达式的值就为“假”。因此,C语言规定在计算这种逻 辑表达式时,先计算表达式1,如果其值是逻辑真或非0,就接着计算表达式2,以此类推, 如果计算到第一个为逻辑假的表达式,就可判定整个逻辑表达式为逻辑假,其后的表达式 将不被计算。 【例3-11】 逻辑表达式中的特殊计算规则。程序如下: #include void main() { int a=1,b=2,c=3,d=4,m=2,n=2; (m=a>b)&&(n=c>d); printf("%d %d\n",m,n); } 程序第3行是逻辑表达式构成的语句,而该逻辑表达式是求两个赋值表达式的逻辑 与。执行时,先计算m=a>b,结果得到m=0。对于逻辑与来说,只要有一个运算量为0, 整个逻辑表达式的值即为0,因此表达式n=c>d并不被计算。这样,变量n就没有被赋 值,其值仍为2,因此程序输出结果如下: 0 2 (5)同样,在计算形如(表达式1)||(表达式2)||…的逻辑或表达式时,先计算表达 式1,如果其值是逻辑“假”或0,就接着计算表达式2,如果计算到第一个为逻辑“真”的表 达式,就可判定整个逻辑表达式为逻辑“真”,其后的表达式将不被计算。 【例3-12】 设x为int型变量,请写出与关系表达式x==0等价的最简单的逻辑表 达式及能表示x为正奇数的逻辑表达式。 关系表达式x==0表示的含义如下:当x等于0时,为逻辑真;否则,为逻辑假。与 之等价的逻辑表达式是!x。因为对!x而言,若x等于0,则!x为非0,即逻辑真;若x不等 于0,则!x为0,即逻辑假。 为了判断x是不是奇数,相当于判断x能否被2整除,可以用x%2!=0或x%2==1 ·47· 来判断。因此,表示正奇数的逻辑表达式是x>0&&x%2!=0,也可以写成x>0&&x%2 等多种形式。 3.5 位运算 C语言是介于汇编语言和高级语言之间的一种程序设计语言,它综合了高级语言的 要素和汇编语言的机能,可以实现汇编语言位操作的功能。其中,C的低级语言功能主要 是通过指针、位运算和位段结构体现的。所谓位运算,是对字节或字节内部的二进制位进 行测试、设置、移位或逻辑运算。在一些系统软件和应用软件中,常要处理二进制位的问 题。二进制位操作符及功能如表3-2所示。 表3-2 二进制位操作符及功能 名 称运算符功 能 按位与运算符& 用于将一个单元清零、取一个数中的某些指定位以及保留指定位 按位或运算符| 常被用来将一个数的某些位置“1” 按位异或运算符^ 判断两个位值,不同为1,相同为0。常用来使特定位翻转等 按位取反运算符~ 常用来配合其他位运算符使用,常用来设置屏蔽字 左移位运算符<< 将一个数的各二进制位全部左移,高位左移后溢出,舍弃不起作用。左移一 位相当于该数乘以2,左移n 位相当于乘以2n 。左移比乘法运算要快得多 右移位运算符>> 右移时,要注意符号问题。对无符号数,右移时左边高位移入0,对于有符 号数,如果原来符号位为0(正数),则左边移入0;如果符号位为1(负数), 则左边移入0还是1要取决于计算机系统。移入0的称为“逻辑右移”,移 入1的称为“算术右移” 位操作经常应用于设备驱动程序中,如调制解调器驱动程序中用于屏蔽某些位,实现 奇偶校验;在磁盘文件管理中如果希望文件不可读,可用密码子程序将文件中的字符逐位 取反;有些C编译系统用左移位实现乘2运算,用右移位实现除2运算,其速度非常快。 位操作分为两类:一类为位逻辑运算,另一类为移位运算。位逻辑运算实现二进制 位与二进制位之间的逻辑运算,移位运算实现二进制位的顺序向左或向右移位。 1.位逻辑运算符 位逻辑运算用来对某一个或某一对二进制位进行操作,其中: ~表示按位取反。 & 表示按位与。 |示按位或。 ^表示按位异或。 除了“~”是一元运算符外,其他都是二元运算符,它们的运算优先级和结合性参见附 录A。 ·48· 2.位逻辑运算规则 (1)和前面介绍的逻辑运算(&&、||、!)一样,位逻辑运算规则也用真值表表示。假 设p 和q 分别表示一个二进制位,则按位逻辑运算符的真值表如表3-3所示。 表3-3 位逻辑运算真值表 p q p&q p|q p^q ~p 0 0 0 0 0 1 0 1 0 1 1 1 1 0 0 1 1 0 1 1 1 1 0 0 (2)按位与(&)是对参加运算的两个整数按二进制位逐对进行“与”运算:如果一对 二进制位中有一个位是0,结果就是0;只有两个位都是1时,结果才为1。例如,令a=65, b=120;则c=a&b 为64。 01000001 (操作数a:整数65) 01111000 (操作数b:整数120) & 01000000 (结果c:整数64) (3)按位或(|)是对参加运算的两个整数按二进制位逐对进行“或”运算:如果一对 二进制位中有一个是1,结果就是1;只有两个位都是0时,结果才是0。例如,令a=65, b=120;则c=a|b 为121。 01000001 (操作数a:整数65) 01111000 (操作数b:整数120) | 01111001 (结果c:整数121) (4)按位异或(^)是对参加运算的两个整数按二进制位逐对进行“异或”运算:如果 一对二进制位中两个位相同(都是1或都是0),则结果为0;当两个位不同时,结果为1。 换言之,“异或”就是当参加运算的两个二进制位不同(异)时,进行“或”运算。例如,令 a=65,b=120;则c=a^b 为57。 01000001 (操作数a:整数65) 01111000 (操作数b:整数120) ^ 00111001 (结果c:整数57) (5)按位取反(~)是将参加运算的一个整数的各个二进制位逐位取反,相当于求该 数的反码。例如,令a=65;则c=~a 为190(无符号数)或者-66(有符号数,以补码形 式存放)。 ·49· 01000001 (操作数a:整数65) ~ 10111110 (结果c:190或-66) 注意: (1)位逻辑运算的运算对象只能是包括char型和各种长短不同的int型在内的 整数。 (2)和逻辑运算的结果只能是0或1不同,按位逻辑运算的结果可能是任何整数值。 3.移位运算符 (1)左移位运算符。左移位运算符是<<,它是一个二元运算符,用它连接两个操作 数的一般格式如下: v <>,它也是一个二元运算符,用它连接两个操 作数的一般格式如下: v >>n 其中,v 是操作数,可以是char型或整型的变量或表达式;n 必须是正整数。功能是将v 中的所有二进制位顺序向右移动n 位。 4.移位运算规则 (1)左移位运算规则。在移位过程中,各个二进制位顺序向左移动,右端空出的位 补0,移出左端之外的位则被舍弃。 例如,设a=25,其二进制存储形式如图3-3所示。 图3-3 25的二进制存储形式 则a<<3表示将a 的各个二进制位顺序左移3位,如图3-4所示。 图3-4 a左移3位后的二进制形式 对无符号数而言,左移位相当于乘2运算,左移n 位相当于该数乘2n ,在本例中, 25<<3表示25×23=200。 (2)右移位运算规则。在移位过程中,各个二进制位顺序向右移动,移出右端之外的 位被舍弃,左端空出的位是补0还是补1取决于具体的机器和被移位的数是有符号数还 ·50· 是无符号数。具体如下: ① 对无符号数进行右移时,左端空出的位一律补0。 ② 对用补码表示的有符号数进行右移时,有的机器采取逻辑右移,有的机器则采取 算术右移。逻辑右移时,不管是正数还是负数,左端空位一律补0。算术右移时,正数右 移,左端的空位全部补0;负数右移,左端的空位全部补1(即符号位)。VisualC++ 2010 采用的是算术右移。 例如,设a=-32768(补码表示),其二进制存储形式如图3-5所示。 图3-5 -32768的二进制存储形式 则a>>2表示将a 的各个二进制位顺序右移2位。若采用逻辑右移,其值为8192,如 图3-6所示。 图3-6 8192的二进制存储形式 若采用算术右移,值为-8192,如图3-7所示。 图3-7 -8192的二进制存储形式 采用算术右移相当于除2运算。右移1位,其值等于该数除以2,右移n 位,其值等 于该数除以2n 。本例中,-32768>>2表示-32768/22=-8192。 5.位操作运算符组成的复合赋值运算符 本章第2节介绍了5种由算术运算符组成的复合赋值运算符,本节介绍的6种位操 作运算符中,除了一元运算符~外,其他5种二元运算符可组成另外5种复合赋值运 算符: v& =e 等价于v=v&e v|=e 等价于v=v|e v^=e 等价于v=v^e v<<=e 等价于v=v<>=e 等价于v=v>>e 【例3-13】 位操作及由位操作运算符组成的复合赋值运算符。程序如下: #include main() { int a=25,b=120,c1,c2,n=1; c1=a,c2=b; ·51· a&=b; b|=a; printf("a=%d&%d=%d b=%d|%d=%d\n",c1,c2,a,c2,c1,b); a=c1,b=c2; a>>=n+1; b<<=n+1; printf("a=%d>>%d=%d b=%d<<%d=%d\n",c1,n+1,a,c2,n+1,b); a=c1,b=c2; a=~a; b^=a; printf("a=~%d=%d b=%d^%d=%d\n",c1,a,c2,c1,b); } 程序的运行结果如下: 3.6 常用数学函数 C编译系统在标题文件math.h中定义了众多的数学函数。当需要调用这些数学函 数时,要用#include命令行包含该标题文件。表3-4列出的函数中,自变量x 和y 为double型,n 为int型,所有的函数值均为double型,三角函数的自变量以弧度为 单位,反三角函数的函数值以弧度为单位。 表3-4 常用的数学函数 函 数功 能 sin(x) x 的正弦,值域为[-1,1] cos(x) x 的余弦,值域为[-1,1] tan(x) x 的正切 asin(x) x 的反正弦,定义域为[-1,1],值域为[-π/2,π/2] acos(x) x 的反余弦,定义域为[-1,1],值域为[0,π] atan(x) x 的反正切,定义域为[-1,1] atan2(y,x) y/x 的反正切,y/x 的取值范围为[-1,1] sinh(x) x 的双曲正弦,即(ex -e-x )/2 cosh(x) x 的双曲余弦,即(ex +e-x )/2 tanh(x) x 的双曲正切,即(ex -e-x )/(ex +e-x ) exp(x) 指数函数,ex log(x) 自然对数,即lnx,x>0 ·52· 续表 函 数功 能 log10(x) 常用对数,即lgx,x>0 pow(x,y) xy ,若x=0且y≤0,或x<0但y 不是整数时出错 pow10(n) 幂函数,10n sqrt(x) x 的平方根,x≥0 ceil(x) 不小于x 的最小整数,double型函数,例如:ceil(1.02)=2.0,ceil(-1.02)=-1.0 floor(x) 不大于x 的最大整数,double型函数,例如:floor(1.02)=1.0,floor(-1.02)=-2.0 fabs(x) x 的绝对值 fmod(x,y) x/y 的浮点余数,其符号与x 相同。例如:fmod(10.0,3.0)=1.0 【例3-14】 求cos(30°)的值。程序如下: #include main() { double x,y; x=30.0; y=cos(x/180*3.14159); printf("%f\n",y); } 程序运行结果如下: 0.866026 【例3-15】 求65.2值。 #include main() { double x; x=pow(6.0,5.2); printf("x=%.3f\n",x); } 程序运行结果如下: x=11127.216 【例3-16】 求e3.2145689的值。 #include main() { double x; x=exp(3.2145689); printf("x=%.7lf\n",x); } ·53· 程序运行结果如下: x=24.8925584 习题3 一、选择题 1.C语言中要求运算量必须是整型的运算符是( )。 A.+ B./ C.% D.- 2.在C语言中,不同类型的数据混合运算时,先要转换成同一类型,然后进行计算。 设一表达式中含有int、long、unsigned和char类型的常数和变量,则表达式的最后运算 结果是( 【1】 )类型。这4种类型的转换规律是( 【2】 )。 【1】A.int B.char C.unsigned D.long 【2】A.int→unsigned→long→char B.char→int→long→unsigned C.char→int→unsigned→long D.char→unsigned→long→int 3.a、b均为整数,且b≠0,则表达式a/b*b+a%b的值为( )。 A.a B.b C.a被b除的整数部分 D.a被b除商的整数部分 4.a、b均为整数,且b≠0,则表达式a-a/b*b的值为( )。 A.0 B.a C.a被b除的余数部分 D.a被b除商的整数部分 5.表达式18/4*sqrt(4.0)/8的数据类型为( )。 A.int B.float C.double D.不确定 6.若变量已正确定义,且k的值是4,执行表达式j=k--后,j、k的值是( )。 A.j=4,k=4 B.j=4,k=3 C .j=3,k=4 D .j=3,k=3 7.设intx=10,x+=3+x%(-3),则x=( )。 A.14 B.15 C.11 D.12 8.表达式(int)(3.0/2.0)的值是( )。 A.1.5 B.1.0 C.1 D.0 9.设floatm=4.0,n=4.0;使m 为10.0的表达式是( )。 A.m-=n*2.5 B . m/=n+9 C .m*=n-6 D . m+=n+2 10.C语句x*=y+2;还可以写成( )。 A.x=x*y+2; B.x=2+y*x; C.x=x*(y+2); D.x=y+2*x; 11.若变量已正确定义,要将a和b 中的数进行交换,则下列不正确的语句组 是( )。 A.a=a+b,b=a-b,a=a-b; B. t= a ,a =b,b=t; C.a=t;t=b;b=a; D .t =b ;b=a;a=t; ·54·