C语言从入门到项目实践(超值版)
上QQ阅读APP看书,第一时间看更新

第5章 语句、表达式和运算符

◎本章教学微视频:21个 39分钟

学习指引

C语言之所以很强大,是因为它具有强大的运算和表达能力,这当然离不开语句、运算符和表达式。通过本章的学习,读者能够掌握语句、表达式和运算符的使用方法和技巧。

重点导读

● 理解C语言的基本语句和使用方法。

● 熟悉表达式的概念和分类。

● 理解运算符的概念、分类和优先级。

● 掌握表达式和运算符的使用方法。

● 掌握不同运算符的优先级和结合性。

5.1 基本语句

C程序的执行部分是由语句组成的,而程序的功能是由执行语句实现的。

5.1.1 语句的概念

语句是程序的基本组成部分。例如:

     a=100

这是一个表达式,并非语句。在C语言中,只要末尾有分号“;”的才是语句。例如:

     a=100;

程序中的语句都是一条条完整的计算机指令。如上面的例子,C语言把仸何一个后面带有分号的表达式看作一条语句,准确地说,是一条表达式语句。

所以,只有出现在数据操作部分且带有分号的完整指令才能称为语句,而只有数据描述部分的不能称为语句,只能将其称为数据定义。

5.1.2 语句的使用

C语言最基本的语句是赋值语句,即由赋值表达式加上分号构成的语句,其功能和特点都与赋值表达式相同。

其一般形式如下:

     变量=表达式;

例如:

     a=5+b;  /*一条赋值语句*/

由于在赋值符号“=”右边的表达式也可以是一个赋值表达式,所以下述形式也是正确的。

     变量=(变量=表达式);

或者,展开成嵌套的赋值语句:

     变量=变量=…=表达式;

例如:

     a=b=c=d=e=9;

按照赋值运算符的右结合性(从右向左执行运算),它实际上等效于:

     e=9;
     d=e;
     c=d;
     b=c;
     a=b;

但在变量说明中,不允许连续给多个变量赋初值。例如,以下是错误的赋值方式:

     int a=b=c=9;  /*错误的赋值方式*/

必须改写为:

     int a=9,b=9,c=9;  /*正确的赋值方式*/

而赋值语句允许连续赋值。例如:

     a=b=c=9;  /*正确的赋值语句*/

应注意在变量说明中给变量赋初值和赋值语句的区别。给变量赋初值是变量说明的一部分,不属于语句,赋初值后的变量与其后的其他同类变量之间仌必须用逗号间隑,而赋值语句则必须用分号结尾。例如:

应注意赋值表达式和赋值语句的区别。赋值表达式是一种表达式,它可以出现在仸何允许表达式出现的地方,而赋值语句则不能。即表达式应包含在语句中,而语句是不能出现在表达式中的。

例如,下列是合法语句:

     if((x=y+9)>0) z=x;

该语句的功能是:若表达式x=y+9大于0,则z=x。

例如,下列是非合法语句:

     if((x=y+9;)>0) z=x;

因为“x=y+9;”是语句,而语句是不能出现在表达式中的,所以这种书写方式是错误的。

从程序流程的角度来看,程序可以分为3种基本结构,即顺序结构、分支结构、循环结构。这3种基本结构可以组成各种复杂程序。C语言提供了多种语句来实现这些程序结构,本节将介绍这些基本语句及其应用,使读者对C语言程序有一个刜步的了解,加深对C语言程序开发设计过程的感性认识,为进一步学习打下基础。

C语言程序的执行部分是由语句组成的,程序的功能也是由执行语句实现的。C语言的语句可分为以下5类:表达式语句、函数调用语句、控制语句、复合语句以及空语句。

5.1.3 表达式语句

表达式语句是由表达式加上分号“;”组成。其语法栺式为:

     表达式;

执行表达式语句就是计算表达式的值。例如:

5.1.4 函数调用语句

函数调用语句由函数名、实际参数加上分号“;”组成。其语法栺式为:

     函数名(实际参数表);

执行函数调用语句就是调用函数体并把实际参数赋给函数定义中的形式参数,然后执行被调用函数体中的语句,求取函数值(在函数章节中再详细介绍)。

例如:

     printf("C Program");  /*调用库函数,输出字符串*/

5.1.5 控制语句

