for
for(i = 0; i < 10; i++) {
// do something
}
while
.
int cursor = 0;
while(cursor++ < 10) {
// do something
}
do-while
.
int cursor = 0;
do {
// do something
} while (cursor++ < 10);
int cursor = 0;
while(true) {
// do something
if (cursor++ == 10) {
break;
}
// do something
}
带退出的循环(Loop-with-exit)就是指上述第四种,终止条件出现在循环中间而不是开始或者末尾的循环。
因为带退出的循环可以有效避免 “一个半循环(loop-and-a-half)” 。下面代码就是典型的“一个半循环”,
int targetScore = 90;
int score = getNextScore();
while(score < targetScore) {
score = getNextScore();
}
如果修改成带退出条件的循环,能让循环更加容易理解,条理清晰的”单入单出”结构:
int targetScore = 90;
while(true) {
score = getNextScore();
if (score >= 90) { //单一出口
break;
}
}
这一条和我自己的实践体会一致。外面用一个最无脑的while(true)的无限循环,控制权全部交给循环体里,最好是只有一个单一的出口,这样是最简单易懂的结构。
承接上一条原则,普遍认为把控制权交给循环内部,比在一开始完全决定循环次数,逼近退出条件,要更好。
实际上,遍历容器元素的情况,Java中更简单的一个语法糖是foreach语法。
for(String str : stringArray) {
// so something to each string in array
}
和所有构建复杂事物的诀窍相同,构建复杂循环,最好从内部简单的核心内容开始。 具体步骤如下:
例如,先用文字描述要做的事,
// 从表中取得费率
// 将费率加到总和上
增加数据细节,
int rate = table[census.age][census.gender];
int totalrate = totalRate + rate;
最后套上循环,
for(Census c : censusList) {
int rate = table[c.getAge()][c.getGender()];
int totalRate = totalRate + rate;
}
在循环外面声明变量,
int totalRate = 0;
for(Census c : censusList) {
int rate = table[c.getAge()][c.getGender()];
totalRate += rate;
}
循环体也要遵循模块化原则。逻辑复杂的多层套嵌也最好分割成独立的多个循环。
一个简单的标准是:能不能一目了然。屏幕不用翻屏能完整显示循环体。
实验显示,超过三层套嵌,程序员对循环体的理解就开始有些吃力。
既然用了for循环,就把循环控制权交给头部信息。不要在循环体内部对循环下标做改动。
不要用类似goto语句,从循环体的中间开始执行。这会让循环体的行为变得难以理解。
根据就近原则,循环体内要用到的数据,最好是贴近循环体的地方声明。
除非是简单的递增或递减迭代,尽量不要用i,j这样简单的字母做下标。
浮点数的问题前几章已经说过了。因为小数部分转成二进制,经常是无限小数,四舍五入容易出问题。尽量不要拿来比较大小,判断相等。
Java允许在for语句内部声明变量,出了循环体,变量就失效。这点不太用担心。
必要的时候拿出笔来计算一下,不要靠猜,或者+1,-1这样去试循环下标的范围。靠猜的话,可能偶然看似符合了要求,其实里面有非常隐蔽的bug。越是明显容易出错的地方,越应该花精力搞清楚,而不是蒙混过关。
就算循环体鼓励使用while(true)
,也就是循环体头部不承担循环控制的责任,也要把循环控制语句放在重要的位置。并且控制语句最好都集中在一起。读起来,写起来思路都比较清晰。
在循环开头使用if-continue
结构,可以减少套嵌。
int score = 0;
while (true) {
score = getScore();
if (score > 100) { // stop condition
break;
}
if (score%2 == 0) { // skip all even
continue;
}
// code to treat odd score
}
Java支持带标号的break,continue语句。
label_1:
for ( out iteration ){
for ( inner iteration ){
// ...
break; // (1)
// ...
continue; // (2)
// ...
continue label1; // (3)
// ...
break label1; // (4)
}
}
(1)处break中断内部迭代,回到外部迭代。 (2)处continue继续内部迭代。 (3)处break中断所有迭代。 (4)处continue跳回到外部迭代。
尽管使用break和continue有助于减少循环套嵌,减少复杂度。但循环的退出点也不宜太多。单入单出这样最简单的模型是追求的目标。