C++面向对象程序设计
上QQ阅读APP看书,第一时间看更新

2.2.5 运算符与表达式

现在为止,我们了解了C++语言中各种类型数据的特点及其表示形式。那么如何对这些数据进行处理和计算呢?通常当要进行某种计算时,都要首先列出算式,然后求解其值。当利用C++语言编写程序求解问题时也是这样。在程序中,表达式是计算求值的基本单位。

我们可以简单地将表达式理解为用于计算的公式,它由运算符(例如:+,-,*,/)、运算量(也称操作数,可以是常量、变量等)和括号组成。执行表达式所规定的运算,所得到的结果值便是表达式的值。例如:a+b,x/y都是表达式。

下面再用较严格的语言给表达式下一个定义,读者如果不能够完全理解也不要紧,表达式在程序中无处不在,而且接下来还要详细介绍各种类型的表达式,用得多了自然也就理解了。

表达式可以被定义为:

● 一个常量或标识对象的标识符是一个最简单的表达式,其值是常量或对象的值。

● 一个表达式的值可以用来参与其他操作,即用作其他运算符的操作数,这就形成了更复杂的表达式。

● 包含在括号中的表达式仍是一个表达式,其类型和值与未加括号时的表达式相同。

C++语言中定义了丰富的运算符,如算术运算符、关系运算符、逻辑运算符等。有些运算符需要两个操作数,使用形式为:

操作数1 运算符 操作数2

这样的运算符称为二元运算符(或二目运算符)。另一些运算符只需要一个操作数,称为一元运算符(或单目运算符)。

运算符具有优先级与结合性。当一个表达式中包含多个运算符时,先进行优先级高的运算,再进行优先级低的运算。如果表达式中出现了多个相同优先级的运算,运算顺序就要看运算符的结合性了。所谓结合性是指当一个操作数左右两边的运算符优先级相同时,按什么样的顺序进行运算,是自左向右,还是自右向左。

1.算术运算符与算术表达式

执行算术运算是程序对数据进行处理的最基本能力,早期发明计算机的目的就是协助人们进行计算。C++中用于算术运算的运算符包括自增(++)和自减(-)运算符、加法类运算符加(+)和减(-)、乘法类运算符乘(*)、除(/)和取余(%)。*、/、%、++和-运算符有前置形式和后置形式。算术运算符的优先次序如表2-3所示。

表2-3 算术运算符的优先次序

由算术运算符构成的表达式称为算术表达式。

【例2-2】算术运算的应用。

主要知识点:算术运算符的使用和相关注意事项:

(1)算术运算符+、-、*、/的含义及运算次序与数学中是一样的,但是要注意两个整数相除时,结果是取整,小数部分会被截掉。

(2)%运算符的作用是两数相除,取余数作为结果。

(3)++、-运算符实现变量值增加1和减小1的功能。后置的情况是先使用量的值然后增1或减1;前置的情况是变量先增加1或减小1之后再参与其他运算。

源代码:

程序运行结果:

2.赋值运算符与赋值表达式

C++提供了几个赋值运算符,其基本赋值运算符号为“=”,其功能是将“=”右边的表达式值赋给“=”左边的对象,例如:

赋值运算符“=”与数学上的等号相同,在编程过程中很容易和逻辑等“==”相混淆,这也是初学者特别需要注意的问题。

另外,C++还提供了10种复合赋值运算符:*=、/=、%=、+=、-=、>>=、<<=、&=、^=、|=。其中前5个是赋值运算符与算术运算符复合而成的,后5个是赋值运算符与位运算符复合而成的(关于位运算,稍后再作介绍)。这10种复合的赋值运算符的优先级相同,高于逗号运算符,低于其他运算符,运算次序为自右向左。除了将“=”右边的值赋给等号左边,表达式本身也有计算结果,就是赋值操作所赋予的值。

表达式a=b=5等效于a=(b=5)。

表达式El op=E2等效于El=El op E2。例如:a+=b等同于a=a+b。

还有一个需要注意的问题就是,当赋值操作两边的类型不相同时,会引发C++的隐式类型转换,表达式的类型转换成左边变量的类型,

int ival=3.1415926;

cout<<"ival="<<ival<<endl;

在编译的时候并不会引起报错或是警告,但是运行的结果却是:

ival=3

也就是说,C++作了从double到int的类型转换。

但是,未必所有的“不搭配”都可以成功地进行隐式类型转换。如果转换不成功就会引发编译器错误。

【例2-3】赋值运算的应用。

主要知识点:赋值运算符“=”。

C++没有赋值语句,将赋值作为一个运算。赋值运算符将右边操作数的值赋给左边的操作数,整个赋值表达式的值就是被赋给左边变量的值。

源代码:

程序运行结果:

3.逗号运算符与逗号表达式

在C++中,逗号也是一个运算符。逗号表达式是一系列由逗号分开的表达式,这些表达式从左向右计算。逗号表达式的结果是最右边表达式的值。例如:

运行时将输出下列内容:

Ival1=1

iva12=1

4.关系运算符和关系表达式

关系运算用于比较数据之间的大小关系,其运算结果只能为true或false。在C++中也提供了用于比较、判断的关系运算符,如表2-4所示,关系运算符的优先级相同,运算次序为自左向右。例如,如果有如下变量定义及初始化:

int i=35,j=36;

则表达式“i<j”的值为true。由关系运算符构成的表达式,称为关系表达式。

表2-4 关系运算符