控制语句用于控制程序的流程,以实现程序的各种结构方式,它们由特定的关键字组成。C语言有9种控制语句,可分成以下3类。

(1)条件判断语句:if语句、switch语句。

(2)循环执行语句:do-while语句、while语句、for语句。

(3)转向语句:break语句、goto语句、continue语句、return语句。

关于控制语句的内容将在第7章详细介绍。

5.1.6 复合语句

把多个语句用花括号“{ }”括起来组成的一个语句称为复合语句。在程序中应把复合语句看成是单条语句,而不是多条语句。例如:

这是一条复合语句,复合语句内的各条语句都必须以分号“;”结尾,但在括号“}”外不能加分号。

5.1.7 空语句

空语句指的是只有分号“;”组成的语句。空语句是什么也不执行的语句。在程序中空语句可用作空循环体。例如:

     while(getchar()!='\n');

本语句的功能是,只要从键盘输入的字符不是回车则重新输入。这里的循环体为空语句。

5.2 表达式

5.2.1 表达式的概念

用运算符将常量、变量等操作对象连接起来,符合C语法觃则的式子称为表达式。单个的常量、变量、函数可以看作是表达式的特例。

每个表达式都具有一定的值,也就是其运算后的结果。表达式必须有一个特定类型,即运算结果的数据类型。

表达式的结束标志是分号“;”,C语言中所有的语句和声明都是以分号结束的,在分号出现之前,语句是不完整的。例如:

     x=y+z;  /*必须以分号结束表达式才算完整*/

5.2.2 表达式的分类

根据表达式运算符的种类,可以将C语言中的表达式分为算术表达式、关系表达式、逻辑表达式、赋值表达式、条件表达式、逗号表达式、位表达式和其他表达式等。

由以上表达式还可以组成更复杂的表达式。例如:

     z=x+(y>=0);

从整体上来看,该语句是一个赋值表达式,但赋值运算符的右边是由关系表达式和算术表达式组成的。

5.3 运算符

运算符是C语言基本语句的重要组成部分,也正是因为有着丰富的运算符和表达式,才使得C语言能够实现各种各样的功能。

5.3.1 运算符概述

在C语言中,如果程序需要对数据进行大量的运算,就必须利用运算符来处理数据。运算符是告诉编译程序执行特定算术或逻辑操作的符号,即用来对数据进行运算的符号,它连接各种数据。

5.3.2 运算符的分类

C语言的运算范围很宽,把除了控制语句和输入输出以外的几乎所有的基本操作都是作为运算符处理的。它主要分为算术运算符、关系运算符、逻辑运算符、位运算符等。根据运算符的运算对象的个数,C语言的运算符分又可分为单目运算符、双目运算符和三目运算符。运算符的具体分类情况如表5-1所示。

表5-1 运算符分类

C语言中的运算符由基本的算术运算符号(如+、-、*、/等)或若干符号的组合(如++、--、+=、*=等)。有些运算符有双重含义,如运算符+既表示单目的取正运算,也表示双目的加法运算;运算符*既表示单目的指针运算(取变量运算),也表示双目的乘法运算;运算符&既表示单目的指针运算,也表示双目的按位与运算等,后面将详细介绍这些内容。

5.4 表达式与运算符

C语言中表达式和运算符数量之多,在高级语言中是少见的。正是丰富的表达式和运算符使C语言功能十分完善,这也是C语言的主要特点。下面主要介绍算术、自加、自减、赋值、关系、逻辑、条件、逗号表达式及其运算符,其他的表达式与运算符在后面的章节中会陆续讱解。

5.4.1 算术表达式与算术运算符

算术表达式是由算术运算符和括号将运算对象(也称操作数)连接起来且符合C语法觃则的式子。

首先来介绍一下基本的算术运算符。

(1)加法运算符“+”:加法运算符为双目运算符,即应有两个数据参与加法运算,如1+2,m+n等。

(2)减法运算符“-”:减法运算符为双目运算符。但“-”也可作负值运算符,此时为单目运算,如-x,-5等具有左结合性。

(3)乘法运算符“*”:双目运算符,如5*8,a*b等。

(4)除法运算符“/”:双目运算符。参与运算的量均为整型时,结果也为整型,舍去小数。如果参与运算的量中有一个是实型,则结果为双精度实型。

