购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

7.1 多重循环

7.1.1 双重循环的概念

所谓多重循环,其实就是将多个循环结构嵌套在一起,就像嵌套If语句一样。而最简单的多重循环,就是在一个循环体内再书写一个循环结构,从而实现“循环套循环”的效果,也就是“双重循环”。下面结合案例7-1讲解双重循环的基本用法。

案例7-1: 如图7.1所示,工作表中存有多个国外供应商的产品报价,需要编写程序将所有价格转换为人民币,汇率为:1美元=6.5元人民币。

图7.1 案例7-1原始数据及预期运行效果

假如案例7-1的数据只有一行,比如只有第4行一个供应商的报价,那么只需一个普通的For循环就能把该行每列数字都进行转换,代码如下:

但是如果想在一个程序内把第4行到第7行的所有数据都计算一遍,就需要把上面代码中的循环语句复制粘贴4次,而且每次还要把Cells(4 ,j)修改为Cells(5 ,j)、Cells(6 ,j)等不同的行号。这种做法的代码具体如下:

显然,这样书写代码已经失去了编程的意义,因为其中存在大量的机械重复。根据编程最佳实践,存在重复代码就应想到循环,也就是要把重复执行的代码放到循环体中。而在上面的代码里,被反复执行的是一个以j为循环变量的For语句,那么就应该把这个For语句放到一个循环结构中,这就是典型的双重循环,代码如下:

这段代码包含两个For循环结构,循环变量分别为i 和 j。其中j循环的代码完全嵌入i循环的内部,因此每当外层循环执行一次,修改i的数值并进入其循环体,j循环就会从头开始,让j从3逐次变化到5,也就是执行3次。具体来说,这段代码的执行顺序如表7-1所示。

表7-1 代码的执行顺序

续表

如果读者是第一次学习编程,那么我们强烈建议耐心地逐行读完上述表格,然后回到前面的双重循环代码中,把自己当作计算机,用手指“点读”每行,按照表格中的顺序把这34个步骤依次执行一遍。这样反复几次,就可以真正领会多重循环的执行流程,为编写稍微复杂的程序奠定非常重要的基础。

另一个有助于看懂程序流程的办法,就是使用“单步调试”工具。读者可以先将这段代码写在自己计算机的VBA编辑器中,然后按“F8”键让其单步运行,并添加3个监视:i、j和Cells(i,j),从而能够实际观察程序的执行流程,并且随时看到各个变量的取值变化。

理解这段代码的关键在于想通一个问题:Cells(i,j)=Cells(i,j)*6.5语句一共执行了12次。这是因为外层的i循环每执行1步,内层的j循环就会从头开始执行,共计3步(j从3变化到5)。由于这行语句写在 j 循环里面,所以 实际运行次数是外层循环的步数乘以内层循环的步数 。理解执行次数的计算方法非常重要,因为在编写程序处理大批量数据时,稍微调整循环的次序往往能够大幅减少关键语句的执行次数,从而提高程序运行速度。

这段代码中还有一点与之前的示例不同,就是我们在使用Cells属性时,将它的两个参数(行号和列号)都写成了变量形式(分别为i和j)。通过这种方式,我们将Cells属性的位置与i和j两个循环语句关联在一起,从而实现了“外层循环控制行号,内层循环控制列号”的效果。

其实双重循环这种流程在日常生活中比比皆是,比如时钟的时针和分针,时针每走一格,分针就要循环一圈。还有地球的公转与自转,因为自转周期是一天,公转周期是一年,所以公转执行1次则自转执行365次(不考虑闰年等问题),公转执行10次则自转执行3650次。

7.1.2 初学者常见错误

案例7-1是多重循环最典型且最简单的应用情景,因此相对容易理解。不过根据作者的教学经验,即使对于这种比较简单的应用,初学者往往会也犯一些语法和逻辑错误。因此本节把使用多重循环时最常见的几个错误列示出来,以供读者参考。

1.循环变量不能重用

在VBA语言中,一个For … To … Next语句必须有一个循环变量,否则无法控制循环的次数。而一个循环变量只能同时用于一个For语句,否则会使程序逻辑混乱难懂,极易出错。比如在案例7-1的代码中,如果把两个循环变量都写成i,程序的流程和结果就会发生巨大变化,如图7.2所示。

图7.2 重复使用循环变量导致的错误警告

