前言

如果你正在寻找一个跨平台的原生应用开发方案,你可能已经了解过Flutter,ReactNative等。 Dart是Flutter应用的开发语言,它专为客户端优化,支持自动内存管理和面向对象编程。 这里的跨平台指的是一套代码,可发布成 iOS、Android、macOS、Linux、Windows及Web应用。 原生应用是指代码最终被编译为平台相关的机器码;对web而言,对应的是javascript+WebAssembly。

如何你初学编程,一下子听到这么多概念,也许还没开始写一行代码就已经开始糊涂了,没有关系。 要知道,计算机编程就像英语一样,要掌握它,除了需要兴趣之外,还需要良好的思维模式以及坚持不懈的练习。 希望你在学习本课程时,打开代码编辑器,跟着一起练习,为以后编程打下坚实的基础。在你掌握了一门编程语言之后,再去学其他编程语言就容易多了。

读者范围

本课程将详细介绍Dart 3编程语言 ,从基本语法与控制结构到异步编程,从面向对象编程到编码准则;课程内容由浅入深,既有理论讲解又有代码示例 。相信无论你是编程新手还是具有一定经验的程序员,你都可以从本课程中获益。

课程内容简介

第1章,从0开始搭建开发环境,学习 dart变量与数据类型及操作符。

第2章,控制流、函数及异常处理。

第3章,Dart 强大的模式匹配功能。

第4-5章,Dart 的 面向对象编程(OOP),第4章学习用于封装信息的class(类)的构造,第5章学习类的继承及扩展。

第6章,集合与范型。

第7章,异步编程。

第8章,单元测试。

附录包括dart命令行工具、编码准则、Dart命令行及服务端编程,以及常用package介绍。

本课程中的代码可免费下载,使用的dart版本为3.8+。

最后,希望本课程能对你的dart学习之旅助一臂之力,祝你学习愉快。

Hello Dart: 搭建开发环境

搭建开发环境是我们编程的起点,新手有可能卡在这一步。如果你是经验丰富的程序员,可以直接跳过。搭建开发环境需要考虑的两个主要问题是开发机的操作系统与网络环境。

我们的开发电脑的操作系统可能是下列之一:

  • macOS Sonoma (14), Ventura (13), Monterey (12)
  • Windows 10,11
  • ChromeOS
  • Linux
    • Debian stable
    • Ubuntu LTS
    • 其他,如Manjaro,CentOS Stream,Linux Mint

这里推荐的操作系统是 MacOS 或 Linux ,因为它们具有更丰富的开发工具及各种命令,极大的方便了软件开发。如果你使用的计算机网络位于中国,由于其网络特殊性,可能需要使用相关资源(各软件包/工具/在线服务)的镜像。

Dart.dev

如果你只是想快速搭建Dart或Flutter开发环境,可以跳过本段。

  1. 首先我们去Dart官网 https://dart.dev ,点击右上角的 Get Dart

注意页面上的提示,如果你已经安装或打算安装Flutter SDK,可以跳过该向导,因为Flutter SDK包含了Dart SDK。 我们学习Dart最主要的目的就是使用Flutter框架,编写跨平台App。因此,我们这里直接安装Flutter SDK。

  1. 点击 install the Flutter SDK, 我们来到了Flutter SDK 安装向导页。

操作系统及网络环境的不同,会导致搭建Dart开发环境的方式也有所不同。但是这些方式有着类似的步骤。本文选取 中国网络环境下的Linux系统 对这些步骤进行说明。

在中国网络环境下使用 Flutter

有必要解释一下镜像或镜像站点。 Flutter SDK (软件开发工具包)或 Dart SDK 以及 dart package(软件包)(发布于pub.dev网站),是我们开发Flutter或Dart程序必备的,但由于中国网络的特殊性,导致许多中国开发者无法直接(从官方服务器,由谷歌维护)获取这些SDK或package(或者下载速度极慢),所以我们就去官方服务器的镜像站点去下载。这些位于中国的镜像站点会定时与官方服务器同步,略有延迟。

  1. 在电脑上打开 terminal (终端命令行界面),以上海交通大学*nix用户组镜像为例,配置镜像
export PUB_HOSTED_URL=https://mirror.sjtu.edu.cn/dart-pub
export FLUTTER_STORAGE_BASE_URL=https://mirror.sjtu.edu.cn
  1. 从镜像站点下载 Flutter 压缩包

