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

4.1 If语句与关系运算

4.1.1 用If语句实现判断结构

与循环结构一样,判断结构也是程序设计中的基本结构之一,有时被称为“分支结构”。它的功能就是让计算机根据运行时遇到的实际情况,选择是否执行某段代码,或者从多段代码中择一而行。比如在案例4-1中,VBA程序需要根据每名学生F列单元格(平均分)的数值大小,来选择是否将其G列单元格(奖学金)设置为“2000”,这就是典型的判断结构。

案例4-1: 在图4.1左侧的表格中列出了多名学生的单科成绩及平均成绩,现在需要编写一个VBA程序,根据每名学生的平均分(F列数值)计算出其应得奖学金金额。奖学金发放规则为:平均80分及以上者得2000元,低于80分者不予发放。预期运行效果如图4.1右图所示。

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

怎样编写程序实现案例4-1的需求呢?我们还是采用老办法——先看看在不使用计算机的情况下是怎样思考和解决这种问题的。如果需要把这个表格交给一位新员工去处理,那么我们会事先把处理规则用类似下面的语言交代给他:

“首先请看第一个人,也就是第3行,观察这一行的F列数字”;

“然后判断一下,如果这个数字大于等于80,就在该行G列输入‘2000'”;

“当然,如果这个数字不大于等于80,那就什么也不用做”;

“接下来看第二个人,也就是第4行,还是看该行F列数字,和前面进行一样的处理”;

“就这样一行行反复处理,直到把第6行处理完就可以结束了”。

如果这位新员工就是计算机,交谈的语言不是汉语而是VBA,那么只要把上面这段话用VBA语法写出来,程序代码就呼之欲出了。

这个思路从整体上看是一个非常明显的For … Next 循环结构,循环变量代表行号,数值从3变化到6。不过还需要用到“如果……那么……”这种结构。在VBA中,这个结构就是我们所说的“判断结构”,一般使用“If…Then…End If”来表达。图4.2所示为If语句最基本的用法及语法解释。

图4.2 If语句的基本结构及含义

在如图4.2所示的If结构中,“If”“Then”和“End If”是VBA语言规定的关键字,书写时必须一字不差。这里需要提醒大家注意的是:“End If”是由两个单词构成的关键字,中间的空格必不可少;而“If”与“Then”必须处于同一行中,除非使用下画线进行分行书写。在“If”与“End If”之间可以书写一行或多行代码,VBA将会根据If后面的判断条件是否成立,选择运行或不运行这些代码。无论这些代码是否运行,“End If”后面,也就是If结构之外的代码都不会受到它的影响,始终按照正常顺序执行。

使用If结构,就可以对案例4-1中每行F列的平均分进行判断(看其数值是否大于等于80),从而决定是否执行“在该行G列输入‘2000'”这个操作。案例4-1的参考答案及代码含义如图4.3所示。

图4.3 案例4-1的参考答案(左图)及代码含义(右图)

对照图4.3右侧的代码注释,相信读者可以理清这个程序的执行流程。此外,在书写这段代码时使用了多层缩进,以便清晰地体现各行代码之间的从属关系。具体来说:

① Dim到Next的代码都属于Sub … End Sub,所以全部相对于Sub缩进一层;

② For到Next之间的代码(If结构)都属于循环体,每次循环时都要执行一遍,所以把它们相对于For缩进一层;

③ If与End If 之间的语句属于判断结构的内容,由判断条件决定是否执行,所以再把这一行语句相对于If缩进一层。

这种缩进格式(包括对空行的使用)可以清楚地反映各个结构(如For和If)之间的关系,从而使阅读者能够正确理解程序的执行流程,一旦发现程序出错也能够迅速找出错误的根源。如果不使用缩进格式和空行,而是写成下面的格式,理解起来就会十分困难。所以再次提醒读者:合理运用缩进格式与空行,是程序开发中的最佳实践之一!

4.1.2 用关系运算比较大小

在案例4-1的参考答案中,使用了“>=”字符来表示“大于等于”的关系,这在程序设计中被称为“关系运算”。

