柒——程序执行方向
一、程序逻辑
结构化程序设计(Structured programming)是一种经典的编程模式,在1960年开始发展,其思想最早是由荷兰著名计算机科学家 E.W. Dijkstra 提出的,他设计了一套规则,使程序设计具有合理的结构,用以保证程序的正确性。这套规则要求程序设计者按照一定的结构形式来设计和编写程序,而不是“天马行空”地根据程序员的意愿来编写。早期的程序员广泛使用 GOTO 语句,而自从结构化编程思想推广以来,它已经日益淡出程序设计的舞台。
GOTO 语句也称无条件转移语句,它破坏了程序设计结构性,导致程序流程的混乱,使理解和调试程序都产生困难。1966年5月 Djikstra 在著名学术期刊《Communications of the ACM》发表论文,说明任何一个有 goto 指令的程序,可以改为完全不使用 goto 指令的程序,即 “ 所有有意义的程序流程都可以使用三种基本的结构来构成 ”。1968年 Dijkstra 等发表了著名的论文《GOTO语句有害论》(Go To Statement Considered Harmtul)。
自此人们的编程方式发生重大变化,每种语言都提供这些基本控制结构的实现方式,并提供把数据访问局部化的能力,以及某种形式的模块化编译机制。正是这个原因,在 Java 程序设计中,虽然 goto 作为关键字保留了下来,但是一直没有启用。
结构化程序设计语言,强调用模块化、积木式的方法来建立程序。采用结构化程序设计方法,可使程序的逻辑结构清晰、层次分明、可读性好、可靠性强,从而提高了程序的开发效率,保证了程序质量,改善了程序的可靠性。
一般来说程序的结构包含以下 3 种:
(1)顺序结构。
(2)选择结构。
(3)循环结构。
这 3 种不同的结构有一个共同点,就是它们都只有一个入口,也只有一个运行出口。程序中使用了上面这些结构的好处是这些单一的入口、出口可让程序可控、易读、好维护。
1.顺序结构
结构化程序的最简单的结构就是顺序结构。所谓顺序结构程序就是按书写顺序执行的语句构成的程序段。
通常情况下,顺序结构是指按照程序语句出现的先后顺序一句一句地执行。但有一些程序并不按顺序执行语句,这个过程称为“控制的转移”,它涉及到了另外两类程序的控制结构,即分支结构和循环结构。
2.分支结构
选择结构也称为分支结构,在许多实际问题的程序设计中,根据输入数据和中间结果的不同,需要选择不同的语句组执行。在这种情况下,必须根据某个变量或表达式的值作出判断,以决定执行哪些语句和不执行哪些语句。
选择结构是根据给定的条件进行判断,决定执行哪个分支的程序段。条件分支不是我们常说的“兵分两路”,而条件分支在执行主要用于两个分支的选择,由 if 语句和 if … else 语句来实现。
if … else 语句可以依据判断条件的结果,来决定要执行的语句。
3.循环结构
循环结构是程序中的另一种重要结构,它和顺序结构、选择结构共同作为各种复杂程序的基本构造部件。循环结构的特点是在给定条件成立时,反复执行某个程序段。通常我们称给定条件为循环条件,称反复执行的程序段为循环体。循环体可以是复合语句、单个语句或空语句。在循环体中也可以包含循环语句,实现循环的嵌套。
二、选择结构
Java 语言中的选择结构提供了以下两种类型的分支结构。
条件分支:根据给定的条件进行判断,决定执行某个分支的程序段。
开关分支:根据给定整型表达式的值进行判断,然后决定执行多路分支中的一支。
条件分支主要用于两个分支的选择,由 if 语句和 if … else 语句来实现。开关分支用于多个分支的选择,由 switch 语句来实现。
1.if 语句
if 语句(if - then Statement)用于实现条件分支结构,它在可选动作中作出选择,执行某个分支的程序段。if 语句有两种格式在使用中供选择。要根据判断的结构来执行不同的语句时,使用 if 语句是一个很好的选择,它会准确地检测判断条件成立与否,再决定是否要执行后面的语句。
if 语句的格式如下:
if(判断条件){
语句1
…
语句n
}
若是在 if 语句主体中要处理的语句只有 1 个,可省略左、右大括号。但不利于阅读和检查错误。
2. if … else 语句
if … else 语句(if - then - else Statement)是根据判断条件是否成立来执行的。
if … else 语句的格式如下:
if(条件表达式){
语句块1
}else{
语句块2
}
若在 if 语句体或 else 语句体中要处理的语句只有一个,也可以将左、右大括号去除。
但程序的正确缩进在这种选择结构中起着非常重要的作用,它可以使设计者编写的程序结构层次清晰,在维护上也就比较简单。
3. if … else if … else 语句
由于 if 语句体或 else 语句体可以是多条语句,所以如果需要在 if … else 里判断多个条件,可以“随意”嵌套。比较常用的是 if … else if … else 语句。
if … else if … else 语句的格式如下:
if(条件判断1){
语句块1
}else if(条件判断2){
语句块2
}
… //多个 else if()语句
else{
语句块n
}
4.多重选择——switch 语句
虽然嵌套的 if 语句可以实现多重选择处理,但语句较为复杂,并且容易将 if 与 else 配对错误,从而造成逻辑混乱。在这种情况下,可使用 switch 语句来实现多重选择情况的处理。switch 结构称为 “ 多路选择结构 ”,switch 语句也叫开关语句,在许多不同的语句组之间作出选择。
switch 语句的格式如下,其中 default 语句和 break 语句并不是必须的。
switch(表达式)
{
case 常量选择值1:语句体1 { break;}
case 常量选择值2:语句体2 { break;}
……
case 常量选择值n:语句体n { break;}
default:默认语句体 { break;}
}
提示:switch 的表达式类型为整型(包括 byte、short、char、int 等)、字符类型及枚举类型。
在 JDK 1.7以后,switch 语句增加了对 String 类型的支持。case(情况)后的常量选择值要和表达式的数据类型一致,并且不能重复。break 语句用于转换程序的流程,在 switch 结构中使用break 语句可以使程序立即退出该结构,转而执行该结构后面的第 1 条语句。
switch 语句执行的流程:
(1)switch 语句先计算括号中表达式的结果。
(2)根据表达式的值检测是否符合执行 case 后面的选择值,若是所有的 case 的选择值皆不符合,则执行 default 后面的语句,执行完毕即离开 switch 语句。
(3)如果某个 case 的选择值符合表达式的结果,就会执行该 case 所包含的语句,直到遇到 break 语句后才离开 switch 语句。
(4)若是没有在 case 语句结尾处加上 break 语句,则会一直执行到 switch 语句的尾端才会离开 switch 语句。可以将 break 语句理解为跳出语句。
(5)若是没有定义 default 该执行的语句,则什么也不会执行,而是直接离开 switch 语句。
三、循环结构
循环结构是程序中的另一种重要结构。循环结构的特点是在给定条件成立时,反复执行某个程序段。通常我们称给定条件为循环条件,称反复执行的程序段为循环体。循环体可以是复合语句、单个语句或空语句。
循环结构包括 while 循环、do…while 循环、for 循环,还可以使用嵌套循环完成复杂的程序控制操作。
1. while 循环
while 循环语句的执行过程是先计算表达式的值,若表达式的值为真,则执行循环体中的语句,继续循环;否则退出该循环,执行 while 语句后面的语句。循环体可以是一条语句或空语句,也可以是复合语句。
while 循环的格式如下:
while(判断条件)
{
语句1;
语句2;
…
语句n;
}
当 while 循环主体有且只有一个语句时,也可将大括号去掉。在 while 循环语句中,只有一个判断条件,它可以是任何逻辑表达式。值得注意的是,while 中的判断条件必须是布尔类型值(不同于 C/C++ 可以是有关整型数运算的表达式)。
while 循环执行的流程:
(1)第 1 次进入 while 循环前,必须先对循环控制变量(或表达式)赋起始值。
(2)根据判断条件的内容决定是否要继续执行循环,如果条件判断值为真(true),则继续执行循环主体。
(3)条件判断值为假(false),则跳出循环执行其他语句。
(4)重新对循环控制变量(或表达式)赋值(增加或减少)。完成后再回到步骤(2)重新判断是否继续执行循环。
2. do…while 循环
while 循环又称 “ 当型循环 ” ,即当条件成立时才执行循环体,与 “ 当型循环 ” 不同的是 “ 直到型循环 ” ,即先 “ 直到 ” 循环体(执行循环体),在判断条件是否成立,所以 “ 直到型循环 ” 至少会执行一次循环体。该循环由其关键字又称为 do…while 循环。
do…while 语句的结构如下:
do
{
语句1;
语句2;
…
语句n;
}while(判断条件);
do…while 循环的执行过程是先执行一次循环体,然后判断表达式的值,如果是真,则再执行循环体,继续循环;否则退出循环,执行下面的语句。循环体可以是单条语句或是复合语句,在语法上它也可以是空语句,但此时循环没有什么实际意义。
do…while 循环执行的流程:
(1)在进入 do…while 循环前,要先对循环控制变量(或表达式)赋起始值。
(2)直接执行循环主体,循环主体执行完毕,才开始根据条件的内容,判断是否继续执行循环:条件判断值为真(true)时,继续执行循环主体;条件判断值为假(false)时,则跳出循环,执行其他语句。
(3)执行完循环主体内的语句后,重新对循环控制变量(或表达式)赋值(增加或减少)。由于 do…while 循环和 while 循环一样,不会自动更改循环控制变量(或表达式)的内容,所以在 do…while 循环中赋值循环控制变量的工作要由自己来做,再回到步骤(2)重新判断是否继续执行循环。
3.for 循环
在 for 循环中,赋初始值语句、判断条件语句、增减标志量语句均可有可无。循环体可以是一条语句或空语句,也可以是复合语句。其语句格式如下:
for(赋初始值;判断条件;增减标志量)
{
语句1;
…
语句n;
}
若是在循环主体中要处理的语句只有 1 个,也可将大括号去掉。
以下列出 for 循环的流程:
(1)第 1 次进入 for 循环时,对循环控制变量赋起始值。
(2)根据判断条件的内容检查是否要继续执行循环,当判断条件值为真(true)时,继续执行循环主体内的语句;判断条件值为假(false)时,则会跳出循环,执行其他语句。
(3)执行完循环主体内的语句后,循环控制变量会根据增减量的要求,更改循环控制变量的值,再回到步骤(2)重新判断是否继续执行循环。
4. foreach 循环
很多时候,从头到尾遍历操作一个数组(array)、集合框架(collections)等中所有元素,是很常见的需求。可以将它们理解为承载数据的 “ 容器 ” 。
例如,我们定义了一个整型数组 numArray。
int[] numArray = { 1,2,3,4,5,6};
如果我们要输出数组中的全部 6 个元素(即遍历输出),通常的做法是:
for(int element;element < numArray.length;element++)
{
System.out.print(numArray[element]);
}
其中 numArray.length 是读取数组的长度。写法虽没错,只不过索引信息(数组下标)基本上是不需要的,所以形式繁琐。
在 SDK 5 以后,Java 提供了 for 语句的特殊简化版本 foreach 语句块(有时也称为增强的 for 循环)。foreach 语句为遍历诸如数组、集合框架等内的元素提供了很大便利。foreach 并不是一个关键字,仅是在习惯上将这种特殊的 for 语句格式称之为 “ foreach ” 语句。从英文字面意思理解 foreach 也就是 “ 为(for)每一个(each) ” 的意思,其本身的含义都有 “ 遍历 ” 元素的意思。
foreach 的语句格式如下:
for(元素类型 type 元素变量 var :遍历对象 obj)
{
引用了 var 的 Java 语句;
}
所以,输出数组中的 6 个元素可写成如下形式:
for(int element :numArray)
{
System.out.print(element);
}
注意:所有 foreach 均可用传统的 for 循环模式代替。由于 foreach 循环会丢失元素的下标信息,当遍历集合或数组时,如果需要集合或数组元素的下标,还是要使用传统的 for 循环方式。
5.循环嵌套
当循环语句中又出现循环语句时,就称为循环嵌套。例如嵌套 for 循环、嵌套 while 循环等。
四、循环的跳转
在 Java 语言中,有一些跳转的语句,如 break、continue 以及 return 等语句。break 语句、continue 语句和 return 语句都是用来控制程序的流程转向的,适当和灵活地使用它们可以更方便或更简洁地进行程序的设计。
1. break 语句
break语句也称之为中断语句,它通常用来在适当的时候退出某个循环,或终止某个 case 并跳出 switch 结构。例如在 for 循环主体中有 break 语句时,当程序执行到 break,即会离开循环主体,而继续执行循环外层的语句。
break 语句有两种用法,最常见的就是不带标签的 break 语句。另外一种情况就是带标签的 break 语句,它可以协助跳出循环体,接着运行指定位置语句。
(1)不带标签的 break
在循环主体中有 break 语句时,当程序执行到 break,即会离开循环主体,而继续执行循环外层的语句。通常设计者都会设定一个条件,当条件成立时,不再继续执行循环主体。所以在循环中出现 break 语句时,if 语句通常也会同时出现。如果 if 语句里只含有一条类似于 break 语句、continue 语句或 return 语句的跳转语句,通常会省略 if 语句的大括号。
(2)带标签的 break
不带标签的break 只能跳出包围它的最小代码块,如果想跳出包围它的更外层的代码块,可以使用带标签的 break 语句。
格式如下:
break 标签名;
当这种形式的 break 执行时,控制被传递出指定的代码块。标签不需要直接的包围 break 块,因此可以使用一个加标签的 break 语句退出一系列的嵌套块。要为一个代码块添加标签,只需要在该语句块的前面加上 “ 标签名:” 格式代码即可。标签名可以是任何合法有效的 Java 标识符。给一个块加上标签后,就可以使用这个标签作为 break 语句的对象。用法如下:
//带标签的break语句的用法
public class show_break{
public static void main(String[] args){
for(int i = 0;i < 10;i++){ //最外层for循环
System.out.println("最外层循环" + i);
loop: // 中间层for循环标签
for(int j = 0;j < 10;j++){
System.out.println("中间层循环" + j);
for(int k = 0;k < 10;k++){ //最内层for循环
System.out.println("最内层循环" + k);
break loop; //跳出中间层for循环
}
}
}
}
}
带标签的 break 语句,在本质上,是作为 goto 语句的一种 “ 文明 ” 形式来使用。
2. continue 语句
在 while、do…while 和 for 语句的循环体中,执行 continue 语句将结束本次循环而立即测试循环的条件,以决定是否进行下一次循环。例如下面的 for 循环,在循环主体中有 continue 语句,当程序执行到 continue,会执行设增减量,然后执行判断条件。也就是说会跳过 continue 下面的语句。
for(初值赋值;判断条件;设增减量){
语句1;
语句2;
…
continue
… //若执行continue语句,则此处将不会被执行
语句n;
}
continue 语句也有两种用法:一种是最常见的就是不带标签的 continue 语句。另一种就是带标签的 continue 语句,它可以协助跳出循环体,接着运行指定位置语句。下面分别简介一下两种用法:
(1)不带标签的 continue 语句
若将上面的 show_break 中的 break 语句改成 continue 语句,可以看出其中的不同之处。break 语句是跳出当前层循环,终结的是整个循环,也不再判断循环条件是否成立;相比而言,continue 语句则是结束本次循环(即 continue 语句之后的语句不再执行),然后重新回到循环的起点,如果成立,则再次进入循环体,若不成立,跳出循环。
(2)带标签的 continue 语句
continue 语句和 break 语句一样可以和标签搭配使用,其作用也是用于跳出深度循环。其格式为:
continue 标签名;
continue 后的标签,必须标识在循环语句之前,使程序的流程在遇到 continue 之后,立即结束当次循环,跳入标签所标识的循环层次中,进行下一轮循环。
3.return 语句
return 语句可以使程序的流程离开 return 语句所在的方法。该语句的语法为:
return 返回值;
五、本文注意事项
1.三元运算符与 if…else 语句的关系
其实三元运算符就相当于 if…else 语句,只不过三元运算符有返回值。为了程序的清晰明了,只有在 if…else 语句的主体部分很少时才使用三元运算符。
2.switch 中并不是每个 case 后都需要 break 语句
在某些情况下,在 switch 结构体中,可以有意的减少一些特定位置的 break 语句,这样可以简化程序。
3.三种循环的关系
其实三种循环结构是可以互相转化的,通常我们只是使用其中一种结构,因为这样可以使程序结构更加清晰。
4.循环的区间控制
在习惯上,我们在循环中通常使用半开区,即从第一个元素开始,到最后一个元素的下一个位置之前。
即:
for(int i = 0;i < 10;i++)
而非:
for(int i = 0;i <= 9;i++)
这样使用,前者更具有可读性,而后者也能达到相同功能。可以根据自己的代码风格进行取舍。