以 Flutter 3.32.2 为例, 使用如下命令下载

wget -c -O flutter_linux_3.32.2-stable.tar.xz  $FLUTTER_STORAGE_BASE_URL/flutter_infra_release/releases/stable/linux/flutter_linux_3.32.2-stable.tar.xz

注: macOS与windows对应的下载地址分别为

$FLUTTER_STORAGE_BASE_URL/flutter_infra_release/releases/stable/macos/flutter_macos_3.32.2-stable.zip

$FLUTTER_STORAGE_BASE_URL/flutter_infra_release/releases/stable/windows/flutter_windows_3.32.2-stable.zip
  1. 创建一个文件夹,用于安装Flutter, 例如 ~/dev
mkdir ~/dev
cd ~/dev
  1. 将 flutter_linux_3.32.2-stable.tar.xz 拷贝到当前目录,然后解压
tar -xf flutter_linux_3.32.2-stable.tar.xz
  1. 将 Flutter 添加到PATH 环境变量中
export PATH="$PWD/flutter/bin:$PATH"
  1. 运行 flutter doctor 来验证安装
$ flutter doctor

Flutter assets will be downloaded from https://mirror.sjtu.edu.cn. Make sure you trust this
source!
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.32.2, on Manjaro Linux 6.6.63-1-MANJARO, locale en_US.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
[✓] Android Studio (version 2023.2)
[✓] IntelliJ IDEA Community Edition (version 2024.3)
[✓] VS Code (version unknown)
    ✗ Unable to determine VS Code version.
[✓] Connected device (2 available)
[✓] Network resources

• No issues found!

查看 Flutter 及 Dart 版本

$ flutter --version                                                        ✔ 
Flutter 3.32.2 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 8defaa71a7 (4 days ago) • 2025-06-04 11:02:51 -0700
Engine • revision 1091508939 (9 days ago) • 2025-05-30 12:17:36 -0700
Tools • Dart 3.8.1 • DevTools 2.45.1

$ dart --version                                                           ✔ 
Dart SDK version: 3.8.1 (stable) (Wed May 28 00:47:25 2025 -0700) on "linux_x64"

配置环境变量

在上述过程过程中, export 设置的环境变量,仅对当前terminal有效;要永久设置这些值, 将这三条 export 指令添加到首选 shell 使用的 *rc 或 *profile 文件中,例如

cat <<EOF >> ~/.zshrc
export PUB_HOSTED_URL=https://mirror.sjtu.edu.cn/dart-pub
export FLUTTER_STORAGE_BASE_URL=https://mirror.sjtu.edu.cn
export PATH="\$HOME/dev/flutter/bin:\$PATH"
EOF

使用 tail 命令查看刚才写入的内容

tail -3 ~/.zshrc

Hello Dart

下面我们来创建第一个Dart.

$ dart create hellodart         
Creating hellodart using template console...

  .gitignore
  analysis_options.yaml
  CHANGELOG.md
  pubspec.yaml
  README.md
  bin/hellodart.dart
  lib/hellodart.dart
  test/hellodart_test.dart

执行完dart create命令之后, 我们得到了一个hello world project.

下面我们来运行一下

$ dart run                        ✔ 
Building package executable... 
Built hellodart:hellodart.
Hello world: 42!

编译为可执行程序

$ dart compile exe bin/hellodart.dart

$ ./bin/hellodart.exe             ✔ 
Hello world: 42!

dart 是一个强大的命令,关于它的基本使用,本课程将其放在附录中说明。

安装IDE (集成开发环境,代码编辑器)

有几款优秀且跨平台的IDE可供选用:

下载安装好IDE后,再打开IDE安装Dart扩展。

另外,dartpad.dev是一个在线编辑器,可用于临时测试一些代码。

如果你看到了这里,并且也跟着一起练习,那么恭喜你,你已经开启了Dart编程之旅。

附: 社区镜像站点

# 上海交通大学 *nix 用户组
export PUB_HOSTED_URL=https://mirror.sjtu.edu.cn/dart-pub;
export FLUTTER_STORAGE_BASE_URL=https://mirror.sjtu.edu.cn

