我觉得我还是学学flutter吧,听说这个可以制作出跨平台应用,感觉挺新奇的

我还是继续制作我的终末地聊天APP吧,因为之前使用html js css这些制作的

当然,我还是先学学怎么做出好看的应用

image-20260415155821048

下载dartsdk

下载完成后终端输入检测是否下载成功

1
2
dart --version
flutter

我习惯用VScode,所以你们要是用VScode的话去下载flutter和dart相关的插件(Trae和cursor还有windsurf本质上也是VScode,插件一般都一样)

dart语言

因为我学过很多语言,而这些语言基本上差不多,dart给我的感觉很像java和C#,包括C#里的很多概念dart里都有(估计是开发dart的照抄C#这种的)

和C#一样,这个必须在main函数中运行

变量

关键字 / 类型 是否可重新赋值 是否可变类型 是否必须初始化 是否编译时常量 说明
var 自动推断类型(最常用)
int / String / num / double / bool 显式类型(推荐规范写法)
dynamic 动态类型(不安全,慎用)
Object ❌(需强转) 所有类型父类(更安全)
final ❌(只能一次) 运行时常量
const ❌(完全不可变) 编译时常量
late ❌(可延迟) 延迟初始化(常用于Flutter)
late final ❌(只能一次) 延迟 + 单次赋值

其中var int String double bool Object const这些我们已经相当熟悉了,其中num表示整数或小数

但是这里面的其他的一些我们是第一次见

数字类型

num (父类)
├── int (整型)
└── double (浮点型)

赋值方向 是否允许 原因
intnum ✅ 允许 子类赋值给父类(向上转型)
doublenum ✅ 允许 子类赋值给父类(向上转型)
numint ❌ 不允许 父类不能隐式转为子类
numdouble ❌ 不允许 父类不能隐式转为子类
intdouble ❌ 不允许 不同类型,不能隐式转换
doubleint ❌ 不允许 不同类型,不能隐式转换

这里我们知道隐式转换和显示转换

对于隐式转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void main() {
int i = 10;
double d = 3.14;

// int 可以赋值给 num
num n1 = i;
print('n1: $n1, 类型: ${n1.runtimeType}'); // n1: 10, 类型: int

// double 可以赋值给 num
num n2 = d;
print('n2: $n2, 类型: ${n2.runtimeType}'); // n2: 3.14, 类型: double

// num 变量可以接受任意数字
num n3 = 100; // 存 int
n3 = 2.718; // 改存 double
print('n3: $n3'); // n3: 2.718
}

对于显示转换(其实就是调用方法比如to…()这种方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void main() {
num n = 10;

// ✅ 显式转 int
int i = n.toInt();
print('i: $i'); // i: 10

// ✅ 显式转 double
double d = n.toDouble();
print('d: $d'); // d: 10.0

// int 转 double
int i2 = 10;
double d2 = i2.toDouble(); // ✅ 10 → 10.0
print('d2: $d2');

// double 转 int(截断小数)
double d3 = 3.99;
int i3 = d3.toInt(); // ✅ 3.99 → 3(不是四舍五入!)
print('i3: $i3'); // i3: 3

// double 四舍五入
int i4 = d3.round(); // ✅ 3.99 → 4
print('i4: $i4');
}

final

当需要存储一个不变的数据,但是在运时才确定,需要使final声明常量

编译时常量:当需要存储一个不变的数据,且在编译时就确定,需要使const声明常量

image-20260415165147420
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Circle {
// final 实例变量必须在声明时或构造函数中初始化
final double radius;
final double pi = 3.14159; // 声明时初始化

// 构造函数初始化列表
Circle(this.radius);

double get area => pi * radius * radius;
}

void main() {
final circle = Circle(5.0);
print('面积: ${circle.area}'); // 78.53975

// circle.radius = 10.0; // ❌ 错误:不能修改 final 字段
}

这个也是和const常量一样赋值之后不可以再次修改

String

String 基础

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
// 三种创建方式
String s1 = '单引号';
String s2 = "双引号";
String s3 = '''多行
字符串
支持换行''';

print(s1);
print(s2);
print(s3);
}

字符串插值

1
2
3
4
5
6
7
8
9
10
11
void main() {
String name = 'Dart';
int version = 3;

// $变量名
print('语言: $name');

// ${表达式}
print('版本: ${version + 1}');
print('大写: ${name.toUpperCase()}');
}

String常用方法

toString()方法将其他类型转换为字符串

