3.4 运算符、表达式与语句

程序是由许多语句组成的,而语句的基本单位是表达式与运算符。像之前讲解过的“+”、“/”实际上都是运算符,下面将为读者介绍Java运算符的用法、表达式与运算符之间的关系,以及表达式里各种变量的数据类型的转换等。

3.4.1 运算符

图3-3 表达式是由操作数与运算符所组成

Java中的语句有很多种形式,表达式就是其中一种形式。表达式是由操作数与运算符所组成,操作数可以是常量、变量也可以是方法,而运算符就是数学中的运算符号,如“+”“-”“*”“/ “%”等。以表达式z+100为例,“z”与“100”都是操作数,而“+”就是运算符,如图3-3所示。

Java提供了许多的运算符,这些运算符除了可以处理一般的数学运算外,还可以做逻辑运算、位运算等。根据其所使用的类的不同,运算符可分为赋值运算符、算术运算符、关系运算符、逻辑运算符、条件运算符、括号运算符等。

1.赋值运算符号

想为各种不同数据类型的变量赋值时,就必须使用赋值运算符“=”,表3-4中所列出的赋值运算符虽然只有一个,但它却是Java语言中必不可缺的。

表3-4 赋值运算符

图3-4 表达式的赋值范例

等号(=)在Java中并不是“等于”的意思,而是“赋值”的意思。表达式的赋值范例如图3-4所示。

上面的语句是将整数22赋值给num这个变量。再来看看下面这个语句:

从未学习过任何编程语言的读者,可能会不习惯这种思考方式。等号(=)是“赋值”的语句,num-3运算之后把结果赋值给num存放。因为之前已经把num的值设为22,所以执行这个语句时,Java会先处理等号后面的部分num-3(值为19),再赋值给等号前面的变量num,执行后,存放在变量num的值就变成了19了。将上面的语句编写成下面的程序。

【例3.10】验证以上概念

程序执行结果:

当然,在程序中也可以将等号后面的值赋值给其他的变量,如:

num1与num2的值经过运算后仍然保持不变,sum会因为“赋值”的操作而更改内容。

2.一元运算符

对于大部分的表达式而言,运算符的前后都会有操作数。但是有一种运算符较特别,它只需要一个操作数,称为一元运算符。下面的语句就是由一元运算符与一个操作数所组成的:

表3-5列出了一元运算符的成员:

表3-5 一元运算符

下面的程序演示了上述3个一元操作符的使用。

【例3.11】一元运算符的使用

程序执行结果:

本程序分别定义了布尔型与int型两种变量,而后使用一元运算符进行处理后输出。

3.算术运算符

算术运算符在数学上面经常会使用到,表3-6列出了它的操作符号:

表3-6 算术运算符

【例3.12】算术运算符的使用

程序执行结果:

提示

可以利用模来判断奇数还是偶数。

如果需要设计程序实现判断给定的数字是偶数还是奇数,偶数模2结果为0,奇数模2为1。实现判断方法的程序如下。

实例1:奇、偶数判断方法

程序执行结果:

依据以上的方式再结合if分支判断语句,就可以轻松实现,代码如下所示。

实例2:判断某一个数字是奇数还是偶数?

程序执行结果:

上面程序所使用的if分支语句在本章的随后部分将为读者讲解,事实上对于程序的开发,最麻烦的地方就在于所有知识点的混合应用,就好比做数学的证明题一样,不要只看概念,多写代码才是学编程的王道。

4.关系运算符

设计者常常会在if语句中使用到关系运算符,if语句的格式如下:

【格式3-2 if语句的格式】

如果括号中的判断条件成立,就会执行行中的语句;若是判断条件不成立,则行中语句就不会被执行,如下面的程序片段:

当x的值大于10,即if语句的判断条件成立,会执行输出字符串“Welcome To MLDN”的操作;相反,当x的值为10或是小于10时,if语句的判断条件不成立,就不会执行上述操作。表3-7列出了关系运算符,这些运算符在数学上也是经常使用的。

