
2.4 运算符与表达式
程序中的大部分数据处理是通过运算符和表达式来实现的,同时也有相当一部分语句是由表达式构成的。对常量或变量进行运算或处理的符号称为运算符,参与运算的数据称为操作数,用运算符将操作数连接起来就构成了表达式。 C++中有丰富的运算符,不同的运算符其运算方法和特点也不同。本节只介绍常用的算术运算符、复合赋值运算符、自增自减运算符和逗号运算符,其他运算符在以后的章节中再逐一介绍。
运算符在使用时,应注意:
(1)运算符的目数。每个运算符都有运算目数,即参与运算的操作数个数,根据操作数的个数可将运算符分为单目、双目和三目运算符。条件运算符是唯一的一个三目运算符。
(2)运算符的优先级。每种运算符都有其优先级别,来决定运算的顺序。表达式中出现不同的运算符时,优先级高的先计算,优先级低的后计算。逗号运算符的优先级别最低,其次是赋值运算符。
(3)运算符的结合性。结合性是指当相邻的两个运算符的优先级别相同时的运算顺序,是从左向右算,还是从右往左算。如果一个运算符对其操作数按从左向右的方向进行运算,称此运算符是左结合的,反之称其为右结合的。如a+b-c中,由于加法运算符和减法运算符的优先级别相同,此时在运算时应按照结合性决定运算顺序,从表2-3可知,加法和减法运算符都是左结合性的,因此先计算a+b,而后再减去c。
运算符的优先级和结合性决定了运算的顺序,表2-3中列出了各种运算符及其优先级和结合性。
表2-3 C++的运算符及其优先级和结合性

由表2-3可知,运算符的优先级比较复杂,笼统地讲优先级别从高到低依次是:单目运算符、算术运算符、关系运算符、逻辑运算符、条件运算符、赋值运算符和逗号运算符。
2.4.1 表达式
1.表达式的组成
表达式是由操作数、运算符和小括号按一定规则组成的式子,其中构成表达式的操作数可以是常量、变量、函数等,小括号则可以改变运算的顺序。根据运算符的不同,表达式又分为算术表达式、关系表达式、逻辑表达式等。一个变量、常量或函数调用都是表达式。不管是什么表达式,经过运算后总能得到一个确定的值,而且是有类型的。表达式的计算要根据运算符的意义、优先级、结合性以及类型转换共同决定。
2.表达式的运算顺序
C++中的表达式运算很灵活,运算顺序由运算符的优先级和结合性决定。如下面的表达式:
x=a+b*c/d
由于算术运算符的优先级别比赋值运算符高,因此先计算赋值号右边的表达式。因为乘法运算符*和除法运算符/的优先级比加法运算符+高,因此先计算b*c/d。*和/的优先级别相同,此时的计算顺序由结合性决定。因为算术运算符是左结合性的,因此先计算b*c,然后再除以d,而后再与a相加,最后把运算结果赋值给变量x。
由于小括号的优先级别最高,因此为达到想要的运算顺序,在表达式中可适当使用小括号来改变运算顺序。
3.表达式的书写原则
程序设计语言中的表达式应该按照程序设计语言的规则来进行书写,而不能按照数学上的习惯书写。在C++中书写表达式时应注意:
(1)乘号不能省略。例如,x乘以y,应书写为x*y,而不能写成xy,否则编译器会认为xy是一个变量名。
(2)小括号可以改变运算顺序,但是小括号必须成对出现,而且只能使用小括号。在表达式中可以出现多个小括号,但是必须配对使用,例如,(-b+2*(a-c))/(2*d)。
(3)表达式中没有上标或下标,也没有分式,应从左到右在同一行上并排书写。例如变量名x2不能写成x2,x2应写作x*x。
(4)数学表达式中的某些符号不能在表达式中使用,要使用其他符号或数值代替。例如,对数学表达式2πr,若要写成C++表达式应为2*3.14*r。又如表示百分数25%时不能直接写作25%,因为在C++中%是取余运算符,而应写作0.25。
2.4.2 算术运算符
算术运算符用于算术运算,由算术运算符连接操作数组成的表达式称为算术表达式。表2-4按优先级由高到低列出了C++中的算术运算符,其中*、/、%为同一级,+、-为同一级。
表2-4 算术运算符

其中负号运算符为单目运算符,加、减、乘、除和取余运算符为双目运算符。除法运算时的除数不能为0。
需要注意的是:
(1)%为取余运算符,它要求两边的操作数都必须是整数,如果对浮点数操作,则会引起语法错误。取余即取整数相除后的余数,余数的符号和被除数的符号相同,例如:

(2)进行双目运算时,运算结果的类型与操作数的类型相同,两个double型数据运算得到的结果是double型的,两个整型数据运算得到的结果是整型的。因此两个整数相除时,得到的结果的类型仍为整型。例如,1/2的结果为0,10/3的结果是3。
【例2.2】若有整型变量totalSeconds表示秒数,将其以小时:分钟:秒的形式显示。
源程序:

