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

循环

上节我们介绍了流程控制中的条件执行,根据具体条件不同执行不同操作。本节我们介绍流程控制中的循环,所谓循环就是多次重复执行某些类似的操作,这个操作一般不是完全一样的操作,而是类似的操作。都有哪些操作呢?这个例子太多了。

  • 展示照片,我们查看手机上的照片,背后的程序需要将照片一张张展示给我们。
  • 播放音乐,我们听音乐,背后程序按照播放列表一首首给我们放。
  • 查看消息,我们浏览朋友圈消息,背后程序将消息一条条展示给我们。

循环除了用于重复读取或展示某个列表中的内容,日常中的很多操作也要靠循环完成。

  • 在文件中,查找某个词,程序需要和文件中的词逐个比较(当然可能有更高效方式,但也离不开循环)。
  • 使用Excel对数据进行汇总,比如求和或平均值,需要循环处理每个单元的数据
  • 群发祝福消息给好友,程序需要循环给每个好友发。

当然,以上这些例子只是冰山一角,计算机程序运行时大概只能顺序执行、条件执行和循环执行,顺序和条件其实没什么特别,而循环大概才是程序强大的地方。凭借
循环,计算机能够非常高效的完成人很难或无法完成的事情,比如说,在大量文件中查找包含某个搜索词的文档,对几十万条销售数据进行统计汇总等。

在Java中,循环有四种形式,分别是 while, do/while, for, foreach,下面我们分别来看一下。

while

while的语法为:

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

while(条件语句) 代码; 

while和if的语法很像,只是把if换成了while,它表达的含义也非常简单,只要条件语句为真,就一直执行后面的代码,为假就停止不做了。例如:

Scanner reader = new Scanner(System.in);
System.out.println("please input password");
int num = reader.nextInt();
int password = 6789;
while(num!=password){
    System.out.println("please input password");
    num = reader.nextInt();
}
System.out.println("correct");
reader.close();

以上代码中,我们使用类型为Scanner的reader变量从屏幕控制台接收数字,reader.nextInt()从屏幕接收一个数字,如果数字不是 6789,就一直提示输入,否则才跳出循环。(以上代码Scanner我们还没有介绍过,可以忽略其细节,另外代码只用于解释语法,不应看做是实际良好代 码)

while循环中,代码块中会有代码影响循环条件,但也经常不知道什么时候循环会退出。如上例所示,匹配的时候会退出但什么时候能匹配取决于用户的输入。

do/while

如果不管条件语句是什么,代码块都会至少执行一次,则可以使用do/while循环。do/while的语法是:

do{
   代码块;
}while(条件语句)

这个也很容易理解,先执行代码块,然后再判断条件语句,如果成立,则继续循环,否则退出循环。也就是,不管条件语句是什么,代码块都会至少执行一次。用上面的例子,其do/while循环是:

Scanner reader = new Scanner(System.in);
int password = 6789;
int num = 0;
do{
    System.out.println("please input password");
    num = reader.nextInt();
}while(num!=password);
System.out.println("correct");
reader.close();

for

实际中应用最为广泛的循环语法可能是for了,尤其是在循环次数已知的情况下。for的语法是:

for(初始化语句; 循环条件; 步进操作){
   循环体
}

for 后面的括号中有两个分号;,分隔了三条语句,除了循环条件必须返回一个boolean类型外,其他语句没有什么要求,但通常情况下第一条语句用于初始化, 尤其是循环的索引变量,第三条语句修改循环变量,一般是步进,即递增或递减索引变量,循环体是在循环中执行的语句。

for循环简化了书写,但执行过程对初学者而言不是那么明显,实际上,它执行的流程是这样的:

  1. 执行初始化指令
  2. 检查循环条件是否为true,如果为false,跳转到第6步
  3. 循环条件为真,执行循环体
  4. 执行步进操作
  5. 步进操作执行完后,跳转到第2步,即继续检查循环条件。
  6. for循环后面的语句

下面是一个简单的for循环:

int[] arr = {1,2,3,4};
for(int i=0;i<arr.length;i++){
    System.out.println(arr[i]);
}

顺序打印数组中的每个元素,初始化语句初始化索引i为0,循环条件为索引小于数组长度,步进操作为递增索引i,循环体打印数组元素。

在for中,每个语句都是可以为空的,也就是说:

for(;;){}

是有效的,这是个死循环,一直在空转,和while(true){}的效果是一样的。可以省略某些语句,但分号;不能省。如:

int[] arr = {1,2,3,4};
int i=0;
for(;i<arr.length;i++){
    System.out.println(arr[i]);
}

索引变量在外面初始化了,所以初始化语句可以为空。

foreach

foreach的语法如下代码所示:

int[] arr = {1,2,3,4};
for(int element : arr){
    System.out.println(element);
}

