西安中交建设集团网站,某些网站dns解析失败,怎么用7牛云做网站,魅族的网站建设与安全前言在上一篇教程中#xff0c;我们成功搭建了 Dart 开发环境#xff0c;并运行了第一个程序。从输出 Hello, World! 的那一刻起#xff0c;你已经踏入了 Dart 的世界。然而#xff0c;一个实用的程序必然需要处理数据——存储用户信息、计算数值、操作文本集合等。这一切都…前言在上一篇教程中我们成功搭建了 Dart 开发环境并运行了第一个程序。从输出Hello, World!的那一刻起你已经踏入了 Dart 的世界。然而一个实用的程序必然需要处理数据——存储用户信息、计算数值、操作文本集合等。这一切都离不开变量和数据类型。变量是程序中用于存储数据的命名容器而数据类型则定义了变量可以存储的数据种类以及可对其执行的操作。Dart 作为一门强类型语言提供了丰富的数据类型和灵活的类型推断机制既能保证代码的健壮性又不失简洁性。本篇将全面系统地介绍 Dart 中的变量声明方式、内置数据类型数字、字符串、布尔、列表、集合、映射等以及类型转换、空安全、常量等相关概念。通过大量的实例和细节讲解帮助你建立对 Dart 类型系统的深刻理解。一、变量声明如何创建一个变量在 Dart 中声明变量有多种方式每种方式都有其适用场景。我们先从最基础的形式开始。1.1 显式类型声明如果你明确知道变量将存储什么类型的数据可以使用类型注解显式声明dartString name Alice; // 字符串类型 int age 25; // 整数类型 double height 1.75; // 浮点数类型 bool isStudent true; // 布尔类型这种方式清晰明了有利于代码的可读性和维护性尤其在团队协作或大型项目中备受推崇。1.2 使用var进行类型推断如果类型可以从初始值明显推断出来可以使用var关键字让编译器自动推断类型dartvar name Alice; // 推断为 String var age 25; // 推断为 int var height 1.75; // 推断为 double var isStudent true; // 推断为 bool一旦推断出类型该变量的类型就固定了不能再赋其他类型的值。例如dartvar value 10; // value 被推断为 int value hello; // 错误类型不匹配String 不能赋值给 int因此var并不等同于动态类型它只是让编译器替我们写类型注解而已。1.3 使用dynamic实现动态类型如果你确实需要一个可以改变类型的变量可以使用dynamic关键字。这会关闭静态类型检查允许变量在运行时持有任何类型的值dartdynamic flexible hello; print(flexible); // 输出 hello flexible 123; // 现在变成了 int print(flexible); // 输出 123 flexible true; // 现在变成了 bool然而滥用dynamic会丧失类型安全容易引发运行时错误应尽量避免仅在必要时如与动态数据交互使用。1.4 使用Object或Object?由于 Dart 中所有类型都继承自Object你也可以用Object声明变量使其能持有任何非空类型的值dartObject obj hello; obj 123; // 允许 obj null; // 错误Object 不能接受 null除非声明为 Object?如果要允许null需使用可空类型Object?这涉及到空安全我们稍后会详述。1.5 未初始化变量如果声明变量时没有初始化且没有使用可空类型则 Dart 的静态分析会报错因为变量在读取前必须被赋值。例如dartint count; // 错误非空实例变量必须初始化 print(count);若要延迟初始化可以使用late关键字后面会讲或声明为可空类型dartint? count; // OK可空类型默认值为 null print(count); // 输出 null二、数据类型总览Dart 中一切皆对象所有类型都是Object的子类。这意味着你可以调用任何对象的方法例如toString()、hashCode等。内置数据类型大致分为以下几类类别类型描述数字int,double整数和双精度浮点数字符串StringUTF-16 编码的字符序列布尔bool只有true和false列表List有序的可变集合类似数组集合Set无序且元素唯一的集合映射Map键值对集合符文RunesUnicode 码点符号Symbol用于表示标识符的符号对象空类型Null仅有一个值null下面我们逐一深入介绍。三、数字类型int 和 doubleDart 的数字类型包括int整数和double双精度浮点数它们都继承自num抽象类。num提供了基本的算术运算符和常用方法。3.1 声明与基本运算dartvoid main() { int a 10; int b 3; double c 3.14; print(a b); // 13 print(a - b); // 7 print(a * b); // 30 print(a / b); // 3.3333333333333335注意结果是 double print(a ~/ b); // 3整数除法返回整数部分 print(a % b); // 1取余 print(c * 2); // 6.28 }3.2 常用属性与方法每个数字对象都有一些有用的属性和方法例如isNaN、isFinite、isInfinite判断是否为非数字、有限值、无穷大。abs()绝对值。round()、floor()、ceil()、truncate()四舍五入、向下取整、向上取整、截断取整。toInt()、toDouble()类型转换。toString()转为字符串。dartvoid main() { double d 3.14159; print(d.round()); // 3 print(d.floor()); // 3 print(d.ceil()); // 4 print(d.truncate()); // 3 int negative -5; print(negative.abs()); // 5 print(double.infinity.isInfinite); // true }3.3 进制表示Dart 支持十进制、十六进制0x开头的字面量从 Dart 2.14 开始也支持二进制0b开头字面量。dartvoid main() { int decimal 42; int hex 0x2A; // 十六进制等于 42 int binary 0b101010; // 二进制等于 42 print([decimal, hex, binary]); // [42, 42, 42] }3.4 数值转换与解析字符串转数字使用parse方法数字转字符串使用toString()或带格式的方法。dartvoid main() { // 字符串 - 数字 int intVal int.parse(42); double doubleVal double.parse(3.14); print(intVal); // 42 print(doubleVal);// 3.14 // 数字 - 字符串 String str1 42.toString(); String str2 3.14159.toStringAsFixed(2); // 保留两位小数 print(str1); // 42 print(str2); // 3.14 // 异常处理parse 可能抛出 FormatException try { int.parse(abc); } catch (e) { print(转换失败$e); } // 更安全的转换tryParse 返回 null int? parsed int.tryParse(abc); print(parsed); // null }四、字符串类型StringDart 字符串是 UTF-16 编码的字符序列使用单引号或双引号创建。字符串是不可变的一旦创建就不能修改任何操作都会返回新字符串。4.1 字符串字面量dartvoid main() { String s1 Hello; String s2 World; String s3 可以使用双引号 在单引号内; String s4 同样可以使用单引号 在双引号内; // 三引号用于多行字符串 String multiLine 这是第一行 这是第二行 这是第三行; print(multiLine); // 也可以使用双引号的三引号 String multiLine2 多行字符串 第二行; }4.2 字符串插值使用${表达式}可以将变量或表达式的值嵌入字符串。如果表达式只是一个标识符可以省略花括号。dartvoid main() { String name Alice; int age 25; print(My name is $name, and I am $age years old.); // 更复杂的表达式需要花括号 print(Next year, I will be ${age 1} years old.); }4.3 字符串连接可以用运算符连接字符串但拼接大量字符串时效率较低推荐使用字符串插值或StringBuffer。dartvoid main() { String hello Hello; String world World; String combined hello world; print(combined); // Hello World // 相邻字符串字面量自动连接 String auto Hello World; print(auto); // Hello World }4.4 常用属性和方法字符串提供了丰富的方法以下是常用的一些length获取字符UTF-16 码元数量。isEmpty、isNotEmpty判断是否为空。toUpperCase()、toLowerCase()转换大小写。trim()去除首尾空白字符。split(分隔符)分割字符串为列表。substring(起始[, 结束])截取子串。contains(子串)、startsWith(前缀)、endsWith(后缀)检查是否包含、以什么开头/结尾。indexOf(子串)、lastIndexOf(子串)查找子串位置。replaceAll(原, 新)替换所有匹配项。replaceFirst(原, 新)替换第一个匹配项。dartvoid main() { String text Dart programming ; print(text.length); // 20包括空格 print(text.trim()); // Dart programming print(text.toUpperCase()); // DART PROGRAMMING String csv apple,banana,orange; ListString fruits csv.split(,); print(fruits); // [apple, banana, orange] print(Hello.contains(ell)); // true print(Hello.indexOf(l)); // 2 print(Hello.substring(1, 4)); // ell String dirty bad ; print(dirty.trim()); // bad }4.5 原始字符串在字符串前加r前缀可以创建原始字符串转义字符如\n不会被处理。dartvoid main() { String raw r换行符是 \n 而不是真的换行; print(raw); // 输出换行符是 \n 而不是真的换行 String normal 换行符是 \n 真的换行; print(normal); // 换行符是 \n 真的换行\n 实际产生换行 }4.6 使用正则表达式Dart 通过RegExp类提供正则表达式支持。dartvoid main() { RegExp exp RegExp(r\d); // 匹配一个或多个数字 String text abc123def456; // 是否包含匹配 print(exp.hasMatch(text)); // true // 获取第一个匹配 Match? firstMatch exp.firstMatch(text); print(firstMatch?.group(0)); // 123 // 获取所有匹配 IterableMatch matches exp.allMatches(text); for (var m in matches) { print(m.group(0)); // 依次输出 123, 456 } // 替换 String replaced text.replaceAll(exp, X); print(replaced); // abcXdefX }五、布尔类型boolDart 的布尔类型只有两个对象true和false。它们是编译时常量。dartvoid main() { bool isRaining true; bool isSunny false; // 逻辑运算 print(isRaining isSunny); // false print(isRaining || isSunny); // true print(!isRaining); // false }注意Dart 的if语句及其他需要布尔条件的地方只能接受bool类型的表达式不像 JavaScript 那样可以接受其他类型的“真值”或“假值”。例如dartString name Alice; if (name) { // 错误条件必须是布尔类型不能是 String print(name); } // 正确写法 if (name.isNotEmpty) { print(name); }六、列表 List列表List是 Dart 中最常用的集合类型它表示一个有序的元素序列可以通过索引访问。列表是可变的除非显式创建不可变列表。6.1 创建列表dartvoid main() { // 字面量方式 Listint numbers [1, 2, 3, 4]; var fruits [apple, banana, orange]; // 推断为 ListString // 空列表 ListString empty []; var empty2 String[]; // 使用泛型指定类型 // 使用 List 构造函数 var list List.filled(3, 0); // [0, 0, 0] 固定长度列表默认不可变长度 var growableList List.generate(5, (index) index * 2); // [0, 2, 4, 6, 8] // 使用扩展运算符...和空安全扩展...? var list1 [1, 2, 3]; var list2 [4, 5, 6]; var combined [...list1, ...list2]; // [1, 2, 3, 4, 5, 6] Listint? maybeNullList null; var combined2 [0, ...?maybeNullList]; // [0] 如果 maybeNullList 为 null 则忽略 // 集合中的 if 和 for集合控制流 var listWithCondition [1, 2, if (true) 3]; // [1, 2, 3] var listWithLoop [for (int i 1; i 3; i) i]; // [1, 2, 3] }6.2 访问与修改元素dartvoid main() { var fruits [apple, banana, orange]; // 通过索引访问索引从 0 开始 print(fruits[0]); // apple print(fruits[1]); // banana // 修改元素 fruits[1] grape; print(fruits); // [apple, grape, orange] // 获取长度 print(fruits.length); // 3 // 判断是否为空 print(fruits.isEmpty); // false print(fruits.isNotEmpty); // true // 获取第一个和最后一个元素 print(fruits.first); // apple print(fruits.last); // orange }6.3 常用操作列表提供了很多方法来操作元素。添加元素dartvoid main() { var list [1, 2]; list.add(3); // 末尾添加一个元素 list.addAll([4, 5]); // 添加多个 list.insert(1, 10); // 在索引 1 处插入 10 list.insertAll(2, [20, 30]); // 在索引 2 处插入多个 print(list); // [1, 10, 20, 30, 2, 3, 4, 5] }删除元素dartvoid main() { var list [1, 2, 3, 2, 4]; list.remove(2); // 删除第一个遇到的 2 print(list); // [1, 3, 2, 4] list.removeAt(1); // 删除索引 1 的元素 print(list); // [1, 2, 4] list.removeLast(); // 删除最后一个元素 print(list); // [1, 2] list.removeWhere((e) e.isOdd); // 删除所有奇数 print(list); // [2] list.clear(); // 清空列表 print(list); // [] }查找与遍历dartvoid main() { var list [10, 20, 30, 40]; // 包含 print(list.contains(20)); // true // 查找索引 print(list.indexOf(30)); // 2 print(list.lastIndexOf(20)); // 1从后往前找 // 遍历 for (var item in list) { print(item); } list.forEach((item) print(item * 2)); // 使用迭代器 var iterator list.iterator; while (iterator.moveNext()) { print(iterator.current); } }排序与反转dartvoid main() { var numbers [3, 1, 4, 1, 5, 9]; numbers.sort(); // 升序排序 print(numbers); // [1, 1, 3, 4, 5, 9] // 自定义排序 numbers.sort((a, b) b.compareTo(a)); // 降序 print(numbers); // [9, 5, 4, 3, 1, 1] var reversed numbers.reversed; // 返回一个反转的可迭代对象 print(reversed.toList()); // [1, 1, 3, 4, 5, 9] }转换操作dartvoid main() { var list [1, 2, 3]; // map对每个元素进行转换 var doubled list.map((e) e * 2).toList(); print(doubled); // [2, 4, 6] // where过滤 var evens list.where((e) e.isEven).toList(); print(evens); // [2] // expand展开嵌套列表 var pairs [[1, 2], [3, 4]]; var flattened pairs.expand((e) e).toList(); print(flattened); // [1, 2, 3, 4] // reduce归约 var sum list.reduce((value, element) value element); print(sum); // 6 // fold带初始值的归约 var product list.fold(1, (prev, e) prev * e); print(product); // 6 }6.4 不可变列表使用const关键字可以创建编译时常量的不可变列表dartvoid main() { const Listint constList [1, 2, 3]; // constList[0] 10; // 错误无法修改常量列表 // 另一种方式使用 .unmodifiable 工厂方法 var original [1, 2, 3]; var unmodifiable List.unmodifiable(original); // unmodifiable[0] 10; // 抛出 UnsupportedError }七、集合 Set集合Set是一个无序、元素唯一的容器。它不保留元素的插入顺序但某些实现如LinkedHashSet保留常用于去重和快速成员检测。7.1 创建集合dartvoid main() { // 字面量方式 SetString fruits {apple, banana, orange}; var numbers {1, 2, 3, 4}; // 推断为 Setint // 注意空集合必须指定类型或使用泛型 SetString emptySet {}; var emptySet2 String{}; // 使用构造函数 var setFromList Set.from([1, 2, 3, 2, 1]); // {1, 2, 3} var generatedSet Set.of([1, 2, 3]); }注意{}默认是Map类型除非上下文明确需要Set。所以创建空集合务必使用String{}或SetString()。7.2 常用操作dartvoid main() { var set {1, 2, 3}; // 添加元素 set.add(4); // 添加单个 set.addAll([5, 6]); // 添加多个 // 删除元素 set.remove(3); // 删除指定元素 set.removeWhere((e) e.isEven); // 删除所有偶数 // 检查包含 print(set.contains(2)); // false // 长度与空判断 print(set.length); // 2剩余 1 和 5 // 遍历 for (var item in set) { print(item); } // 集合运算 var a {1, 2, 3, 4}; var b {3, 4, 5, 6}; print(a.union(b)); // {1, 2, 3, 4, 5, 6} print(a.intersection(b)); // {3, 4} print(a.difference(b)); // {1, 2} }八、映射 Map映射Map是一个键值对集合每个键唯一值可以重复。Dart 中的Map默认是LinkedHashMap保留键值对的插入顺序。8.1 创建 Mapdartvoid main() { // 字面量方式 MapString, int scores {Alice: 95, Bob: 87}; var info {name: Charlie, age: 25}; // 推断为 MapString, Object // 空 Map MapString, int emptyMap {}; var emptyMap2 String, int{}; // 使用构造函数 var mapFrom Map.from({a: 1, b: 2}); var ofMap Map.of({a: 1, b: 2}); var fromIterables Map.fromIterables([x, y], [10, 20]); // {x: 10, y: 20} }8.2 访问与修改dartvoid main() { var scores {Alice: 95, Bob: 87}; // 通过键访问值 print(scores[Alice]); // 95 print(scores[Charlie]); // null键不存在 // 添加或修改 scores[Charlie] 90; // 添加新键值对 scores[Alice] 98; // 修改现有值 // 批量添加 scores.addAll({David: 88, Eve: 92}); // 删除 scores.remove(Bob); // 获取所有键或值 print(scores.keys); // (Alice, Charlie, David, Eve) 可迭代对象 print(scores.values); // (98, 90, 88, 92) // 检查键是否存在 print(scores.containsKey(Alice)); // true print(scores.containsValue(100)); // false // 长度与空 print(scores.length); // 4 print(scores.isEmpty); // false }8.3 遍历 Mapdartvoid main() { var scores {Alice: 95, Bob: 87, Charlie: 90}; // 遍历键值对 scores.forEach((key, value) { print($key: $value); }); // 遍历键 for (var key in scores.keys) { print($key: ${scores[key]}); } // 遍历值 for (var value in scores.values) { print(value); } }8.4 常用方法dartvoid main() { var map {a: 1, b: 2, c: 3}; // 如果键不存在提供默认值 print(map.putIfAbsent(d, () 4)); // 添加 d: 4 print(map.putIfAbsent(a, () 100)); // 键已存在返回现有值 1不修改 // 获取值或提供默认值 var value map[e] ?? 0; // 如果键不存在返回 0 print(value); // 0 // 更新值 map.update(a, (value) value 10); // 更新为 11 map.update(f, (value) 100, ifAbsent: () 50); // 键不存在添加 f: 50 // 移除满足条件的键值对 map.removeWhere((key, value) value.isOdd); // 移除值为奇数的条目 print(map); // {b: 2, d: 4, f: 50} }九、Runes 与字符Dart 字符串是 UTF-16 编码的因此对于超出基本多文种平面BMP的 Unicode 字符如表情符号一个字符可能由两个码元表示。Runes类型用于表示字符串的 Unicode 码点code points。9.1 Runes 的使用可以通过字符串的runes属性获取Runes对象实际上是Iterableint。dartvoid main() { String emoji ; // 笑脸表情码点为 U1F600 print(emoji.length); // 2因为 UTF-16 用了两个码元 print(emoji.runes.length); // 1一个码点 // 遍历所有码点 for (var rune in emoji.runes) { print(rune.toRadixString(16)); // 输出 1f600 } // 从码点构建字符串 Runes input Runes(\u{1f600} is smiling); print(String.fromCharCodes(input)); // is smiling }9.2 使用 characters 包为了更方便地处理 Unicode 字符Dart 团队提供了characters包它支持将字符串视为用户感知的字符grapheme clusters序列。虽然它不是核心库但非常实用。在pubspec.yaml中添加依赖yamldependencies: characters: ^1.2.1使用示例dartimport package:characters/characters.dart; void main() { String family ‍‍; // 家庭表情由多个码点组合而成 print(family.length); // 8UTF-16 码元数 print(family.characters.length); // 1一个字符 for (var char in family.characters) { print(char); // 输出一个完整的字符 } }十、SymbolSymbol对象表示 Dart 程序中声明的运算符或标识符。你可能永远不需要使用 Symbol但它们对于通过名称引用标识符的 API如反射非常有用。字面量语法是在标识符前加#dartvoid main() { Symbol sym1 #foo; Symbol sym2 Symbol(foo); print(sym1 sym2); // true }在实际开发中Symbol 很少直接使用了解即可。十一、类型转换不同数据类型之间经常需要转换。Dart 提供了多种转换方式。11.1 数字与字符串转换前面已经提到使用int.parse()、double.parse()或toString()。dartvoid main() { // 数字 - 字符串 int num 42; String str num.toString(); String hex num.toRadixString(16); // 转十六进制字符串 2a // 字符串 - 数字 int a int.parse(42); double b double.parse(3.14); // 带进制解析 int c int.parse(2a, radix: 16); // 42 // 安全转换 int? d int.tryParse(abc); // null }11.2 其他类型转换列表与集合互转List.from(set)或Set.from(list)列表与映射键/值互转使用map.keys.toList()、map.values.toList()或Map.fromIterable字符串与列表互转split()和join()dartvoid main() { // 列表 - 集合 var list [1, 2, 3, 2]; var set Set.from(list); // {1, 2, 3} var newList List.from(set); // [1, 2, 3] // 字符串 - 列表 var str a,b,c; var parts str.split(,); // [a, b, c] var joined parts.join(-); // a-b-c }11.3 显式类型转换强制转换对于引用类型可以使用as关键字进行向下转型。但前提是该对象确实是指定类型否则会抛出异常。dartvoid main() { Object obj hello; String str obj as String; // 正确obj 实际是 String // int num obj as int; // 抛出 CastError // 更安全的做法是使用 is 检查 if (obj is String) { print(obj.length); // 这里 obj 被自动转换为 String } }十二、类型检查与判断Dart 提供了几种运行时类型检查机制。12.1is和is!is运算符用于检查对象是否属于某种类型is!取反。dartvoid main() { var value hello; print(value is String); // true print(value is int); // false print(value is! int); // true }12.2runtimeType每个对象都有一个runtimeType属性返回其运行时类型的Type对象。不过通常不推荐用runtimeType做逻辑判断因为Type对象不一定能跨隔离区比较且不利于性能。dartvoid main() { var value 42; print(value.runtimeType); // int }12.3 类型转换与检查的最佳实践尽量使用is配合自动类型提升而不是使用as然后捕获异常。dartvoid printLength(Object obj) { if (obj is String) { print(obj.length); // 自动提升为 String } else { print(Not a string); } }十三、常量与只读变量final 和 constDart 提供了两种方式定义不可变变量final和const。13.1 finalfinal变量只能被赋值一次。它可以在运行时确定值。dartvoid main() { final name Alice; // 类型推断为 String final int age 25; // 显式类型 // name Bob; // 错误final 变量只能赋值一次 final now DateTime.now(); // 运行时确定合法 }13.2 constconst变量是编译时常量必须在编译时就确定值。const不仅可以用于变量还可以用于创建常量值。dartvoid main() { const pi 3.14159; // 编译时常量 const double e 2.71828; // const invalid DateTime.now(); // 错误DateTime.now() 不是编译时常量 // const 可以用于创建不可变集合 const list [1, 2, 3]; // 整个列表在编译时固定 const map {a: 1, b: 2}; // 注意const 变量指向的常量对象本身不可变且所有元素也必须是常量 // list.add(4); // 错误不能修改常量列表 }13.3 final 与 const 的区别特点finalconst赋值时机运行时第一次使用时确定编译时确定是否可以由函数确定可以如final now DateTime.now();不可以必须编译时常量内存分配普通变量每次创建新对象常量池化相同常量共享同一份内存集合元素是否可变集合本身可变除非用 final 指向可变集合集合本身及其元素均不可变dartvoid main() { final list1 [1, 2, 3]; list1.add(4); // OKfinal 只保证变量不能指向新对象但对象本身可变 const list2 [1, 2, 3]; // list2.add(4); // 错误常量列表不可变 final list3 const [1, 2, 3]; // final 变量指向常量列表不可变 // list3.add(4); // 错误 }13.4 编译时常量与const构造函数在 Dart 中类可以定义const构造函数使得对象可以在编译时创建。例如const Point(0, 0)。这在 Flutter 中很常见用于优化性能。十四、空安全与默认值自 Dart 2.12 起语言强制启用了空安全sound null safety。这意味着默认情况下变量不能为null除非显式声明为可空类型。14.1 可空类型与非空类型在类型后面加?表示该变量可以为null。dartvoid main() { String name Alice; // 非空不能赋 null // name null; // 错误 String? nullableName Bob; nullableName null; // 允许 // 使用可空变量前需判空 print(nullableName?.length); // 如果为 null 则返回 null安全调用 // print(nullableName.length); // 错误不能直接访问可能为 null 的属性 }14.2 类型提升Dart 编译器会根据条件自动将可空类型提升为非空类型。dartvoid printLength(String? text) { if (text ! null) { print(text.length); // 这里 text 被提升为 String } else { print(text is null); } }14.3 延迟初始化late如果你确定某个非空变量在使用前一定会被赋值但无法在声明时立即初始化可以使用late关键字。dartlate String description; void init() { description This is a late variable; } void main() { init(); print(description); // 此时已初始化 // 如果忘记初始化就使用会抛出 LateInitializationError }late也可以用于延迟初始化开销较大的对象dartlate final int heavy _computeHeavy(); int _computeHeavy() { print(Computing...); return 42; } void main() { print(Before access); print(heavy); // 此时才调用 _computeHeavy }14.4 必要参数required在命名参数中可以使用required关键字强制调用者传入该参数。这在构造函数中特别常见。dartclass Person { final String name; final int age; Person({required this.name, required this.age}); } void main() { // var p Person(); // 错误缺少必要参数 var p Person(name: Alice, age: 25); // 正确 }14.5 默认值对于可空类型未初始化时的默认值是null。对于非空类型必须显式初始化。局部变量必须在使用前赋值实例变量可以在声明时、构造函数初始化列表中或构造函数体中初始化。dartclass Example { int a 0; // 声明时初始化 int b; // 错误非空实例变量必须初始化 int? c; // 可空默认为 null late int d; // 延迟初始化必须在使用前赋值 }十五、变量作用域与生命周期变量的作用域由声明位置决定。Dart 采用词法作用域静态作用域即变量的作用域在代码编写时确定。dartvoid main() { var x 10; if (true) { var y 20; print(x); // 可以访问外部变量 x print(y); // 可以访问 y } // print(y); // 错误y 不在作用域内 }生命周期局部变量在函数执行时创建函数返回后销毁。全局变量顶层变量在程序启动时创建直到程序结束才销毁。十六、总结在本篇中我们系统地学习了 Dart 中的变量声明和各类数据类型。从数字、字符串到集合、映射从类型转换到空安全这些都是构建 Dart 程序的基石。掌握这些知识后你已经能够编写处理各种数据的代码了。然而这仅仅是开始。数据类型是静态的而程序的逻辑需要动态的控制流和函数来驱动。在下一篇中我们将深入探讨运算符与控制流包括各类运算符的详解与优先级条件语句if、switch循环语句for、while、do-while异常处理以及一些流程控制的高级话题敬请期待