flutter可以用来制作跨平台应用,配套语言是dart
配置环境
下载完成后终端输入检测是否下载成功
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 (浮点型)
| 赋值方向 | 是否允许 | 原因 |
|---|---|---|
int → num | ✅ 允许 | 子类赋值给父类(向上转型) |
double → num | ✅ 允许 | 子类赋值给父类(向上转型) |
num → int | ❌ 不允许 | 父类不能隐式转为子类 |
num → double | ❌ 不允许 | 父类不能隐式转为子类 |
int → double | ❌ 不允许 | 不同类型,不能隐式转换 |
double → int | ❌ 不允许 | 不同类型,不能隐式转换 |
这里我们知道隐式转换和显示转换
对于隐式转换
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...()这种方法)
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声明常量
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 基础
void main() {
// 三种创建方式
String s1 = '单引号';
String s2 = "双引号";
String s3 = '''多行
字符串
支持换行''';
print(s1);
print(s2);
print(s3);
}
字符串插值
void main() {
String name = 'Dart';
int version = 3;
// $变量名
print('语言: $name');
// ${表达式}
print('版本: ${version + 1}');
print('大写: ${name.toUpperCase()}');
}
String常用方法
toString()方法将其他类型转换为字符串
void main() {
num a = 5555;
a.toString();
print(a);
}
{% hideToggle 其他方法 %}
一、长度 / 判空
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() |
{% endhideToggle %}
List列表类型
这个和C#里的几乎一样,C#里面也有list,不过list和数组差不多,包括访问数组里的元素方法也是一样的
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
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}");
}
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(),
);
}
{% hideToggle 具体例子 %}
访问与修改
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(不会报错)
}
添加元素
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);
}
删除元素
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(); // []
}
查询与查找
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');
}
切片与范围
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);
}
排序与反转
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 没有原地反转方法,需要手动或转数组
}
映射与过滤
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);
}
聚合操作
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 可增长
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');
}
展开运算符
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() |
{% endhideToggle %}
Map字典类型
这个和C#里Dictionary的一样
字典这个已经很熟悉了只不过这里的字典格式和列表差不多,反正和其他语言里的一样就是键值对
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
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); //{}
}
{% hideToggle 具体例子 %}
添加与修改
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}
}
删除
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}
}
访问与查询
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'));
}
遍历方式
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);
}
转换与映射
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}
}
合并与操作
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,
};
}
常用构造函数
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
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);
}
默认值处理
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 |
{% endhideToggle %}
dynamic(不要用)
dynamic 是 Dart 的一个特殊类型,表示禁用静态类型检查,可以在运行时持有任何类型的值,并可以调用任何方法(编译时不检查)
说人话就是完全不做类型检查,啥都能放
dynamic和var和object的区别:
dynamic------运时可自由改变类型,(无编译检查,方法和属性直接调用)
var-----根据初始值进行推断类型,确定类型后类型确定,(有编译检查,仅限推断的属性和方法)
Object------所有类型的父类,可接收任意类型,但访问成员需进行类型判断或强制转换,(有编译检查,不可直接调用具体类型的方法)
简而言之就是var和Object一旦确定类型就不可改,而dynamic随便改
void main() {
dynamic a = "hello";
print(a);
a = 123;
print(a);
a = true;
print(a);
}
由此可见这玩意太自由了,所以能不用就不用
late
late 是 Dart 的空安全特性,用于延迟初始化
它告诉编译器:"这个变量稍后一定会被赋值,现在先别检查它是否为空"
late String name;
void main() {
name = "Tom"; // 后面再赋值
print(name);
}
核心特点
- 变量先声明
- 稍后再赋值
- 使用前必须赋值,否则报错
late final
这个等于late+ final就是不可更改的延迟初始化的类型,没什么好说的
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] |
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 |
逻辑运算符
| 运算符 | 名称 | 含义 | 示例 |
|---|---|---|---|
! | 非 | 取反 | !true → false |
&& | 与 | 两边都为 true | a && b |
|| | 或 | 至少一边为 true | a || b |
