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

3.2 For…Next循环语句

3.2.1 For…Next循环语句的基本语法

For…Next循环语句是VBA的循环语句之一,作用就是让一个变量(比如上面例子中的 i)从一个数值开始逐步增加(或减少),直到变为另一个指定的数值。而且,数值每增加或减少一次,都会重复执行一次指定代码。

上面的例子就可以使用For…Next循环写成VBA程序:

从这段代码中可以看到,For…Next循环语句由四个必不可少的关键字构成:“For”“=”“To”和“Next”。如果把这四个关键字分别读成“让”“从”“变化到”和“下一个”,那么这段代码的含义就十分明显了:“让 i 从3变化到5……执行两行代码……下一个 i”。具体来说,执行 For循环的关键步骤如下。

(1)执行到For语句时,VBA会让循环变量(本例中的i)等于等号后面的数值(如本例中的“3”)。

(2)如果这个数值没有超出To后面的指定数值(如本例中的“5”),就执行For与Next之间的所有语句(我们称之为“循环体”);否则直接跳过For循环,执行Next后面的语句。

(3)每次执行到Next时,都会自动将循环变量(本例中的i)增加1,并判断它是否超出To后面的数值。如果没有超出,则回到For的下一行,重复执行循环体;如果已经超出,则执行Next后面的语句,循环终止。

清楚了For循环的几个关键步骤,就可以理解上面这段代码的详细执行流程:

(1)定义变量 i,默认值是0。

(2)遇到For循环语句,要求让i的值从3变到5。于是先让 i=3,由于3没有超出To 后面指定的上限(5),所以进入循环体,执行下一行语句。

(3)执行循环体的第一行:Cells(i ,6)=……。由于 i=3,所以实际计算的是第3行数据的总分。同理,再向下执行一行,即 Cells(i ,7)=……,计算的是第3行数据的平均分。

(4)再向下执行,遇到Next i语句,即“下一个 i”,于是让i的值变为4。接下来判断一下:i的指定范围是从3到5,那么4是否超出了范围?显然没有超出范围,于是重复执行For结构内部的代码,即Cells(i ,6)=…… 和 Cells(i ,7)=……。

(5)这一次执行Cells(i ,6)=…… 和 Cells(i ,7)=……这两个语句时,i的值已经变为4,所以实际计算的是第4行数据的总分与平均分。

(6)再向下执行,又遇到Next i语句,于是将i的值变为5。由于5也在指定的范围内,所以再次执行For结构内部的代码。由于 i=5,所以这次计算的是第5行数据的总分与平均分。

(7)再向下执行,又一次遇到Next i,于是将i的值变为6,但6已经超出了范围,说明For循环的任务已经完成,于是执行Next i 的下一行语句。

(8)Next i 的下一行语句是 End Sub,所以程序执行至此宣告结束。

之所以使用这么多文字详细讲解For循环的执行流程,是因为很多人在初学VBA时没有注意到这个流程中暗藏的一些“陷阱”,结果导致编写程序时发生了一些莫名其妙的错误。因此请读者仔细阅读上述流程,后面会详细讨论这些“陷阱”问题。

总之,For循环的意义就是:当程序中有需要重复执行的代码时,可以把它们写在For与Next之间,通过在For语句中设置一个数值范围,来控制这段代码的执行次数。熟练使用For循环可以实现非常多的功能,下面就是一些最常用到的场景。

3.2.2 For…Next循环的典型用法

1.用循环变量控制次数

使用For循环最简单的方式,就是将一段代码重复运行指定次数。比如在Excel VBA中,可以使用 Worksheets.Add 语句为当前工作簿插入1张新的工作表,如下面左侧代码所示。那如果想在工作簿中插入100张新的工作表,就可以通过一个For循环,让命令重复执行100次,如下面右侧的代码。