# 清华大学 TUNA 协会
export PUB_HOSTED_URL=https://mirrors.tuna.tsinghua.edu.cn/dart-pub
export FLUTTER_STORAGE_BASE_URL=https://mirrors.tuna.tsinghua.edu.cn/flutter

# 中国 Flutter 社区 (CFUG)
export PUB_HOSTED_URL=https://pub.flutter-io.cn
export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn

参考资料

变量与Null safety

什么是变量

计算机编程语言里的变量(Variable)与数学公式里的变量类似。请看下面这段Dart代码:

// ch0102-1
void main() {
  var x = 3;  // 1
  var (y, z) = (4, 5); // 2
  print('x = $x, y = $y, z = $z'); // 3 Output: x = 3, y = 4, z = 5
  print('x + y + z = ${x + y + z}'); // 4 Output: x + y + z = 12
}

这段简短的代码只有一个函数,那就是main,整个程序的入口。代码中的 // 是注释,是为了帮助人们阅读代码,Dart编译器会忽略它。

  1. 使用var关键字,声明变量x,并给x赋值为 3,Dart编译器自动推断其类型为 int (integer, 整数);
  2. 同时声明两个变量xy,分别赋值为4和5,这里使用了record的模式匹配(第3章内容,这里看不懂也没关系);
  3. 分别将xyz的值打印(显示)到控制台(标准输出设备 stdout);
  4. 计算 x + y + z 并打印。

第1句也可以写成 int x = 3; 也等同于

int x;
x = 3;

第2句也可以写成

  var y= 4, z = 5;

第3、4两句中的 $x, $y, $z 以及 ${x + y + z} 为 字符串插值,非常直观和方便。

var x 声明了一个变量,它的标识符为x; 标识符,通俗地说就是给变量、函数、类、方法等起个名字,方便使用,就像我们给宠物取个名,方便呼唤。Dart语言的标识符可以是下列字符的组合:

  • 字母
  • 数字
  • _$

但不能以数字开头。这些都是合法的标识符: practiceMakesPerfect, _imPrivate, _$GeneratedCode, $happy365

var修饰的变量 ,可以后续更改,比如

// ch0102-3
void main() {
  var x = 3;
  x = 30;
  print('x = $x'); // Output: x = 30
}

变量除了可以表示数字,还可以表示文本(字符串)等. 例如

// ch0102-4
void main(){
  var name = 'Dart';
  print('Hello, $name!'); // Output: Hello, Dart!
}

这里的数字、字符串等,就是所谓的数据类型(本章第4节内容,这里不再展开)。

优先使用 var 声明变量

对于 var x = 3; 也可以写成 int x = 3,你可能有个疑问,哪种情况下用哪种方式呢?官方的建议是,多数情况下,我们应该使用 var (而非实际类型) 声明变量,这样做除了可以使代码更短之外,还能提高代码的可读性。

如果我们仅仅声明变量(不赋值),应该写出实际的类型,因为这时候Dart无法推断出变量的数据类型,如

int x;

如果我们这样写

var x;

Dart编译器就认为 x 的类型为 dynamic ,这种类型可以表示任何数据,通常情况下我们应该避免这样做,因为Dart的静态类型检查对dynamic无效(可能暗藏bug)。

使用 late 延迟初始化

有时候我们希望延迟变量的初始化(特别在Flutter的StatefulWidget中),这时就用到 late 关键字, 例如

// ch0102-5
void main() {
  late String action = 'go camping';
  var isReady = true; // it may take some time to prepare
  if (isReady) {
    print("Let's $action!"); // Output: Let's go camping!
  }
}

这里先了解下,第4章会有更多关于 late 的讨论。

Null safety

NNBD

从Dart 2.10 开始,变量默认非 null(non-nullable by defaullt, NNBD);null 表示空值。 比如下面这段代码无法编译

// ch0102-6
void main() {
  int x;
  print('x = $x'); 

  // Error: Non-nullable variable 'x' must be assigned before it can be used.
  // print('x = $x'); 
  //             ^
}

当我们尝试编译上面这段代码时,出现编译错误,告诉我们在使用变量x之前必须初始化它。

如果要使用null, 可以像下面这样:

// ch0102-7
void main() {
  int? x;
  print('x = $x'); // Output: x = null
}

这里的 int? x 表示变量x 的初始值为null, x 是一个nullable变量。