1
2
3
4
5
void main() {
num a = 5555;
a.toString();
print(a);
}
其他方法
一、长度 / 判空
1
String s = "hello";
方法 作用 示例
length 字符长度 s.length → 5
isEmpty 是否为空 "".isEmpty → true
isNotEmpty 是否不为空 "hi".isNotEmpty → true

二、查找 / 判断
方法 作用 示例
contains() 是否包含 "hello".contains("he") → true
startsWith() 是否以…开头 "hello".startsWith("h")
endsWith() 是否以…结尾 "hello".endsWith("o")
indexOf() 第一次出现位置 "hello".indexOf("l") → 2
lastIndexOf() 最后一次出现 "hello".lastIndexOf("l") → 3

三、截取 / 分割
方法 作用 示例
substring(start, end) 截取字符串 "hello".substring(1,3) → "el"
split() 分割字符串 "a,b,c".split(",") → ["a","b","c"]

四、替换
方法 作用 示例
replaceAll() 替换全部 "aabb".replaceAll("a","x") → "xxbb"
replaceFirst() 替换第一个 "aabb".replaceFirst("a","x") → "xabb"

五、大小写转换
方法 作用 示例
toLowerCase() 转小写 "HELLO".toLowerCase()
toUpperCase() 转大写 "hello".toUpperCase()

六、去空格
方法 作用 示例
trim() 去两边空格 " hi ".trim() → "hi"
trimLeft() 去左边 " hi".trimLeft()
trimRight() 去右边 "hi ".trimRight()

List列表类型

这个和C#里的几乎一样,C#里面也有list,不过list和数组差不多,包括访问数组里的元素方法也是一样的

1
2
3
4
5
6
7
8
9
void main() {
// 三种创建方式
List<int> numbers = [1, 2, 3]; // 字面量
var fruits = <String>['apple', 'banana']; // 类型推断
List empty = List.empty(); // 空列表(growable: false)
List growable = List.empty(growable: true); // 可增长的空列表

print(numbers); // [1, 2, 3]
}

常用的方法:

在尾部添加————add(内容)

在尾部添加一个列表————addAll (列表)

删除满足内容的第一个———-remove(内容)

删除最后一个———–removeLast()

删除索引范围内数据———-removeRange(start,end)

循环————-forEach((item) {});

是否都满足条件—————every((item){return 布尔值 });

筛选出满足条件的数据—————where((item){return 布尔值 });

列表的长度(属性)-length

最后一个元素(属性)————–last

第一个元素(属性)-first是否为空(属性)———–isEmpty

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
void main() {
List<String> colors = ['red', 'green', 'blue'];
List<String> color1 = ['skyblue'];

// 尾部添加
colors.add("yellow");
print(colors);

// 尾部添加一个列表
colors.addAll(color1);
print(colors);

// 删除某一个(删除第一个匹配的)
colors.remove('blue');
print(colors);

// 删除最后一个
colors.removeLast();
print(colors);

// 删除索引范围内的数据(start <= index < end)
colors.removeRange(1, 2);
print(colors);

// 循环遍历
colors.forEach((item) {
print(item);
});

// 是否都满足条件
bool allStartWithR = colors.every((item) {
return item.startsWith("r");
});
print("是否全部以 r 开头: $allStartWithR");

// 筛选满足条件的数据
var result = colors.where((item) {
return item.contains("e");
});
print("包含 e 的元素: $result");

// 长度
print("长度: ${colors.length}");

// 第一个元素
print("第一个: ${colors.first}");

// 最后一个元素
print("最后一个: ${colors.last}");

// 是否为空
print("是否为空: ${colors.isEmpty}");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
List<String> colors = ['红red', '绿green', '蓝blue'];
List<String> color1 = ['天蓝skyblue'];

//是否所有元素都满足条件
print(
colors.every((item) {
return item.toString().startsWith('红');
}),
);

//筛选出满足条件的元素
print(
colors.where((item) {
return item.toString().startsWith('绿');
}).toList(),
);
}
具体例子

访问与修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main() {
List<String> colors = ['red', 'green', 'blue'];

// 访问元素
print(colors[0]); // red(第一个)
print(colors.first); // red
print(colors.last); // blue
print(colors.length); // 3

// 修改元素
colors[1] = 'yellow';
print(colors); // [red, yellow, blue]

// 安全访问
print(colors.elementAtOrNull(5)); // null(不会报错)
}

