Skip to content

Dart语言入门 - 流程控制详解(新手指南:避坑大全)

⚠️ 警示区:这里藏着你可能在实战中踩过的80%流程控制相关的坑!包括[漏写break导致错误贯穿][空循环分号陷阱]等高频错误


目录

  1. 条件判断的隐藏规则
  2. 循环控制的深度机制
  3. Switch语句的雷区
  4. 异常处理核心技巧
  5. 流程中断特殊用法

1. 条件判断的隐藏规则

必须明确的布尔条件(与JS不同!)

dart
var name = '';
if (name.isEmpty) { /* 正确方式 */ }
// if (name) { ... } ❌ 无法编译!非布尔类型不能直接作为条件

else if 的实质

dart
// 以下代码完全等效 → 掌握语法糖原理
if(a) {...}
else if(b) {...}
// 等价于
if(a) {...}
else {
  if(b) {...}
}

三元运算符类型推断陷阱

dart
var result = condition ? 1 : 'error'; // 类型变成 Object → 要明确类型声明
    ↓ ↓ ↓ 修改方案 ↓ ↓ ↓
num result = condition ? 1 : 0; // 强制保持统一类型

2. 循环控制的深度机制

for循环的作用域剖析

dart
for (var i = 0; i < 5; i++) {
  print(i);
}
print(i); // ❌ 报错!i的作用域限定在循环体内(Dart 2.15+ 行为变化)

While与do-while本质区别

dart
int counter = 5;
while (counter < 5) { ... }  // 0次执行 → 先判断
do { ... } while(counter <5) // 1次执行 → 后判断

死循环的正确写法

dart
// 最安全的形式 → 可配合break退出
while (true) {
  if (exitCondition) break;
}

for-in循环隐藏要求

dart
var numbers = [1,2,3];
// 等同于:var iterator = numbers.iterator;
for (var num in numbers) {
  print(num);
}

// 不能直接在普通Object上使用:
var myObject = MyCustomClass(); // 必须实现 Iterable

💥 forEach的局限性

dart
// 无法用break/continue控制流程 → 全程执行所有元素
list.forEach((item) {
  if (item == 2) return; // 相当于continue,无法break
});

3. Switch语句的雷区

必须遵守的黄金法则

dart
String fruit = 'apple';
switch (fruit) {
  case 'apple':
    print('🍎');
    // ❌ 若此处漏写break → 会发生case贯穿直到遇到break
    break; 
  case 'banana':
    print('🍌');
    return; // 可用return取代break
  default:
    print('未知水果');
}

高级匹配模式

dart
// List模式匹配(新手极少用但非常实用)
var list = [1,2];
switch (list) {
  case [int a, int b]:
    print('包含两个整数');
    break;
  case [_, ...]:
    print('长度>=1');
}

when与switch的选择

dart
// Dart原生没有when语法 → 可用带条件判断的case替代
switch (true) {
  case (score >= 90):
    print('优秀');
    break;
  case (score >= 60):
    print('及格');
}

4. 异常处理核心技巧

完整的异常处理结构

dart
try {
  // 可能抛出异常的代码
} on FormatException catch(e) { // 具体类型捕获
  print('格式错误:$e');
} on IOException {             // 可省略异常对象
  print('IO异常');
} catch (e, s) {               // 捕获所有异常 + 堆栈跟踪
  print('未知错误:$e');
  print('堆栈:$s');
} finally {                    // 必定执行的区域
  cleanUpResources();
}

重要注意事项

  1. finally总是在return前执行
dart
String test() {
  try {
    return '结果';
  } finally {
    print('finally执行');
  }
} // 输出顺序:finally执行 → 返回'结果'
  1. 避免在catch块中忘记处理异常
dart
// bad:
catch (e) {
  print(e); // 仅打印不够,需考虑是否需要重新抛出
}

// good:
catch (e) {
  logError(e); // 记录日志
  rethrow;     // 或做其他处理
}

5. 流程中断特殊用法

循环标签的使用

dart
outerLoop: 
for (var i = 1; i <= 3; i++) {
  innerLoop:
  for (var j = 1; j <= 3; j++) {
    if (i == 2 && j == 2) {
      break outerLoop; // 直接跳出外层循环
    }
  }
}

断言(assert)的适用场景

dart
// 仅用于开发调试,生产环境自动忽略
assert(num > 0, '数字必须为正数'); // 第二个参数是可选消息

Continue的灵活应用

dart
// 可指定跳转到指定标签的循环层次
list.forEach((number) {
  if (number == 2) continue;
  print(number);
});

综合应用案例

案例1:分数等级判断

dart
String getGrade(int score) {
  switch (score ~/ 10) { // 整除操作符
    case 10: case 9: return 'A';
    case 8: return 'B';
    case 7: return 'C';
    case 6: return 'D';
    default: 
      if(score < 0) throw ArgumentError('分数不能为负');
      return 'E';
  }
}

案例2:安全解析嵌套数据

dart
dynamic jsonData = fetchData();

try {
  var userName = jsonData['user']['profile']['name'] as String;
} on NoSuchMethodError {
  print('数据路径错误');
} on TypeError catch(e) {
  print('类型转换失败:${e.toString()}');
}

错误排查清单

  • 死循环
    • 检查循环条件是否永远不会变为false
    • 验证循环体内是否没有改变条件的操作
  • 预期之外的case穿透
    • 每个case的最后是否有break/return/throw
    • 是否需要故意穿透(这种情况需注释说明)
  • 异常未被捕获
    • 是否所有可能的异常都被try覆盖
    • 类层次结构中最具体的异常应放在前面
  • 异步流程失控
    • 在async函数中的循环体内是否需要await
    • 避免在forEach中直接使用async函数(大部分情况无法正常等待)

💡 最佳实践总结

  1. Switch语句如无必要阻止贯穿时必须写上注释说明
  2. 高危操作(网络请求/文件操作)必须使用异常捕获
  3. 尽可能用break配合标签替代复杂的嵌套条件判断
  4. 迭代集合时优先考虑for-in而不是forEach(除非确定无需控制流程)