本系列文章经补充和完善,已修订整理成书《Java编程的逻辑》,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接http://item.jd.com/12299018.html


条件执行

前面几节我们介绍了如何定义数据和进行基本运算,为了对数据有透彻的理解,我们介绍了各种类型数据的二进制表示。

现在,让我们回顾程序本身,只进行基本操作是不够的,为了进行有现实意义的操作,我们需要对操作的过程进行流程控制。流程控制中最基本的就是条件执行,也就
是说,某些操作只能在某些条件满足的情况下才执行,在一些条件下执行某种操作,在另外一些条件下执行另外某种操作。这与交通控制中的红灯停、绿灯行条件执行是类似的。

Java中表达这种流程控制的基本语法是If语句。

if

if的语法为:

if(条件语句){
代码块
}

if(条件语句) 代码; 

它表达的含义也非常简单,只在条件语句为真的情况下,才执行后面的代码,为假就不做了。具体来说,条件语句必须为布尔值,可以是一个直接的布尔变量,也可以 是变量运算后的结果,我们在第3节介绍过,比较运算和逻辑运算的结果都是布尔值,所以可作为条件语句。条件语句为true,则执行括号{}中的代码,如果后面没有括号,则执行后面第一个分号(;)前的代码。

如,只在变量为偶数的情况下输出:

int a=10;
if(a%2==0){
System.out.println("偶数");
}

int a=10;
if(a%2==0) System.out.println("偶数");

if的陷阱

初学者有时会忘记在if后面的代码块中加括号,有时希望执行多条语句而没有加括号,结果只会执行第一条语句。建议所有if后面都跟括号。

if/else

if实现的是条件满足的时候做什么操作,如果需要根据条件做分支,即满足的时候执行某种逻辑,而不满足的时候执行另一种逻辑,则可以用if/else。

if/else的语法是:

if(判断条件){
代码块1
}else{
代码块2
}

if/else也非常简单,判断条件是一个布尔值,为true的时候执行代码块1,为假的时候执行代码块2。

三元运算符

我们之前介绍了各种基本运算,这里介绍一个条件运算,和if/else很像,叫三元运算符,语法为:

判断条件 ? 表达式 1 : 表达式2

三元运算符会得到一个结果,判断条件为真的时候就返回表达式1的值,否则就返回表达式2的值。三元运算符经常用于对某个变量赋值,例如求两个数的最大值:

int max = x > y ? x : y;

三元运算符完全可以用if/else代替,但在某些场景下书写更简洁。

if/else if/else

如果有多个判断条件,而且需要根据这些判断条件的组合执行某些操作,则可以使用if/else if/else。

语法是

if(条件1){
代码块1
}else if(条件2){
代码块2
}...
else if(条件n){
代码块n
}else{
代码块n+1
}

if/else if/else也比较简单,但可以表达复杂的条件执行逻辑,它逐个检查条件,条件1满足则执行代码块1,不满足则检查条件2,...,最后如果没有条件满 足,且有else语句,则执行else里面的代码。最后的else语句不是必须的,没有就什么都不执行。

if/else if/else陷阱

需要注意的是,在if/else if/else中,判断的顺序是很重要的,后面的判断只有在前面的条件为false的时候才会执行。初学者有时会搞错这个顺序,如下面的代码:

if(score>60){
return "及格";
}else if(score>80){
return "良好";
}else{
return "优秀"
}

看出问题了吧?如果score是90,可能期望返回"优秀",但实际只会返回"及格".

switch

在if/else if/else中,如果判断的条件基于的是同一个变量,只是根据变量值的不同而有不同的分支,如果值比较多,比如根据星期几进行判断,有7种可能性,或者根据英文字母进行判断,有26种可能性,使用if/else if/else显的比较啰嗦,这种情况可以使用switch,switch的语法是:

switch(表达式){
case 值1:
代码1; break;
case 值2:
代码2; break;
...
case 值n:
代码n; break;
default: 代码n+1
}

switch也比较简单,根据表达式的值执行不同的分支,具体来说,根据表达式的值找匹配的case,找到后,执行后面的代码,碰到break时结束,如果没有找到匹配的值则执行default中的语句。

表达式值的数据类型只能是 byte, short, int, char, 枚举, 和String (Java 1.7以后)。枚举和String我们在后续文章介绍。