foreach使用冒号 : ,冒号前面是循环中的每个元素,包括数据类型和变量名称,冒号后面是要遍历的数组或集合(关于集合我们后续文章介绍),每次循环element都会自动更新。对于不需要使用索引变量,只是简单遍历的情况,foreach语法上更为简洁。

循环控制 - break

在循环的时候,会以循环条件作为是否结束的依据,但有时候可能会根据别的条件提前结束循环。比如说,在一个数组中查找某个元素的时候,循环条件可能是到数组结束,但如果找到了元素,可能就会想提前结束循环,这时候可以使用break。

我们在介绍switch的时候提到过break,它用于跳转到switch外面。在循环的循环体中也可以使用break,它的含义和switch中类似,用于跳出循环,开始执行循环后面的语句。以在数组中查找元素作为例子,代码可能是:

int[] arr = ... ; //在该数组中查找元素
int toSearch = 100; //要查找的元素
int i = 0;
for(;i<arr.length;i++){
    if(arr[i]==toSearch){
        break;
    }
}
if(i!=arr.length){
    System.out.println("found");
}else{
    System.out.println("not found");
}

如果找到了,会调用break, break执行后会跳转到循环外面,不会再执行i++语句,所以即使是最后一个元素匹配,i也小于arr.length,而如果没有找到,i最后会变为arr.length,所以可根据i是否等于arr.length来判断是否找到了。

以上代码中,也可以将判断是否找到的检查放到循环条件中,但通常情况下,使用break可能会使代码更清楚一些。

循环控制 - continue

在循环的过程中,有的代码可能不需要每次循环都执行,这时候,可以使用continue语句,continue语句会跳过循环体中剩下的代码,然后执行步进操作。我们看个例子,以下代码统计一个数组中某个元素的个数:

int[] arr = ... //在该数组中查找元素
int toSearch = 2; //要查找的元素
int count = 0;
for(int i=0;i<arr.length;i++){
    if(arr[i]!=toSearch){
        continue;
    }
    count++;
}
System.out.println("found count "+count);

上面代码统计数组中值等于toSearch的元素个数,如果值不等于toSearch,则跳过剩下的循环代码,执行i++。以上代码也可以不用 continue,使用相反的if判断也可以得到相同的结果,这只是个人偏好的问题,如果类似要跳过的情况比较多,使用continue可能会更简洁。

循环嵌套

和if类似,循环也可以嵌套,在一个循环体中开启另一个循环。在嵌套循环中,break语句只会跳出本层循环,continue也一样。

循环本质

和if一样,循环内部也是靠条件转移和无条件转移指令实现的。比如说下面的代码:

int[] arr = {1,2,3,4};
for(int i=0;i<arr.length;i++){
    System.out.println(arr[i]);
}

其对应的跳转过程可能为:

1. int[] arr = {1,2,3,4};
2. int i=0;
3. 条件跳转:如果i>=arr.length,跳转到第7行
4. System.out.println(arr[i]);    
5. i++
6. 无条件跳转,跳转到第3行
7. 其他代码

在if中,跳转只会往后面跳,而for会往前面跳,第6行就是无条件跳转指令,跳转到了前面的第3行。break/continue语句也都会转换为跳转指令。

循环小结

循环的语法总体上也是比较简单的,初学者需要注意的是for的执行过程,以及break和continue的含义。

虽然循环看起来只是重复执行一些类似的操作而已,但它其实是计算机程序解决问题的一种基本思维方式,凭借循环(当然还有别的),计算机程序可以发挥出强大的能力,比如说批量转换数据,查找过滤数据,统计汇总等。

使用基本数据类型、数组、基本运算、加上条件和循环,其实已经可以写很多程序了,但使用基本类型和将代码都放在一起,程序难以理解,尤其是程序逻辑比较复杂的时候。

解决复杂问题的基本策略是分而治之,将复杂问题分解为若干不那么复杂的子问题,然后子问题再分解为更小的子问题……程序由数据和指令组成,大程序可以分解为小程序,小程序接着分解为更小的程序。那如何表示子程序,以及子程序之间如何协调呢?

时间: 2024-10-27 09:15:22

计算机程序的思维逻辑 (10) - 强大的循环【转】的相关文章

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

循环 上节我们介绍了流程控制中的条件执行,根据具体条件不同执行不同操作.本节我们介绍流程控制中的循环,所谓循环就是多次重复执行某些类似的操作,这个操作一般不是完全一样的操作,而是类似的操作.都有哪些操作呢?这个例子太多了. 展示照片,我们查看手机上的照片,背后的程序需要将照片一张张展示给我们. 播放音乐,我们听音乐,背后程序按照播放列表一首首给我们放. 查看消息,我们浏览朋友圈消息,背后程序将消息一条条展示给我们. 循环除了用于重复读取或展示某个列表中的内容,日常中的很多操作也要靠循环完成. 在

计算机程序的思维逻辑 (23) - 枚举的本质