表3-7 关系运算符

在Java中,关系运算符的表示方式和在数学中很类似,但是由于赋值运算符为“=”,为了避免混淆,当使用关系运算符“等于(==)”时,就必须用两个等号表示;而关系运算符“不等于”的形式有些特别,用“!=”代表,这是因为在键盘上想要取得数学上的不等于符号“¹”较为困难,所以就用“!=”表示不等于符号。

【例3.13】关系运算符的使用

程序执行结果:

从上面的程序可以发现,当使用关系运算符去判断一个表达式的成立与否时,若是判断式成立会产生一个布尔值true,若是判断式不成立,则会产生布尔值false。下面的程序用来判断if语句括号中的条件是否成立,若是成立则执行if后面的语句。

【例3.14】在if语句中使用关系运算符

程序执行结果:

从上面的程序中可以发现,如果在if语句中条件满足了,则会执行if语句中的内容。另外,如果一个if语句中只有一条语句,可以不写“{}”,但是为了程序便于调试,本书不建议读者采用这种简写的方式,此种方式读者只做了解即可。

5.自增与自减运算符

自增与自减运算符也是在各个语言中最常见到的语句,在Java仍然将它们保留了下来,是因为它们具有相当大的便利性。表3-8列出了自增与自减运算符。

表3-8 自增与自减运算符

善用自增与自减运算符可使程序更加简洁。例如,声明一个int类型的变量a,在程序运行中想让它加1,语句如下:

将a的值加1后再赋值给a存放。也可以利用递增运算符“++”写出更简洁的语句:

在程序中还可以看到另外一种自增运算符“++”的用法,就是自增运算符“++”在变量的前面,如++a,这和a++所代表的意义是不一样的。a++会先执行整个语句后再将a的值加1,而++b则先把b的值加1后,再执行整个语句。下面的程序是将a与b的值皆设为3,将a++及++b输出来,可以轻易地比较出两者的不同。

【例3.15】验证自增和自减

程序执行结果:

技术穿越:不要过于在意“++”或“--”的位置。

实际上在开发中,笔者并不建议广大读者使用这些混合的操作。在开发中如果真出现了这种自增1或自减1的操作,那么直接编写“numA+=1”或“numA-=1”可能会更好理解,之所以在本处强调这样的语法,是因为在循环中往往会单独编写“numA++”这种形式的代码,在本章随后讲解的循环操作中可以清楚地看到此类用法。

6.逻辑运算符

使用逻辑运算符可以连接多个逻辑运算。表3-9列出了常用的逻辑运算符。

表3-9 逻辑运算符

当使用逻辑运算符“&&”时,运算符前后的两个操作数的返回值皆为真,运算的结果才会为真;使用逻辑运算符“||”时,运算符前后的两个操作数的返回值只要有一个为真,运算的结果就会为真。如下面的语句:

在a>0而且b>0时,表达式的返回值为true,即表示这两个条件必须同时成立才行;在a>0或者b>0时,表达式的返回值即为true,这两个条件仅需要一个成立即可,读者可以参考表3-10中所列出的结果。

表3-10 AND及OR结果表

【例3.16】验证逻辑运算符

程序执行结果:

从上面程序运行结果可以发现,与和或的结果都符合表3-10给出的结果。另外,如果一个if语句中需要同时判断多个条件的话,则也可以使用以上的逻辑运算符,具体程序如下所示。

【例3.17】判断多个条件

程序执行结果:

上面的程序中的判断语句同时使用了多个条件判断,多个条件之间使用逻辑运算符进行连接。

说明

提问:为什么会有两种与和或的操作?

与和或都分为两种:

(1)“&&”与“&”。

(2)“||”与“|”。

这两种在使用上有哪些区别?该使用哪种呢?

回答:与分为短路与和非短路与,或分为短路或和非短路或。

从表3-10中读者可以发现这样的规律:

(1)对于与操作来说,如果第一个条件为假,则后面不管是真是假,最终的结果都是假。