switch会简化一些代码的编写,但break和case语法会对初学者造成一些困惑。

容易忽略的break

break是指跳出switch语句,执行switch后面的语句。每条case语句后面都应该跟break语句,否则的话它会继续执行后面case中的代码直到碰到break语句或switch结束,例如:下面的代码会输出所有数字而不只是1.

int a = 1;
switch(a){
case 1:
System.out.println("1");
case 2:
System.out.println("2");
default:
System.out.println("3");
}

case堆叠

case语句后面可以没有要执行的代码,如下所示:

char c = 'A';//某字符
switch(c){
case 'A':
case 'B':
case 'C':
System.out.println("A-Z");break;
case 'D':
....
}

case 'A'/'B'后都没有紧跟要执行的代码,他们实际会执行第一块碰到的代码,即case 'C'匹配的代码

条件小结

条件执行总体上是比较简单的,单一条件满足时执行某操作使用if,根据一个条件是否满足执行不同分支使用if/else,表达复杂的条件使用if/else if/elese,条件赋值使用三元运算符,根据某一个表达式的值不同执行不同的分支使用switch。

从逻辑上讲,if/else, if/else if/else,三元运算符,switch都可以只用if代替,但使用不同的语法表达更简洁,在条件比较多的时候,switch从性能上也更高(马上解释为什么)。

条件本质

正如我们探讨数据类型的时候,研究数据的二进制表示一样,我们也来看下这些条件执行具体是怎么实现的。

程序最终都是一条条的指令,CPU有一个指令指示器,指向下一条要执行的指令,CPU根据指示器的指示加载指令并且执行。指令大部分是具体的操作和运算,在执行这些操作时,执行完一个操作后,指令指示器会自动指向挨着的下一个指令。

但有一些特殊的指令,称为跳转指令,这些指令会修改指令指示器的值,让CPU跳到一个指定的地方执行。跳转有两种,一种是条件跳转,另一种是无条件跳转。条件跳转检查某个条件,满足则进行跳转,无条件跳转则是直接进行跳转。

if, else实际上会转换为这些跳转指令,比如说下面的代码:

 int a=10;
if(a%2==0)
{
System.out.println("偶数");
}
//其他代码

转换到的转移指令可能是:

 int a=10;
条件跳转: 如果a%2==0,跳转到第4行
无条件跳转:跳转到第7行
{
System.out.println("偶数");
}
//其他代码

你可能会奇怪其中的无条件跳转指令,没有它不行吗?不行,没有这条指令,不管什么条件,括号中的代码都会执行。

不过,对应的跳转指令也可能是:

 int a=10;
条件跳转: 如果a%2!=0,跳转到第6行
{
System.out.println("偶数");
}
//其他代码

这个就没有无条件跳转指令,具体怎么对应和编译器实现有关。在单一if的情况下可能不用无条件跳转指令,但稍微复杂一些的情况都需要。if, if/else, if/else if/else, 三元运算符都会转换为条件跳转和无条件跳转。但switch不太一样。

switch的转换和具体系统实现有关,如果分支比较少,可能会转换为跳转指令。但如果分支比较多,使用条件跳转会进行很多次的比较运算,效率比较低,可能会使用一种更为高效的方式,叫跳转表。跳转表是一个映射表,存储了可能的值以及要跳转到的地址,形如:

值1 代码块1的地址
值2 代码块2的地址
...  
值n 代码块n的地址

跳转表为什么会更为高效呢?因为,其中的值必须为整数,且按大小顺序排序。按大小排序的整数可以使用高效的二分查找,即先与中间的值比,如果小于中间的值则在开始和中间值之间找,否则在中间值和末尾值之间找,每找一次缩小一倍查找范围。如果值是连续的,则跳转表还会进行特殊优化,优化为一个数组,连找都不用找了,值就是数组的下标索引,直接根据值就可以找到跳转的地址。即使值不是连续的,但数字比较密集,差的不多,编译器也可能会优化为一个数组型的跳转表,没有的值指向default分支。

程序源代码中的case值排列不要求是排序的,编译器会自动排序。之前说switch值的类型可以是byte, short, int, char, 枚举和String。其中byte/short/int本来就是整数,在上节我们也说过,char本质上也是整数,而枚举类型也有对应的整 数,String用于switch时也会转换为整数(通过hashCode方法,后文介绍),为什么不可以使用long呢?跳转表值的存储空间一般为32位,容纳不下long。