前面系列,我们介绍了Java中表示和操作数据的基本数据类型.类和接口,本节探讨Java中的枚举类型. 所谓枚举,是一种特殊的数据,它的取值是有限的,可以枚举出来的,比如说一年就是有四季.一周有七天,虽然使用类也可以处理这种数据,但枚举类型更为简洁.安全和方便. 下面我们就来介绍枚举的使用,同时介绍其实现原理. 基础 基本用法 定义和使用基本的枚举是比较简单的,我们来看个例子,为表示衣服的尺寸,我们定义一个枚举类型Size,包括三个尺寸,小/中/大,代码如下: public enum Size {

计算机程序的思维逻辑 (29) - 剖析String

上节介绍了单个字符的封装类Character,本节介绍字符串类.字符串操作大概是计算机程序中最常见的操作了,Java中表示字符串的类是String,本节就来详细介绍String. 字符串的基本使用是比较简单直接的,我们来看下. 基本用法 可以通过常量定义String变量 String name = "老马说编程"; 也可以通过new创建String String name = new String("老马说编程"); String可以直接使用+和+=运算符,如: S

计算机程序的思维逻辑 (28) - 剖析包装类 (下)

本节探讨Character类,它的基本用法我们在包装类第一节已经介绍了,本节不再赘述.Character类除了封装了一个char外,还有什么可介绍的呢?它有很多静态方法,封装了Unicode字符级别的各种操作,是Java文本处理的基础,注意不是char级别,Unicode字符并不等同于char,本节详细介绍这些方法以及相关的Unicode知识. 在介绍这些方法之前,我们需要回顾一下字符在Java中的表示方法,我们在第六节.第七节.第八节介绍过编码.Unicode.char等知识,我们先简要回顾一

计算机程序的思维逻辑 (25) - 异常 (下)

上节我们介绍了异常的基本概念和异常类,本节我们进一步介绍对异常的处理,我们先来看Java语言对异常处理的支持,然后探讨在实际中到底应该如何处理异常. 异常处理 catch匹配 上节简单介绍了使用try/catch捕获异常,其中catch只有一条,其实,catch还可以有多条,每条对应一个异常类型,比如说: try{ //可能触发异常的代码 }catch(NumberFormatException e){ System.out.println("not valid number"); }

计算机程序的思维逻辑 (22) - 代码的组织机制

使用任何语言进行编程都有一个类似的问题,那就是如何组织代码,具体来说,如何避免命名冲突?如何合理组织各种源文件?如何使用第三方库?各种代码和依赖库如何编译连接为一个完整的程序? 本节就来讨论Java中的解决机制,具体包括包.jar包.程序的编译与连接,从包开始. 包的概念 使用任何语言进行编程都有一个相同的问题,就是命名冲突,程序一般不全是一个人写的,会调用系统提供的代码.第三方库中的代码.项目中其他人写的代码等,不同的人就不同的目的可能定义同样的类名/接口名,Java中解决这个问题的方法就是包

计算机程序的思维逻辑 (21) - 内部类的本质

内部类 之前我们所说的类都对应于一个独立的Java源文件,但一个类还可以放在另一个类的内部,称之为内部类,相对而言,包含它的类称之为外部类. 为什么要放到别的类内部呢?一般而言,内部类与包含它的外部类有比较密切的关系,而与其他类关系不大,定义在类内部,可以实现对外部完全隐藏,可以有更好的封装性,代码实现上也往往更为简洁. 不过,内部类只是Java编译器的概念,对于Java虚拟机而言,它是不知道内部类这回事的, 每个内部类最后都会被编译为一个独立的类,生成一个独立的字节码文件. 也就是说,每个内部

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

函数 前面几节我们介绍了数据的基本类型.基本操作和流程控制,使用这些已经可以写不少程序了. 但是如果需要经常做某一个操作,则类似的代码需要重复写很多遍,比如在一个数组中查找某个数,第一次查找一个数,第二次可能查找另一个数,每查一个数,类似的代码都需要重写一遍,很罗嗦.另外,有一些复杂的操作,可能分为很多个步骤,如果都放在一起,则代码难以理解和维护. 计算机程序使用函数这个概念来解决这个问题,即使用函数来减少重复代码和分解复杂操作,本节我们就来谈谈Java中的函数,包括函数的基础和一些细节. 定义

计算机程序的思维逻辑 (34) - 随机

随机 本节,我们来讨论随机,随机是计算机程序中一个非常常见的需求,比如说: 各种游戏中有大量的随机,比如扑克游戏洗牌 微信抢红包,抢的红包金额是随机的 北京购车摇号,谁能摇到是随机的 给用户生成随机密码 我们首先来介绍Java中对随机的支持,同时介绍其实现原理,然后我们针对一些实际场景,包括洗牌.抢红包.摇号.随机高强度密码.带权重的随机选择等,讨论如何应用随机. 先来看如何使用最基本的随机. Math.random Java中,对随机最基本的支持是Math类中的静态方法random,它生成一个