运行结果为:
3:22:9
2.4.3 复合的赋值运算符
使用复合的赋值运算符可以实现简写的赋值方法,与算术运算符相关的复合赋值运算符是由算术运算符与赋值运算符组合而成,分别为:+=、-=、*=、/=、%=,注意在算术运算符和赋值号之间不能有空格。表2-5列出了复合赋值运算符的使用示例,表中的x为整型变量,值为5。
表2-5 复合赋值运算符

当复合赋值运算符的右侧为表达式时,如:x/=y+2,等价于x=x/(y+2),此时表达式两边的括号不能省略。
复合赋值表达式仍属于赋值表达式,它不仅可以简化书写,而且能提高表达式的运算效率。
2.4.4 自增、自减运算符
自增、自减运算符为++和--,其功能是将操作数的值增1或减1后再赋给操作数本身。自增、自减运算符都是单目运算符,只需要一个操作数,而且这个操作数必须是变量,而不能是常量或表达式。
自增、自减运算符可以写在变量的前面,如++i、--i,分别称为先增和先减;若写在变量的后面,如i++、i--,则分别称为后增和后减。
单独作为一个语句时,i++;和++i;都是实现i=i+1,--i;和i--;实现i=i-1,先增(减)和后增(减)是没有区别的。但如果用在表达式中,则会导致不同的结果。这里以自增运算符为例,分析i++和++i表达式的值。计算++i表达式时,i先进行增1运算,而后将i的新值作为表达式的值。计算i++表达式时,先将i的值作为表达式的值,然后再对i进行增1运算。
常见的自增、自减运算的四种形式为:
(1)i++:后增,i++表达式的值为i的值,然后i进行自增,即先取值后自增;
(2)++i:先增,i先进行自增,++i表达式的值为自增后的i的值,即先自增后取值;
(3)i--:后减,i--表达式的值为i的值,然后i进行自减,即先取值后自减;
(4)--i:先减,i先进行自减,--i表达式的值为自减后的i的值,即先自减后取值。
若i的值为3,表2-6所示为这4种形式下表达式和变量的值的变化情况。
表2-6 自增、自减运算的示例

从以上示例中可以看到,不管是先增或先减还是后增或后减,对于变量i而言,均是进行了增1或减1操作,而对自增、自减表达式而言,其值则是不同的。
【例2.3】自增、自减运算符的使用示例。
源程序:

运行结果为:

分析:语句m=x-i++;中,i++为后增,因此i++表达式的值为i的值2,与x进行减法运算后的结果为8,赋给变量m,最后i再进行自增运算,i的值为3。语句n=x-++j;中,++j为先增,即变量j先进行自增,其值为3,此时++j表达式的值即为3,然后再与x进行减法运算,将结果7赋给变量n,因此m、n的值分别为8、7;对于变量i和j而言,无论是先增还是后增,都只进行了一次自增操作,因此i和j的值都为3。
自增、自减运算符经常被用于循环结构或迭代运算中,通常用来计数。
2.4.5 逗号运算符
1.逗号运算符
在C++中,逗号也是一个运算符,在所有运算符中它的优先级别最低。
2.逗号表达式
用逗号连接起来的表达式称为逗号表达式,一般形式为:
表达式1,表达式2,表达式3,……表达式n
其运算过程是从左向右依次计算各个表达式的值,并将最后一个表达式的值作为整个逗号表达式的值。例如:
a=1,b=a+2,c=b+3
该表达式的计算过程依次是:将1赋给a,再将a+2(即3)赋给b,最后将b+3(即6)赋给c,整个表达式的值为变量c的值,即6。
逗号表达式的值为最后一个表达式的值,即表达式n的值。例如:
d=(a=1,b=a+2,c=b+3)则d的值为6。
在所有运算符中,逗号运算符的优先级别最低,因此总是在最后进行计算。为了先进行逗号运算,可将逗号表达式用小括号括起来,如下列表达式:

注意,并非所有的逗号都构成逗号表达式,有些情况下逗号只作为分隔符,如定义多个变量时的分隔符和函数参数之间的分隔符等。
2.4.6 常用数学函数
C++中预定义了大量的标准函数供用户编程时使用,这些函数按功能可分为数学函数、字符串函数、输入输出函数、时间函数、图形函数等。在使用内部函数时,应把相关的头文件通过#include预处理命令包含到程序中。在标准数学库函数cmath中提供了丰富的数学函数,如三角函数、平方根、绝对值函数等,表2-7中列出了一些常用的数学函数。如果程序中要使用表中的数学函数,只要在程序的最前面加入文件包含命令即可:
#include<cmath>
表2-7 常用的数学函数