所谓“关系运算”或称“比较运算”,就是对两个元素进行比较,判断两者是否符合某种特定关系。这种关系一般包括“相等”“不等”“大于”“小于”“大于等于”及“小于等于”,VBA语言中的运算符如表4-1所示。由于在大部分情况下,关系运算都是与If语句等需要进行判断的语句配合使用 ,所以表4-1围绕If语句对各种运算符的用法进行了演示。

表4-1 VBA语言中的关系运算符

这些运算符的含义很容易理解,比如“大于等于”就是键盘上的“>”和“=”组合在一起;而“不等”就是“>”和“<”的组合,代表“可以大于也可以小于,就是不能等于”。需要注意的是,等号“=”在VBA语言中具有两种含义:在关系运算中用于判断“左边的值与右边的值是否相等”;在赋值语句中则代表“把左边的值设置为右边的值”。等号在功能上的这种“二义性”,有时候会给阅读代码带来一些困扰,此时应当先搞清楚这个语句是用于关系运算中,还是用于赋值操作,再确定它在这段代码中的具体含义。

4.1.3 用Else和ElseIf实现多分支判断

1.使用Else实现双分支判断

从实际工作角度来看,图4.3中给出的参考答案并不完美。因为对于李四和赵六这两位没有得到奖学金的同学程序未做任何处理,G列单元格仍然是空白,如图4.4左图所示)。但是根据通常的财务规定,这些单元格不能留白,应该明确填写“0”,以免造成混淆,如图4.4右图所示。

图4.4 案例4-1的预期运行效果

为什么之前编写的程序会忽视了李四和赵六这两条记录呢?因为If Cells(i,6)>=80 Then的含义是“只有当第i行第6列(F列)的数值大于等于80时,才执行Then与End If之间的内容”。而对于第4行(李四)和第6行(赵六)来说,F列的数值小于80,不符合上述条件,因此跳过If结构,直接执行End If之后的语句。而在我们的程序中,End If之后并未对单元格进行任何操作,而是通过Next i 继续循环,前往处理下一行数据,所以这两行单元格没有得到任何赋值操作。

怎样在判断结构中既处理符合条件的情况,也兼顾不符合条件的情况呢?换言之,能否在VBA代码中表达出“否则”的意思,从而将前面的解题思路改成下面这样呢?

如果平均分大于等于80,就在这一行的G列输入“2000”;否则就在这一行的G列输入“0”。

答案是肯定的。VBA的“If… End If”结构允许使用“Else”关键字实现上述思路。仍以案例4-1为例,使用Else关键字之后的参考答案如图4.5所示。

图4.5 案例4-1使用Else关键字之后的参考答案

可以看到,通过在If与End If 之间插入Else,就在这个判断结构中提供了两种不同的备选操作方案——将G列赋值为2000,或者将G列赋值为0。显然,在每次执行这个判断语句时,VBA只能根据实际情况选择一种方案执行。所以将 If…Else…End If 这种结构称为“双分支”判断结构,流程如图4.6所示。

图4.6 双分支结构的流程示意

在If结构中,Else子句是可选子句,即在代码中可以不使用它,但是如果决定使用它,那么就必须保证它处于“If … Then”与“End If”之间,否则它将无法被视作这个判断结构的一部分。如果VBA发现某个Else子句不属于任何If结构(比如图4.7所示的代码),就会弹出编译错误提示。

图4.7 Else位置错误警告

还需要注意的一点是,一个If结构中只能有一个Else子句。想象一下,如果老师在班会上说“明天下雨就正常上课,否则就开运动会, 再否则 就放假”,那么同学们一定会怀疑老师的真诚或智商,因为“放假”这个选项显然毫无意义,永远无法实现。同样的道理,如果我们在同一个 If结构中使用了两次Else语句,也会导致程序出错,如图4.8所示。因为每个If结构中只能有一个Else子句,所以第一个Else就会被认为与这个If搭配使用,那么第二个Else就无法找到与之搭配的If语句了。