在右侧代码的For语句中,我们把循环变量 a 的变化范围指定为1到100。第一次执行循环操作时a=1,并执行Worksheets.Add 语句添加工作表。接下来Next语句让 a 变成2,重复执行Worksheets.Add 语句,直到 Next 语句让 a 变成101时终止循环。在这个过程中,循环体(Worksheets.Add)一共执行了100次,而循环变量a的作用就是精确地控制循环次数。

接下来再看一个有趣的例子:假如我们想计算3 10 ,并且不允许使用“^”运算符(事实上,很多语言中都没有专门提供幂运算符号),那么怎样编写程序呢?我们可以分析一下3 n 的计算规则:

3 2 =3×3 : 对3执行1次乘以3的操作

3 3 =3×3×3 : 对3执行2次乘以3的操作

3 4 =3×3×3×3 :对3执行3次乘以3的操作

……

显然,3 10 就是对3执行9次乘以3的操作。换言之,就是把“乘以3”这个操作重复执行9次。所以可以写出下面的代码:

这段代码一共声明了两个变量:x 用于计算最终结果,i 则用于控制计算的次数(循环次数)。程序首先执行 x=3 一句,将 x 这个“小房子”中的内容变为3。接下来进入For循环,让 i 从1 逐次变化到9,每次变化均执行一遍 x=x * 3 这个操作。

2.用循环变量代表行号(列号)

作为一款经典的表格计算软件,Excel的最大特点就是把数据按照“行列结构”进行存储。所以使用VBA处理Excel数据时,也会频繁遇到“按行/列扫描数据”的需求,案例3-1就是一个典型的“按行扫描”问题,如图3.2所示。

图3.2 案例3-1及示例代码

在这个例子中,需要处理第3行到第5行的数据,因此设计了一个从3到5的循环变量i。接下来,在循环体中又把i作为Cells的第一个参数来代表行号,从而实现了对第3行到第5行数据的逐次扫描。

同样的道理,如果将循环变量作为Cells的第二个参数,也可以实现“逐列扫描”的效果。

案例3-2: 在图3.3左侧所示工作表的第3行中,存有某食品商店常用配料的库存量,单位为千克。要求编写一个程序,能够以“磅”为单位重新计算各配料的库存量,并显示在原单元格中,如图3.3右图所示。(按1千克=2.2 磅进行换算)

图3.3 案例3-2数据及运行效果示例

对于案例3-2,我们需要循环处理第3行第2列(B列)到第7列(G列)的数据,所以可以指定循环变量从2变化到7,并将它作为Cells的第二个参数。代码如下:

总之,由于 Cells 属性的两个参数都是使用数字代表行(列)号,所以只要将循环变量作为Cells属性的参数,就可以实现对行(列)数据的逐次扫描。这就是在VBA的各种单元格表示法(比如Range(“D3”)、[D3]等)中经常使用Cells属性的原因。

3.用循环变量控制内容

循环变量除了可以在循环体中代表行号或列号,还经常直接用于循环体中的各种计算。

案例3-3: 编写一个程序,能够计算出1 2 ,2 2 ,3 2 ,…,9 2 ,并将结果分别保存在A1到A9单元格中。该程序代码及运行效果如图3.4所示。

图3.4 案例3-3程序代码及运行效果

在图3.4所示的代码中,循环变量 i 其实有两个用途:一是作为循环体中Cells属性的第一个参数,用于控制每次循环时需要写入的单元格行号;二是变量 i 又出现在等号右边的算式中,使其在每次循环时都会得到一个不同的平方数,并写入等号左边第i 行的单元格中。

思考 在案例3-3的基础上,请读者思考一个问题:假如把图3.4代码中的Cells(i,1)修改为 Cells(i ,i),运行程序之后会得到怎样的结果?为什么?请读者先不要在电脑上操作,而是在心里运行代码,待推理出一个合理答案后再上机实际运行,看看答案是否与自己的推理一样。

3.2.3 Step子句

前面提到,每当执行到For循环的Next语句时,循环变量都会被自动增加1。不过有的时候,我们会希望循环变量每次能够“多走几步”,比如遇到案例3-4中的情况时。