(2)对于或操作来说,如果第一个条件为真,则后面不管是真是假,最终的结果都是真。

也就是说对于与和或,第一个条件就是一个关键性的条件,所以在Java中设置了短路与和短路或,对于短路与和与、短路或和或有以下两点区别:

(1)对于与来说,要求所有的条件都判断,使用短路与如果第一个条件为false,则后面的条件将不再判断。

(2)对于或来说,要求所有的条件都判断,使用短路或如果第一个条件为true,则后面的条件将不再判断。

下面通过两段代码来验证以上的两点,在讲解具体代码前,请读者先来观察一下测试代码(一)的错误。

测试代码1:观察被除数为0的情况

程序执行结果:

造成以上问题的根本原因在于被除数为0。下面为读者进一步讲解短路与和短路或的区别。

测试代码2:验证“&”的作用

程序执行结果:

造成以上问题的根本原因在于与操作要把所有的条件进行判断,所以在计算10/0时就出现了错误,而如果修改为短路与呢?见下面的代码。

测试代码3:验证“&&”的作用

上面的程序运行后,不再出现以上的错误提示,因为第一个条件不满足,所以后面的条件就不再判断了,这就是短路与的作用。

测试代码4:验证“|”的作用

上面的程序运行后,出现了与之前一样的错误提示,如果现在改为“短路或”呢?见下面的代码。

测试代码5:验证“||”的作用

程序执行结果:

从上面程序运行结果可以发现,程序不再出现错误,就是因为使用了“短路或”,如果第一个条件为true,则后面的条件将不再判断。

7.括号运算符

除了前面讲解的内容外,括号(“()”)也是Java的运算符,如表3-11所示:

表3-11 括号运算符

括号运算符用来处理表达式的优先级的。以一个简单的加减乘除式子为例:

用加减乘除的优先级(*、/的优先级大于+、-)来计算结果,这个式子的答案为25。但是如果想先计算3+5+4及6-7之后再将两数相乘时,就必须将3+5+4及6-7分别加上括号,而表示成下面的式子:

使用了括号运算符后,计算结果为-12,所以括号运算符可以使括号内表达式的处理顺序优先。

【例3.18】使用括号改变运算顺序

程序执行结果:

8.位运算符

在Java中除了具备高级语言的特点之外,也支持位运算操作。位运算操作就是指进行二进制位的运算,在Java中支持的位操作符如表3-12所示。

表3-12 位操作符

在Java中所有的数据都是以二进制数据的形式进行运算的,即如果是一个int型变量,要采用位运算的时候则必须将其变为二进制数据。每一位二进制进行与、或、异或操作的结果如表3-13所示。

表3-13 位运算的结果表

下面通过一段代码来观察位运算符,现在假设有3和6两个数字进行与、或、异或的操作,具体代码如下所示。

【例3.19】位运算

程序执行结果:

上面的程序分别进行了与、或、异或的操作,下面为读者讲解一下操作的流程。3对应二进制数据是:11,但是在Java中整型数据的长度为32位,所以前面要补上30个0,所以结果为00000000 00000000 00000000 00000011,6对应的二进制数据为00000000 00000000 00000000 00000110,计算的过程如图3-5所示。

图3-5 与、或、异或的操作

在计算机的数据表示中只定义了正数的表示形式,并没有定义负数的表示形式,所以,一般都是利用补码的形式表示的,正数的原码、反码、补码都相同,负数的反码是除符号位为1外,其他位全取反;补码就是“反码+1”。

【例3.20】求出负数的反码

程序执行结果:

上面程序的运行结果是2,因为在计算机中,负数都使用补码的形式计算,补码的计算是“反码+1”,之后再对-3进行反码的计算,操作过程如图3-6所示。

图3-6 反码的计算

在Java中也提供了左移“<<”及右移“>>”两种操作。左移操作是将运算数的二进制码整体左移指定位数,左移之后的空位使用0来填充,如下面代码所示。

