用Flutter极速构建原生应用
上QQ阅读APP看书,第一时间看更新

2.5 Dart中的流程控制语句

流程控制是一个程序的灵魂,之前我们编写的代码都是顺序执行的,即代码从上到下一行一行地依次执行。在实际开发中,这种顺序执行的程序少之又少,我们往往需要结合使用分支语句、循环语句、中断语句等来实现复杂的逻辑。本节将介绍Dart中常用的一些流程控制语句,使用这些语句可以编写出功能更加灵活强大的Dart程序。

2.5.1 条件分支语句

条件分支语句是Dart中分支语句的一种,当被判定的值或者表达式符合某个条件时,才执行预定的逻辑代码。和许多编程语言类似,Dart中也使用if-else结构作为条件分支语句。示例代码如下:

运行上面的代码,控制台将依次输出字符串“成功”和“程序结束”。如果将res变量的值修改为false,程序就不会执行if语句块中的代码,直接执行打印“程序结束”的代码。

上面演示的if语句首先需要进行条件的判定,条件必须为布尔值或者返回布尔值的表达式,如果条件成立,就会执行其后紧跟的代码块,否则跳过此代码块。if-else是一种更为常用的条件分支结构,示例如下:

上面的代码首先会对if后面的条件进行判定,如果条件成立,就执行if后面代码块中的代码,如果不成立,就执行else后面代码块的代码。也就是说,在if-else结构中,总有一个代码块会被执行。对应的,还有另一种if-else的变体结构,示例如下:

if-else if-else结构可以进行多级条件判断,从第一个if判定条件开始,如果条件成立,就直接执行其后的代码块,如果不成立,就继续向后进行下一个if条件的判定,如果所有判定条件都不成立,最终会执行最后的else中的代码。当然,最后一个else代码块可以省略。

一个完整的程序往往需要有和用户进行交互的能力,和用户交互实际上就是让用户做出选择,条件语句的作用就是根据用户的响应来使程序做出不同的反应,实现程序的智能化。

2.5.2 循环语句

和人脑相比,计算机的最大优势在于其可以非常迅速地完成大量且重复的计算。循环语句的作用就是将某段代码重复地执行。首先,我们思考一个问题,如何编写代码计算1+2+…+100这个算式的值。你可能马上就能答出来,这不就是一个等差数列求和的问题,使用公式很容易计算出来:

没错,这正是人脑相较于计算机最大的优势,人脑善于总结、归纳以及推导出方法,而计算机则善于循规蹈矩地做重复大量的工作。使用循环语句可以不依赖公式进行大量等差数值求和运算,示例如下:

本小节我们一起来学习Dart中循环语句的用法。

Dart支持4种类型的循环,以上代码中的while循环是最为简单的一种,其while关键字后面的小括号中需要填入要判定的条件表达式或布尔值变量。当判定为true,即条件成立时,会执行循环体中的代码块,当代码块执行完成后,程序会回到while条件判定处,再次判定条件是否成立,如果成立,就继续执行循环体内的代码块,如此循环,直到条件不再成立为止。因此,对于while循环结构,一般会在循环体中修改判定条件,否则程序会陷入无限循环,永远无法跳出while循环结构。

while语句还有一种变种,叫作do-while,它的结构如下:

    do{
    循环体
    }while(条件);

do-while结构和while结构的区别在于:while语句会首先进行循环条件的判定,如果不满足,就不再执行循环体,满足条件才会进行循环;而do-while语句则是首先执行一次循环体中的代码,之后进行循环条件的判定,如果满足,就继续执行循环体,如果不满足,就跳出循环,例如:

for循环也是一种非常常用的循环结构,并且相对于while循环,for循环的写法更加简洁。使用for循环解决同样的累加问题,示例代码如下:

for循环的结构如下:

在for关键字后的小括号中需要填入3个表达式,其中第1个表达式用来初始化循环变量,这个变量用来控制循环执行的次数;第2个表达式为循环的判定条件,不满足条件时会跳出循环;第3个表达式会在每次循环体执行结束后执行,一般用来对循环变量进行操作。

很多时候,我们使用循环语句都是用来对集合对象进行遍历的,例如下面的代码会将列表中所有的元素依次进行打印:

对于集合类型对象的遍历,for-in循环是一种更加快速的方式。for-in语句也被称为迭代语句,专门用来进行集合遍历,例如:

for-in语句的结构如下:

    for(变量 in 集合){
    循环体
    }

在for-in语句中,in关键字前为对象变量,每次循环后都会将集合中遍历出的元素赋值给这个变量,in关键字后为要进行遍历的集合,集合中的元素会被依次取出赋值给对象变量,并执行循环体中的代码。

2.5.3 中断语句

中断语句常常与循环语句配合使用,中断语句的用途是提前中断循环。对于2.5.2小节的等差数列累加问题,使用下面的代码也可以很好地解决:

