【Dart】004-流程控制

109 阅读4分钟

【Dart】004-流程控制

说明:对于一个 java 程序员来讲,这太简单了!学习过程非常无聊,所以对教程做了很多摘录!

[toc]

一、条件流程

摘录:根据条件的满足情况,来执行不同的逻辑代码块,这就是 条件流程 的作用。Dart 编程中,主要有两种语法形式:if 条件语句和 switch 条件语句,两者适用于不同的场景。

在现实生活中,if...else... 的语境是 如果...就...否则...就... ,而 switch当...时,就...

1、if 语句

概述

ifbool 型变量形影不离,它会根据条件的真假,来执行不同的代码块。所以一个 if...else 语句可以表示两段分支的抉择,另外如果 else 代码块中没有处理内容,可以省略

闲聊*:这些东西,基本所有的编程语言都是极为相似的!

两个分支判断

import 'dart:math';

main() {
  bool condition = Random().nextBool();
  if (condition) {
    // condition = true 时执行逻辑
  } else {
    // condition = false 时执行逻辑
  }
}

多个分支判断使用 else if

import 'dart:math';

main() {
  // 获取随机数
  int random = Random().nextInt(100);
  // 多分支判断
  if (random < 50) {
    print('小于50');
  } else if (random < 80) {
    print('小于80');
  } else {
    print('大于等于80');
  }
}

2、switch 语句

概述

if 通过对 bool 型变量进行检验,来选择执行的分支。 switch 是针对一般对象进行的校验,按分支执行,可以说 switch 是更高级的分支结构。它可以很轻松地实现多分支的结构,而不必像 if..else 那样冗长的判断。

代码示例

import 'dart:math';

main() {
  // 获取随机数
  int random = Random().nextInt(3);
  // switch 语句
  switch (random) {
    case 0:
      print('0');
      break;
    case 1:
      print('1');
      break;
    case 2:
      print('2');
      break;
    default:
      print('default');
  }
}

switch 校验对象的两个限制

  1. case 后面匹配值,必须是 常量;
  2. 自定义的类型不可以重写并实现 == 方法(顺带说一句,像 Stringint 这样的类中,确实覆写了 == 操作符,但在 dart 层并未实现,这是允许的。)。

二、循环流程

一般编程中的主要循环是 for 循环和 while 循环。

1、标准 for 循环

基本的编程语言都有 for 循环,这里不做过多阐述。

摘录教程:标准的 for 循环结构如下,for 关键字后的括号中有三个部分,且通过 ; 隔开。其中 startExp 是进入循环体前执行的表达式,只会执行一次; conditionbool 型的变量,相当于循环的 阀门 ,只有为 true 时,才允许执行循环体。eachLoopExp 是在每次循环体结束之后触发的表达式,一般用于修改 condition 的条件。

语法格式

for ( startExp ; condition ; eachLoopExp ) {
  loopBody
}

代码演示

main() {
  // 一个字符串列表
  List<String> list = ['apples', 'bananas', 'oranges'];
  // 使用 for 循环遍历列表
  for (int i = 0; i < list.length; i++) {
    print(list[i]);
  }
}

运行结果

apples
bananas
oranges

2、for...in 循环

摘录:for...in 只是对列表遍历的一个语法糖,它可以屏蔽循环三大件,直接遍历访问元素。这样有益有弊,好处是使用方便,坏处是无法直接感知遍历到的索引位置。

代码演示

main() {
  // 一个字符串列表
  List<String> list = ['apples', 'bananas', 'oranges'];
  // 使用 for...in 循环遍历列表
  for (String item in list) {
    print(item);
  }
}

运行结果

apples
bananas
oranges

3、while 循环

概述

while 循环的结构比较简单,但它要比 for 循环更难理解一点。while 关键字后的括号中填入校验条件,条件满足会进入循环体。从这可以看出,必须在 conditionloopBody 中动态修改条件,否则就会死循环。

语法格式

while (condition) {
  loopBody
}

while 循环与 for 循环的等价改写

for 循环和 while 循环本质上没有什么区别,两者可以进行相互转化。其实对于循环而言,最重要的是对校验条件的维,如下是通过 while 循环对上面 for 循环的等价改写。

List<String> cnNumUnits = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'];
int i = 0;
bool condition = i < cnNumUnits.length;
while (condition){
  print(cnNumUnits[i]);
  i++;
  condition = i < cnNumUnits.length;
}

代码演示

main() {
  // 一个字符串列表
  List<String> list = ['apples', 'bananas', 'oranges'];
  // 使用 while 循环遍历列表
  int index = 0;
  while (index < list.length) {
    print(list[index]);
    index++;
  }
}

运行结果

apples
bananas
oranges

4、do...while 循环

概述摘录

do...while 作为循环的一种,和前两者也没有什么本质性的区别,都是依靠校验条件来不断执行循环体。它有一个最大的特点,就是:loopBody 至少执行一次,而 forwhile 可能会由于条件不满足而一次都不执行。

语法格式

do {
    loopBody
} while(condition)

代码演示

main() {
  // 一个字符串列表
  List<String> list = ['apples', 'bananas', 'oranges'];
  // 使用 do while 循环遍历列表
  int i = 0;
  do {
    print(list[i]);
    i++;
  } while (i < list.length);
}

运行结果

apples
bananas
oranges

三、中断流程