案例3-4: 在图3.5所示的成绩表中,每个学生的信息均占用两行单元格,其中第一行是分科成绩。请编写 VBA 程序,可以根据每个学生的分科成绩计算出其总分与平均分,并写入对应的单元格中。

图3.5 案例3-4数据示例(右图为运行VBA程序后

这个案例与案例3-1几乎相同,只不过各科成绩分别存放在第3、5、7 行单元格中。也就是说,当程序在左图所示的工作表中读取了第3行的3个数据,并进行求和与求平均数计算之后,应当直接跳到第5行进行数据的读取和计算,而不需要读取第4行的数据。因此,如果仍然采用案例3-1的解法,使用For 循环按照“3、4、5、6、7、8”的顺序去读取表格中的每行数据是不合适的。

那么能否让For 循环每读取一行数据就自动跳过下一行,直接从再下一行开始读取数据呢?换言之,能否让代表行号的循环变量每次增加2,从而能够从3增加到5,再从5增加到7呢?答案是肯定的。For循环中提供了一个可选子句—— Step子句,专门用于控制循环变量每次增加的幅度。比如图3.6所示的代码也使用For循环向表格中逐行填写数据,但由于在For语句后面指定了“Step 3”,所以循环变量 i 每次都会增加3,导致Cells(i ,1)在每次执行操作时都跳过两行。

图3.6 使用Step子句实现跨行输出

Step关键字可以形象地翻译为“步长”,也就是循环变量每走一步(一次循环)所迈过的距离。之所以将Step称为可选子句,是因为它在For语句中“可写可不写”。当不写Step子句时,VBA默认循环变量的步长为1,即每次循环自动增加1。

所以对于案例3-4,只要在For循环中指定i 的步长为2,就可以按照“3、5、7”的顺序读取数据,自动跳过第2行和第4行。下面的代码就是按照这种思路解决问题的。

在这段代码中,第一次执行循环时循环变量 i 的数值为3,因此Cells(i+1 ,4)代表的是第4行D列单元格;同样,Cells(i+1,7)代表的则是第4行G列单元格。所以此时执行的计算,就是先将第3行3个数值的总和存入D4单元格,再根据D4单元格的数值计算出平均分并存入G4单元格。

接下来执行到Next i 时,由于已经用Step子句指定该For循环的步长为2,所以i 直接从3变为5。这时再重复执行循环体,读取的就是第5行的数据,并将结果存入第6(i+1)行中。如此反复,直到处理完第7行数据后执行Next语句时,由于i 的数值变为9已超出循环范围,所以终止循环,结束程序。

使用Step子句不仅能够“几步并做一步走”,而且还能让循环变量“倒着走”。原理很简单:将Step的步长设置为负数,比如-1,则每次执行Next i时就会让循环变量“增加负一”,也就是自动减一。这种用法也许是Step子句带给我们的最大便利,因为它允许在Excel工作表中按照“从下向上”的顺序对单元格进行“倒序”遍历,从而解决类似案例3-5中提出的问题。

案例3-5: 在图3.7左侧所示的工作表中,存有某健身俱乐部客户的数据统计信息,包括去年年底的总会员数量,以及今年每个月的新增会员数量(假设没有会员退出)。要求编写VBA程序,能够根据表格已有数据计算出今年每个月月底的会员总数,并填入表格D列。预期运行结果如图3.7右图所示。

图3.7 案例3-5原始表格(左图)与预期运行结果(右图)

对于这个案例,如果读者一时找不到思路,可以换个角度思考一下:假如我们使用 Excel 的公式来解决这个问题,会采用什么方法?相信大多数读者都会先在D11单元格中输入“=D12+C11”,然后使用拖动柄或复制等方式,在D10单元格中输入“=D11+C10”……直至在D3单元格中输入“=D4+C3”为止。换言之,从第11行数据开始,让它的D列单元格的数据等于下一行D列单元格的数据加上本行C列单元格的数据。对其他各行重复此操作,直到第3行结束。

从VBA的角度看,这种思路显然也是一个循环结构,只不过循环的起点是第11行,终点则是第3行,属于典型的倒序循环,循环变量每次增加负一,于是就可以使用Step子句实现这个循环,代码如下:

这段代码使用 i 作为循环变量,代表每次需要计算的数据所在行号。计算的规则与前面介绍的公式一样,就是让第i 行D列单元格等于下一行(i+1行)D列单元格与该行C列单元格之和。由于在For语句中指定了 i 从11开始且每次减1,直到循环至 3 结束,所以第一次循环时计算的是第11行的数据,然后是第10行的数据,直至计算到第3行结束。

当然,这个问题也可以不使用“Step-1”这种倒序循环,仅用普通的正序循环来解决。不过这种思路需要使用一些算术技巧,比如:先做一个普通循环,计算出C3到C11所有数字的总和,再加上 D12,从而得到目前俱乐部的会员总数,并写入 D3单元格;然后再做一个循环,从第4行开始循环到第11行,让每行的D列单元格的数据等于上一行D列单元格的数据减去该行C列单元格的数据,以此类推。具体代码如下,可以看到,这比使用倒序循环的解法还是麻烦了很多。

可能有的读者看到案例3-5时又会产生疑问:这个问题使用公式可以轻松解决,为什么还要考虑 VBA 的方案呢?这是因为:一方面,当数据量庞大(如十万行)时,在每行都书写一个公式会显著降低 Excel 的运行效率和计算速度。特别是在默认设置下,每当表格中的任何一个单元格发生改动,Excel都会自动重算所有公式,因而打开工作簿、编辑工作表等日常操作都会受到明显的影响。另一方面,正如我们已经多次强调的,设计这些案例只是为了让大家快速理解 VBA语法,一旦掌握了循环等语法和倒序循环等技巧,就可以用 VBA 解决很多公式、数据透视表等工具难以处理的问题。比如在“全民一起 VBA——基础篇”视频课程第10回中,我们演示了一个删除数据行的例子,就是使用倒序循环实现的。

3.2.4 For…Next循环的“初学者陷阱”

与很多技术一样,For语句虽然功能强大,但也存在很多“陷阱”,在编写程序时必须格外留心,以免产生各种莫名其妙的错误。下面就是笔者在多年教学观察中总结出来的一些最容易发生在初学者身上的问题。

1.在倒序循环中忘写Step子句

由于For语句中的等号“=”和“To”容易给人一种“从……到……”句式的感觉,所以我经常看到初学者在编写一个倒序循环时,直接写下“For i=5 To 1”这种代码,以为VBA会自动将其理解成“让 i 从 5 变化到 1,每次减 1”,结果忘记在后面加上“Step-1”这个子句。

事实上本书前面已经讲解过,如果在For语句中没有写Step子句,那么VBA就会默认每次循环后将循环变量增加1。所以对于上述情况,VBA在第一次读到“For i=5 To 1”时就认定这是一个不可能完成的循环。因为从算术角度来看,i从5开始每次增加1,是永远也不可能变成1的。不过,VBA也不会因此弹出错误提示,而是先将 i 的数值设置为等号后面的5,接下来直接跳出循环并转到Next语句后面继续运行,完全没有执行过循环体内的代码。

所以当编写了一个倒序循环而又忘记写 Step 子句时,VBA 程序看上去似乎运行正常,没有报错,但实际上这个循环却一次都没有执行过,也就得不到正确的结果。

2.在循环体内修改循环变量

使用For 循环的关键就是用好循环变量,前面也演示了循环变量的各种用法,比如依据循环变量指定行号、列号及内容等。不过读者可能注意到,在这些代码中我们只是读取了循环变量的数值,却从未尝试修改循环变量,比如在循环体里面写“i=7”等。

这并不是因为 VBA 禁止在循环体中修改循环变量,事实上,我们完全可以在循环的任何地方像使用其他变量一样读写循环变量。之所以不推荐在循环体中修改循环变量,是因为它能够控制For循环的次数,一旦不小心修改了它,就很容易造成死循环这个严重的问题。

比如在下面的代码中,循环体内“i=i-1”这句代码的功能就是修改变量i的数值,而变量i又恰巧是这个For循环的循环变量。作者原本希望它运行后能够在表格第1行到第10行的A列单元格中输出10个数字“2”,但实际运行的效果会是怎样呢?读者可以自己思考一下它的执行过程。

第一次进入这个循环时,变量i的数值为1,于是在表格第1行第1列(A1单元格)中写入数字2。接下来遇到“i=i-1”,这句代码的效果就是将变量 i 由1变为0。然后执行到下一句“Next i”,VBA会自动将循环变量 i 增加1,结果就是将 i 又变回1。由于这个For语句是让i从1递增到10,所以当前的i没有超出循环范围。于是再次重复循环体,重新更新A1单元格的数值(尽管上一次循环时刚刚更新过),然后再次变成1,再次循环……如此永不停止,谓之“死循环”也。

在大多数情况下,死循环都是导致程序崩溃的最严重错误之一 。而对于For循环来说,造成死循环的主要原因就是在循环体中修改循环变量,所以读者在初学编程时一定要格外注意。随着学习不断深入,我们会看到有些情况下在程序中巧妙地修改循环变量,也会轻松解决一些棘手问题,比如后面讲解“录制宏”时演示的“删除指定行”的技巧。但是对于这些技巧,我们能够找到其他方式进行替代,虽然这些方式可能不那么巧妙,但是减少了由于思考不周导致死循环的风险,所以还是建议大家不要随意修改循环变量。

如果不小心编写并运行了含有死循环的VBA代码,导致Excel进入完全无响应的假死机状态,那么可以尝试使用以下方式解决问题。

(1)先在键盘上找到“Pause”键(一般位于主键盘右上方区域,有些键盘上写为“Break”),然后多次同时按“Ctrl”和“Pause”这对组合键。“Ctrl+Pause”组合键是VBA指定的“程序暂停”快捷键,所以多数情况下这种操作可以让代码暂停执行,进入单步调试状态,此时再单击VBE工具栏上的“重置”按钮就可以结束程序。

(2)如果按“Ctrl+Pause”组合键不起作用,或者在键盘上找不到“Pause”键(比如在一些使用精简键盘的笔记本电脑上),那么就只能使用操作系统的“终止进程”功能强行退出VBE甚至Excel。以Windows系统为例,先在屏幕下方的任务栏上单击鼠标右键,然后在弹出的“任务管理器”中找到VBA或Excel的任务图标(见图3.8),再单击右下角的“结束任务”按钮,等待一段时间就可以看到Windows强行关闭了VBA和Excel。如果这样做仍然不起作用,还可以在“任务管理器”中单击“进程(英文系统为 Processes)”标签,找到含有“Excel”字样的进程并单击右下角的“结束进程”按钮即可。

图3.8 使用“任务管理器”关闭VBA或Excel

即使长年编写程序的开发人员,也无法避免遇到死循环这种情况。所以请读者一定养成良好的习惯:写好代码后先保存文件,再运行程序。这样即使在运行中发生问题,程序强行关闭,也不会丢失数据和之前写好的代码。

3.在Next语句中忘写循环变量名

在我们的所有案例中,Next的后面都是写有循环变量名的,比如在使用a作为循环变量时,就要写“Next a”。不过事实上,VBA并不要求我们写变量名,如果在这句代码中删除了“a”,只写“Next”也是完全没有问题的。但是仍然强烈建议大家养成在Next后面书写变量名的习惯,因为随着学习不断深入,我们会大量编写嵌套循环的代码,如下面的代码。在这种模式下,如果每个Next后面都写有自己对应的循环变量名(右侧),会比不写变量名(左侧)更容易让阅读者分清每层循环的边界。 FswA9ZGm6M/KiGf8MSqwyZ0ANL1C1ogtvAESA5P2Pl+Bb/dHPGD81mcuQSmdXhA1

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