添加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
void main() {
List<int> list = [];

// 添加单个
list.add(1); // [1]
list.addAll([2, 3]); // [1, 2, 3]

// 指定位置插入
list.insert(0, 0); // [0, 1, 2, 3](在索引0插入0)
list.insertAll(2, [99, 100]); // [0, 1, 99, 100, 2, 3]

print(list);
}

删除元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
List<String> items = ['a', 'b', 'c', 'b', 'd'];

// 按值删除(第一个匹配)
items.remove('b'); // ['a', 'c', 'b', 'd']

// 按索引删除
items.removeAt(0); // ['c', 'b', 'd']

// 删除最后一个
items.removeLast(); // ['c', 'b']

// 按条件删除
items.removeWhere((e) => e == 'b'); // ['c']

// 清空
items.clear(); // []
}

查询与查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void main() {
List<int> numbers = [10, 20, 30, 40, 50];

// 包含判断
print(numbers.contains(20)); // true

// 查找位置
print(numbers.indexOf(30)); // 2
print(numbers.lastIndexOf(10)); // 0

// 查找元素(按条件)
int first = numbers.firstWhere((n) => n > 25); // 30
int? safe = numbers.firstWhere((n) => n > 100, orElse: () => -1); // -1

// 查找索引
int idx = numbers.indexWhere((n) => n > 25); // 2

print('first: $first, idx: $idx');
}

切片与范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
List<String> list = ['a', 'b', 'c', 'd', 'e'];

// 获取子列表(视图,修改会影响原列表)
List<String> sub = list.sublist(1, 4); // ['b', 'c', 'd']

// 范围替换
list.replaceRange(1, 3, ['X', 'Y']); // ['a', 'X', 'Y', 'd', 'e']

// 填充范围
list.fillRange(0, 2, 'Z'); // ['Z', 'Z', 'Y', 'd', 'e']

print(list);
}

排序与反转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
List<int> nums = [3, 1, 4, 1, 5, 9, 2, 6];

// 原地排序
nums.sort(); // [1, 1, 2, 3, 4, 5, 6, 9]
nums.sort((a, b) => b - a); // 降序 [9, 6, 5, 4, 3, 2, 1, 1]

// 自定义排序
List<String> fruits = ['banana', 'apple', 'pie'];
fruits.sort((a, b) => a.length.compareTo(b.length)); // ['pie', 'apple', 'banana']

// 反转(返回新列表)
var reversed = nums.reversed.toList(); // [9, 6, 5, 4, 3, 2, 1, 1]

// 原地反转
nums = [1, 2, 3];
// Dart 没有原地反转方法,需要手动或转数组
}

映射与过滤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main() {
List<int> numbers = [1, 2, 3, 4, 5];

// map:转换每个元素
List<String> strings = numbers.map((n) => 'No.$n').toList();
print(strings); // [No.1, No.2, No.3, No.4, No.5]

// where:过滤
List<int> evens = numbers.where((n) => n.isEven).toList();
print(evens); // [2, 4]

// expand:展开嵌套
List<List<int>> nested = [[1, 2], [3, 4]];
List<int> flat = nested.expand((e) => e).toList();
print(flat); // [1, 2, 3, 4]

// 链式操作
var result = numbers
.where((n) => n > 2) // [3, 4, 5]
.map((n) => n * 2) // [6, 8, 10]
.toList();
print(result);
}

聚合操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void main() {
List<int> scores = [85, 92, 78, 90, 88];

// 遍历
scores.forEach((s) => print(s));

// 折叠/归约
int sum = scores.reduce((a, b) => a + b); // 433
int total = scores.fold(0, (acc, s) => acc + s); // 433(带初始值)

// 条件判断
bool allPass = scores.every((s) => s >= 60); // true
bool hasExcellent = scores.any((s) => s >= 90); // true

// 查找极值
int max = scores.reduce((a, b) => a > b ? a : b); // 92
int min = scores.reduce((a, b) => a < b ? a : b); // 78

print('总分: $total, 最高分: $max');
}

固定长度 vs 可增长

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void main() {
// 可增长列表(默认)
List<int> growable = [1, 2, 3];
growable.add(4); // ✅ 可以添加
growable.length = 10; // ✅ 可以改长度(填充null)

// 固定长度列表
List<int> fixed = List.filled(3, 0); // [0, 0, 0]
fixed[0] = 10; // ✅ 可以修改元素
// fixed.add(4); // ❌ 错误:不能添加
// fixed.length = 5; // ❌ 错误:不能改长度

// 从可增长转固定
var fixed2 = List.unmodifiable(growable); // 不可修改的列表
// fixed2[0] = 100; // ❌ 运行时错误

print('growable: $growable');
print('fixed: $fixed');
}