之所以出现这个警告,就是因为VBA的设计者认为重复使用循环变量是极度危险且没有实际意义的行为,所以直接在VBA语法中禁止了这种操作 。如果在开发程序时看到这个提示,就要想到重复使用循环变量的问题。

另外需要提醒读者的是,引发这种错误的原因是把一个变量“同时”用于多个循环。如果当一个循环结束后把它的循环变量用于另一个循环,并不属于这个范畴,也不会引发错误。比如在下面的代码中,虽然两个 For 循环都使用变量 j 作为循环变量,但是因为二者互不嵌套,不可能同时运行,所以程序并不存在语法错误和逻辑问题。

其实深入思考一下就会发现,重复使用循环变量之所以危险,根本原因在于这种方式相当于在循环体中修改了循环变量,这也是初学者常犯的错误。比如图7.2中导致错误警告的代码,就是在外层循环已经使用i作为循环变量时,又在内层循环中将i修改为3,并且每次增加1。

2.循环语句不能共享

另一个初学者常犯的语法错误,是试图将两个循环的For语句与Next语句合写在一起,从而实现“精简代码”的效果。比如作者在实际教学中经常见到有人将案例7-1的代码写成如下形式:

这种写法看上去确实比之前的双层For语句简洁优雅(是否合理另说),但是编程也要遵守“基本法”,而 VBA 的“基本法”就是微软公司为我们定义好的语法规范。按照语法规范,一个 For语句中只能有一个循环变量,一个Next语句中也只能有一个循环变量。所以,任何尝试用逗号等形式投机取巧的违法行为,都将受到严重警告。

3.语句位置必须搞清

使用循环结构特别是多重循环结构时,必须时刻提醒自己:哪些语句应该放在循环体内,哪些语句应该放在循环体外。而正确判断的关键就是要牢记一个原则:循环体外的代码只会执行一次,但循环体内的代码可能执行很多次。

很多初学者都会在这个问题上犯糊涂,结果放错语句位置导致程序低效甚至出错。比如在案例7-1的参考代码中,“Cells(2,5)=“人民币””这个语句只需运行一次就能满足要求,即在全部表格计算完后修改 E2单元格的币种。所以如果像下面的代码一样把它误写在循环结构之内,相当于每计算完一行数据就向E2单元格中写入一个字符串“人民币”,尽管这个单元格在上次循环时已经被修改为“人民币”。假如工作表中有几万行数据,E2单元格就会被毫无意义地修改几万次,严重降低程序的运行速度。(读者可以对下面的代码做进一步思考:如果把语句Cells(2,5)=“人民币” 写在内层j循环中,程序效率是否会进一步降低?)

效率的降低尚可容忍,但是很多时候写错语句位置还会导致程序逻辑混乱,输出错误计算结果。比如遇到案例7-2所示的情况。

案例7-2: 如图7.3所示,工作表中存有某产品不同季度在不同地区的销量数据,请编写程序对每个季度的全国销量进行汇总,并将结果写入K列单元格中。

图7.3 案例7-2的原始数据

显然,这个VBA代码需要用到“累加器”,以便对每行数据进行求和运算。因此正确的代码应该如下:

在这段代码中,每次进入外层循环的循环体时,首先会将变量s清零。接下来进入内层循环,将第3列到第10列数据逐个加到s上。于是s从0开始逐渐增加,直到最后内层循环结束时,变成该行各列数字的总和。这时再将s写入该行第11列(J列)单元格,然后让外层循环去处理下一行数据即可。

上面的说明很容易理解。不过在独立完成类似程序时,很多初学者会搞不清“s=0”这个语句的位置(或者干脆忘记书写这个语句),把程序写成下面的样子:

在这段代码中,“s=0”被放在外层For循环之前,因此只在程序最开始时执行过一次 。这样当第一次进入外层循环(i=4)时,由于s为0,所以第4行的累加结果没有错误,在内层循环结束后s的数值就是第4行数据的总和。但是再次循环(i=5)时,s的数值没有发生任何变化,仍然是第4行数据的总和。此时再次执行内层循环,把第5行数据依次加到s上,最后得到的s将等于第4行数据与第5行数据相加的结果,显然这不是我们想要的结果。如此循环下去,最终每行得到的“全国总计”的数据都将是前面各行数据之和,如图7.4所示。