【例3.21】左移操作

程序执行结果:

程序的执行过程如图3-7所示。

图3-7 左移2位

右移操作“>>”是将运算数的二进制码整体右移,右移后空出来的位置以符号位填充。如果是正数使用“0”填充,如果是负数使用“1”填充。

【例3.22】右移操作

程序执行结果:

程序的执行过程如图3-8所示。

图3-8 右移操作

图3-8(b)中可以看出因为其是负数,所以采用补码的形式保存,最终的计算结果为-1。

以上的右移操作属于带符号位的右移操作,在Java中也提供了无符号的右移操作符“>>>”,使用此操作将以0填充空出来的位。

【例3.23】无符号右移

程序执行结果:

程序的执行过程如图3-9所示。

图3-9 无符号右移操作

对于位操作只适合于byte、short、int、char、long类型,而且位操作后原始的操作内容并不会发生任何的改变。

常见面试题分析:请解释&和&&、|和||的区别?

(1)逻辑运算:

①与运算分为普通与(&)和短路与(&&)两种。

  • 普通与:所有的判断条件都要判断。
  • 短路与:如果前面的判断返回了false,后面不再判断,最终结果就是false。

②或运算分为普通或(|)和短路或(||)两种。

  • 普通或:所有的判断条件都要判断。
  • 短路或:如果前面的判断返回了true,后面不再判断,最终结果就是true。

(2)位运算:位与运算(&)、位或运算(|),其中“&&”和“||”不能应用在位运算上。

常见面试题分析:请问如何可以更快地计算出23

如果直接采用“2*2*2”很明显不是最快的,因为需要数学计算过程,由于计算机的数据都是按位保存的,所以面对此问题移位的速度是最快的。

实例:向左边移位2位实现功能

9.运算符的优先级

表3-14列出了各个运算符的优先级的排列,数字越小的表示优先级越高。

表3-14 运算符的优先级

表3-14的最后一栏是运算符的结合性。结合性可以让程序设计者了解到运算符与操作数之间的关系及其相对位置。例如,当使用同一优先级的运算符时,结合性就非常重要了,它决定谁会先被处理。如下面的例子:

这个表达式中含有不同优先级的运算符,其中“/”与“*”的优先级高于“+”,而“+”又高于“=”,但是“/”与“*”的优先级是相同的,究竟d该先除以5再乘以4呢?还是5乘以4后d再除以这个结果呢?结合性的定义就解决了这方面的困扰,算术运算符的结合性为“由左至右”,就是在相同优先级的运算符中,先由运算符左边的操作数开始处理,再处理右边的操作数。上面的式子中,由于“/”与“*”的优先级相同,因此d会先除以5再乘以4得到的结果加上b后,将整个值赋给a存放。

提示

多使用括号改变优先级。

部分刚刚入门的读者见到表3-14中的内容会比较头疼,因为一下子要记住这么多的内容似乎是一件很费劲的事。这里要告诉读者的是,对于以上的运算符优先级并没有必要完全记下来,必要时通过括号改变其优先级即可。

3.4.2 简洁表达式

表达式是由常量、变量或是其他操作数与运算符所组合而成的语句,如下面例子,均是表达式正确的使用方法:

此外,Java还有一些将算术运算符和赋值运算符结合成为新的运算符,表3-15列出了这些运算符。

表3-15 简洁的表达式

下面的几个表达式都是简洁的写法:

上面的写法可以减少程序的行数,提高执行效率。

【例3.24】验证简洁表达式

程序执行结果:

除了前面所提到的算术运算符和赋值运算符的结合可以存在于简洁的表达式中,自增、自减运算符也同样可以应用在简洁的表达式中。表3-16列出了一些简洁写法的运算符及其范例说明。

表3-16 简洁表达式的范例

下面通过实例来说明这些简洁的表达式在程序中该如何应用。定义两个整型变量,经过运算之后,来看这两个变量所存放的值有什么变化。

【例3.25】验证简洁表达式

程序执行结果: