Skip to content

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保存完整程序:

dart
void main() {
  print('Hello, Dart!');
}
shell
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可替换为:

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和命名参数:

dart
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:

dart
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,不以手算代替运行。

依据:语言概览、空安全、中文变量说明、模式、类修饰符。

继续阅读