展开运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void main() {
List<int> list1 = [1, 2, 3];
List<int> list2 = [4, 5, 6];

// 展开 ...(合并列表)
List<int> combined = [...list1, ...list2]; // [1, 2, 3, 4, 5, 6]

// 条件展开
bool showExtra = true;
List<int> conditional = [
1,
2,
if (showExtra) ...[3, 4, 5], // 条件成立才展开
];

// for 展开
List<String> repeated = [
for (var i in [1, 2, 3]) 'item-$i', // ['item-1', 'item-2', 'item-3']
];

print(combined);
print(conditional);
print(repeated);
}
操作 方法
添加 add(), addAll(), insert(), insertAll()
删除 remove(), removeAt(), removeLast(), removeWhere(), clear()
访问 [], first, last, elementAt(), single
查找 contains(), indexOf(), indexWhere(), firstWhere()
切片 sublist(), getRange(), replaceRange(), fillRange()
排序 sort(), reversed
转换 map(), where(), expand(), cast<T>()
聚合 forEach(), reduce(), fold(), every(), any(), join()
其他 shuffle(), asMap(), toSet(), toString()

Map字典类型

字典这个已经很熟悉了只不过这里的字典格式和列表差不多,反正和其他语言里的一样就是键值对

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
// 三种创建方式
Map<String, int> scores = {'Alice': 90, 'Bob': 85}; // 字面量
var ages = <String, int>{'Tom': 20, 'Jerry': 22}; // 类型推断
Map empty = {}; // 空 Map

// 构造函数创建
Map<String, String> config = Map();
Map<int, String> numbers = Map.from({1: 'one', 2: 'two'});

print(scores); // {Alice: 90, Bob: 85}
}

循环———forEach

在添加一个字典————-addAll

是否包含某个key————containsKey

删除某个key———-remove

清空———clear

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void main() {
Map<String, String> player = {'女儿': '阿尼亚', '母亲': '约尔', '父亲': '劳埃德'};
print(player);

//循环 forEach
player.forEach((key, value) {
print('$key: $value');
});

//再添加一个字典--addAll
Map<String, String> player2 = {'同学': '达米安'};
player.addAll(player2);
print(player);

//是否包含某个键
print(player.containsKey('女儿')); //true
print(player.containsKey('儿子')); //false

//删除某个键
player.remove('同学');
print(player);

//清空字典
player.clear();
print(player); //{}
}
具体例子

添加与修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void main() {
Map<String, int> map = {};

// 添加键值对
map['a'] = 1; // 直接赋值
map.putIfAbsent('b', () => 2); // key不存在时才添加

// 批量添加
map.addAll({'c': 3, 'd': 4});

// 修改值
map['a'] = 100; // 已存在则覆盖

// 更新(基于旧值)
map.update('a', (v) => v * 2); // 100 -> 200
map.update('z', (v) => v * 2, ifAbsent: () => 0); // 不存在则设为0

print(map); // {a: 200, b: 2, c: 3, d: 4, z: 0}
}

删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main() {
Map<String, int> map = {'a': 1, 'b': 2, 'c': 3, 'd': 4};

// 按键删除
map.remove('a'); // 删除key 'a',返回被删的值(1),不存在返回null

// 条件删除
map.removeWhere((k, v) => v.isEven); // 删除值为偶数的项(b:2, d:4)

// 清空
// map.clear();

print(map); // {c: 3}
}

访问与查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main() {
Map<String, int> map = {'Alice': 90, 'Bob': 85, 'Carol': 95};

// 访问值
print(map['Alice']); // 90
print(map['Unknown']); // null(key不存在)

// 安全访问
int? score = map['Unknown'];
int safe = map['Unknown'] ?? 0; // 不存在返回默认值0

// 检查存在性
print(map.containsKey('Alice')); // true
print(map.containsValue(100)); // false

// 获取键值集合
print(map.keys); // (Alice, Bob, Carol)
print(map.values); // (90, 85, 95)
print(map.entries); // (MapEntry(Alice: 90), ...)

// 遍历
map.forEach((k, v) => print('$k: $v'));
}

遍历方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void main() {
Map<String, int> map = {'a': 1, 'b': 2, 'c': 3};

// 方式1:forEach
map.forEach((key, value) {
print('$key = $value');
});

// 方式2:for-in entries
for (var entry in map.entries) {
print('${entry.key}: ${entry.value}');
}

// 方式3:解构遍历(Dart 3)
for (var MapEntry(:key, :value) in map.entries) {
print('$key -> $value');
}

// 方式4:只遍历key或value
for (var k in map.keys) print(k);
for (var v in map.values) print(v);
}

