3、Dart语言基础回顾:变量与类型、函数与闭包、异步编程(Future、Stream)、面向对象编程在嵌入式中的注意事项

好,咱们直接进入正题。这一章我打算带大家快速过一遍 Dart 语言的核心基础。你可能会问:「我都准备搞嵌入式了,还要学语言基础?」嗯,我当年也有这个疑问。但后来在嵌入式设备上调试一个诡异的 UI 卡顿问题时,才发现自己对 Dart 的异步机制理解不够深。所以,这一章咱们不废话,直接讲干货。

3.1 变量与类型:别在嵌入式上「放飞自我」

Dart 是强类型语言,这点在嵌入式开发中特别重要。为什么?因为嵌入式设备的资源有限,你每定义一个变量,都得清楚它占多少内存。

基本类型:int、double、String、bool。这些没啥好说的,但我要提醒你一点——在嵌入式设备上,int 在 Dart 中默认是 64 位。如果你确定数值范围很小,可以考虑用 int32int16 来节省内存。我在一个传感器数据采集项目里,就因为用了默认的 int,导致内存占用比预期高了 30%。后来改成 int16,问题直接解决。

核心原则:能用 final 就别用 var,能用 const 就别用 final。这不仅是代码风格问题,更是性能问题。

// 推荐写法
const int maxRetryCount = 3;
final String deviceId = 'sensor_01';

// 不推荐(在嵌入式上)
var count = 0;  // 类型推断虽然方便,但可读性差

另外,空安全是 Dart 的一大特色。在嵌入式开发中,空指针异常是导致设备重启的常见原因之一。我个人习惯是:所有可能为空的变量,都显式加上 ?,并且在使用前做非空判断。

小技巧:用 late 关键字延迟初始化,可以避免不必要的空检查。但注意,如果你在赋值前访问了 late 变量,程序会直接崩溃。我曾经因为这个 bug 排查了一整天……

3.2 函数与闭包:嵌入式里的「轻量级武器」

函数在 Dart 中是「一等公民」。这意味着你可以把函数当作参数传递,也可以把函数赋值给变量。这在嵌入式 UI 开发中特别有用——比如按钮的回调函数。

函数定义:支持位置参数、命名参数、默认参数。我建议在嵌入式开发中多用命名参数,因为可读性更好。你想想看,一个函数有 5 个参数,全是位置参数,调用的时候谁记得住顺序?

// 推荐:命名参数
void configureSensor({required int pin, int baudRate = 9600, bool enableLog = false}) {
  // 配置传感器
}

// 调用时一目了然
configureSensor(pin: 4, baudRate: 115200);

闭包:说白了,闭包就是一个函数「记住」了它创建时的环境。在嵌入式开发中,闭包常用于事件处理。但要注意——闭包会捕获外部变量,如果使用不当,可能导致内存泄漏。

避坑指南:我曾经在循环中创建闭包,结果每个闭包都共享了同一个循环变量,导致所有按钮的回调都触发了同一个逻辑。解决方案是用一个立即执行函数来「固定」变量的值。

// 错误示例
for (var i = 0; i < 3; i++) {
  buttons[i].onPressed = () {
    print('Button $i pressed');  // 所有按钮都打印 3
  };
}

// 正确示例
for (var i = 0; i < 3; i++) {
  buttons[i].onPressed = (index) {
    return () => print('Button $index pressed');
  }(i);
}

3.3 异步编程:Future 与 Stream

嵌入式开发中,异步编程是家常便饭。读取传感器数据、发送网络请求、处理用户输入——这些操作都不能阻塞主线程。Dart 提供了 FutureStream 两种异步模型。

3.3.1 Future:一次性异步操作

Future 代表一个「将来会完成」的任务。你可以用 then 链式调用,也可以用 async/await 让代码看起来像同步的。我个人强烈推荐 async/await,因为可读性更好。