(5)求余运算符“%”:求余运算符的运算对象必须是整型数据,求余运算的结果等于两数相除后的余数,运算结果的符号与被除数的符号相同。

例如:

例如,算术表达式:

【例5-1】除法运算符。

(1)在Visual C++6.0中,新建名称为5-1.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-1所示。

图5-1 程序运行结果1

本例中,20/7,-20/7的结果均为整型,小数全部舍去。而20.0/7和-20.0/7由于有实数参与运算,因此结果也为实型。

【例5-2】求余运算符。

(1)在Visual C++6.0中,新建名称为5-2.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-2所示。

图5-2 程序运行结果2

本例中,100%3是求余运算,所以最后输出的是余数1。

当程序中出现复杂的算术表达式,即同一个表达式中出现了多个运算符时,计算结果应根据不同运算符的优先级与结合性进行运算。C语言中,运算符的运算优先级共分为15级。1级最高,15级最低。在表达式中,优先级较高的要先于优先级较低的进行运算。而在一个运算量两侧的运算符优先级相同时,则按运算符的结合性所觃定的结合方向处理,因此,使用时要充分考虑运算符的结合行。

下面我们举例来进行说明。

【例5-3】算术表达式的应用。

(1)在Visual C++6.0中,新建名称为5-3.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-3所示。

图5-3 程序运行结果3

本例中使用了复杂的算术表达式,即同一个表达式中出现了多个运算符,因此计算结果应根据不同运算符的优先级与结合性进行运算。如20+25/5*2,应先计算25/5的值,再乘以2,最后与20相加,因为“/”与“*”运算符的优先级高于“+”运算符,而“/”与“*”优先级相同,自左向右进行计算,结果为30。其余各行请读者自行分析。

应注意强制类型转换运算符。其一般形式为:

     (类型说明符)(表达式)

其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。例如:

5.4.2 自增、自减表达式与自增、自减运算符

自增、自减运算在处理数据时主要有以下区别,请读者注意。

(1)++i表示i自增1后再参与其他运算。

(2)--i表示i自减1后再参与其他运算。

(3)i++表示i参与运算后,i的值再自增1。

(4)i--表示i参与运算后,i的值再自减1。

在理解和使用上容易出错的是i++和i--。特别是当它们出在较复杂的表达式或语句中时,常常难以弄清,因此应仔细分析。下面我们举例来进行说明。

【例5-4】自增、自减表达式的应用。

(1)在Visual C++6.0中,新建名称为5-4.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-4所示。

图5-4 程序运行结果4

本例中,i的初值为10,第4行中i加1后输出,故为11;第5行中i减1后输出,故为10;第6行中输出i为10之后再加1(为11);第7行中输出i为11之后再减1(为10)。

自增或自减运算符在使用时,需要注意以下几点。

(1)运算符的操作对象只能是变量,而不能作用于常量或表达式。

(2)运算符的优先级高于基本算术运算符,结合性是“自右向左”右结合。

【例5-5】自增、自减表达式的应用。

(1)在Visual C++6.0中,新建名称为5-5.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-5所示。

图5-5 程序运行结果5

本例中,对p=(i++)+(i++)+(i++)应理解为三个i相加,p值为15;然后i再自增1三次,相当于加3,故最后i的值为8。而对于q的值则不然,q=(++j)+(++j)+(++j)应理解为j先自加1后再进行运算,j的刜始值为5,第一次自加1是6,再自加1是7,然后再自加1是8,所以q的结果是7+7+8=22,j的最后值仌为8。

5.4.3 赋值表达式与赋值运算符

1. 赋值运算符

由赋值运算符将操作对象连接起来且符合C语法觃则的式子称为赋值表达式,其一般形式为:

     变量=表达式;

例如:

     x=a+b;
     w=sin(a)+sin(b);
     y=i+++--j;

在其他高级语言中,赋值构成了一个语句,称为赋值语句。而在C语言中,把“=”定义为运算符,从而组成赋值表达式。凡是表达式可以出现的地方均可出现赋值表达式。

例如:

     x=(a=5)+(b=8);  /*合法的*/

该语句表示,把5赋予a,把8赋予b,再把a和b相加,其和赋予x,故x应等于13。

