Dart语法练习深度解析
通过编写常见数据结构类、Mixin使用、异步编程等综合练习,将Dart核心语法融会贯通
Dart语法练习深度解析
一句话概括
通过实战编写二叉树、自定义Mixin链与异步任务流水线等综合练习,将Dart的类、Mixin、泛型、异步编程等核心语法融会贯通。
背景与意义
为什么语法练习如此重要?
学习编程语言有三个层次:
1
2
3
知道语法 → 看懂代码 → 写对代码
↓ ↓ ↓
理解概念 能Debug 能设计架构
大多数教程停留在第一层——解释语法概念。但真正的掌握来自于刻意练习——通过解决实际问题来内化语法规则。
这套练习的设计思路
本套练习覆盖Dart语法的关键领域:
1
2
3
练习1:数据结构类 ── 类设计、泛型、运算符重载、toString
练习2:Mixin链 ── Mixin线性化、super调用、状态管理
练习3:异步编程 ── Future链、Stream处理、并发控制
每个练习都附带测试用例和设计思路,不仅仅是代码抄写。
练习一:用Dart实现二叉树与遍历
要求
- 实现一个泛型二叉搜索树(BST)
- 支持插入、查找、删除操作
- 实现前序、中序、后序、层序遍历(返回列表)
- 运算符重载支持
+合并两棵树
设计思路
1
2
3
4
// 使用泛型 T extends Comparable<T> 保证元素可比
// 递归方式实现插入和查找
// 删除使用 Hibbard Deletion(找后继节点替代)
// 遍历使用递归 + 辅助列表
完整实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// bst.dart - 二叉搜索树
import 'dart:math';
class BinarySearchTree<T extends Comparable<T>> {
_Node<T>? _root;
int _size = 0;
int get size => _size;
bool get isEmpty => _size == 0;
// ----- 插入 -----
void insert(T value) {
_root = _insertRec(_root, value);
_size++;
}
_Node<T> _insertRec(_Node<T>? node, T value) {
if (node == null) return _Node(value);
final cmp = value.compareTo(node.value);
if (cmp < 0) {
node.left = _insertRec(node.left, value);
} else if (cmp > 0) {
node.right = _insertRec(node.right, value);
} else {
// 值已存在,不重复插入
_size--;
}
return node;
}
// ----- 查找 -----
bool contains(T value) {
var current = _root;
while (current != null) {
final cmp = value.compareTo(current.value);
if (cmp == 0) return true;
current = cmp < 0 ? current.left : current.right;
}
return false;
}
T? findMin() => _findMin(_root)?.value;
T? findMax() => _findMax(_root)?.value;
_Node<T>? _findMin(_Node<T>? node) {
if (node == null) return null;
while (node!.left != null) {
node = node.left;
}
return node;
}
_Node<T>? _findMax(_Node<T>? node) {
if (node == null) return null;
while (node!.right != null) {
node = node.right;
}
return node;
}
// ----- 删除(Hibbard Deletion)-----
bool delete(T value) {
final oldSize = _size;
_root = _deleteRec(_root, value);
return _size < oldSize;
}
_Node<T>? _deleteRec(_Node<T>? node, T value) {
if (node == null) return null;
final cmp = value.compareTo(node.value);
if (cmp < 0) {
node.left = _deleteRec(node.left, value);
} else if (cmp > 0) {
node.right = _deleteRec(node.right, value);
} else {
// 找到要删除的节点
_size--;
// 情况1:没有右子树 → 左子树替代
if (node.right == null) return node.left;
// 情况2:没有左子树 → 右子树替代
if (node.left == null) return node.right;
// 情况3:有左右子树 → 找右子树的最小节点替代
final successor = _findMin(node.right)!;
node.value = successor.value;
node.right = _deleteRec(node.right, successor.value);
_size++; // 删除rec少计了一次,补回
}
return node;
}
// ----- 遍历 -----
List<T> inorder() {
final result = <T>[];
_inorderRec(_root, result);
return result;
}
void _inorderRec(_Node<T>? node, List<T> result) {
if (node == null) return;
_inorderRec(node.left, result);
result.add(node.value);
_inorderRec(node.right, result);
}
List<T> preorder() {
final result = <T>[];
_preorderRec(_root, result);
return result;
}
void _preorderRec(_Node<T>? node, List<T> result) {
if (node == null) return;
result.add(node.value);
_preorderRec(node.left, result);
_preorderRec(node.right, result);
}
List<T> postorder() {
final result = <T>[];
_postorderRec(_root, result);
return result;
}
void _postorderRec(_Node<T>? node, List<T> result) {
if (node == null) return;
_postorderRec(node.left, result);
_postorderRec(node.right, result);
result.add(node.value);
}
List<T> levelOrder() {
final result = <T>[];
if (_root == null) return result;
final queue = <_Node<T>>[_root!];
while (queue.isNotEmpty) {
final node = queue.removeAt(0);
result.add(node.value);
if (node.left != null) queue.add(node.left!);
if (node.right != null) queue.add(node.right!);
}
return result;
}
// ----- 运算符重载:合并两棵树 -----
BinarySearchTree<T> operator +(BinarySearchTree<T> other) {
final result = BinarySearchTree<T>();
for (final val in inorder()) {
result.insert(val);
}
for (final val in other.inorder()) {
result.insert(val);
}
return result;
}
// ----- 可视化 -----
int get height => _heightRec(_root);
int _heightRec(_Node<T>? node) {
if (node == null) return 0;
return 1 + max(_heightRec(node.left), _heightRec(node.right));
}
@override
String toString() {
final values = inorder();
return 'BST(size=$size): [${values.join(", ")}]';
}
}
// 节点类(内部使用)
class _Node<T> {
T value;
_Node<T>? left;
_Node<T>? right;
_Node(this.value, {this.left, this.right});
}
// ===== 测试用例 =====
void main() {
print('===== 二叉搜索树练习 =====');
// 1. 基本插入和遍历
final bst = BinarySearchTree<int>();
[5, 3, 7, 2, 4, 6, 8].forEach((v) => bst.insert(v));
print('树: $bst');
print('高度: ${bst.height}');
print('中序: ${bst.inorder()}'); // [2, 3, 4, 5, 6, 7, 8]
print('前序: ${bst.preorder()}'); // [5, 3, 2, 4, 7, 6, 8]
print('后序: ${bst.postorder()}'); // [2, 4, 3, 6, 8, 7, 5]
print('层序: ${bst.levelOrder()}'); // [5, 3, 7, 2, 4, 6, 8]
// 2. 查找
print('包含3: ${bst.contains(3)}'); // true
print('包含9: ${bst.contains(9)}'); // false
print('最小值: ${bst.findMin()}'); // 2
print('最大值: ${bst.findMax()}'); // 8
// 3. 删除
bst.delete(3);
print('删除3后: ${bst.inorder()}'); // [2, 4, 5, 6, 7, 8]
bst.delete(5);
print('删除5后: ${bst.inorder()}'); // [2, 4, 6, 7, 8]
print('大小: ${bst.size}'); // 5
// 4. 运算符重载
final bst1 = BinarySearchTree<int>();
[1, 3, 5].forEach((v) => bst1.insert(v));
final bst2 = BinarySearchTree<int>();
[2, 4, 6].forEach((v) => bst2.insert(v));
final merged = bst1 + bst2;
print('合并树: ${merged.inorder()}'); // [1, 2, 3, 4, 5, 6]
}
练习二:使用Mixin构建可组合的能力链
要求
- 定义一系列”能力”Mixin:
CanWalk、CanSwim、CanFly、CanClimb - 构建不同生物类,使用不同的Mixin组合
- 验证Mixin线性化顺序
- 添加
LoggerMixin跟踪方法调用
设计思路
1
2
3
// 每个Ability Mixin有一个move方法返回移动描述
// 通过super.move()构建调用链
// 最终打印出完整的移动能力描述
完整实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// ability_mixins.dart
// ===== 基础能力Mixin =====
mixin CanWalk {
String move() => '走路';
}
mixin CanSwim {
String move() => '游泳';
}
mixin CanFly {
String move() => '飞行';
}
mixin CanClimb {
String move() => '攀爬';
}
// ===== 日志Mixin(追踪方法调用链)=====
mixin LoggerAbility {
String move() {
print('[日志] 调用了 ${runtimeType}.move()');
return '基础移动';
}
}
// ===== 带追踪的Mixin =====
mixin WalkLogger on LoggerAbility {
@override
String move() {
final parent = super.move();
final mine = '走路';
print('[追踪] WalkLogger: $mine + ← $parent');
return '$mine + $parent';
}
}
mixin SwimLogger on LoggerAbility {
@override
String move() {
final parent = super.move();
final mine = '游泳';
print('[追踪] SwimLogger: $mine + ← $parent');
return '$mine + $parent';
}
}
mixin FlyLogger on LoggerAbility {
@override
String move() {
final parent = super.move();
final mine = '飞行';
print('[追踪] FlyLogger: $mine + ← $parent');
return '$mine + $parent';
}
}
mixin ClimbLogger on LoggerAbility {
@override
String move() {
final parent = super.move();
final mine = '攀爬';
print('[追踪] ClimbLogger: $mine + ← $parent');
return '$mine + $parent';
}
}
// ===== 生物类 =====
class Human with LoggerAbility, WalkLogger {
String name;
Human(this.name);
void describe() => print('$name: ${move()}');
}
class Duck with LoggerAbility, WalkLogger, SwimLogger, FlyLogger {
String name;
Duck(this.name);
void describe() => print('$name: ${move()}');
// Duck.move() 的线性化顺序:
// Duck → FlyLogger.move → SwimLogger.move → WalkLogger.move → LoggerAbility.move
}
class Monkey with LoggerAbility, WalkLogger, ClimbLogger {
String name;
Monkey(this.name);
void describe() => print('$name: ${move()}');
}
// ===== 简单版(无日志追踪)=====
class SimpleHuman with CanWalk {}
class SimpleDuck with CanWalk, CanSwim, CanFly {}
class SimpleMonkey with CanWalk, CanClimb {}
class Platypus with CanWalk, CanSwim {} // 鸭嘴兽
// ===== 测试 =====
void main() {
print('===== 追踪版能力演示 =====');
print('--- 人类 ---');
final human = Human('张三');
human.describe();
print('\n--- 鸭子 ---');
final duck = Duck('唐老鸭');
duck.describe();
print('\n--- 猴子 ---');
final monkey = Monkey('悟空');
monkey.describe();
print('\n===== 简单版能力组合 =====');
final animals = [
SimpleHuman(),
SimpleDuck(),
SimpleMonkey(),
Platypus(),
];
for (final animal in animals) {
print('${animal.runtimeType}: ${animal.move()}');
}
// 验证Mixin线性化顺序
print('\n===== Mixin顺序验证 =====');
// 后面的Mixin覆盖前面的同名方法
class CreatureA with CanWalk, CanSwim, CanFly {}
class CreatureB with CanFly, CanSwim, CanWalk {}
final a = CreatureA();
final b = CreatureB();
print('CreatureA(步行,游泳,飞行): ${a.move()}'); // 飞行
print('CreatureB(飞行,游泳,步行): ${b.move()}'); // 步行
// 后面的Mixin的move方法在super链中更靠近类,优先级更高
}
练习三:异步编程——任务调度器
要求
- 实现一个泛型异步任务调度器
- 支持并发控制(限制同时运行的任务数)
- 支持任务优先级
- 提供Stream实时通知任务状态变更
- 优雅关闭(等待当前任务完成)
设计思路
1
2
3
4
// 使用 StreamController 广播状态
// 使用 Completer 控制异步流程
// 使用泛型 Future<T> 支持不同类型任务
// 使用 PriorityQueue 处理优先级
完整实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// task_scheduler.dart
import 'dart:async';
import 'dart:collection';
// ===== 任务定义 =====
enum TaskStatus { pending, running, completed, failed }
enum TaskPriority { low, normal, high, critical }
class Task<T> implements Comparable<Task> {
final String id;
final Future<T> Function() work;
final TaskPriority priority;
Task({
required this.id,
required this.work,
this.priority = TaskPriority.normal,
});
@override
int compareTo(Task other) {
// 优先级高的(enum值大的)排在前面
return other.priority.index.compareTo(priority.index);
}
@override
String toString() => 'Task($id, $priority)';
}
// ===== 任务事件 =====
sealed class TaskEvent {}
class TaskStarted extends TaskEvent {
final String taskId;
TaskStarted(this.taskId);
}
class TaskCompleted<T> extends TaskEvent {
final String taskId;
final T? result;
TaskCompleted(this.taskId, this.result);
}
class TaskFailed extends TaskEvent {
final String taskId;
final Object error;
TaskFailed(this.taskId, this.error);
}
// ===== 任务调度器 =====
class TaskScheduler {
final int _maxConcurrency;
final PriorityQueue<Task> _queue = PriorityQueue<Task>();
final Set<String> _running = {};
final List<Completer> _pendingCompleters = [];
bool _isShuttingDown = false;
int _activeCount = 0;
// 状态广播
final _eventController = StreamController<TaskEvent>.broadcast();
Stream<TaskEvent> get events => _eventController.stream;
TaskScheduler({int maxConcurrency = 3})
: _maxConcurrency = maxConcurrency;
int get pendingCount => _queue.length;
int get runningCount => _running.length;
bool get isIdle => _queue.isEmpty && _running.isEmpty;
// ----- 提交任务 -----
Future<T> submit<T>(Task<T> task) {
if (_isShuttingDown) {
throw StateError('调度器已关闭,无法接受新任务');
}
final completer = Completer<T>();
_pendingCompleters.add(completer);
// 包装任务,在完成时resolve completer
final wrappedTask = Task<T>(
id: task.id,
priority: task.priority,
work: () async {
try {
final result = await task.work();
if (!completer.isCompleted) {
completer.complete(result);
}
return result;
} catch (e) {
if (!completer.isCompleted) {
completer.completeError(e);
}
rethrow;
}
},
);
_queue.add(wrappedTask);
_tryExecuteNext();
return completer.future;
}
// ----- 执行调度 -----
void _tryExecuteNext() {
while (_activeCount < _maxConcurrency && _queue.isNotEmpty && !_isShuttingDown) {
final task = _queue.removeFirst();
_executeTask(task);
}
}
Future<void> _executeTask(Task task) async {
_activeCount++;
_running.add(task.id);
_eventController.add(TaskStarted(task.id));
try {
final dynamic result = await task.work();
_running.remove(task.id);
_eventController.add(TaskCompleted(task.id, result));
} catch (e) {
_running.remove(task.id);
_eventController.add(TaskFailed(task.id, e));
} finally {
_activeCount--;
_tryExecuteNext();
_checkShutdownComplete();
}
}
// ----- 优雅关闭 -----
Future<void> shutdown() async {
_isShuttingDown = true;
if (isIdle) {
await _eventController.close();
return;
}
// 等待当前所有任务完成
await _waitForIdle();
await _eventController.close();
}
Future<void> _waitForIdle() async {
while (!isIdle) {
await Future.delayed(Duration(milliseconds: 100));
}
}
void _checkShutdownComplete() {
if (_isShuttingDown && isIdle) {
_resolvePending();
}
}
void _resolvePending() {
for (final completer in _pendingCompleters) {
if (!completer.isCompleted) {
completer.completeError(StateError('调度器已关闭'));
}
}
_pendingCompleters.clear();
}
void dispose() {
_queue.clear();
_running.clear();
_eventController.close();
}
}
// ===== 使用示例 =====
class DownloadTask {
final String url;
final int size; // KB
final int _simulateDelay;
DownloadTask(this.url, this.size)
: _simulateDelay = (size / 10).round();
Future<String> download() async {
print(' 开始下载 [$url] (${size}KB)');
var downloaded = 0;
while (downloaded < size) {
await Future.delayed(Duration(milliseconds: 200));
downloaded += 10;
if (downloaded > size) downloaded = size;
print(' [$url] 进度: ${(downloaded / size * 100).round()}%');
}
print(' 完成下载 [$url]');
return 'downloaded_${url.hashCode}';
}
}
void main() async {
print('===== 任务调度器 =====\n');
final scheduler = TaskScheduler(maxConcurrency: 2);
// 监听事件
final subscription = scheduler.events.listen((event) {
switch (event) {
case TaskStarted(:final taskId):
print('[事件] 任务开始: $taskId');
case TaskCompleted(:final taskId, :final result):
print('[事件] 任务完成: $taskId -> $result');
case TaskFailed(:final taskId, :final error):
print('[事件] 任务失败: $taskId -> $error');
}
});
// 提交任务
print('提交任务...');
final task1 = Task<String>(
id: '下载-图片A',
priority: TaskPriority.high,
work: () async {
final dl = DownloadTask('https://img.example.com/a.jpg', 50);
return await dl.download();
},
);
final task2 = Task<String>(
id: '下载-图片B',
priority: TaskPriority.normal,
work: () async {
final dl = DownloadTask('https://img.example.com/b.jpg', 30);
return await dl.download();
},
);
final task3 = Task<String>(
id: '下载-视频C',
priority: TaskPriority.critical,
work: () async {
final dl = DownloadTask('https://vids.example.com/c.mp4', 80);
return await dl.download();
},
);
final task4 = Task<String>(
id: '下载-文档D',
priority: TaskPriority.low,
work: () async {
final dl = DownloadTask('https://docs.example.com/d.pdf', 20);
return await dl.download();
},
);
// 启动任务(不await,并行执行)
final results = await Future.wait([
scheduler.submit(task1),
scheduler.submit(task2),
scheduler.submit(task3),
scheduler.submit(task4),
], eagerError: false); // 不因一个失败中断其他
print('\n所有任务结果:');
for (var i = 0; i < results.length; i++) {
final result = results[i];
if (result is String) {
print(' [$i] ✅ $result');
} else {
print(' [$i] ❌ $result');
}
}
// 清理
await subscription.cancel();
scheduler.dispose();
print('\n调度器已关闭');
}
扩展练习:将三个练习串联
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 综合应用:使用BST作为任务调度器的优先级队列后端
// 使用Mixin为调度器添加额外能力
mixin StatisticsMixin on TaskScheduler {
// 通过Mixin添加统计能力
int get totalSubmitted => runningCount + pendingCount;
Duration? get averageWaitTime => null; // 扩展预留
Map<String, dynamic> get stats => {
'running': runningCount,
'pending': pendingCount,
'total': totalSubmitted,
'idle': isIdle,
};
}
mixin LoggingMixin on TaskScheduler {
void logStatus() {
print('[调度器] 运行中: $runningCount, 等待: $pendingCount');
}
}
// 使用Mixin增强的调度器
class EnhancedScheduler extends TaskScheduler
with StatisticsMixin, LoggingMixin {
EnhancedScheduler({super.maxConcurrency});
}
底层原理分析
Future的事件循环调度
Dart的异步任务由事件循环(Event Loop)驱动:
1
2
3
4
5
6
7
8
9
10
Dart事件循环(单线程):
┌─────────────────────────┐
│ MicroTask Queue (微任务) │ ← setState回调、Future.then回调
├─────────────────────────┤
│ Event Queue (事件队列) │ ← I/O、Timer、用户事件
│ ├── Future.then │
│ ├── Stream events │
│ ├── Timer callbacks │
│ └── IO completions │
└─────────────────────────┘
1
2
3
4
5
6
7
8
9
// 事件循环的执行顺序
void main() {
print('1: 同步代码');
Future(() => print('3: Future事件队列'));
Future.microtask(() => print('2: MicroTask优先'));
// 输出顺序:1 → 2 → 3
}
Mixin线性化的底层实现
Mixin不是”代码复制”,而是通过Super调用链实现:
1
2
3
4
5
6
7
8
// 编译器对Mixin的处理:
// 源码: class C with A, B { ... }
// 编译后: C关联了A和B的"方法表",并在运行时通过super链串联
// 在Dart VM中:
// 每个类维护一个"方法表数组"(Method Table Array)
// Mixin混合后,方法表按线性化顺序排列
// super.call()沿着该数组向前查找
练习总结
各练习覆盖的知识点
| 练习 | 覆盖知识点 |
|---|---|
| BST(二叉树) | 泛型、递归、运算符重载、toString、内部类、const构造函数 |
| Mixin链 | Mixin声明、on约束、super链、线性化、组合优于继承 |
| 任务调度器 | Future、Stream、Completer、优先级队列、sealed class、泛型方法 |
| 串联综合 | 类层次设计、Mixin增强已有类、面向接口编程 |
调试技巧
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 使用'runtimeType'在运行时检查类型
print(obj.runtimeType); // 输出具体类型名
// 2. 使用Dart DevTools查看隔离区
// dart --observe main.dart
// 3. 使用print栈追踪:
void debug(String msg, [StackTrace? stack]) {
final frame = stack ?? StackTrace.current;
print('[$msg] at ${frame.toString().split('\n')[1].trim()}');
}
// 4. Future调试:
Future<T> tracked<T>(Future<T> Function() fn, String label) async {
print('[$label] 开始');
try {
final result = await fn();
print('[$label] 完成: $result');
return result;
} catch (e) {
print('[$label] 失败: $e');
rethrow;
}
}
进一步练习建议
1
2
3
4
5
6
// 如果想继续深入,可以尝试:
// 扩展1:为BST添加平衡旋转(AVL树)
// 扩展2:使用Mixin为BST添加"可持久化"能力(每次修改返回新树)
// 扩展3:任务调度器支持取消任务
// 扩展4:在调度器上使用StreamBuilder构建Flutter UI进度展示
本文由作者按照 CC BY 4.0 进行授权