Dart 基础到建模:空安全、集合、函数与对象
先运行纯 Dart
Dart不等于Flutter。命令行程序只需要Dart SDK;Flutter SDK自带Dart,同一Flutter工程应使用匹配的那一份,不在两个SDK之间来回混用。本课程核对于2026-10-17,实际使用官方稳定Dart3.13.5,纯标准库项目无需pub依赖。
从 Dart SDK archive 下载Windows/Linux对应x64稳定包,解压到自己的工具目录。Windows PowerShell用 & 'D:/tools/dart-sdk/bin/dart.exe' --version,Linux Bash用 ./dart-sdk/bin/dart --version。需通过PATH调用时仅加入实际bin目录;路径中空格用引号。新建hello.dart保存完整程序:
void main() {
print('Hello, Dart!');
}dart run hello.dart
dart analyze hello.dart
dart format hello.dart预期Hello, Dart!。run执行、analyze静态检查、format格式化,三者检查不同问题。main是入口,void无返回结果,分号结束语句。导入标准库写 import 'dart:io';;包依赖则在pubspec.yaml里声明,使用pub get生成锁文件,不能靠import下载。
类型推断与空安全
完整hello.dart可替换为:
void main() {
var completed = 2;
const goal = 3;
String? nickname;
final title = nickname ?? 'reader';
if (completed < goal) print('$title: $completed/$goal');
nickname = 'Dart';
print(nickname.length);
}输出reader: 2/3与4。var推断固定类型;final限制再次赋值,const要求编译时常量。String不接受null,String?允许;控制流证明非空后才可直接访问属性。! 是运行时非空断言,不是“把null转换为字符串”;仅在不变量确实成立时使用。late 延迟初始化,先读后赋会抛 LateInitializationError,不能当默认空值。dynamic放弃很多静态检查,Object?仍要求收窄后操作,接收JSON时应选后者并验证结构。
int、double、bool、String是对象类型;if条件必须bool,0不是false。~/ 为截断除法,/返回double。Dart VM与JavaScript目标的整数表示和精度边界不同,本项目最大9990000落在两者精确范围,但实际CLI验证是VM平台。字符串length按UTF-16代码单元,不是用户看到的字形数。
集合、分支、函数与延迟迭代
下面完整示例连接List、for-in和命名参数:
int total(Iterable<int> values, {int minimum = 0}) {
var result = 0;
for (final value in values) {
if (value >= minimum) result += value;
}
return result;
}
void main() {
final values = <int>[80, 90, 100];
values.add(70);
print(total(values, minimum: 90));
final counts = <String, int>{'dart': 1};
counts.update('dart', (old) => old + 1);
print(counts['dart']);
}输出190、2。List保存顺序与重复,Set保存不同值,Map保存键值。final values仍允许add;List.unmodifiable限制容器写入,元素自身可变时仍是浅层保证。Map索引返回V?,缺键与存null可能需要containsKey区分。
函数是值,参数可接收bool Function(int)等回调;required命名参数在调用时必须提供。where/map创建惰性的Iterable,重复length或遍历会重新执行回调;需要快照时toList。迭代期间改变源集合可能抛ConcurrentModificationError。这是单线程里也会出现的结构性变化错误,不等于一定有两个CPU线程。集合泛型可协变,但经宽化引用写入错误元素仍可能在运行期失败;不要把泛型转换当容器复制。
类、值语义与 sealed 模式
类以构造器初始化状态,下面完整程序也演示Dart3的sealed与穷尽switch:
sealed class Result {}
final class Success extends Result {
final int value;
Success(this.value);
}
final class Failure extends Result {
final String reason;
Failure(this.reason);
}
String describe(Result result) => switch (result) {
Success(value: final n) => 'value=$n',
Failure(reason: final text) => 'error=$text',
};
void main() { print(describe(Success(3))); }输出value=3。sealed限制外部库直接扩展/实现,使分析器能检查子类型覆盖;pattern按结构提取字段,不是字符串比较。对象默认==通常比较身份,值对象若重写==必须同时实现hashCode,并避免修改影响哈希键的状态。record适合有限字段的结构化值,如(name: 'dart', minutes: 30);类适合承载行为与不变量。mixin复用行为,extends建立单一继承,implements要求自己实现接口成员;不要把所有复用都写成继承树。
练习与参考解法
练习:去掉String?中的问号并给变量赋null,确认analyze报错;解释final列表为何可add;给Result增加Pending并观察switch缺分支;用where两次消费统计回调次数。
参考解法
非空类型禁止null;final只约束变量绑定。增加sealed直接子类后应补穷尽分支,随意加默认分支会隐藏未来新增状态。每次消费惰性Iterable都重新执行规则;toList可固定当时元素序列。完整集合实验见下一页及项目lab.dart,不以手算代替运行。
继续阅读
- 开发实践
- 开发实践
- 知识专题