摘录:对于循环来说有个非常重要的事:如何中断循环。正常来说,循环的终止是靠修改校验条件实现的。除此之外,我们还有其他的手段在 循环体内 中断或终止流程。常用的关键字有:

continue
break
return

1、continue 关键字

概述摘录

continue 表示该 循环体到此执行完毕,进入下一次循环。比如下面案例中,当 i 是偶数时,tag1 触发 continue ,比如当 i = 2 时,该次循环到此结束,也就是下一步不会到达 tag2 。进入下一次循环,是指触发 eachLoopExp 表达式,这里是 i++ ,由于循环条件仍然满足,所以会继续进入循环,此时 `i = 3

代码示例

int sum = 0;
for (int i = 0; i < 10; i++) {
  if(i.isEven){// 如果是偶数
    continue; //tag1
  }
  sum += i; //tag2
  print("第 $i 次循环,计入 sum");
}
print("sum: $sum");

2、break 关键字

概述摘录

break 相对而言就比较 "强硬" 一些,只要某次循环中走到 break , 就会完全中断循环,不会进入下次循环。把上面代码的 tag1 处改为 break ,也就是说只要 i 是偶数,整个循环就会被完全终止。

代码示例

int sum = 0;
for (int i = 0; i < 10; i++) {
  if(i.isEven){// 如果是偶数
    break; //tag1
  }
  sum += i; //tag2
  print("第 $i 次循环,计入 sum");
}
print(sum); // tag3

3、return 关键字

概述摘录

严格来说,return 关键字并非用于循环的流程控制,而是表示当前方法已经结束。由于方法结束,其中的循环自然也就不会再进行了。比如下面 tag1 处如果是 return ,那么该方法直接退出。连 tag3 都不会执行。

代码示例

int sum = 0;
for (int i = 0; i < 10; i++) {
  if(i.isEven){// 如果是偶数
    return; //tag1
  }
  sum += i; //tag2
  print("第 $i 次循环,计入 sum");
}
print("sum: $sum"); // tag3

四、异常流程

摘录:在程序运行的过程中,可能会出现一些可以预见的错误:比如,网络异常、文件读取异常、日期格式化异常、数值越界异常等。一旦发生异常,不作处理的话,整个程序就会因错误而停止,无法执行之后的任务。另外异常的信息记录也能让开发者更容易定位问题,所以对异常的捕获和处理是非常重要的。

1、异常的发生

如下测试案例中 task1 方法将入参字符串转换为数字,如果传入非数字,就会产生转换异常。从而中断程序,导致 task2 无法触发。

void main(){
  task1('a');
  task2();
}

int task1(String num){
  return int.parse(num);
}

void task2(){
  print("Task2");
}

2、异常的捕捉

通过 try...catch... 可以对异常进行捕捉。在 catch 关键字后的括号中可以回调两个参数,如下

  • e 表示异常对象,此处为 FormatException
  • s_StackTrace 对象,用于记录异常的栈信息
void main(){
  try{
    task1('a');
  }catch(e,s){
    print("${e.runtimeType}: ${e.toString()}"); 
    print("${s.runtimeType}: ${s.toString()}"); 
  }
  task2(); // Task2
}

int task1(String num){
  return int.parse(num);
}

void task2(){
  print("Task2");
}

3、自定义异常与抛出

异常的顶层抽象类是 Exception ,其中有个 factory 构造。也就是说 Exception 可以进行构造,本质上创建的运行时类型是 _Exception ,可传入一个对象用于表示异常信息。

exception

如下做个简单的测试,在 getMean 方法中,传入单词名称,根据映射表来获取单词示意。当没有示意时,抛出异常,通过 throw 关键字,后面加 Exception 对象即可。

void main() {
  try {
    getMean("about");
  } catch (e,s) {
    print("${e.runtimeType}: ${e.toString()}");
    print("${s.runtimeType}: ${s.toString()}");
  }
}

String getMean(String arg) {
  Map<String, String> dict = {"card": "卡片", "but": "但是"};
  String? result = dict[arg];
  if (result == null) {
    throw Exception("empty $arg mean in dict");
  }
  return result;
}

有些场景可能存在多种不同的异常,我们期望可以区别对待,我们可以对 Exception 类进行拓展。如下自定义一个 NoElementInDictException 表示映射中没有相关元素的异常:

class NoElementInDictException implements Exception{
  final String arg;

  NoElementInDictException(this.arg);

  @override
  String toString() => "empty $arg mean in dict";
}

另外,一个方法可能抛出多种异常,如果希望明确抓取的类型种类,可以通过 on/catch 关键字创建分支。注意,允许有若干个 on 分支进行不同类型异常处理,对于未匹配到的异常,会走默认的 catch 分支。

void main() {
  try {
    getMean("about");
  } on NoElementInDictException catch(e,s){
    // 特定种类的异常处理
  } catch (e,s) {
    // 其余异常处理
  }
}

4、final 关键字

finally 关键字用于 catch 代码块之后,无论异常与否,其后代码块的逻辑总会被执行。如果不用 finally 关键字,那就需要在每个异常分支以及外界都写一遍 finally 代码块,这无疑是很麻烦的。这也是 finally 关键字的价值所在。

void foo2(){
  try {
    getMean("about");
  } catch (e,s) {
    print("${e.runtimeType}: ${e.toString()}");
    print("${s.runtimeType}: ${s.toString()}");
  } finally{
    print("finally bloc call");
  }