3、Dart语言基础回顾:变量与类型、函数与闭包、异步编程(Future、Stream)、面向对象编程在嵌入式中的注意事项
好,咱们直接进入正题。这一章我打算带大家快速过一遍 Dart 语言的核心基础。你可能会问:「我都准备搞嵌入式了,还要学语言基础?」嗯,我当年也有这个疑问。但后来在嵌入式设备上调试一个诡异的 UI 卡顿问题时,才发现自己对 Dart 的异步机制理解不够深。所以,这一章咱们不废话,直接讲干货。
3.1 变量与类型:别在嵌入式上「放飞自我」
Dart 是强类型语言,这点在嵌入式开发中特别重要。为什么?因为嵌入式设备的资源有限,你每定义一个变量,都得清楚它占多少内存。
基本类型:int、double、String、bool。这些没啥好说的,但我要提醒你一点——在嵌入式设备上,int 在 Dart 中默认是 64 位。如果你确定数值范围很小,可以考虑用 int32 或 int16 来节省内存。我在一个传感器数据采集项目里,就因为用了默认的 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 提供了 Future 和 Stream 两种异步模型。
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 的背压处理很重要。如果数据生产速度大于消费速度,内存会不断增长。我建议用 StreamController 的 add 方法时,设置 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 嵌入式开发环境,到时候咱们再聊。