转换与映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void main() {
Map<String, int> scores = {'Alice': 90, 'Bob': 85, 'Carol': 95};

// map:转换每个值
Map<String, String> grades = scores.map((k, v) =>
MapEntry(k, v >= 90 ? 'A' : 'B')
);
print(grades); // {Alice: A, Bob: B, Carol: A}

// 过滤(没有直接filter,需要转换)
Map<String, int> excellent = Map.fromEntries(
scores.entries.where((e) => e.value >= 90)
);
print(excellent); // {Alice: 90, Carol: 95}

// 键值互换
Map<int, String> reversed = Map.fromEntries(
scores.entries.map((e) => MapEntry(e.value, e.key))
);
print(reversed); // {90: Alice, 85: Bob, 95: Carol}
}

合并与操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main() {
Map<String, int> m1 = {'a': 1, 'b': 2};
Map<String, int> m2 = {'b': 20, 'c': 3}; // b冲突

// 展开合并(Dart 2.3+)
Map<String, int> combined = {...m1, ...m2}; // m2的b覆盖m1的b
print(combined); // {a: 1, b: 20, c: 3}

// 自定义合并(处理冲突)
Map<String, int> merged = {};
merged.addAll(m1);
m2.forEach((k, v) {
merged[k] = (merged[k] ?? 0) + v; // 值相加
});
print(merged); // {a: 1, b: 22, c: 3}

// 条件展开
bool useM2 = true;
Map<String, int> conditional = {
...m1,
if (useM2) ...m2,
};
}

常用构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void main() {
// 固定key,延迟计算value
Map<String, int> lazy = Map.fromIterable(
['a', 'b', 'c'],
key: (item) => item,
value: (item) => item.codeUnitAt(0),
);
print(lazy); // {a: 97, b: 98, c: 99}

// 分组(Dart 2.13+)
List<String> words = ['apple', 'banana', 'apricot', 'blueberry'];
Map<String, List<String>> grouped = {};
for (var w in words) {
String key = w[0]; // 按首字母分组
grouped.putIfAbsent(key, () => []).add(w);
}
print(grouped); // {a: [apple, apricot], b: [banana, blueberry]}

// 不可变Map
Map<String, int> readonly = Map.unmodifiable({'a': 1});
// readonly['a'] = 2; // ❌ 运行时错误
}

排序与排序后的Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void main() {
Map<String, int> scores = {'Bob': 85, 'Alice': 90, 'Carol': 95};

// 按键排序(返回List)
var sortedByKey = Map.fromEntries(
scores.entries.toList()..sort((a, b) => a.key.compareTo(b.key))
);
print(sortedByKey); // {Alice: 90, Bob: 85, Carol: 95}

// 按值排序
var sortedByValue = Map.fromEntries(
scores.entries.toList()..sort((a, b) => a.value.compareTo(b.value))
);
print(sortedByValue); // {Bob: 85, Alice: 90, Carol: 95}

// SplayTreeMap:自动按键排序(需要import 'dart:collection')
// final sortedMap = SplayTreeMap<String, int>.from(scores);
}

默认值处理

1
2
3
4
5
6
7
8
9
10
11
12
void main() {
Map<String, List<int>> groups = {};

// 方式1:putIfAbsent(推荐)
groups.putIfAbsent('A', () => []).add(1);
groups.putIfAbsent('A', () => []).add(2); // 不会创建新列表

// 方式2:扩展方法(需要collection_for)
(groups['B'] ??= []).add(3);

print(groups); // {A: [1, 2], B: [3]}
}
操作 方法
添加/修改 [] =, putIfAbsent(), addAll(), update()
删除 remove(), removeWhere(), clear()
访问 [], containsKey(), containsValue()
获取集合 .keys, .values, .entries
遍历 forEach(), for-in
转换 map(), Map.fromEntries()
合并 {...m1, ...m2}
其他 cast(), updateAll(), isEmpty, length

dynamic(不要用)

dynamic 是 Dart 的一个特殊类型,表示禁用静态类型检查,可以在运行时持有任何类型的值,并可以调用任何方法(编译时不检查)

说人话就是完全不做类型检查,啥都能放

dynamic和var和object的区别:

dynamic——运时可自由改变类型,(无编译检查,方法和属性直接调用)