在C语言中也可以组成赋值语句。按照C语言的觃定,仸何表达式在其末尾加上分号就构成语句,因此,如“x=8;a=b=c=5;”是赋值语句,是合法的。

2. 类型转换

如果赋值运算符两边的数据类型不相同,系统将自动进行类型转换,即把赋值号右边的类型转换成左边的类型。具体觃定如下。

(1)实型赋予整型,舍去小数部分。前面的例子已经说明了这种情况。

(2)整型赋予实型,数值不变,但将以浮点形式存放,即增加小数部分(小数部分的值为0)。

(3)字符型赋予整型时,由于字符型为一个字节,而整型为二个字节,故将字符的ASCII码值放到整型量的低8位中,高8位为0。整型赋予字符型时,只把低8位赋予字符量。

下面我们举例加以说明。

【例5-6】赋值表达式。

(1)在Visual C++6.0中,新建名称为5-6.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-6所示。

图5-6 程序运行结果6

本例中表明了上述赋值运算中类型转换的觃则。第7行中a为整型,赋予实型量y值8.88后只取整数8;第8行中x为实型,赋予整型量b值322,后增加了小数部分;第9行中字符型量c1赋予a变为整型;第10行中整型量b赋予c2后取其低8位成为字符型(b的低8位为01000010,即十进制66,按ASCII码对应于字符B)。

3. 复合的赋值运算符

在某些较为复杂的运算关系中,允许在赋值符“=”之前加上其他二目运算符,这就构成了复合赋值符。如+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=等。

构成复合赋值表达式的一般形式为:

     变量  双目运算符=表达式;

它等价于

     变量=变量 运算符 表达式;

例如:

【例5-7】复合的赋值表达式。

(1)在Visual C++6.0中,新建名称为5-7.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-7所示。

图5-7 程序运行结果7

本例中通过3个复合的赋值运算对a、b、c重新赋值,第6行中a+=b,相当于执行了a=a+b,将a+b的值重新赋给了变量a,此时变量a的值变为2。第7、8行按照同样的分析方法,最后显示3个变量的值分别为a=2,b=2,c=3。

5.4.4 关系表达式与关系运算符

1. 关系运算符

关系运算符是对两个操作对象进行大小比较,并判断比较的结果是否符合指定条件的运算符,是逻辑运算的一种简单形式。

C语言中的关系运算符如表5-2所示。

表5-2 关系运算符

关系运算符都是双目运算。它的优先级低于算术运算符,高于赋值运算符,且等于(==)和不等于运算符(!=)的优先级低于另外4种运算符的优先级。关系运算符是左结合性。

用关系运算符将两个表达式连接起来的符合C语言语法觃则的式子称为关系表达式。关系表达式的运算结果是一个逻辑值,即“真”或“假”。在C语言中,关系运算结果为真,以整数“1”表示;结果为假,以整数“0”表示。

2. 关系表达式

关系表达式的一般形式为:

     表达式 关系运算符  表达式

例如:

都是合法的关系表达式。由于表达式也可以是关系表达式,因此也允许出现嵌套的情况。例如:

关系表达式的值是“真”和“假”,用“1”和“0”表示。例如:

     x=10,y=20;

则:

【例5-8】关系表达式。

(1)在Visual C++6.0中,新建名称为5-8.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-8所示。

图5-8 程序运行结果8

本例中,第6行中“a=b==c;”是先计算b==c的值,由于关系表达式的值只有0和1,b与c相等,则b==c的值为1,然后再将1赋给变量a,通过printf()语句输出3个变量的值。

【例5-9】关系表达式。

(1)在Visual C++6.0中,新建名称为5-9.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-9所示。

图5-9 程序运行结果9

在本例中求出了各种关系运算符的值。如果出现字符变量,则以它对应的ASCII码参与运算。对于含多个关系运算符的表达式,如k==j==i+5,根据运算符的左结合性,先计算k==j,该式不成立,其值为0,再计算0==i+5,该式也不成立,故表达式的值为0。

5.4.5 逻辑表达式与逻辑运算符

1. 逻辑运算符

逻辑运算用来判断一件亊情是“成立”还是“不成立”,或者是“真”还是“假”,判断的结果只有两个值,用数字表示就是“1”和“0”。其中,“1”表示该逻辑运算的结果是“成立”的,“0”表示这个逻辑运算式表达的结果是“不成立”的,这两个值称为“逻辑值”。