Future<int> readSensorData() async {
  // 模拟传感器读取延迟
  await Future.delayed(Duration(milliseconds: 100));
  return 42;
}

void main() async {
  int data = await readSensorData();
  print('传感器数据: $data');
}

注意:在嵌入式设备上,await 会暂停当前函数,但不会阻塞主线程。这意味着 UI 仍然可以响应用户操作。但如果你在 await 期间做了太多计算,还是会导致卡顿。

3.3.2 Stream:连续数据流

Stream 适合处理连续的数据,比如传感器每隔 100ms 上报一次温度值。你可以用 listen 监听,也可以用 await for 循环处理。

Stream<int> temperatureStream() async* {
  int temp = 25;
  while (true) {
    await Future.delayed(Duration(seconds: 1));
    temp += (Random().nextInt(3) - 1);  // 模拟温度波动
    yield temp;
  }
}

void main() async {
  await for (int temp in temperatureStream()) {
    print('当前温度: $temp°C');
    if (temp > 30) {
      print('⚠️ 温度过高!');
    }
  }
}

经验之谈:在嵌入式设备上,Stream 的背压处理很重要。如果数据生产速度大于消费速度,内存会不断增长。我建议用 StreamControlleradd 方法时,设置 sync: true 来避免缓冲区堆积。

3.4 面向对象编程:嵌入式中的「克制」

面向对象编程(OOP)在嵌入式开发中是一把双刃剑。用好了,代码清晰易维护;用过了,内存和性能双双爆炸。

类与对象:Dart 的类定义很简洁。但我要提醒你——在嵌入式设备上,不要滥用继承。每多一层继承,就意味着更多的虚函数表查找开销。我建议多用组合,少用继承。

// 推荐:组合优于继承
class Sensor {
  final String name;
  final int pin;
  
  Sensor(this.name, this.pin);
  
  int read() {
    // 读取传感器
    return 0;
  }
}

class DeviceManager {
  final List<Sensor> sensors;
  
  DeviceManager(this.sensors);
  
  void readAll() {
    for (var sensor in sensors) {
      print('${sensor.name}: ${sensor.read()}');
    }
  }
}

抽象类与接口:Dart 没有 interface 关键字,但每个类都可以当作接口使用。在嵌入式开发中,我习惯用抽象类来定义「协议」,然后用具体类来实现。这样方便后期更换硬件。

避坑指南:我曾经在一个项目中,为了「优雅」地复用代码,设计了 5 层继承结构。结果在低端设备上,每次方法调用都要经过多层查找,性能直接腰斩。后来我重构为扁平结构,问题解决。记住:嵌入式开发中,性能优先于设计模式。

Mixin:这是 Dart 的一个特色功能。你可以把一些公共方法「混入」到类中,而不需要继承。在嵌入式开发中,我常用 Mixin 来添加日志、调试等功能。

mixin Logger {
  void log(String message) {
    print('[LOG] $message');
  }
}

class Sensor with Logger {
  void read() {
    log('开始读取传感器');
    // 读取逻辑
    log('读取完成');
  }
}

3.5 嵌入式中的特殊注意事项

最后,我总结几个在嵌入式 Dart 开发中容易踩的坑:

注意事项 说明 建议
内存管理 Dart 有垃圾回收,但嵌入式设备内存小 避免频繁创建临时对象,复用对象池
异常处理 未捕获的异常会导致设备重启 所有异步操作都要加 try-catch
字符串操作 字符串拼接会产生大量临时对象 用 StringBuffer 代替 + 操作符
集合类型 List、Map 等集合会动态扩容 预分配容量,如 List.filled()

总结一下:Dart 语言本身很强大,但在嵌入式环境中,你需要时刻关注「资源消耗」和「性能开销」。多用 const、少用继承、谨慎处理异步——做到这三点,你的嵌入式 Dart 代码基本就稳了。

好了,这一章的内容就到这里。下一章我们会开始搭建 Flutter 嵌入式开发环境,到时候咱们再聊。