var—–根据初始值进行推断类型,确定类型后类型确定,(有编译检查,仅限推断的属性和方法)

Object——所有类型的父类,可接收任意类型,但访问成员需进行类型判断或强制转换,(有编译检查,不可直接调用具体类型的方法)

简而言之就是var和Object一旦确定类型就不可改,而dynamic随便改

1
2
3
4
5
6
7
8
9
10
void main() {
dynamic a = "hello";
print(a);

a = 123;
print(a);

a = true;
print(a);
}

由此可见这玩意太自由了,所以能不用就不用

late

late 是 Dart 的空安全特性,用于延迟初始化

它告诉编译器:”这个变量稍后一定会被赋值,现在先别检查它是否为空”

1
2
3
4
5
6
late String name;

void main() {
name = "Tom"; // 后面再赋值
print(name);
}

核心特点

  • 变量先声明
  • 稍后再赋值
  • 使用前必须赋值,否则报错

late final

这个等于late+ final就是不可更改的延迟初始化的类型,没什么好说的

1
2
3
4
5
6
late final String name;

void main() {
name = "Tom"; // ✅ 第一次赋值
name = "Jack"; // ❌ 报错
}

空安全机制(空安全运算符)

在dart中,默认情况下,变量不能是 null,除非你明确允许它为 null

操作符 符号 作用说明 示例
可空类型 ? 声明变量可以为 null String? name
安全访问 ?. 对象为 null 时跳过调用并返回 null user?.name
非空断言 !. 强制认为变量不为 null(否则运行时报错) name!.length
空合并 ?? 左侧为 null 时返回右侧默认值 name ?? "Guest"
空值赋值 ??= 仅当变量为 null 时才进行赋值 name ??= "Tom"
级联安全访问 ?.. 对象为 null 时不执行后续级联操作 user?..setName("Tom")
空展开 ...? 集合为 null 时跳过展开 [...?list]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
void main() {
//可空类型(声明变量可以为null)
String? name;

name = null; // ✅ 可以
name = "Tom"; // ✅ 也可以

print(name);

//安全访问(对象为 null 时跳过调用并返回 null)
String? name_1;

print(name_1?.length); // 输出null

name_1 = "Tom";
print(name_1?.length); // 输出3

//非空断言(强制认为变量不为 null(否则运行时报错))
String? name_2 = "Tom";

print(name_2!.length); // ✅ 3

String? a;
//print(a!.length); // ❌ 运行时崩溃

//空合并(左侧为 null 时返回右侧默认值)
String? name_3;

print(name_3 ?? "Guest"); // Guest

name_3 = "Tom";
print(name_3 ?? "Guest"); // Tom

//空合并赋值(仅当变量为 null 时才进行赋值)
String? name_4;

name_4 ??= "Tom";
print(name_4); // Tom

name_4 ??= "Jack";
print(name_4); // 还是 Tom(不会被覆盖)

//级联安全访问(对象为 null 时不执行后续级联操作)
User? user;

user?..setName("Tom"); // ✅ 不会报错(因为 user 是 null)

user = User();
user?..setName("Tom");

print(user.name); // Tom

//空展开(集合为 null 时跳过展开)
List<int>? list;

var newList = [1, 2, ...?list];
print(newList); // [1, 2]

list = [3, 4];
var newList2 = [1, 2, ...?list];
print(newList2); // [1, 2, 3, 4]
}

class User {
String name = "";

void setName(String n) {
name = n;
}
}

运算符

算术运算符

运算符 作用 示例
+ 加法 1 + 2
- 减法 5 - 3
* 乘法 2 * 3
/ 除法(结果是double) 5 / 2 = 2.5
~/ 整除 5 ~/ 2 = 2
% 取余 5 % 2 = 1

自增自减

运算符 作用 示例
++ 自增 a++
-- 自减 a--

关系运算符

运算符 作用 示例
== 等于 a == b
!= 不等于 a != b
> 大于 a > b
< 小于 a < b
>= 大于等于 a >= b
<= 小于等于 a <= b

赋值运算符

运算符 作用 示例
= 赋值 a = 10
+= 加后赋值 a += 2
-= 减后赋值 a -= 2
*= 乘后赋值 a *= 2
/= 除后赋值 a /= 2
~/= 整除赋值 a ~/= 2
%= 取余赋值 a %= 2

逻辑运算符

运算符 名称 含义 示例
! 取反 !truefalse
&& 两边都为 true a && b
|| 至少一边为 true a || b