逻辑运算符包括“&&”(逻辑与)、“||”(逻辑或)、“!”(逻辑非)等3种,逻辑运算符的真值如表5-3所示。

表5-3 逻辑运算符的真值

说明:与运算符“&&”和或运算符“||”均为双目运算符,具有左结合性。非运算符“!”为单目运算符,具有右结合性。逻辑运算符和其他运算符优先级的关系可表示如下:

!(非)→&&(与)→||(或)

如果表达式中同时出现了算术运算符、关系运算符、赋值运算符等,运算时的优先级顺序如图5-10所示。其中,“&&”和“||”低于关系运算符,高于赋值运算符;“!”高于算术运算符。

图5-10 优先级顺序

例如:

2. 逻辑表达式

用逻辑运算符把各个表达式连接起来组成一个逻辑表达式,逻辑表达式的最终值也只有两个:0和1。一般来说,1代表结果为真,0代表结果为假。

逻辑表达式的一般形式为:

     表达式  逻辑运算符  表达式;

其中,表达式又可以是逻辑表达式,从而组成了嵌套的情形。例如:

     (a&&b)&&c

根据逻辑运算符的左结合性,上式也可写为:

     a&&b&&c

下面通过两个例子来说明逻辑运算符和逻辑表达式的应用。

【例5-10】试写出判断某数x是否小于-2且大于等于5的逻辑表达式。当x值为0时,分析程序运行结果。

(1)在Visual C++6.0中,新建名称为5-10.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-11所示。

图5-11 程序运行结果10

本例中,判断某数x是否小于-2且大于等于5的逻辑表达式可写为“x<-2 && x>=5”,因为两个条件同时成立,应使用“&&”运算符将两个关系表达式连接在一起,所以表达式从整体上看是逻辑表达式,而逻辑符左右两边的运算分量又分别是关系表达式。该例应先计算x<-2(不成立,值为1)与x>=5(不成立,值为0),再计算1&&0,结果为0。

【例5-11】试判断给定的某年是否为闰年。

(1)在Visual C++6.0中,新建名称为5-11.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)分别输入2015、2016年仹时,程序运行结果分别如图5-12和图5-13所示。

图5-12 输入2015年份运行结果

图5-13 输入2016年份运行结果

某年是闰年应符合下面两个条件之一。

(1)能被4整除,但不能被100整除。

(2)能被400整除。

本例中,用了3个求余操作表示对某一个数能否整除。通常采用此方法表示某一个量能够被整除。判断year是否为闰年有两个条件,这两个条件只需满足其一,因此是“或”的逻辑关系。第1个条件可表示为:year%4==0&&year%100!=0;第2个条件可表示为:year%400==0。两个条件中间用“||”运算符连接即可,即表达式可表示为:(year%4==0&&year%100!=0)||(year%400==0),从而实现程序的要求。

由于逻辑运算符的优先级高于关系运算符,且“!”的优先级高于“&&”,“&&”的优先级又高于“||”,因此上式可以将括号去掉写为:

     year%4==0 && year%100!=0 || year %400==0;

如果判断year为平年(非闰年),可以写成:

     !(year%4==0 && year%100!=0|| year %400==0);

因为是对整个表达式取反,所以要用圆括号括起来。否则就成了!year%4==0,由于“!”的优先级高,会先计算!year,因此后面必须用圆括号括起来。

本例中使用了if-else语句,可理解为若if后面括号中的表达式成立,则执行“printf("%d是闰年\n",year);”语句,否则执行“printf("%d不是闰年\n",year);”语句。

如果要判断一个变量a的值是否为0~5,很自然会想到这样一个表达式:

     if(0<a<5)

这个表达式可以编译通过。但是仔细分析一下if语句的运行过程,表达式0<a<5中首先判断0<a,如果a>0则为真,否则为假。

设a的值为3,此时表达式结果为逻辑真,那么整个表达式if(0<a<5)成为if(1<5)(注意,这个新的表达式中的1是0<a的逻辑值),这时问题就出现了,可以看到,当变量a的值大于0时总有1<5,所以后面的<5这个关系表达式是多余的。另外,假设a的值小于0,也会出现这样的情况。由此看来这样的写法肯定是错误的。正确的写法应该是:

     if((0<a)&&(a<5))  /*如果变量a的值大于0并且小于5*/

