1 Dart简介与安装
Dart语言是google开发的语言,目前对外主要用于flutter开发,Google内部使用Dart开发大型应用。它是一个纯面向对象的语言,其语法比较灵活,也比较简单。
Ubuntu安装Dart环境可使用apt-get命令安装:
1 | //用apt-get安装时,如果是第一次安装,需要走1、2、3步 |
Mac安装Dart环境可使用
1 | 1.<Install Homebrew if needed.> |
2 Dart语言
由于Dart语言与go、python具有较大的相似性,因此本节只着重介绍不同点。
2.1 关键字
1. 保留关键字(33个)
- 流程控:
if、else、switch、case、default、break、continue、for、while、do、return、try、catch、finally、throw、rethrow - 类型与类:
class、enum、extends、implements、abstract、super、this、new、const、final、static、void、`var`` - 逻辑:
true、false、null - 其它:
assert、with、in、is、as、typedef
2. 内置标识符(17个)
- 异步与库管:
async、await、sync*、async*、yield、yield* - 库与类:
library、import、export、part、external、factory、operator、mixin - 泛型与反:
covariant、dynamic、get、set
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);1
2print(point.$1); // 输出 42(位置字段)
print(user.name); // 输出 'Alice'(命名字段)1
2
3
4var (x, y) = point; // 解构位置字段
var (name: n, age: a) = user; // 解构命名字段
print(x); // 输出 42
print(n); // 输出 'Alice'
使用场景:
- 多返回值函数,替代返回
List或自定义类,类型安全 - 临时数据组合,避免为简单数据定义冗余类:
- 模式匹配与解构,结合 switch 或 if-case 使用:
1
2
3
4
5
6switch (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);
[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 | switch (obj) { |
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
16try {
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{
//一些回收、清零操作
}on、catch,也可以两者结合使用;一般的是具体异常使用on 具体异常类,其他的使用catch或on 具体异常 catch(e,s)
-
catch可以支持两个参数,e是跑出的异常值,s即是StackTrace堆栈跟踪。当然可以单独使用catch(e)
-
finally块:无论是否异常、异常是否被throw、rethrow,该finally块一定会执行
2.3.4 断言assert
在开发中,断言是另一个常用到的语句,assert(condition, <optionalMessage>);,它首先判断条件语句condition是否为true,若true,则进行程序向下执行,为false则会抛出异常,其异常描述即为可选项<optionalMessage>
1
2
3
4
5assert(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-case和switch-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
7class 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
5class 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
19class 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 | class Vector { |
getter和setter方法提供读取和写入类私有成员的的API接口,(在dart中,以_的变量是该类的私有或者包的私有属性):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class 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 | void awaitTest()async{ |
上面示例可以看出,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 | import 'dart:async'; |
3.2 dart中的生成器、异步、
正如前面示例,dart中使用异步,一个是使用await+async,await+async*和创建Future对象。在正式使用前,我们必须明白他们的区别
| 关键字 | 含义 | 返回值 | 搭配使用 | 控制效果 | 典型应用场景 |
|---|---|---|---|---|---|
async |
修饰函数,声明异步能力但不强制实现异步 | Future<类型>单个返回值 |
直接return,不能使用yiled和yiled* |
有await时,暂停该函数交给事件队列 | 网络请求 |
async* |
修饰函数,声明异步能力但不强制实现异步 | Stream<类型> |
与yiled或yiled*搭配使用为生成器 |
有await时,暂停 | 实时+数据流 |
sync |
修饰函数(可省略),函数一定是同步实现 | 正常返回值,跟普通函数一样 | 直接return,不能与yiled、yiled*搭配使用 |
- | - |
sync* |
修饰函数,函数一定是同步实现 | Iterable<类型> |
与yiled或yiled*搭配使用为异步生成器 |
立即生成 | 内存数据遍历 |
Future类 |
本身就是异步 | 看你传递函数的返回值 | 调用链.then() |
异步 | 一次性使用场景 |
3.2.1 生成器
生成器主要实现是yiled和yiled*搭配同步sync*或异步async*使用。其中两者的区别是
yiled:生成单个值并暂停函数执行。yield*:用于嵌套调用其他生成器函数,将其产生的值序列“扁平化”输出1
2
3Stream<int> nestedStream() async* {
yield* countAsync(3); // 嵌套调用异步生成器,countAsync(3)使用了yiled
}
1.
同步生成器sync*示例:sync*标记生成器,返回值为迭代器对象Iterable<>,yield指示返回该值到Iterable<>
1
2
3
4
5
6
7
8
9
10Iterable<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
8void main() {
final iterable = countTo(3); // 调用该函数,创建了迭代对象Iterable<int>,
final iterator = iterable.iterator; // 获取迭代器
while (iterator.moveNext()) { // print内部隐式执行此循环
print(iterator.current);
}
}1
2
3
4trluper
trluper
trluper
(1, 2, 3)
2.
异步生成器async*:async*支持
await 和 yield组合 1
2
3
4
5
6
7
8
9
10
11
12
13
14Stream<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对象,通过匿名函数执行异步任务,返回类型为
Futureasync修饰的函数则返回有类型的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
8Future<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类型,表示流中第一个事件