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

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

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

​ image-20260415155821048

先下载dartsdk

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

已复制
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 ❌ 不允许 不同类型,不能隐式转换

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

对于隐式转换

DART已复制
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…()这种方法)

DART已复制
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
DART已复制
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 基础

DART已复制
void main() {
  // 三种创建方式
  String s1 = '单引号';
  String s2 = "双引号";
  String s3 = '''多行
  字符串
  支持换行''';
  
  print(s1);
  print(s2); 
  print(s3);
}

字符串插值

DART已复制
void main() {
  String name = 'Dart';
  int version = 3;
  
  // $变量名
  print('语言: $name');
  
  // ${表达式}
  print('版本: ${version + 1}');
  print('大写: ${name.toUpperCase()}');
}

String常用方法

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

DART已复制
void main() {
  num a = 5555;
  a.toString();
  print(a);
}
其他方法
一、长度 / 判空
已复制
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和数组差不多,包括访问数组里的元素方法也是一样的

DART已复制
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

DART已复制
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}");
}
DART已复制
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(),
  );
}
具体例子

访问与修改

DART已复制
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(不会报错)
}

添加元素

DART已复制
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);
}

删除元素

DART已复制
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();         // []
}

查询与查找

DART已复制
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');
}

切片与范围

DART已复制
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);
}

排序与反转

DART已复制
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 没有原地反转方法,需要手动或转数组
}

映射与过滤

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);
}

聚合操作

DART已复制
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 可增长

DART已复制
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');
}

展开运算符

DART已复制
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字典类型

这个和C#里Dictionary的一样

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

DART已复制
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

DART已复制
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); //{}
}
具体例子

添加与修改

DART已复制
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}
}

删除

DART已复制
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}
}

访问与查询

DART已复制
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'));
}

遍历方式

DART已复制
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);
}

转换与映射

DART已复制
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}
}

合并与操作

DART已复制
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,
  };
}

常用构造函数

DART已复制
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

DART已复制
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);
}

默认值处理

DART已复制
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随便改

DART已复制
void main() {
  dynamic a = "hello";
  print(a);

  a = 123;
  print(a);

  a = true;
  print(a);
}

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

late

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

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

DART已复制
late String name;

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

核心特点

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

late final

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

DART已复制
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]
DART已复制
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