总结

条件执行的语法是比较自然和容易理解的,需要注意的是其中的一些语法细节和陷阱。它执行的本质依赖于条件跳转、无条件跳转和跳转表。

条件执行中的跳转只会跳转到跳转语句以后的指令,能不能跳转到之前的指令呢?

----------------

未完待续,查看最新文章,敬请关注微信公众号“老马说编程”(扫描下方二维码),深入浅出,老马和你一起探索Java编程及计算机技术的本质。原创文章,保留所有版权。

-----------

更多相关原创文章

计算机程序的思维逻辑 (10) - 强大的循环

计算机程序的思维逻辑 (11) - 初识函数

计算机程序的思维逻辑 (12) - 函数调用的基本原理

Java编程的逻辑 (9) - 条件执行的本质的更多相关文章

  1. 《Java编程的逻辑》 - 文章列表

    <计算机程序的思维逻辑>系列文章已整理成书<Java编程的逻辑>,由机械工业出版社出版,2018年1月上市,各大网店有售,敬请关注! 京东自营链接:https://item.j ...

  2. Java编程的逻辑 (12) - 函数调用的基本原理

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  3. Java编程的逻辑 (10) - 强大的循环

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  4. Java编程的逻辑 (11) - 初识函数

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  5. Java编程的逻辑 (72) - 显式条件

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  6. Java编程的逻辑 (77) - 异步任务执行服务

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  7. Java编程的逻辑 (92) - 函数式数据处理 (上)

    本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...

  8. Java编程的逻辑 (91) - Lambda表达式

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

  9. Java编程的逻辑 (83) - 并发总结

    ​本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http: ...

随机推荐

  1. luogu1312

    有趣的题面 超有趣的dfs大模拟,码了巨久,卡时过了此题qaq #include <cstdio> #include <cstring> #include <algori ...

  2. linux下MySQL使用方法

    一.登录MySQL 登录MySQL的命令是mysql, mysql 的使用语法如下:  mysql [-u username] [-h host] [-p[password]] [dbname] us ...

  3. 分布式为什么使用Redis

    一 为什么使用 Redis 在项目中使用 Redis,主要考虑两个角度:性能和并发.如果只是为了分布式锁这些其他功能,还有其他中间件 Zookpeer 等代替,并非一定要使用 Redis. 性能: 如 ...

  4. scheme 之门

    scheme 之门 开始之前 这是一篇 Scheme 的介绍文章. Scheme 是一个 LISP 的方言, 相对于 Common LISP 或其他方言, 它更强调理论的完整和优美, 而不那么强调实用 ...

  5. NOIP2018 No regrets youth

    NOIP2018在即,20181009总结一些易错的知识点和解题方法 ——by ljc20020730 HGOI NOIP2018 No regrets youth ! NOIP2018 No reg ...

  6. noip2018 d2t3 保卫王国 解题报告

    保卫王国 电脑卡懒得把题面挪过来了. 朴素 \[ dp_{i,0}=\sum dp_{s,1}\\ dp_{i,1}=\sum \min(dp_{s,0},dp_{s,1})+p_i \] 然后直接动 ...

  7. 使用ImageMagick 在图片上绘制粗斜体的中文也许是一个错误。

    测试发现: ImageMagick使用中文字体,在图片上绘制带粗或斜体的中文,看不到效果. 如果使用英文字体,绘制粗或斜体的英文,99%都有效果. 今天无意看到一篇文章提到: convert -lis ...

  8. linux查看进程的线程数

    top -H -p $PID  #查看对应进程的那个线程占用CPU过高 1.top -H 手册中说:-H : Threads toggle 加上这个选项启动top,top一行显示一个线程.否则,它一行 ...

  9. hdu 3022 Sum of Digits

    http://acm.hdu.edu.cn/showproblem.php?pid=3022 题意: 最多不超过10000组数据,每组数据给定两个数n,m,求一个最小的数,使得该数每一位之和等于n,每 ...

  10. [转载]Brackets - 强大免费的开源跨平台Web前端开发工具IDE (HTML/CSS/Javascript代码编辑器)

    http://brackets.io/ Brackets 是一个免费.开源且跨平台的 HTML/CSS/JavaScript 前端 WEB 集成开发环境 (IDE工具).该项目由 Adobe 创建和维 ...