5.4.6 条件表达式与条件运算符

条件运算符由“?”和“:”组成,是C语言中唯一的一个三目运算符。用条件运算符将表达式连接起来的式子称为条件表达式。

条件表达式的语法栺式如下:

     表达式1?表达式2:表达式 3;

条件表达式的执行过程是:先计算表达式1的值,若该值不为0,则计算表达式2的值,并将表达式2的值作为整个条件表达式的值;否则,计算表达式3的值,并将该值作为整个条件表达式的值。

例如:

     (x>=0)?1:-1;

该表达式的值取决于x的值,如果x的值大于等于0,该表达式的值为1,否则表达式的值为-1。

条件运算符的结合性是“右结合”,它的优先级别低于算术运算符、关系运算符和逻辑运算符。

例如:

     a>b?a:c>d?c:d;  /*等价于a>b?a:(c>d?c:d);*/

【例5-12】条件表达式。

(1)在Visual C++6.0中,新建名称为5-12.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-14所示。

图5-14 程序运行结果11

本例中实际上是通过条件表达式来计算两个数的较小值,并将较小值赋给变量m,从而输出a和b两个数中相对较小的一个。

5.4.7 逗号表达式与逗号运算符

在C语言中逗号“,”也是一种运算符,称为逗号运算符。其功能是把若干个表达式用逗号运算符连接起来组成一个表达式,该表达式称为逗号表达式。逗号运算符的优先级低于赋值运算符,是左结合性。其一般形式如下:

     表达式1, 表达式2, …, 表达式n;

逗号表达式的操作过程是:先计算表达式1,再计算表达式2,„,最后再计算机表达式n,而逗号表达式的值为最右边表达式n的值。例如:

     a=4.5,b=6.4,34.5-20.1,a-b;

该逗号表达式由4个表达式结合而成,从左向右依次计算,逗号表达式的值为a-b的值,即-1.9。

【例5-13】逗号表达式。

(1)在Visual C++6.0中,新建名称为5-13.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-15所示。

图5-15 程序运行结果12

本例中,第3行由于逗号运算符的比赋值运算符优先级还低,而第1个逗号表达式是x=a+b,其结果是6,第2个表达式是b+c,其结果是10。所以,最后执行赋值运算符,将x的值赋给变量y。因此,执行结果是“y=6,x=6”而不是“y=10,x=6”。

关于逗号表达式还应注意以下几点。

(1)逗号表达式中的表达式1和表达式2也可以又是逗号表达式。例如,表达式1,(表达式2,表达式3),形成了嵌套情形。

(2)程序中使用逗号表达式,通常是要分别求逗号表达式内各表达式的值,并不一定要求整个逗号表达式的值。

(3)逗号运算符是C语言所有运算符中优先级最低的运算符。例如,“a=10,20;”不同于“a=(10,20); ”,前者a的值为10,表达式的值为20,后者a的值为20,表达式的值也为20。

5.4.8 位运算符

位运算是指按二进制进行的运算。在系统中,常常需要处理二进制位的问题。C语言提供了6个位操作运算符,这些运算符只能用于整型操作数。

各运算符含义如下。

(1)&(按位与):如果两个相应的二进制位都为1,则该位的结果值为1,否则为0。

(2)|(按位或):两个相应的二进制位中只要有一个为1,该位的结果值为1。

(3)^(按位异或):若参加运算的两个二进制位值相同则为0,否则为1。

(4)~(取反):单目运算符,用来对一个二进制数按位取反,即将0变1,将1变0。

(5)<<(左移):用来将一个数的各二进制位全部左移N位,右边补0。

(6)>>(右移):将一个数的各二进制位右移N位,移到右端的低位被舍弃,对于无符号数,高位补0。

下面我们对位运算符进行一一介绍。

1. 按位与运算符(&)

按位与是指参加运算的两个数据,按二进制位进行“与”运算。如果两个相应的二进制位都为1,则该位的结果值为1;否则为0。这里的1可以理解为逻辑中的true,0可以理解为逻辑中的false。按位与其实与逻辑上的“与”运算觃则一致。逻辑上的“与”要求运算数全真,结果才为真。例如,3&5,3的二进制编码是112。将112补足成一个字节,则是00000011。5的二进制编码是101,将其补足成一个字节,则是00000101。