上面的break语句就是中断语句,当代码执行到break语句时,会直接跳出当前的循环执行后面的代码,因此即使我们不对循环的判定条件进行操作,在循环过程中也很容易结束循环。

Dart中还提供了一个很常用的中断语句:continue语句。break语句会直接跳出本层循环,执行循环后面的代码,而continue语句则是跳过本次循环后,还会进行循环条件的判定,如果条件依然满足,就会继续执行循环。示例代码如下:

break中断语句,其也可以在多分支选择结构中使用,用来命中某个分支后跳出整个结构。在2.5.4小节中,我们会介绍多分支选择语句的使用。

2.5.4 多分支选择语句

其实多分支选择语句可以完成的工作使用if-else语句都可以完成,但是在某些场景下,使用多分支选择语句switch-case能够写出更加整齐规则的代码。在学习if-else语句时举过一个小例子,以学生的分数来划分学生的成绩段,代码如下:

假如现在我们需要把上面代码的逻辑反过来,根据学生的成绩来输出学生的分数段,使用switch-case语句编写如下:

运行上面的代码,将输出“成绩在85分以上”。switch-case语句的作用是用来进行条件的匹配,switch关键字后面填写要进行匹配的变量,之后列举case语句进行匹配,如果匹配成功,就会执行对应的case代码块。需要额外注意,每一个case块的结尾都需要使用break语句进行中断,否则运行时会有异常产生。

由于switch-case进行精准的值匹配,要进行匹配的case语句也有限,因此可能会出现所有case语句都没有匹配的情况,这时会跳过switch-case结构执行后面的代码,如果需要提供默认的处理逻辑,就可以在switch结构中添加default块,代码如下:

上面的代码所有的case语句都没有匹配上,这时默认会执行default代码块中的代码。

2.5.5 异常处理

任何代码都有产生异常的可能,程序的逻辑越复杂、代码量越大,产生异常的可能性也越大。在编写代码时,我们不能强求没有异常产生,而是要将注意力放在产生异常后的处理工作。

大部分的程序都需要和用户进行交互,和用户交互的基础是接收用户的操作输入,而对于用户的操作开发者往往是不可预料的,因此在编写代码时,我们要时刻注意对用户输入的数据进行限制,当用户输入了错误的数据时,让程序中断掉。

在编写代码时,当我们调用了错误的方法或者用错了变量的类型时,程序会中断,并将异常信息打印出来。其实,我们也可以自己产生和抛出异常,例如下面的代码:

假设上面代码中的变量a为用户输入的数据,程序之后会使用这个数据进行后续处理,但是要保证数据a的值大于0。此时,如果发现输入的a小于0,就使用throw关键字抛出异常,运行代码,控制台会输出如下文本:

    Unhandled exception:
    输入有误

上面输出的意思是产生了未处理的异常,程序提前中断,并且将异常对象打印了出来。前面抛出的异常对象为字符串“输入有误”,其实要抛出的异常对象可以是任意类型的对象。更通用的做法是通过定义异常类来封装异常,异常类中有具体的异常原因、类型、错误码等信息。

当程序运行到throw抛出异常语句时,会中断掉,这往往会造成很差的用户体验。一个完整的应用程序可能不止一个功能,因为一个小功能的异常而造成整个应用程序无法工作是非常不明智的。在Dart中,提供了对异常进行捕获的方法,开发者可以选择对捕获到的异常进行处理,也可以忽略它,如果进行了异常捕获,程序就不会中断。使用try语句进行异常的捕获,示例代码如下:

再次运行代码,可以看到程序完整运行到了最后。

try结构有这样的特点,首先其后面的代码块中需要将可能产生异常的代码放入,可以是几行代码,也可以是函数,等等。如果这些代码在执行时抛出异常,就会将异常捕获,并根据异常的类型将其分配入指定的处理模块。try结构块后面跟随的on语句用来指定要捕获的异常类型,例如,如果抛出的异常是字符串类型的对象,就会进入on String对应的代码块继续执行代码,这里面我们可以根据实际情况来进行异常的处理,完成后程序会继续向后执行。如果需要获取具体的异常对象,就可以使用catch语句来捕获,示例如下:

运行上面的代码,将输出“捕获了字符串类型的异常:输入有误”。catch语句后面的括号中也可以将异常的堆栈信息捕获到,catch括号后面的第一个参数为异常对象,可以添加第二个参数来获取堆栈信息,代码如下:

即使捕获到异常,开发者也可以根据实际情况决定是处理、忽略还是继续将异常抛出,如果需要继续抛出异常,那么使用rethrow关键字即可,对于函数在嵌套调用中产生的异常,常常会用这个关键字来传递异常。rethrow的示例代码如下:

try-catch结构的最后还可以追加一个finally块,finally块的作用是无论异常是否产生,也无论是否捕获,最终都会执行该代码,例如:

finally块通常用来执行数据清理相关操作。