0%

Dart

1 Dart简介与安装

Dart语言是google开发的语言,目前对外主要用于flutter开发,Google内部使用Dart开发大型应用。它是一个纯面向对象的语言,其语法比较灵活,也比较简单。

Ubuntu安装Dart环境可使用apt-get命令安装:

1
2
3
4
5
6
7
8
9
//用apt-get安装时,如果是第一次安装,需要走1、2、3步
1.sudo apt-get update && sudo apt-get install apt-transport-https
2、wget -qO- https://dl-ssl.google.com/linux/linux_signing_key.pub \
| sudo gpg --dearmor -o /usr/share/keyrings/dart.gpg
3.echo 'deb [signed-by=/usr/share/keyrings/dart.gpg arch=amd64] https://storage.googleapis.com/download.dartlang.org/linux/debian stable main' \
| sudo tee /etc/apt/sources.list.d/dart_stable.list

//如果不是第一次安装,直接第4步:
4.sudo apt-get update && sudo apt-get install dart

Mac安装Dart环境可使用

1
2
3
4
5
6
7
1.<Install Homebrew if needed.>
2.brew tap dart-lang/dart
3.brew install dart

//安装完成后确认你的Path已经配置了Homebrew 的bin目录,这样下面的命令才成功
brew info dart //dart的版本
//

2 Dart语言

由于Dart语言与go、python具有较大的相似性,因此本节只着重介绍不同点。

2.1 关键字