图4.8 使用多个Else语句导致的编译错误(代码中

2.使用“默认值”技巧替代Else子句

在有些情况下,不使用Else子句也能够实现“否则”的效果。仍以图4.5中案例4-1的参考答案为例,其计算规则是:

如果平均分大于等于80,就在这一行的G列输入“2000”;否则就在这一行的G列输入“0”。

也可以换一个角度思考:

每找到一名学生,先在G列输入“0”;然后判断其平均分是否大于等于80,如果是,就把G列的数值由“0”修改为“2000”。

按照这个思路,可以将图4.5中的代码修改为如下内容,如图4.9所示。

图4.9 使用默认值替代Else子句

在这段代码中,每扫描到一行数据,就将该行的奖学金数额设置为“0”;再使用一个单分支的If结构判断是否将其修改为“2000”。假如数据不符合判断条件,那么If结构就不会执行,其奖学金数额仍然为“0”。换句话说,这个思路首先默认所有人的奖学金都是0元,就像司法理论中的“无罪推定”一样——先默认嫌疑人无罪,再寻找证据,以判断是否应该判其为有罪。

需要注意的是,使用默认值代替Else语句可能造成程序运行效率降低。比如在图4.9所示的代码中,那些符合奖学金发放条件的学生记录将被执行两次赋值操作(先在G列的单元格中写上“0”,再将其修改为“2000”),而在使用 Else 子句的代码中只需执行一次赋值操作即可。虽然以计算机的计算速度来讲,多执行一次操作所消耗的时间微乎其微,但是假如数据数以万计,且操作比赋值更复杂(比如要求修改单元格背景色),那么运行速度的降低就会非常明显。

尽管如此,在很多数据量较小、不需要担心运行速度的情况下,巧妙使用默认值往往可以使复杂的逻辑判断关系变得非常简洁,可以巧妙地解决问题。

3.If…Else…End If的单行写法

一个完整的If结构至少应包含两行代码:以“If … Then”为起始行,以“End If”为结束行。不过为了使代码看起来更加简洁,VBA语法允许将整个If结构(包括Else语句)写在一行之中,从而不必书写“End If”。以下就是单行If结构的几种常见用法。

(1)对于单分支If结构,删除“End If”,直接将剩余代码合并为一行即可。比如在下面左侧的代码中,If结构一共占了三行,而改写为右侧的代码后只需占一行。

(2)对于双分支If结构,同样可以把“End If”之外的代码(包括Else子句)合并为一行,比如下面左侧的代码就可以改写为右侧的样子:

(3)即使每个判断分支中都包含多行语句,也可以把它们合并成单行If结构,只要使用冒号“:”将它们分隔开即可:

在这种写法中,只有每个分支内的各行之间需要使用冒号分隔,而对于Then、Else这两个关键字是不需要使用冒号的

必须注意的是,将If结构改成单行写法后,绝对不可以再写“End If”。因为在单行写法中,这行代码就是一个完整的If结构,行尾就代表了If结构的结束。所以如果下面再写一行“End If”,VBA 就会认为它对应另一个 If 语句,于是尝试将它与其他 If 语句进行配对,从而导致“End If没有If块”等错误提示。

在一般情况下,我们还是推荐大家使用标准的If结构写法,即明确地使用“End If”表示判断结构的结束。标准写法一方面使阅读更加清晰;另一方面在需要修改判断结构,特别是需要在某个分支中添加代码时会很方便。不过如果程序中需要用到很多相互平行的简单判断,使用单行写法可以让代码看起来更加美观,如同写作时使用排比句一样。比如对于案例4-1来说,如果将其修改为以下规则,那么就适合使用单行写法:

语文成绩85分以上可得1000元单科奖学金;数学成绩90分以上可得1500元单科奖学金;英语成绩90分以上可得800元单科奖学金;如果多门学科的成绩符合上述规则,奖学金总金额为各科奖学金之和。

4.使用ElseIf实现多分支判断

使用“If … Else … End If”结构可以实现双分支判断,也就是提供两种备选方案供VBA选择。但是在实际工作中,还会经常遇到需要提供更多备选方案的情况。

案例4-2: 与案例4-1一样,图4.10左侧的表格仍为多名同学的成绩信息,要求编写程序计算出奖学金并填入G列,效果如图4.10右侧的表格所示。不过奖学金发放规则发生变化:平均分在85分及以上者发放奖学金2000元,80~84分者发放奖学金1500元,60~79分者发放奖学金800元,低于60分者为0元。

图4.10 案例4-2的原始数据及预期运行效果

在案例4-2中,需要为程序提供4个备选操作,即将G列单元格的内容设置为2000、1500、800或0。换句话说,这次的判断结构将需要4个分支,如图4.11所示。

图4.11 用多分支结构表述案例4-2中的奖学金计算规则

为了实现这种多分支判断结构,VBA 在If 语句中提供了一个专门的可选关键字——ElseIf。显而易见,这个关键字是由“Else”和“If”两个单词合并而来,意为“否则如果”。下面的代码就使用这个关键字解决了案例4-2的问题,其中关键语句的含义均已给出注释。

从这段代码中也可以看到,在一个If … End If结构中,可以根据需要插入多个ElseIf子句,从而实现多个判断分支。不过对于初学者来说,在使用 ElseIf子句时必须注意以下问题,否则很容易导致各种错误。

(1)切记“ElseIf”是一个单词,书写时不能插入空格使其变成“Else If”。

比如在前面的例子中,如果将第一个ElseIf子句误写成“Else If Cells(i,6)>=80 Then…”,那么VBE马上就会把这行标记为红色高亮显示,并报出编译错误:“必须为该行的第一条语句”。这是因为在插入空格后,这一行语句中就包括了“Else”和“If”两个关键字。而VBA的语法规定:如果一行语句中含有If关键字,那么其必须处于该行的最前面。但是此处If关键字前面还有一个Else关键字,显然这违背了语法规定,于是报出错误,如图4.12所示。

图4.12 将“ElseIf”误写为“Else

此外,经常被初学者忘记的是,每个ElseIf子句必须含有一个“Then”关键字,不能将其省略。事实上,在一个If结构中,只有Else子句没有“Then”关键字。

(2)ElseIf子句在执行时存在先后顺序,必须注意判断条件之间的包含关系。

当一个If结构中含有多个分支时,VBA会严格按照“由上而下”的顺序检查。一旦发现符合某个分支的判断条件,就会执行其中的代码,并跳过后面的其他分支,直接执行End If后面的代码。这就意味着,如果有多个ElseIf符合条件,VBA只会执行第一个ElseIf中的代码。

比如在案例4-2的奖学金计算程序中,如果我们把第一个ElseIf(平均分>=80)和第二个ElseIf(平均分>=60)位置对调,程序虽然能够正常执行,但是得到的结果却是错误的,如图4.13所示。因为所有大于等于80的平均分必然也大于等于60,所以王五和郑八两位同学的成绩同时满足这两个ElseIf的条件。由于这两个ElseIf子句中最先出现的是大于等于60,因此VBA只执行此分支,从而将两者的奖学金设置为800元,直接忽略了另一条规则的存在。

图4.13 调整分支顺序后的代码及其运行结果

换言之,如果在一个If结构中存在多个分支,而且前面分支的判断条件包含后面分支的判断条件,那么后面的分支将永远不会被执行。所以在书写多分支判断代码时,必须先厘清各个判断条件之间的顺序,避免出现“前面包含后面”的情况。

此外需要注意的是,这里所说的“多个分支”,不仅包括ElseIf子句,而且包括If语句中的表达式,以及Else分支所代表的“否则”分支。由于Else分支的含义是“假如不符合上述所有分支条件,则执行本分支”,所以Else分支必须是If结构中的最后一个分支,位于所有ElseIf子句的后面。否则,Else分支后面的其他分支永远得不到运行机会(事实上,如果发现Else分支不是If结构的最后一个分支,VBE会直接报出语法错误警告)。

(3)含有ElseIf的判断结构不能使用单行写法。

If结构单行写法只适用于最多两个分支(If和Else)的情况。假如在If结构中出现了ElseIf关键字,就无法将其合并为一行书写,否则会出现语法错误。

5.老调重弹:使用变量优化代码

尽管上面的代码能够顺利实现计算奖学金的功能,但是从最佳实践的角度来看,还是达不到简洁优雅的要求。比如“Cells(i,6)”在代码中出现了3次,而“Cells(i,7)”则出现了4次,这样不仅书写起来十分麻烦,也给以后的阅读和修改工作带来了隐患。

举一个例子:假如现在需要对工作表的格式进行调整,将平均分从第6列(F 列)改到第5列(E列),将奖学金从第7列(G列)改到第8列(H列),那么就要对所有的“Cells(i,6)”和“Cells(i,7)”进行修改,共计修改7处。一旦某处发生错误,就会导致“数据不一致”的错误。

怎样才能够优化这段代码,从而避免上述风险呢?解决之道就是前面专门讲解过的最佳实践:

把每个将会重复出现的数据定义为变量!

比如在案例4-2中,既然存放平均成绩的Cells(i,6)重复出现了3次,那么就可以创建一个变量代替它反复出现。为使代码清晰易读,可以将这个变量命名为“score(成绩)”。同样,可以再创建另一个变量“amount(金额)”来代替显示奖学金数额的Cells(i,7)。用这个思路修改之前的程序,就可以得到下面这段优化后的代码:

在这段优化后的程序里,如果需要修改平均成绩和奖学金数额所在的列号,一共只需要修改两处代码,而且绝不会出现数据不一致的情形。同时,由于变量名能够反映其业务含义,所以“amount=2000”这种语句要比“Cells(i,7)=2000”更容易理解,不需要查看工作表就能搞清楚Cells(i,7)里面存放的是什么数据。

正如第2章中讲到的:将重复的数据抽取为变量,是非常重要的编程习惯,应当在开始学习编程时就牢记于心并随时应用。不过在实际教学中,很多初学程序设计的同学在尝试应用这个原则时也会犯下一些“新手错误”,下面将最常见的错误列举如下。

(1)误以为“赋值”就是“别名”。

在上面的代码中,每次循环到一行学生记录,都会使用变量 amount 来存放计算结果,即应发奖学金的金额,而且将amount的数值通过“Cells(i,7)=amount”传递给第i 行的G列单元格,使其内容变为这个计算出的数字。显然,这是一个典型的赋值操作。

不过常有同学会想到另一种思路:如果在程序开始时写下“amount=Cells(i,7)”,那么amount就是Cells(i,7),这样后面只需写amount=2000,就相当于让Cells(i,7)变成2000。于是按此思路写出下面的代码,但运行程序后工作表却没有任何变化。

之所以此程序没有向工作表输出任何结果,是因为写下这段代码的同学误以为 amount 是Cells(i,7)的别名,以为给amount赋值就是给Cells(i,7)赋值。事实上,根据第2章讲解的知识,amount与 Cells(i,7)在内存中是两个完全不同的“房子”,因此将 amount 的数值设为2000,对 Cells(9,7)中的内容没有任何影响。只有在执行“Cells(i,7)=amount”时,Cells(i,7)的数值才会被修改为与amount相同。所以请初学编程的读者注意:赋值不是起别名。

(2)写错赋值语句的位置。

在前面优化过的代码中,“score=Cells(i,6)”等操作都是写在循环结构的内部,即每次循环均会执行一次,也就是每找到一个学生记录,就将其平均成绩读取到 score 变量中,完全符合业务逻辑。

不过在实际编写代码时,很多同学会由于疏忽将这些语句放到循环结构之外,比如下面的代码(对于被错误地放到循环结构外的语句,注释中带有“注意:”字样)

执行这段代码时会马上报错。因为当程序执行到第二行语句“score=Cells(i,6)”时,i 还没有被赋予过任何数值,所以此时i 的数值默认为 0,于是 Cells(i,6)就代表F0单元格,这个语句的意思就成了“将变量score的数值设置为F0单元格的内容”。然而,Excel工作表中并不存在行号为0的单元格,所以VBA无法执行这个命令,只能报出错误。

即使把这个语句放回正确的位置(For循环的内部),执行上述程序也无法得到正确的结果。因为在整个循环期间,每次计算完一个学生的奖学金并将其存入变量amount后,没有进行任何将amount输出到单元格的操作,就直接通过“Next i”转去处理下一行学生记录。直到第8行计算完毕,amount等于最后一个学生的成绩后,再次执行“Next i”,使i的数值变为9,从而结束For循环。直到这个时候,程序才会执行“Cells(i,7)=amount”语句,将amount中存放的最后一个学生的奖学金金额输出到G9单元格中,并结束整个程序。

放错赋值语句的位置是初学编程者经常犯的错误,本书在讲解循环结构时就曾提及这一点,请读者在实践中要格外注意。 GMMV9qQPw6bTZpqzOTdhyswBPgSxH+NB8qoBYflN/22/8vrJJnrnbdwiSr7LpmcJ

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