图7.4 案例7-2的正确结果(左)与错误结果(右)对比

还有的同学会把“s=0”写到内层循环的里面,类似下面的代码:

关于这段代码的错误,我们在介绍单步调试时已经结合案例6-2详细讲解过。由于每次遇到一个单元格时程序都会先将 s 清零,所以变量 s 完全失去了累加的意义。待内层循环结束后,s的数值只等于该行最后一列(第10列)的数值,所以输出结果仍然是错误的,如图7.5所示。

图7.5 案例7-2的正确结果(左)与错误结果(右)对比

所以,做代码与做人一样,摆正位置方为正道。具体到循环结构来说,就是要明确每个语句的实际作用和意义,从而想清楚需要执行一次还是多次,或者需要在哪层循环中执行多次。如果实在想不清楚,也可以先把它写出来(哪怕是错误的位置),然后使用单步调试功能逐行运行代码,直接观察程序流程并得出正确的结论。

7.1.3 更多层次的嵌套循环

双重循环是多重循环最简单的形式,却是初学者从一维步入多维最关键的一步。理解了双重循环的流程和意义,我们就可以轻松将它扩展到更多层次的循环嵌套。比如案例7-3就是在案例7-2的基础上进一步扩充数据所得,因而可以用三重循环解决。

案例7-3: 如图7.6所示,工作表中包括三个大小完全相同、起止行号也完全相同的子表格。请编写程序对每个子表格每行的数据进行季度汇总操作,并将汇总结果写入“总计”列。

图7.6 案例7-3数据示例

如果案例7-3中只有一个子表格,就会完全等同于案例7-2,使用一个双重循环便可解决问题。现在有三个子表格,就意味着这个双重循环需要被重复执行三次,也就是把双重循环再放到一个循环中,构成三重循环。

进一步思考,在将双重循环重复三次的过程中,每次都要处理不同的子表格,而每个子表格的数据范围分别是:第3列到第5列(子表格1)、第9列到第11列(子表格2)、第15列到第17列(子表格3)。所以在重复三次这个循环中,应该体现出每次循环(每个子表)的起始列号变化。所以参考代码如下:

结合代码中的注释,读者应该可以读懂这个程序。不过相信很多读者在没有看到这个参考代码前,仍然难以独立编写出这段代码,特别是不知道怎样构思出外面这一层k循环的写法。其实这个思路十分容易理解:对于本案例来说,三个子表格的唯一区别就是起始列号不同,所以让 k等于不同的列号,就相当于找到不同的子表格。巧合的是,本例中三个子表格的起始列号恰巧是 3、9、15 这个等差数列,所以可以使用For循环配合Step子句准确地得到这三个数字。

接下来,由于每次处理不同子表格时,最内层的 j 循环起始范围也不相同,因此无法像案例7-2那样直接写成“For j=3 to 5”。不过既然k已经代表了当前子表格的起始列号,那么子表格的数据列自然就是第k、k+1和k+2列。因此只要让j从k循环到k+2,变量j的数值就是该子表格的每个数据列号。搞清楚这几个问题,案例7-3的解决方案就呼之欲出了。

在 Excel 中,多重循环会经常用在处理多个工作表甚至工作簿的程序里。比如在后面章节中会看到,如果要对多个工作表中的各行各列数据进行汇总,就可以用第一层循环扫描每个工作表,然后用第二层循环扫描工作表中的每行,最后用第三层循环扫描该行中的每列。假如进一步提升需求,需要对多个工作簿文件进行汇总,而且每个文件中包含多个工作表,那么就要再加上一层循环用于扫描所有工作簿文件。

其实从程序设计的最佳实践角度看,太多层次的嵌套循环(以及嵌套判断等)会显得逻辑复杂难懂,运行效率也难以提高。所以在实践中我们会利用各种方式减少代码中的嵌套深度,例如在学过Application对象后,可以考虑使用工作表的Sum函数代替案例7-3最里面的j循环等。不过作为初学者建立编程思想的重要环节之一,我们现在必须做到能够熟练驾驭多重循环等复杂结构,先理解怎样“自己造轮子”,然后才能得心应手地调用“别人造好的轮子”。 JUOImDwpmLKQW7J1bLFBOH+iBaV8Ecb5b55x9VgQ59vgG3deOXiC6OVlj3GZkpDg

点击中间区域
呼出菜单
上一章
目录
下一章
×