5.逻辑运算符与逻辑表达式

仅仅有简单的大小和相等比较,无法表示数据间的复杂关系。比如,要判断是否a大于b并且x小于y,仅仅用关系运算和相等运算就不能实现,这时就需要用逻辑运算来实现这个“并且”关系。C++提供了逻辑运算符,如表2-5所示。逻辑运算可以将多个关系表达式和相等表达式组合起来,构成复杂的逻辑判断。3种逻辑运算的优先级依次为!、&&、||,逻辑非(!)运算符的优先级高于关系运算符,而&&和||的优先级低于关系运算符和相等运算符。

表2-5 逻辑运算符

【例2-4】比较一组数据,并输出比较结果。

源代码:

程序运行结果:

6.sizeof运算符

sizeof运算符的作用是返回一个对象或类型的字节长度。使用sizeof运算符,可以有3种形式:

【例2-5】sizeof运算符的应用。

源代码:

程序运行结果:

7.位运算符

位运算操作的是“位”,在C++中提供了6个位运算符,可以对整数进行位操作。表2-6详细列出了关于位运算符的相关信息。

表2-6 位运算符

【例2-6】位运算符的应用。

源代码:

程序运行结果:

8.混合运算时数据类型的转换

表达式中出现了多种类型数据的混合运算时,往往需要进行类型转换。表达式中的类型转换分为两种:隐含转换和显式转换。

(1)隐含转换

算术运算符、关系运算符、逻辑运算符、位运算符和赋值运算符这些二元运算符,要求两个操作数的类型一致。在算术运算和关系运算中如果参与运算的操作数类型不一致,编译系统会自动对数据进行转换(即隐含转换)。转换的基本原则是将低类型数据转换为高类型数据。类型越高,数据的表示范围越大,精度也越高,各种类型的高低顺序如下:

下面列出了隐含转换的规则。这种转换是安全的,因为在转换过程中数据的精度没有损失。

①一个操作数是long double型,将另一个操作数转换为long double型。

②前述条件不满足,并且有一个操作数是double型,将另一个操作数转换为double型。

③前述条件不满足,并且有一个操作数是float型,将另一个操作数转换为float型。

④前述条件不满足(两个操作数都不是浮点数):

● 有一个操作数是unsigned long型,将另一个操作数转换为unsigned long型。

● 有一个操作数是long型,另一个操作数是unsigned long型,将两个操作数都转换为unsigned long型。

⑤前述条件不满足,并且有一个操作数是long型,将另一个操作数转换为long型。

⑥前述条件不满足,并且有一个操作数是unsigned int型,将另一个操作数转换为unsigned int型。

⑦前述条件都不满足,将两个操作数都转换为int型。

逻辑运算符要求参与运算的操作数必须是bool型,如果操作数是其他类型,编译系统会自动将其转换为bool型。转换方法是:非0数据转换为true,0转换为false。

位运算的操作数必须是整数,当二元位运算的操作数是不同类型的整数时,编译系统也会自动进行类型转换,转换时会遵循上述隐含转换的规则。

赋值运算要求左值(赋值运算符左边的值)与右值(赋值运算符右边的值)的类型相同,若类型不同,编译系统会自动进行类型转换。但这时的转换不适用上述列出的隐含转换的规则,而是一律将右值转换为左值的类型。

下面的程序段说明了类型转换的规则。

(2)显式转换

显式类型转换的作用是将表达式的结果类型转换为另一种指定的类型。例如,显式类型转换语法形式有两种:

类型说明符(表达式)  //C++风格的显式转换符号

(类型说明符)表达式  //C语言风格的显式转换符号

这两种写法只是形式上有所不同,功能完全相同。

显式类型转换的作用是将表达式的结果类型转换为类型说明符所指定的类型。例如:

标准C++也支持上面两种类型显式转换语法,此外又定义了4种类型转换操作符:static_cast,dynamic_cast,const_cast和reinterpret_cast,语法形式如下:

static_cast,const_cast和reinterpret_cast三种类型转换操作符的功能,都可以用标准C++之前的两种类型转换语法来描述。用“类型说明符(表达式)”和“(类型说明符)表达式”所描述的显式类型转换,也可以用static_cast,const_cast和reinterpret_cast中的一种或两种的组合加以描述。也就是说,标准C++之前的类型转换语法所能完成的功能被细化为3类,分别对应于static_cast,const_cast和reinterpret_cast。初学者如果不能区分这3种类型转换操作符,可以统一写成“(类型说明符)表达式”的形式,但这3种类型转换操作符的好处在于,由于分类被细化,语义更加明确,也就更不容易出错。

本章所介绍的基本数据类型之间的转换都适用于static_cast。例如,上例中的int(z)和(int)z都可以替换为static_cast<int>(z)。static_cast除了在基本数据类型之间转换外,还有其他功能。static_cast的其他功能和const_cast,dynamic_cast,reinterpret_cast的用法将在后面的章节中陆续加以介绍。

使用显式类型转换时,应该注意以下两点:

● 这种转换可能是不安全的。从上面的例子中可以看到,将高类型数据转换为低类型时,数据精度会受到损失。

● 这种转换是暂时的、一次性的。比如在上面的例子中第3行,强制类型转换int(z)只是将float型变量z的值取出来,临时转换为int型,然后赋给wholePart。这时变量z所在的内存单元中的值并未真正改变,因此再次使用z时,用的仍是z原来的浮点类型值。