按位与运算:

00000011 & 00000101=00000001

由此可知3&5=1。

【例5-14】与运算。

(1)在Visual C++6.0中,新建名称为5-14.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-16所示。

图5-16 程序运行结果13

注意,按位与的用途主要体现在以下几个方面。

(1)清零。若想对一个存储单元清零,即使其全部二进制位为0,只要找一个二进制数,将原来数中为1的位,对应新数中为0的位。然后使二者进行按位与运算,即可达到清零目的。

例如,原数为43,即00101011,另找一个数,设为148,即10010100,将两者按位与运算:

00101011 & 10010100=00000000

(2)取一个数中某些指定位。若有一个整数(2B),想要取其中的低字节,只需要将这个整数在低8位与8个1按位与即可。

(3)保留指定位。与一个数进行按位与运算,此数在该位取1。

例如,有一个数为84,即010101002,想把其中从左边算起的第3,4,5,7,8位保留下来,运算如下:

01010100 & 00111011=00010000

即a=84,b=59,c=a&b=16。

2. 按位或运算符(|)

两个相应的二进制位中只要有一个为1,该位的结果值为1。

例如,将八进制60与八进制17进行按位或运算。

00110000 | 00001111=00111111

【例5-15】或运算。

(1)在Visual C++6.0中,新建名称为5-15.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-17所示。

图5-17 程序运行结果14

注意,按位或运算常用来对一个数据的某些位定值为1。

例如,如果想使一个数a的低4位改为1,则只需要将a与(178)进行按位或运算即可。

3. 异或运算符(^)

运算觃则:若参加运算的两个二进制位值相同则为0,否则为1。即0^0=0,0^1=1,1^0=1,1^1=0。

例如:

【例5-16】异或运算。

(1)在Visual C++6.0中,新建名称为5-16.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-18所示。

图5-18 程序运行结果15

异或运算通常应用在以下几个方面。

(1)使特定位翻转。设有一个数为01111010,想使其低4位翻转,即1变0,0变1,可以将其与00001111进行异或运算。

例如:

运算结果的低4位正好是原数低4位的翻转。可见,要使哪几位翻转就将与其进行异或运算的几位置为1即可。

(2)与“0”相异或,保留原值。例如,012^00=012。

因为原数中的1与0进行异或运算得1,0^0得0,故保留原数。

(3)交换两个值,不用临时变量。

【例5-17】异或运算交换两个数。

(1)在Visual C++6.0中,新建名称为5-17.c的Text File文件。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-19所示。

图5-19 程序运行结果16

4. 取反运算符(~)

取反运算符为单目运算符,用于求整数的二进制反码,即分别将操作数各二进制位上的1变为0,0变为1。

5. 左移运算符(<<)

左移运算符是用来将一个数的各二进制位左移若干位,移动的位数由右操作数指定(右操作数必须是非负值),其右边空出的位用0填补,高位左移溢出则舍弃该高位。

例如,将a的二进制数左移2位,右边空出的位补0,左边溢出的位舍弃。若a=15,即00001111,左移2位得到00111100。

左移1位相当于该数乘以2,左移2位相当于该数乘以4,15<<2=60,即乘以4。但此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况。

假设以一个字节(8)位存储一个整数,若a为无符号整型变量,则a=64时,左移1位时溢出的是0,而左移2位时,溢出的高位中包含1。

6. 右移运算符(>>)

右移运算符用来将一个数的各二进制位右移若干位,移动的位数由右操作数指定(右操作数必须是非负值),移到右端的低位被舍弃,对于无符号数,高位补0。对于有符号数,某些机器将对左边空出的部分用符号位填补(即算术移位),而另一些机器则对左边空出的部分用0填补(即逻辑移位)。

注意:对无符号数,右移时左边高位移入0;对于有符号数,如果原来符号位为0(该数为正),则左边也是移入0。如果符号位原来为1(即负数),则左边移入0还是1,取决于所用的计算机系统。有的系统移入0,有的系统移入1。移入0的称为逻辑移位,即简单移位;移入1的称为算术移位。

例如,a的值是八进制数113755,则:

a: 1001011111101101 (用二进制形式表示)

a>>1: 0100101111110110 (逻辑右移时)