注: 熟悉Java的朋友想必对NullPointerException (NPE) 并不陌生,这个Exception是一个运行时异常;为了提高代码的健壮性,经常要对方法的入参进行非空检查,否则就很容易遭遇NPE(这个问题有时难以排查,尤其在大型软件项目里)。例如下面这段Java代码

void doSomething(String str) {
  if (str == null){
    throw new IllegalArgumentException("doSomething: str cannot be null");
  }
  // Do something
}

我想这便是Dart NNBD的原因。

访问nullable变量

有几个nullable变量相关的访问/赋值,有必要介绍一下,你以后再看到下面这些符号就不会感到陌生了。

????=!

// ch0102-8
void main() {
  int? x;
  var y = x ?? 10; // 1
  var z = y ?? 20; // 1a
  print('y = $y, z = $z'); // Output: y = 10, z = 10

  int? y2;
  y ??= 100; // 2
  y2 ??= 100; // 2a
  print('y = $y, y2 = $y2'); // Output: y = 10, y2 = 100

  var a = y!; // 3
  print('a = $a'); // Output: a = 10

  var b = x!; // 3a
  print('b = $b');
  // Unhandled exception:
  // Null check operator used on a null value
}
  1. 如果 xnully赋值为10, 否则将x的值赋给y
  2. 如果 ynully赋值为100,否则啥也不做;
  3. 断言y一定非空(null),并将y的值赋给a;如果ynull,在程序运行时会出现一个Null check异常(见3a)。

请对比 1和1a, 2和2a,以及3和3a的结果。

?.?[]

// ch0102-9
void main() {
  var abc = 'ABCD';
  String? def;

  var first = abc?[0]; // 1
  var second = def?[0]; // 1a
  print('first = $first, second = $second'); // Output: first = A, second = null

  var len1 = abc?.length; // 2
  var len2 = def?.length; // 2a
  print('len1 = $len1, len2 = $len2'); // Output: len1 = 4, len2 = null

  var len3 = def?.length ?? 0; // 3
  print('len3 = $len3'); // Output: len3 = 0
}
  1. 如果abcnullfirstnull, 否则将abc[0]的值赋给first
  2. 如果abcnulllen1null, 否则将abc.length的值赋给len1
  3. 这是一个综合的例子,同时使用了 ?.?? 符号。

注:本例中的 abc[0] 返回第0个(下标从0开始)unicode字符,abc.length返回abc的长度。

...?

// ch0102-a
void main() {
  var arr = [1, 2, 3];
  var arr2 = [...arr, 4, 5]; // 1
  print('arr2 = $arr2'); // Output: arr2 = [1, 2, 3, 4, 5]

  var arr3 = [...?arr, 6, 7]; // 1a
  print('arr3 = $arr3'); // Output: arr3 = [1, 2, 3, 6, 7]

  List<int>? arr4;
  var arr5 = [...?arr4, 8, 9]; // 2
  print('arr5 = $arr5'); // Output: arr5 = [8, 9]

  List<int>? arr6 = []; // 3
  var arr7 = [...arr6, -1, -2]; // 3a
  print('arr7 = $arr7'); // Output:  arr7 = [-1, -2]
}
  1. arr是一个列表(数组), ...arr将展开arr, 此行代码将arr以及 4, 5 一起组成一个新的列表,并将其赋值给arr2;
  2. 本行代码 与 1 类似, 区别在于,这里必须使用 ...?arr4 执行展开操作,否则无法编译通过;
  3. arr6arr4一样,是一个nullable变量,但其初始值为一个空的列表(不是null),就可以用...对其进行展开,可见Dart编译器非常智能。

参考资料

finalconst 及wildcard

final

声明为final的变量,只能被赋值一次。 如果尝试修改已经赋值过的final变量,会引起编译错误。

// ch0103_1.dart
void main() {
  final myPet = 'Doggy'; // 1
  myPet = 'Kitty'; // 2
  // Error: Can't assign to the final variable 'myPet'.
  // myPet = 'Kitty'; // 2
  // ^^^^^
}
  1. 声明一个final变量myPet, 并为其赋值;
  2. 尝试修改myPet的值,引起编译错误, 告诉我们不能给final 变量 myPet 赋值。

late final

late final 这两个关键字经常一起出现,这里构建了一个简单的例子:

