if 与 switch 语句

程序的控制结构包括:

  • 顺序(顺序执行)
  • 分支(ifswitch
  • 循环(forwhile

分支语句包括 ifswitch语句。

if

下例中,如果 isSunny的值为true,就执行打印,否则什么也不做。

// ch0201_1.dart
import 'dart:math';

void main() {
  final rand = Random();
  final isSunny = rand.nextInt(10) % 2 == 0;
  if (isSunny) {
    print("Let's go to the park");
  }
}  
flowchart TD
    B{条件}-->|true| C(代码块)
    C --> E([End])
    B -->|false| E

if 有一个可选的else分支。

  if (isSunny) {
    print("Let's go to the park");
  } else {
    print("It's not sunny");
  }
flowchart TD
    B{条件}-->|true| C(主分支)
    C --> E([End])
    B -->|false| D(else分支)
    D --> E

还可以使用 else if,像下面这样。

  if (isSunny) {
    print("Let's go to the park");
  } else if (isRaining) {
    print("Let's bring our raincoats");
  } else {
    print("Let's stay home");
  }

从语法上讲,上例中最后的else分支也是可选的。

if-case

从 Dart 3.0 开始, if 语句支持 case 子句,case后面接一个模式(pattern,第3章内容)。

// ch0201_2.dart
void main() {
  var dat = [1, 2]; 
  if (dat case [_, _]) { // 1
    print('matched'); // 2
  } else {
    print('not matched'); // 2a
  }

  if (dat case [int x, int y]) { // 3
    print('x=$x y=$y'); // 4
  }
}

  1. 使用 if-case 检查 dat 是否匹配一个包含2个元素的列表;
  2. 如果匹配,就打印字符串 "matched",否则打印"not matched" (2a);
  3. 使用 if-case 将 dat与列表进行匹配,并捕获列表中的元素。

switch

如果需要匹配多个case,应使用switch 语句,而非一个又一个的else if

flowchart TD
    A{switch}-->|case1| B(case1)
    A --> |case2| C(case2)
    A --> |case...| D(case...)
    A --> |default| E(default分支)
    B --> Z([End])
    C --> Z
    D --> Z
    E --> Z
// ch0201_3.dart
void main() {
  var status = 'done';
  switch (status) {
    case 'done':
      print('done');
    case 'ongoing':
      print('ongoing');
    case 'pending':
      print('pending');
    default: // case _:
      print('unknown status: $status');
  }
}

如果其他case都不匹配,则执行default case的代码,也就是 defaultcase _ 标记的代码 。default case 要放在最后。

fallthrough 与 标签(lablel)

空case(empty case)是指不做任何处理的case。默认情况下空case会fall through到下一个case,使得cases可以共享代码块。如果想让空case fall through到其他的case(非下一个case),使用标签(label)和 continue来实现。

// ch0201_4.dart
void main() {
  var status = 'canceled';
  switch (status) {
    case 'canceled': // 1 empty case
    case 'done': // 1a
      print('done');
    todo: // 2
    case 'todo':
      print('todo');
    case 'pending':
      print('pending');
      continue todo; // 3
    case 'ongoing':
      print('ongoing');
    case _:
      print('unknown status: $status');
  }
}
  1. 这是一个空case( canceled ),它没有对应的代码块,fall through 到下一个case(done ), 这两个case共享了代码块(print('done')) ; 此外这两个case也可以写成
    case 'canceled' || 'done':
      print('done');
  1. 这里定义了一个标签 todo
  2. 在执行完上一行代码后(print('pending')),跳转至 todo 标签所在位置继续执行( print('todo'))。

如果不想让空case fall through, 使用 break 即可。

    case 'canceled':
      break;

switch 表达式

switch表达式将返回一个值,这个值来自于匹配的case的表达式。先来看一个普通的switch语句:

// ch0201_5.dart
void main() {
  var x = 1.0, y = 2.0;
  var op = '+';
  double r;
  switch (op) {
    case '+':
      r = x + y;
    case '-':
      r = x - y;
    case '*':
      r = x * y;
    case '/':
      r = x / y;
    case '%':
      r = x % y;
    case _:
      throw 'unknown op: $op';
  }
  print('$x$op$y=$r');
}

switch表达式改写:

// ch0201_6.dart
void main() {
  var x = 1.0, y = 2.0;
  var op = '+';
  var r = switch (op) {
    '+' => x + y,
    '-' => x - y,
    '*' => x * y,
    '/' => x / y,
    '%' => x % y,
    _ => throw 'unknown op: $op',
  };
  print('$x$op$y=$r');
}

在语法方面,与switch 语句相比,switch表达式:

  1. case不以 case 关键字开头;
  2. case主体是一个单一的表达式,而不是一系列的语句;
  3. 每个case都必须有一个主体;对于空case,没有隐式的 fallthrough;
  4. case模式与其主体使用 => 而不是 : 分隔;
  5. 各个case之间用 , 分隔(允许在结尾添加可选的 , );
  6. default case 只能用 _, 无法用default

详尽性检查

下面这段代码将引起编译错误:

//ch0201_7.dart
void main() {
  bool? win;
  var score = switch (win) {
    true => 2,
    false => 0,
  };
  // Error: The type 'bool?' is not exhaustively matched by the switch cases since it doesn't match 'null'.
  // Try adding a wildcard pattern or cases that match 'null'.
  //   var score = switch (win) {
  //                       ^
}

对于一个 boo?类型的变量,它的值是穷举的,即 truefalsenull,Dart编译器将对这类可穷举类型的变量进行详尽性检查。上例的编译错误告诉我们缺少了对 null case 的处理。常见的可穷举类型还有bool、枚举(Enum)和密封类(sealed class)

下例演示了针对枚举的详尽性检查。

// ch0201_8.dart
void main() {
  var x = 1.0, y = 2.0;
  var op = Op.plus;
  var r = switch (op) {
    Op.plus => x + y,
    // Op.minus => x - y,
  };
  // Error: The type 'Op' is not exhaustively matched by the switch cases since it doesn't match 'Op.minus'.
  //  - 'Op' is from 'bin/ch02/ch0201_8.dart'.
  // Try adding a wildcard pattern or cases that match 'Op.minus'.
  //   var r = switch (op) {
  //                   ^
}

enum Op { plus, minus }

下例演示了针对密封类的详尽性检查。一个密封类的子类是有限个的,switch语句或表达式将对其子类进行详尽性检查。

// ch0201_9.dart
void main() {
  Op op = Plus(1.0, 2.0);
  var r = switch (op) {
    Plus(:double x, :double y) => x + y,
    // Minus(:double x, :double y) => x - y,
  };
  // Error: The type 'Op' is not exhaustively matched by the switch cases since it doesn't match 'Minus()'.
  //  - 'Op' is from 'bin/ch02/ch0201_9.dart'.
  // Try adding a wildcard pattern or cases that match 'Minus()'.
  //   var r = switch (op) {
  //                   ^
}

sealed class Op {
  final double x;
  final double y;

  Op(this.x, this.y);
}

class Plus extends Op {
  Plus(super.x, super.y);
}

class Minus extends Op {
  Minus(super.x, super.y);
}

第4-5章将详细讨论类的相关概念。

守护子句

守护子句(guard clause)是可选的,它出现在case子句之后,使用关键字 when。守护子句可以出现在 if-case 以及 switch 语句和表达式中。

// ch0201_a.dart
void main() {
  var year = 2025;
  bool showAges = true;
  switch (year) {
    case >= 2000 when showAges && year < 2010:
      print('2000s');
    case >= 2010 when showAges && year < 2020:
      print('2010s');
    case >= 2020 when showAges && year < 2030:
      print('2020s');
  }

  var dat = [3, 4];
  if (dat case [int x, int y] when x < y) {
    print('x=$x y=$y');
  }
}

参考资料