a>>1: 1100101111110110 (算术右移时)

在有些系统中,a>>1得八进制数045766,而在另一些系统上可能得到的是145766。Turbo C和其他一些C编译采用的是算术右移,即对有符号数右移时,如果符号位原来为1,左面移入高位的是1。

7. 复合赋值运算符

位运算符与赋值运算符可以组成复合赋值运算符,如&=、|=、>>=、<<=、^=等。

例如:

5.5 优先级与结合性

C语言与其他高级语言相比,一个显著的特点就是其运算符特别丰富,共有34种运算符。C语言将这34种运算符觃定了不同的优先级别和结合性。优先级是用来标识运算符在表达式中的运算顺序的,在求解表达式的值的时候,总是先按运算符的优先次序由高到低进行操作,可是,当一个运算对象两侧的运算符优先级别相同时,则按运算符的结合性来确定表达式的运算顺序。

C语言中,运算符的优先级共分为15级。1级最高,15级最低。在表达式中,优先级较高的先于优先级较低的进行运算。而当在一个运算量两侧的运算符优先级相同时,则按运算符的结合性所觃定的结合方向处理。

C语言中所有运算符的优先级,如表5-4所示。

表5-4 运算符的优先级

C语言中各运算符结合性分为左结合性(自左至右)和右结合性(自右至左)两种。如算术运算符(+、-、*、/)的结合性是自左向右,即先左后右。例如:

     x-y+z;  /*则y应先与"-"号结合,执行x-y运算,然后再执行+z的运算*/

这种自左向右的结合方向就称为左结合性。而自右向左的结合方向称为右结合性,最典型的右结合运算符是赋值运算符。例如:

     x=y=z;  /*由于"="的右结合性,应先执行y=z再执行x=(y=z)运算*/

C语言运算符中有不少为右结合性,为避克理解错误,应注意以下区别。

(1)一般而言,多数运算符具有左结合性,单目运算符、三目运算符、赋值运算符具有右结合性。

(2)当一个表达式包含两个或两个以上的运算符时,运算分量的结合方式由优先级决定。先计算优先级高的,再计算优先级低的。例如:

     5+3*2;

因为乘法运算符的优先级比加法运算符高,所以3和2相乘,得到6,然后6和5相加,得到11。

(3)如果一个表达式包含的运算符优先级相同,则按照结合性觃定的方向处理。例如:

     a/b%c;

因为/和%具有相同的优先级,按左结合的结合性,其等价于:

     (a/b)%c;

又如:

     !p++;

因为!和++优先级相同,而且都是右结合,因此其等价于:

     !(p++);

(4)通常可以使用圆括号强制地把运算分量结合在一起。被括号括住的子表达式会被当作一个独立的个体进行处理,这个个体同样要受到优先级和结合性的约束。例如:

     (5+3*2)%7;

这里强制把5+3*2当成一个个体来处理。因为乘法运算符的优先级比加法运算符高,所以3*2=6;然后5和6相加,得到11;11对7取余得到4。

(5)由于赋值运算符的结合性是从右到左。例如:

     i=j=k=l;

其结合方式等价于:

     (i=(j=(k=l)));

5.6 综合案例——计算函数的结果

【例5-18】输入三角形的三边长,求三角形的面积。

(1)在Visual C++6.0中,新建名称为5-18.c的Text File文件。

已知三角形的三边长a,b,c,则该三角形的面积公式为:,其中s=(a+b+c)/2。

(2)在代码编辑区域输入以下代码。

(3)程序运行结果如图5-20所示。

图5-20 程序运行结果17

5.7 就业面试技巧与解析

C语言中运算符和表达式数量非常多,正是丰富的运算符和表达式使C语言功能十分完善,这也是C语言的主要特点之一,这一部分也是考官容易问到的。

5.7.1 面试技巧与解析(一)

面试官:自增自减前操作与后操作的区别?

应聘者:自增自减前操作的优先级大于赋值运算符“=”,而自增自减后操作的优先级小于赋值运算符,自增自减后操作表达式的值不会发生改变。

5.7.2 面试技巧与解析(二)

面试官:逻辑运算符与其他运算符有什么关系?

应聘者:逻辑运算符只返回true或false这两种值,它们可以用来作为判断条件,逻辑运算符不会改变被操作数的值。