// ch0103_2.dart
void main() {
  final width = 3.0; // 1
  final height = 4.0; // 1a
  late final double perimeter; // 2
  // ... (Do somehting else)
  perimeter = 2 * (width + height); // 3
  print('width: $width, height: $height, perimeter: $perimeter'); // 4
  // Output: width: 3.0, height: 4.0, perimeter: 14.0
}
  1. 声明final变量 widthheight ,并分别赋值为3.0与4.0,Dart自动推断其数据类型为double (浮点数);
  2. 使用 late final 声明一个变量perimeter,在这里它表示一个矩形的周长,同时显示指定这个变量的数据类型为double,所以这里有3个关键字late final double,它们一起修饰变量perimeter;
  3. 计算变量 perimeter的值,公式为(长+宽)×2;
  4. 使用字符串插值,打印相关变量。

这个例子似乎看不出 late final 有什么特别之处,这里只是让你熟悉一下,在面向对象编程(OOP)的章节还会继续讨论它。

final 的兄弟 const

const关键字用于:

  • 声明一个常量,常量具有编译时(compile-time)不可变性,这里的编译时是与运行时(runtime)相对应的;
  • 创建常量值(constant values);
  • 定义const构造器,该构造器用于创建常量值。

后面两点与OOP有关,这里提前了解下。

声明一个常量

试图修改常量会引起编译错误,例如

// ch0103_3.dart
void main(){
  const score = 50; // 1
  score = 80; // 2
  // Error: Can't assign to the const variable 'score'.
  // score = 80; // 2
  // ^^^^^
}

创建常量值

任何变量都可以有常量值。

// ch0103_4.dart
void main() {
  final luckyNumbers = [5, 6]; // 1
  luckyNumbers.add(8); // 2
  print('luckyNumbers: $luckyNumbers');
  // Output: luckyNumbers: [5, 6, 8]

  const myFriends = ['Alice', 'Bob']; // 3
  myFriends.add('Charlie'); // 4
  // Unhandled exception:
  // Unsupported operation: Cannot add to an unmodifiable list;
}
  1. 声明一个final 列表 luckyNumbers并对其初始化,包含5、6两个数字;
  2. luckyNumbers添加数字8,然后打印出来,luckyNumbers 现在包含了5、6、8三个数字; 这说明了虽然final修饰的luckyNumbers本身(引用)不可更改,但它引用的列表是可以改变的;
  3. 声明一个const列表myFriends,初始值包含"Alice和"Bob"两个字符串;
  4. myFriends添加一个新元素"Charlie",这引起了一个运行时异常,告诉我们不能向不可变列表添加新的元素,可见constfinal具有更强的不可变性。

定义const构造器

构造器是OOP里的概念,const构造用于创建常量值。请看下面这个示例:

// ch0103_5.dart
class Pet { // 1
  final String name; // 1a
  const Pet(this.name); // 2
}

void main() {
  var myPet = Pet('Doggy'); // 3
  myPet = Pet('Little Doggy'); // 3a
  const yourPet = Pet('Kitty'); // 4

  print('myPet: ${myPet.name}, yourPet: ${yourPet.name}');
  // Output: myPet: Little Doggy, yourPet: Kitty
}
  1. class关键字用于声明一个名叫Pet的类 ,类封装了数据和行为(方法)(第4-5章专门讨论OOP); class Pet 有一个final成员变量叫做 name ;
  2. 这是Pet的const构造器;
  3. 通过调用Pet的构造器,得到一个Pet的实例(对象)myPet,随后又将myPet指向另一个Pet对象(3a);
  4. 通过调用Pet的const构造器,创建Pet的常量对象 yourPet,这行代码等于
  const yourPet = const Pet('Kitty'); // 4

在常量上下文中,Pet('Kitty')之前省略了const关键字。

常量对象

const Pet('Kitty') Pet('Kitty')是不一样的,前者创建(实例化)了一个常量对象,而后者创建的是非常量对象(除非是在常量上下文中)。

// ch0103_6.dart
void main() {
  const pet1 = Pet('Rabbit'); // 1
  var pet2 = const Pet('Rabbit'); // 2
  final pet3 = Pet('Rabbit'); // 3

  print('${pet1 == pet2}'); // Output: true
  print('${pet1 == pet3}'); // Ouptput: false
}

class Pet {
  final String name;
  const Pet(this.name); 
}