2.4.7 类型转换
1.表达式计算过程中的自动类型转换
在C++中,整型、单精度、双精度及字符型数据可以进行混合运算,即允许双目运算符两边的操作数的类型不同。当表达式中不同类型的数据进行运算时,会发生数据类型的转换,即先将不同类型的数据转换成相同类型,再进行计算。这种数据类型的转换是由系统自动转换完成的,在转换时按照以下的规则进行转换:
(1)操作数为字符型或短整型时,系统自动将其转换为整型。例如'A'+'a'的结果为162(65+97=162),其实质是将两个字符的ASCII码值相加,运算结果类型为int。
(2)操作数为单精度float型时,系统自动将其转换为双精度double型。
(3)当两操作数类型不同时,将“所占存储空间小”的操作数的类型向“所占存储空间更大”的操作数的类型转换,转换为同一种数据类型后再进行运算,转换方向如图2.3所示,图中横向箭头表示系统自动转换的方向。注意,图2.3并不代表转换的中间过程。例如若一个double型的操作数与一个int型的操作数进行算术运算,是将int型操作数直接转换为double型,而无须先将int型操作数依次转换为unsigned、long、double。
在经过类型转换后,运算符两边的操作数的类型就完全相同了,因此运算结果的类型与操作数的类型相同。

图2.3 类型转换规则
例如,1/2的结果为0,因为两个操作数都是int型,可直接计算,得到的结果也为int型。1.0/2的结果为0.5,这是因为1.0是double型,系统会自动将整型2转换为double型2.0,然后进行计算,结果也为double型。同理,1/2.0和1.0/2.0的结果均为0.5。
2.赋值过程中的自动类型转换
在进行赋值运算时,如果赋值号右侧表达式的值的类型与左侧变量的类型不一致,在赋值时则会发生自动类型转换。此时,类型转换的原则是将右侧表达式的值的类型转换为左侧变量的类型。例如:

在语句a=5.8;中,由于a为整型变量,因此需先将double型常量5.8转换为整型才能赋给变量a,a只接受5.8的整数部分,即将5赋给变量a,小数部分则丢失,相当于对5.8进行了取整运算。在语句b=5;中,由于赋值号右侧的整型常量5的类型不同于左侧变量b的类型,这里是先将5转换为double型的5.0,然后再赋给变量b。因此不存在数据丢失的问题,但是这样做并不能增加数据的精度,而是改变了数据的表示形式。
再如,若有以下两条语句:
int x='A';
char s=x;
则x的值为65,s的值为'A'。
在为x赋值时,由于'A'为字符常量,x为整型变量,因此会将'A'转换为整型,实际是将字符'A'的ASCII码转换为整型赋给x,已知'A'的ASCII码为65,因此x的值为65。在为s赋值时,由于x的值为65,而s是char型变量,因此需先将x的值65作为ASCII码转换为对应的字符赋给变量s,此时s的值为字符'A'。
一般情况下,将数值取值范围小的类型转换为取值范围大的类型是安全的,而反之则是不安全的,则可能导致部分数据(如小数部分)丢失,从而进一步导致不正确的运算结果。因此在定义变量时,要为变量选取适当的数据类型以保证数值计算的正确性。
3.强制类型转换
除了系统自动进行的类型转换外,C++还提供了在程序中进行强制类型转换的方法,即在表达式中可以根据需要把任意一个数据的类型转换为另一个数据类型。强制类型转换是靠强制类型转换运算符实现的,其一般形式为:
数据类型(操作数)
或(数据类型)操作数
其中操作数可以是常量、变量或表达式。此时,是把操作数的数据类型强行转换为前面指定的数据类型。例如
double f=6.58;
则int(f)的值为整型6,注意此时f的值并没有发生变化,仍为6.58。这是因为变量f的数据类型是在定义时就已经确定了的,一经定义就不能发生变化。
在进行强制转换时,为数据类型加小括号或是为表达式加小括号在语法上都正确,如double(5/2)或(double)(5/2)得到的结果都是2.0,都是先计算5/2得到结果整数2(注意这里是整除,不会得到2.5),然后再将整数2转换为double型的2.0。但是double(5/2)与(double)5/2的结果却是完全不同的。(double)5/2的值应为2.5,这是因为类型转换运算符的优先级别高于除法运算符/,因此首先将5转换为5.0,而后再计算5.0/2,根据以上介绍的表达式计算过程中的自动类型转换可知,该表达式的结果为2.5。
再如,若有以下定义:
double x=5.2,y=2.5;则表达式(int)x%(int)y的值应为1。计算过程为:首先进行强制类型转换,(int)x的值为整数5,(int)y的值为整数2,而后再进行取余运算,即5%2,得到结果为整数1。注意,此时x、y的数据类型与值并没有发生变化。强制数据类型转换并不改变操作数本身的类型,仅仅是通过类型转换运算得到一个与操作数类型不同的数据。
综上所述,本章着重介绍了C++中的基本数据类型以及数据的表示形式,使读者对C++基本语法有大致的了解。数据类型、常量、变量及运算符和表达式是程序设计的基础,因此读者应熟练掌握,以便为后续章节的学习打下坚实的基础。