1. 保留关键字(33个)

  • 流程控ifelseswitchcasedefaultbreakcontinueforwhiledoreturntrycatchfinallythrowrethrow
  • 类型与类classenumextendsimplementsabstractsuperthisnewconstfinalstaticvoid、`var``
  • 逻辑truefalsenull
  • 其它assertwithinisastypedef

2. 内置标识符(17个)

  • 异步与库管asyncawaitsync*async*yieldyield*
  • 库与类libraryimportexportpartexternalfactoryoperatormixin
  • 泛型与反covariantdynamicgetset

2.2 类型

dart语言的类型含有:

类型 示例 补充解释
int int x=1;或者var x=3 最长不超过8字节,原生平台可表示\[-2^{63}~2^{63}-1\]
double double y=10.1或者var y=1.42e5 8字节
String var s2 = "Double quotes work just as well."; 支持+==运算符
bool bool ret; 布尔判断,支持==、>=、<=
记录 var re = ('first', a: 2, b: true, 'last');或者(String, int, bool, string) record; record是轻量级的匿名数据结构,不可变,支持存储不同类型的数据,可以指导字段名称,如上的a,b,
List var l = [1,2,3]或者List<int> l; 列表,基于动态数组,可变类型,有扩容机制
Set Set<String> set = {'trluper'}或者var set=<String>{'trluper'} 哈希表,可变,只有key,哈希冲突时,采用链地址或开发寻址法解决
Map Map<Sting,String> map = {'trluper':'test1'}或者var map=<Sting,String> {'trluper':'test1'} 哈希表存储键值对,可变类型
Object Object a=1 所有非 Null 类型的基类,可存储非空对象
动态类型 dynamic x=1;,List<dynamic> l; 变量在运行时可以存储任意类型的值,通过dynamic修饰,列表、集合可以存储任意类型
Null类型 空值类型,在空安全(Null Safety)模式下,类型默认不可为null,需显式添加 ? 声明可空性(如 int?

Null是类,而null是dart的一个关键字,两者逻辑等价但语法角色不同

2.2.1 dynami、Object与var区别

特性 dynamic Object var
类型检 运行时决定,编译时不检查 编译时检查(需类型转换) 编译时推断,类型固定
灵活 可任意修改类型 需显式类型转换 初始化后类型不可变
方法调 允许调用任何方法(运行时可能失败) 仅限 Object 的方法 仅限推断类型的方法

2.2.2 记录record

Dart 的 ‌记录(Record)‌ 是 Dart 3.0 引入的一种不可变的匿名复合数据类型,用于临时组合多个值而不必声明专门的类,具有:

  • 不可变性与匿名性:记录在创建后无法修改其字段值(类似python元组)。无需预先定义类型,直接通过语法 、(type1, type2, ...) 创建
  • 类型安全:记录的类型由其字段的类型和顺序决定,例如 (int, String)(String, int) 是不同的类型。
  • 内存高效:录是值类型(value type),传递时直接复制内容而非引用。

创建方式如下:

1
2
3
4
5
6
7
8
// 创建记录
var point = (42, 'Answer'); // 类型推断为 (int, String)
var user = (name: 'Alice', age: 30); // 命名字段记录

// 显式类型注解
(int, String) record1 = (1, 'a');
//别名
({String name, int age}) record2 = (name: 'Bob', age: 25);
字段访问:通过 $index 访问(从 1 开始),或者直接通过字段名访问。
1
2
print(point.$1);    // 输出 42(位置字段)
print(user.name); // 输出 'Alice'(命名字段)
通过模式匹配(Pattern Matching)直接解构字段:
1
2
3
4
var (x, y) = point;          // 解构位置字段
var (name: n, age: a) = user; // 解构命名字段
print(x); // 输出 42
print(n); // 输出 'Alice'

使用场景:

  • 多返回值函数,替代返回List或自定义类,类型安全
  • 临时数据组合,避免为简单数据定义冗余类:
  • 模式匹配与解构,结合 switch 或 if-case 使用:
    1
    2
    3
    4
    5
    6
    switch (user) {
    case (name: 'Alice', age: 30):
    print('Found Alice!');
    default:
    print('Unknown user');
    }

2.3 dart中流程控制

dart除了与其他语言一样的if-else if-else,但受其模式匹配特性的影响,dart还支持if-case条件控制和更强大的switch ### 2.3.1 if-case 得益于dart语言中模式匹配特性,dart的if语句支持了case语法:

1
if (pair case [int x, int y]) return Point(x, y);
上面语句表示:若pair能模式匹配[int x, int y],即pair是一个两int元素的列表,则条件成立并解构,返回Point(x,y)

2.3.2 switch

Dart 的 switch 与 C++只支持整型、常量 的 switch 在语法和功能上存在显著差异,dart因模式匹配几乎支持所有类型作为swtch的参数,因此Dart 与 Go 的 switch 设计理念更为接近

1、Dart 与 Go 的 switch 相似

  • 灵活的匹配逻辑两者均支持表达式匹配(如 case x > 0:多值匹配(如 case 1, 2:),而 C++ 仅支持常量值匹配
  • 隐式阻断与简洁:Dart 和 Go 的 case 自动阻止,无需 break,语法更简洁,若要继续执行,go中是使用fallthrough关键字,dart则是contiue;(另外dart中若有空case,则会顺延到非空case执行后再推出)
  • 类型推断支:Dart 和 Go 的 switch 可结合类型推断,直接处理动态类型变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
switch (obj) {
// Matches if 1 == obj.
case 1:
print('one');

// Matches if the value of obj is between the
// constant values of 'first' and 'last'.
case >= first && <= last:
print('in range');

// Matches if obj is a record with two fields,
// then assigns the fields to 'a' and 'b'.
case (var a, var b):
print('a = $a, b = $b');

default:
}

2、switch表达式 dart运行你无论在哪里都能使用switch表达式,如在print()打印函数内、赋值语句、列表内作为元素(需开发者自己要保证case的完整性和产生元素的合法性)

1
2
3
4
//switch表达式
var x = switch (y) { ... };
print(switch (x) { ... });
return switch (x) { ... };

因此,switch表达式的特性使得switch case语法可以改写成这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// slash, star, comma, semicolon, etc.,这些是定义好的常量,switch-case语法
switch (charCode) {
case slash || star || plus || minus: // Logical-or pattern
token = operator(charCode);
case comma || semicolon: // Logical-or pattern
token = punctuation(charCode);
case >= digit0 && <= digit9: // Relational and logical-and patterns
token = number();
default:
throw FormatException('Invalid');
}
//改写成switch 表达式
token = switch (charCode) {
slash || star || plus || minus => operator(charCode),
comma || semicolon => punctuation(charCode),
>= digit0 && <= digit9 => number(),
_ => throw FormatException('Invalid'),
};
>_:相当于占位符,在switch中相当于default >=>:箭头函数,主要用于简化单行函数的定义,为函数语法糖;其中=> number(),等价于{return number()}

2.3.3 异常处理

同样,dart使用try-cath-rethrow语法实现异常错误的捕获,以免是程序宕机。异常捕获可以多层,捕获后后续的捕获操作不会触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try {
breedMoreLlamas();
} on OutOfLlamasException {
// 第一层 catch,通常是具体的子异常
buyMoreLlamas();
} on Exception catch (e, s) {
// 第二层catch,通常是总异常,未明确具体
print('Unknown exception: $e');
throw Exception('异常');
} catch (e, s) {
// 程序本身没有实现的异常,处理所有错误
print('Something really unknown: $e');
rethrow;
}finally{
//一些回收、清零操作
}
- 上述语句表示了,你不会异常可以单独使用 oncatch,也可以两者结合使用;一般的是具体异常使用on 具体异常类,其他的使用catch或on 具体异常 catch(e,s) - catch可以支持两个参数,e是跑出的异常值,s即是StackTrace堆栈跟踪。当然可以单独使用catch(e) - finally块:无论是否异常、异常是否被throwrethrow,该finally块一定会执行

2.3.4 断言assert

在开发中,断言是另一个常用到的语句,assert(condition, <optionalMessage>);,它首先判断条件语句condition是否为truetrue,则进行程序向下执行,为false则会抛出异常,其异常描述即为可选项<optionalMessage>

1
2
3
4
5
assert(text != null);
assert(
urlString.startsWith('https'),
'URL ($urlString) should start with "https".',
);

2.4 模式匹配

Dart的模式匹配是一种结构化数据检查与解构机制,其核心是通过特定语法对数据进行形态验证和内容提取。模式匹配要求一个给定匹配的值要有一些特征:

  • 有确定的大小(列表、集合个数确定)
  • 恒定值或等式关系
  • 确定的类型

模式匹配将数据与预定义的"模式"进行对比,匹配成功后执行解构操作。模式可以是常量、变量或复合结构(如列表、记录),匹配过程会递归检查子模式,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 列表模式匹配
if ([1, 2] case [int x, int y]) {// 匹配并解构出列表元素作x,y表示
print('$x, $y');
}
//列表+常量值
const a = 'a';
const b = 'b';
switch (obj) {
// List pattern [a, b] matches obj first if obj is a list with two fields,
// then if its fields match the constant subpatterns 'a' and 'b'.
case [a, b]:
print('$a, $b');
}

模式匹配可以用于:变量的声明和赋值、在for循环使用、if-caseswitch-case、switch表达式、在集合、列表的控制流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Declaration
var (a, [b, c]) = ('str', [1, 2]);
//赋值
var (a, b) = ('left', 'right');
(b, a) = (a, b); // Swap.
print('$a $b'); // Prints "right left"
//switch表达式
var isPrimary = switch (color) {
Color.red || Color.yellow || Color.blue => true,
_ => false,
};
//loop内
Map<String, int> hist = {'a': 23, 'b': 100};
for (var MapEntry(key: key, value: count) in hist.entries) {
print('$key occurred $count times');
}

2.5 类

dart的类同cpp类似,有成员变量、构造方法、成员方法、静态成员变量、静态成员方法。可以通过继承extends。继承父类的变量、方法;通过实现implements重新实现父类的API接口方法

1
2
3
4
5
6
7
class ProfileMark {
final String name;
final DateTime start = DateTime.now();

ProfileMark(this.name); //构造函数,这样的形式已经默认this.name=name
ProfileMark.unnamed() : name = ''; //命名构造函数
}

2.5.1 构造方法

在Dart中有多种构造方法:

  • 默认构造方法:当你未实现任何构造是,它会生成默认无参的构造方法
  • 生成构造方法:直接以类名(this.成员变量...)的,称为生成构造方法
    1
    ProfileMark(this.name);	//构造函数,这样的形式已经默认this.name=name
  • 命名构造方法:ProfileMark.unnamed() : name = ''; //命名构造函数
  • 常量构造方法:如果你的类有常量/不变量,要有常量构造方法
    1
    2
    3
    4
    5
    class ImmutablePoint {
    static const ImmutablePoint origin = ImmutablePoint(0, 0);
    final double x, y;
    const ImmutablePoint(this.x, this.y);
    }
  • 工厂构造:使用关键字factory进行工厂构造,这个构造器不总是创建一个新的对象,它可能是从①从cache中返回现有的对象②一个新的子类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Logger {
    final String name;
    bool mute = false;
    // _cache is library-private, thanks to
    // the _ in front of its name.
    static final Map<String, Logger> _cache = <String, Logger>{};
    //从cache中获得
    factory Logger(String name) {
    return _cache.putIfAbsent(name, () => Logger._internal(name));
    }
    //从json这个map中获取
    factory Logger.fromJson(Map<String, Object> json) {
    return Logger(json['name'].toString());
    }
    Logger._internal(this.name);
    void log(String msg) {
    if (!mute) print(msg);
    }
    }

2.5.2 方法

除了一些自定义的方法外,需要特别说明的是dart支持重载运算符和getter、setter方法:

< > <= >= == ~
- + / ~/ * %
ˆ & << >>> >>
[]= []
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Vector {
final int x, y;

Vector(this.x, this.y);

Vector operator +(Vector v) => Vector(x + v.x, y + v.y);
Vector operator -(Vector v) => Vector(x - v.x, y - v.y);

@override
bool operator ==(Object other) =>
other is Vector && x == other.x && y == other.y;

@override
int get hashCode => Object.hash(x, y);
}

void main() {
final v = Vector(2, 3);
final w = Vector(2, 2);

assert(v + w == Vector(4, 5));
assert(v - w == Vector(0, 1));
}

getter和setter方法提供读取和写入类私有成员的的API接口,(在dart中,以_的变量是该类的私有或者包的私有属性):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Rectangle {
double left, top, _width, _height;

Rectangle(this.left, this.top, this._width, this._height);

// Define two calculated properties: right and bottom.
double get width => _width;
set width(double value) => _width = value ;
double get height => _height;
set height(double value) => _height = value;
}

void main() {
var rect = Rectangle(3, 4, 20, 15);
assert(rect.left == 3);
rect.height = 12;
assert(rect.height == 12);
}

3 Dart的线程模型

Dart的线程模型采用单线程配合事件循环机制实现异步处理,其核心架构由微任务队列事件队列构成,其中微任务队列的优先级大于事件队列。Dart虽然提供调用堆栈,但是事件循环由单个线程支持。在这之中,

  • 如上图中所示,入口函数 main() 开始,主线程同步代码执行完毕后启动事件循环。所谓同步代码是指若MicroMask为空,并且代码也是微任务性质的,则它会直接执行,也称为同步代码,避免了将其压入微任务队列的额外操作。执行完毕后启动事件循环机制了
  • 先查看MicroTask队列是否为空,不是则先执行MicroTask队列。
  • 一个MicroTask任务执行完后,检查有没有下一个MicroTask任务,直到MicroTask队列为空,才去执行Event队列。
  • Evnet队列取出一个事件处理完后,再次返回第二步,去检查MicroTask队列是否为空。
1
2
3
4
5
6
7
8
9
10
void awaitTest()async{
print("TestStart"); //同步代码
await Future.delayed(Duration(seconds:1),()=>print("event")); //事件队列,暂定当前函数awaitTest(),交出线程占用,线程执行别的任务,等待其返回后才执行后续
print ("TestFinash"); //微任务队列,由于 await会暂停函数执行,TestFinash 的微任务注册会被延迟到 event 打印完成后
}
void main() async {
print('MainSatrt'); //同步代码
awaitTest(); //同步调用,但内部有awit
print('MainFinish'); //同步代码
}

上面示例可以看出,await的作用:

  • 在有async修饰的函数中,await语句会使当前函数暂停,交出dart的工作线程使用权。
  • 将await修饰的操作放入事件队列,事件队列依据任务类型做以下处理:
    • 文件读写、网络请求等I/O操作、定时任务由操作系统内核异步处理,Dart仅通过事件队列接收回调;内核完成操作后,通过Dart的事件循环机制触发对应的Future回调
    • 若await后的操作不涉及I/O(如纯计算),Dart仍会在当前Isolate的主线程处理,‌不会委托内核‌

3.1 队列的更新和取出时机

Dart 中的 ‌微任务队列(Microtask Queue)‌ 和 ‌事件队列(Event Queue)‌会在代码执行过程中动态更新,它们的压入(enqueue)和取出(dequeue)遵循以下规则:

队列更新

  • 压入(Enqueue)‌
    • 微任务:通过 scheduleMicrotask()Future.microtask() 显式添加,或由await 后的代码隐式生成。
    • 事件任务:由 I/O、定时器(Future.delayed)、用户交互等异步操作触发时压入。
  • 取出(Dequeue:事件循环(Event Loop)迭代时,优先清空微任务队列,再处理事件队列的第一个任务。
1
2
3
4
5
6
7
8
9
10
11
import 'dart:async';
void main() {
// 同步代码(立即执行)
print('Sync 1');
// 微任务(压入微任务队列)
scheduleMicrotask(() => print('Microtask 1'));
// 事件任务(压入事件队列)
Future(() => print('Event 1'));
// 同步代码(继续执行)
print('Sync 2');
}

3.2 dart中的生成器、异步、

正如前面示例,dart中使用异步,一个是使用await+asyncawait+async*和创建Future对象。在正式使用前,我们必须明白他们的区别

关键字 含义 返回值 搭配使用 控制效果 典型应用场景
async 修饰函数,声明异步能力但不强制实现异步 Future<类型>单个返回值 直接return,不能使用yiledyiled* 有await时,暂停该函数交给事件队列 网络请求
async* 修饰函数,声明异步能力但不强制实现异步 Stream<类型> yiledyiled*搭配使用为生成器 有await时,暂停 实时+数据流
sync 修饰函数(可省略),函数一定是同步实现 正常返回值,跟普通函数一样 直接return,不能与yiledyiled*搭配使用 - -
sync* 修饰函数,函数一定是同步实现 Iterable<类型> yiledyiled*搭配使用为异步生成器 立即生成 内存数据遍历
Future 本身就是异步 看你传递函数的返回值 调用链.then() 异步 一次性使用场景

3.2.1 生成器

生成器主要实现是yiledyiled*搭配同步sync*或异步async*使用。其中两者的区别是

  • yiled:生成单个值并暂停函数执行。
  • yield*:用于嵌套调用其他生成器函数,将其产生的值序列“扁平化”输出
    1
    2
    3
    Stream<int> nestedStream() async* {
    yield* countAsync(3); // 嵌套调用异步生成器,countAsync(3)使用了yiled
    }

1. 同步生成器sync*示例:sync*标记生成器,返回值为迭代器对象Iterable<>,yield指示返回该值到Iterable<>

1
2
3
4
5
6
7
8
9
10
Iterable<int> countTo(int max) sync* {
for (int i = 1; i <= max; i++) {
print("trluper");
yield i; // 逐个生成数字
}
}

void main() {
print(countTo(3)); // 输出: trluper trluper trluper (1, 2, 3)
}
改代码展开等效于
1
2
3
4
5
6
7
8
void main() {
final iterable = countTo(3); // 调用该函数,创建了迭代对象Iterable<int>,
final iterator = iterable.iterator; // 获取迭代器

while (iterator.moveNext()) { // print内部隐式执行此循环
print(iterator.current);
}
}
因此实际输出是:
1
2
3
4
trluper
trluper
trluper
(1, 2, 3)

2. 异步生成器async*:async*支持 awaityield组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Stream<int> timedCounter(int max) async* {
for (int i = 1; i <= max; i++) {
await Future.delayed(Duration(seconds: 1));
print("trluper");
yield i; // 每秒生成一个数字
}
}

void main() async {
//print(timedCounter(3)); //输出:Instance of '_ControllerStream<int>
await for (final num in timedCounter(3)) {
print(num); // 每秒输出: trluper 1 → trluper 2 → trluper 3
}
}

print可以解析同步流Iterabal,无法接线Stream,因此得使用wait for

3.2.2 异步

dart中使用异步,一个是使用await+async和创建Future对象。在正式使用前,我们必须明白他们的区别。

  • 事件队列调度Future() 构造函数会将传入的函数(无论是否含 async/await)包装为一个任务,并推入事件队列异步执行
  • 与 async/await 的关系‌async/await 是语法糖,用于简化异步代码编写,但 Future() 的异步性由 Dart 的‌事件循环‌机制保证,这个对象本身就异步了,与是否使用 async/await 无关;async/await只是说明你自己定义的函数是否为异步

Future(() { ... }:直接创建一个Future对象,通过匿名函数执行异步任务,返回类型为 Future(未显式指定泛型类型)。async修饰的函数则返回有类型的Future<type>,最终得到的都是Future对象,而Future对象本身就是异步的

Future()

  • 同步函数‌:若传入的函数是同步的(无 async/await),Future() 会异步执行它,但回调函数内部不会暂停(无 await 点)
    1
    Future(() => 42); // 里面的同步函数立即执行,但 Future 自己则异步调度执行,
  • 异步函数‌:若函数被 async 标记,其返回值自动包装为 Future,此时 Future() 会等待该 Future 完成
    1
    Future(() async => await http.get(url)); // 嵌套 Future 处理,内部是异步的,Future也是异步的
    |‌场景‌| ‌行为‌| |:----------------------------:|:--------------------------------------:| |Future(() {})| 无论函数是否含 async/await,均异步执行(通过事件队列)| |async 函数作为参数| 函数返回值自动包装为 Future,外层 Future() 会等待其完成| |同步函数作为参数| 函数立即执行,但由Future()调度到事件队列异步触发|

await+async

有时候,我们希望写定的函数本身就是异步的,那么就可以使用await+async

1
2
3
4
5
6
7
8
Future<int> fetchData() async {
await Future.delayed(Duration(seconds: 1)); // 模拟异步操作
return 100; // 返回 Future<int>,值为 100
}

void main()async{
fetchData().then((data) => print(data));
}
注意:在Future中,then()内的语句是微任务队列

根据上述,同理Stream本身也是异步的,因此也就可以创建Stream异步对象

1
2
3
4
Stream<int> stream = Stream.periodic(Duration(seconds: 1), (count) => count).take(3);
void main() {
stream.first.then((value) => print('第一个值: $value')); // 输出: 第一个值: 0
}
first 返回 Future类型,表示流中第一个事件

4 dart vm