这里的 == 操作符,用于测试左右两边的操作数是否相等。

Wildcard(_

自 Dart 3.7 开始,以 _ 命名 的变量或参数,是一个通配符(wildcard ),它的值会被丢弃。同一个命名空间里多次声明 _ 不会冲突。例如

//ch0103_7.dart
void main() {
  var _ = 10; // 1
  var _ = 'Hello'; // 2
  print('Hello, Dart! $_'); // 3
  // Error: Undefined name '_'.
  // print('Hello, Dart! $_');
}

上面这段代码比较简单,不过多解释。

通配符出现的地方:

  • 变量声明, 如 var _ = 1;
  • for 循环变量 如 for (var _ in list) {}
  • catch 语句参数, 如
try {
  throw 'something bad';
} catch (_) {
  print('oops');
}
  • 函数参数,如 list.where((_) => true);
  • 范型类型及函数类型参数, 如
class T<_> {}
void genericFunction<_>() {}

for循环、函数、catch语句、范型等概念将在后续章节陆续介绍,这里关于通配符有个印象就行。

更多的示例,可查阅官方文档

参考资料

数据类型

通常计算机编程语言里的数据类型可以分为基础数据类型与复合数据类型两大类。Dart的基础数据类型包括:

复合数据类型是基础数据类型的组合。Dart的复合数据类型包括:

Dart还有如下特殊的数据类型:

第2章将讨论函数,第4章将讨论类,第6章将讨论列表(List)、集合(Set)、映射(Map)。

数字

Dart的数字包括64-bit整数 int 和 64-bit浮点数 double。 int的取值范围为 -263 至 to 263 -1。 double是双精度浮点数,遵循 IEEE 754 标准。

Dart是一门OOP编程语言,int和double是num的子类,因此它们有着一些共同的方法,例如:

  • parse(string)
  • abs()
  • ceil()
  • toString()

下面的代码声明了一个整数radius 和一个浮点数 pi

// ch0104_1.dart
void main() {
  int radius = 5;
  double pi = 3.1416;
  print('area: ${radius * radius * pi}'); 
  // Output: area=78.53999999999999
}

除了使用十进制书写整数外,还可以用十六进制。可以使用科学计数法书写浮点数。例如:

// ch0104_2.dart
void main() {
  int radius = 0x10;
  double pi = 0.31416e1;
  print('radius=$radius area=${radius * radius * pi}');
  // Output: radius=16 area=804.2496
}

但是Dart并不直接支持书写八进制数。你可以用 octal 包来实现八进制的书写。

// ch0104_3.dart
// dart pub add octal
import 'package:octal/octal.dart'; 

void main() {
  int decimalValue = octal(123); // 83 in decimal
  print(decimalValue); // Output: 83
  print(decimalValue.toRadixString(8)); // Output: 123
}

字符串(String)

Dart中的String是一个UTF-16值的有序序列,写在一对单引号或双引号里。一个非常nice的功能是字符串插值,即在字符串里使用 ${expr}

// ch0104_4.dart
void main() {
  var str = "It's a beautiful day!";
  var str2 = 'It\'s a beautiful day!'; 
  print(str == str2); // Output: true

  var num = 5;
  print('There are $num apples.'); // Output: There are 5 apples.
  print('There are ${num + 2} oranges.'); // Output: There are 7 oranges.
}

上例中str2 写在一对单引号中,该行中的 \ 为转义符,\'表示一个单引号。

两个字符串写在一起,中间可以使用一个+符号(但通常予以省略), 就表示将这两个字符串顺序拼接在一起,形成一个新的字符串。因此对于多行文本,可以这样写:

  // ch0104_5.dart
  var str =
      'A: Nice to meet you!\n'
      'B: Nice to meet you, too!';

这里的\n是换行符。书写多行文本更方便的做法是将文本写在一对三引号('''""")中,例如:

// ch0104_6.dart
void main() {
  var minAge = 18;
  var query =
      '''
SELECT id, name, age
FROM users
WHERE age >= $minAge
''';
  print(query);
}

布尔(bool)

布尔类型只有2个值,truefalse,且它们都是编译时常量。

// ch0104_7.dart
void main() {
  var isGreater = 43 > 34;
  print('isGreater: $isGreater'); // Output: isGreater: true
  print('2 < -3: ${2 < -3}'); // Output: 2 < -3: false
}

符文(Rune)

符文代表unicode文字系统里的一个符号。unicode为世界上每一个文字或符号分配了一个数字,称之为code point。unicode 最多可支持 1,114,112 个code point,通过 17 个 16 位平面实现,每个平面可支持 65,536 个不同的code point。 unicode常见的编码方式(将code point映射为二进制数)有UTF-8、UTF-16和UTF-32。 Dart采用UTF-16编码,每个code unit为16位(2个字节),每个符文占用1个(0-65,535 code point)或2个code unit(65,536及以上)。对字符编码感兴趣的同学,请继续去了解下ASCII、UTF-8、UTF-16和UTF-32、GBK等。

Dart的String(字符串)本质上是code unit的序列。

// ch0104_8.dart
void main() {
  var s = "笑\u7b11lol😆\u{1f606}"; // 1
  print(s); // 2 Output: 笑笑lol😆😆
  print('len: ${s.length}'); // 3 Output: len: 9

  print('code units: ${s.codeUnits}'); // 4
  // Output: code units: [31505, 31505, 108, 111, 108, 55357, 56838, 55357, 56838]

  print('runes: ${s.runes}'); // 5
  // Output: runes: (31505, 31505, 108, 111, 108, 128518, 128518)

  print(31505.toRadixString(16)); // 6 Output: 7b11
  print(128518.toRadixString(16)); // 7 Output: 1f606
}
  1. 声明变量s,注意这里\u7b11就等于这个字符,\uHHHH表示\u后面是code point的十六进制表示HHHH;如果该十六进制数不是4位,就必须位于一堆花括号{}中,如\u{1f606}就等于😆这个emoji符号;
  2. 打印 s,从输出结果可以看出 s包含了7个符文(字符);
  3. 打印s的长度,结果为9, 可见是code unit的个数,下面一行代码印证了这一点;
  4. 代码s的code point 序列;
  5. 代码s的符文(字符)序列;
  6. 第6-7行代码分别显示31505和128518对应的十六进制表示,以方便分析。

请仔细查看code units 与 runes 并进行对比,这将帮助你理解Rune(符文)和String(字符串)。

使用 characters

下面这个示例同时演示了charactersStringBuffer类的使用。charactersString类添加了一个扩展方法(第5章内容)get charactersStringBuffer是拼接字符串的高效方式。代码中使用的 for-each 循环将在下一章节介绍,这里先了解下。

//ch0104_9.dart
//dart pub add characters
import 'package:characters/characters.dart';

void main() {
  const s = "笑一笑十年少😆"; // 1
  final sb = StringBuffer(); // 2 
  for (var ch in s.characters) { // 3
    sb.write(ch);
    sb.write(' ');
  }
  print(sb);
}

记录(Record)

Dart 3.0 引入了记录这一数据类型。记录是匿名的、不可变的聚合类型,是异质数据的集合。

// ch0104_a.dart
void main() {
  var record = ('class 1', name: 'Alice', id: 1, 'good student'); // 1
  print(record); // 2 Output: (class 1, good student, id: 1, name: Alice)
  print(record.$1); // 3 Output: class 1
  print(record.$2); // Output: good student
  print(record.id); // 4 Ouput: 1
  print(record.name); // Output: Alice

  // record.id = 2; // 5
  // Error: The setter 'id' isn't defined for the class '(String, String, {int id, String name})'.
  // Try correcting the name to the name of an existing setter, or defining a setter or field named 'id'.
  //   record.id = 2;
  //          ^^
}
  1. 声明一个变量record并赋值,如你所见,记录有位置字段和命名字段(如这里的idname);
  2. 打印 record,注意观察字段的显示顺序;
  3. 打印 record 的第1个位置字段record.$1 ;
  4. 打印 record 的命名字段 record.id;
  5. 修改 recordid,遭遇编译错误,这说明了记录是不可变的,同时从错误信息中可以看出,记录是一种特殊的匿名类。

记录类型

记录(Record)没有明确的类型声明,因为它是匿名的,但记录是有形状(Shaple)的。本例中 record的形状为(String, String, {int id, String name});在一对花括号({})里的int id, String name 为命名字段 idname,其类型分别为intString;没有花括号包围的便是位置字段。

// ch01004_b.dart
void main() {
  (int x, int y, int z) point = (1, 2, 3);
  var color = (1, 2, 3);
  print(point); // Output: (1, 2, 3)
  print(point == color); // Output: true

  ({int x, int y, int z}) point2 = (x: 1, y: 2, z: 3);
  var color2 = (r: 1, g: 2, b: 3);
  print(point2); // Output: (x: 1, y: 2, z: 3)
  print(color2); // Output: (b: 3, g: 2, r: 1)
  print(point2 == color2); // Output: false
}

pointcolor具有相同的形状和值,因此他们是相等的,而point2color2的形状不同,自然就不相等。

解构与变量交换

记录的解构与变量交换非常实用。

// ch0104_c.dart
void main() {
  var (x, y) = (1, 2); // 1
  print('x=$x y=$y'); // Output: x=1 y=2

  (y, x) = (x, y); // 2
  print('x=$x y=$y'); // Output: x=2 y=1

  var (:name, :age) = (name: 'Bob', age: 20); // 3
  print('name=$name b=$age'); // Output: name=Bob b=20

  var (x: a, y: b) = (x: 3, y: 4); // 4
  print('a=$a b=$b'); // Output: a=3 b=4
  
  var (who, _, :nickname, fav: _) = (
    'Robert',
    'Naughty boy',
    nickname: 'Bob',
    fav: 'play the guitar',
  ); // 5
  print('who=$who nickname=$nickname'); // Output: who=Robert nickname=Bob
}
  1. 解构记录(1,2)至局部变量xy,这里使用了模式匹配(第3章内容);
  2. 交换xy的值;
  3. 使用冒号(:)语法,解构命名记录 (name: 'Bob', age: 20)至局部变量nameage ;
  4. 解构命名记录 (x: 3, y: 4)至局部变量ab ;
  5. 这是一个综合例子,请运用已学知识自行分析。

枚举(Enum)

枚举是一种用于表示固定数量的常量值的特殊类。使用关键字enum声明个一个枚举。

// ch0104_d.dart
enum Color { red, green, blue }

void main() {
  print(Color.red); // Output: Color.red
  print(Color.blue.name); // Output: blue
  print(Color.blue.index); // Output: 2
}

每个枚举值都有个与之关联的数字,称之为index,该数字从0开始。枚举是一种特殊的类,因此它也可以定义字段(实例变量)和方法,只不过有一些限制,例如枚举的实例变量必须声明为final。第4章会更详细地讨论枚举。

符号(Symbol)

符号对象表示 Dart 程序中声明的操作符或标识符。一般极少用到,在此了解即可。

// ch0104_e.dart
void main() {
  print(#foo); // Output: Symbol("foo")
}

typedef

typedef关键字为数据类型取一个别名。

//ch0104_f.dart
typedef Point = ({int x, int y});
typedef VoidCallback = void Function();
typedef IntList = List<int>;

void main() {
  Point p = (x: 1, y: 2);
  IntList nums = [3, 4, 5];
}

小测验

以下程序的输出是什么?

// ch01004_quiz.dart
void main() {
  var point = (x: 1, y: 2, z: 3);
  ({num x, int y, int z}) point2 = (x: 1, y: 2, z: 3);
  print(point == point2);
}

参考资料

操作符

注释

if 与 switch 语句

for 与 while 循环

断言

函数

arrow语法与 typedef

位置参数与命名参数

匿名函数、嵌套函数

使用try...on...catch捕捉异常

模式简介及使用场景

模式类型

应用案例

封装与可见性

构造函数、this关键字与初始化列表

命名构造器

重定向构造器

factory构造器

使用late延迟初始化

const构造器与const变量

getter 与 setter 方法

操作符重载

callable 类

枚举

注解

super构造器

抽象类

接口与implements关键字

使用Mixin创建可复用代码

使用extension扩展已有的类

范型

列表 List

集合 Set

使用 Map 创建"键值对"集合

hashCode 与equals (==操作符)

Transform方法

Future、async 与 await

Iterable 、Stream 、sync*、 async* 与 yield

Stream命名构造器

使用listen()/await for 订阅 Stream

Isolate介绍

test/group

测试异步代码

使用mockito模拟外部依赖

dart命令行工具

SOLID编码准则

编写Dart命令行程序

编写Dart服务端编程

Dart 常用package介绍