## 空安全
版本 2.12 之后需要解决空安全问题
### 空安全的原则
Dart 的空安全支持基于以下三条核心原则:
* **默认不可空**。除非你将变量显式声明为可空,否则它一定是非空的类型。我们在研究后发现,非空是目前的 API 中最常见的选择,所以选择了非空作为默认值。
* **渐进迁移**。你可以自由地选择何时进行迁移,多少代码会进行迁移。你可以使用混合模式的空安全,在一个项目中同时使用空安全和非空安全的代码。我们也提供了帮助你进行迁移的工具。
* **完全可靠**。Dart 的空安全是非常可靠的,意味着编译期间包含了很多优化。如果类型系统推断出某个变量不为空,那么它 **永远** 不为空。当你将整个项目和其依赖完全迁移至空安全后,你会享有健全性带来的所有优势——更少的 BUG、更小的二进制文件以及更快的执行速度。
### 条件成员访问运算符 ?.
如果调用者是null, 则返回null, 如果 调用者不是null 则返回 调用结果
~~~dart
String s = 'e';
String? s1 = null; // String? 表示值可为 null
print(s?.length); // 1
print(s1?.length); // null
~~~
### 判空赋值运算符 ??
?? 前边为 null 则返回 ?? 后面的值
可用来加默认值
~~~dart
String? s1 = null;
print(s1?.length ?? "默认值"); // 默认值
~~~
## 变量
Dart 是代码类型安全的语言,但是也支持类型推断。**风格建议指南** 中的建议,通过 var 声明局部变量而非使用指定的类型。
~~~dart
// 基于类型推断的变量声明
var name = 'Voyager I';
var year = 1977;
var antennaDiameter = 3.7;
var flybyObjects = ['Jupiter', 'Saturn', 'Uranus', 'Neptune'];
var image = {
'tags': ['saturn'],
'url': '//path/to/saturn.jpg'
};
// 如果一个对象的引用不局限于单一的类型,
// 可以将其指定为 Object(或 dynamic)类型。
Object name = 'Bob';
// 指定类型
String name = 'Bob';
~~~
### 默认值
在 Dart 中,未初始化以及可空类型的变量拥有一个默认的初始值 null。在 Dart 中一切皆为对象,数字也不例外。
### Final 和 Const
一个 final 变量只可以被赋值一次;一个 const 变量是一个编译时常量(const 变量同时也是 final 的)。顶层的 final 变量或者类的 final 变量在其第一次使用的时候被初始化。
实例变量 可以是 final 的但不可以是 const,使用关键字 const 修饰变量表示该变量为 编译时常量。如果使用 const 修饰类中的变量,则必须加上 static 关键字,即 static const(译者注:顺序不能颠倒)。在声明 const 变量时可以直接为其赋值,也可以使用其它的 const 变量为其赋值。
## 内置类型
~~~dart
Numbers (int, double)
Strings (String)
Booleans (bool)
Lists (也被称为 arrays)
Sets (Set)
Maps (Map)
Runes (常用于在 Characters API 中进行字符替换)
Symbols (Symbol)
The value null (Null)
~~~
### Number
Dart 支持两种 Number 类型: int 和 double
int:整数值,长度不超过64位,取值范围:在 DartVM 上其取值位于 -263 至 263 - 1 之间。在 Web 上,整型数值代表着 JavaScript 的数字(64 位无小数浮点型),其允许的取值范围在 -253 至 253 - 1 之间。
double:64 位的双精度浮点数字,且符合 IEEE 754 标准。int 和 double 都是 num 的子类。
### String
~~~dart
// 代码中文解释
var s1 = '使用单引号创建字符串字面量。';
var s2 = "双引号也可以用于创建字符串字面量。";
var s3 = '使用单引号创建字符串时可以使用斜杠来转义那些与单引号冲突的字符串:\'。';
var s4 = "而在双引号中则不需要使用转义与单引号冲突的字符串:'";
~~~
在字符串中,请以 ${表达式} 的形式使用表达式,如果表达式是一个标识符,可以省略掉 {}。如果表达式的结果为一个对象,则 Dart 会调用该对象的 toString 方法来获取一个字符串。
~~~dart
var s = '字符串插值';
assert('Dart 有$s,使用起来非常方便。' == 'Dart 有字符串插值,使用起来非常方便。');
assert('使用${s.substring(3,5)}表达式也非常方便' == '使用插值表达式也非常方便。');
~~~
使用三个单引号或者三个双引号也能创建多行字符串:
~~~dart
// 代码中文解释
var s1 = '''
你可以像这样创建多行字符串。
''';
var s2 = """这也是一个多行字符串。""";
~~~
在字符串前加上 r 作为前缀创建 “raw” 字符串(即不会被做任何处理(比如转义)的字符串):
~~~dart
// 代码中文解释
var s = r'在 raw 字符串中,转义字符串 \n 会直接输出 “\n” 而不是转义为换行。';
~~~
### 字符串和数字之间的转换
注:像 Java String n = "s" + 1; 这种 字符串和数字拼接,自动就会变成字符串的方式,在dart中不支持,会报错。(A value of type 'int' can't be assigned to a variable of type 'String'.)
~~~dart
// String -> int
var one = int.parse('1');
assert(one == 1);
// String -> double
var onePointOne = double.parse('1.1');
assert(onePointOne == 1.1);
// int -> String
String oneAsString = 1.toString();
assert(oneAsString == '1');
// double -> String
String piAsString = 3.14159.toStringAsFixed(2);
assert(piAsString == '3.14');
// 整型字面量将会在必要的时候自动转换成浮点数字面量:
double z = 1; // Equivalent to double z = 1.0.
~~~
### List
~~~dart
var list = [1, 2, 3];
~~~
Dart 的集合类型的最后一个项目后添加逗号。这个尾随逗号并不会影响集合
Dart 在 2.3 引入了 扩展操作符(...)和 空感知扩展操作符(...?),它们提供了一种将多个元素插入集合的简洁方法。
~~~dart
var list = [1, 2, 3];
var list2 = [0, ...list];
assert(list2.length == 4);
~~~
如果扩展操作符右边可能为 null ,你可以使用 null-aware 扩展操作符(...?)来避免产生异常:
~~~dart
var list;
var list2 = [0, ...?list];
assert(list2.length == 1);
~~~
Dart 还同时引入了 **集合中的 if** 和 **集合中的 for** 操作,在构建集合时,可以使用条件判断 (if) 和循环 (for)。
下面示例是使用 **集合中的 if** 来创建一个 List 的示例,它可能包含 3 个或 4 个元素:
~~~dart
var promoActive = true; // true 时 nav 是4个值, false 则3个
var nav = ['Home', 'Furniture', 'Plants', if (promoActive) 'Outlet'];
~~~
下面是使用 **集合中的 for** 将列表中的元素修改后添加到另一个列表中的示例:
~~~dart
var listOfInts = [1, 2, 3];
var listOfStrings = ['#0', for (var i in listOfInts) '#$i'];
for (int i = 0; i < listOfStrings.length; i++) {
print('hello ${listOfStrings[i]}');
// hello #0
// hello #1
// hello #2
// hello #3
}
~~~
### Set
Set 字面量来创建一个 Set 集合的方法:
~~~dart
var halogens = {'fluorine', 'chlorine', 'bromine', 'iodine', 'astatine'};
print(halogens.runtimeType.toString()); // _LinkedHashSet<String>
~~~
可以使用在 {} 前加上类型参数的方式创建一个空的 Set,或者将 {} 赋值给一个 Set 类型的变量
~~~dart
var names = <String>{};
// Set<String> names = {}; // This works, too.
// var names = {}; // Creates a map, not a set.
~~~
Set 还是 map? Map 字面量语法相似于 Set 字面量语法。因为先有的 Map 字面量语法,所以 {} 默认是 Map 类型。如果忘记在 {} 上注释类型或赋值到一个未声明类型的变量上,那么 Dart 会创建一个类型为 Map 的对象。
### Map
Map 字面量创建 Map 的例子
~~~dart
var gifts = {
// Key: Value
'first': 'partridge',
'second': 'turtledoves',
'fifth': 'golden rings'
};
var nobleGases = {
2: 'helium',
10: 'neon',
18: 'argon',
};
~~~
Map 可以像 List 一样支持使用扩展操作符(... 和 ...?)以及集合的 if 和 for 操作。
## 函数
~~~dart
bool isNoble(int atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
// 返回值也可以不定义
isNoble(atomicNumber) {
return _nobleGases[atomicNumber] != null;
}
// 函数体内只包含一个表达式,可以用箭头函数简写,是和js一样的双箭头,不是二笔Java的单箭头->
bool isNoble(int atomicNumber) => _nobleGases[atomicNumber] != null;
注:在 => 与 ; 之间的只能是 表达式 而非 语句。比如你不能将一个 if语句 放在其中,但是可以放置 条件表达式。
~~~
### 参数
函数可以有两种形式的参数:必要参数 和 可选参数。必要参数定义在参数列表前面,可选参数则定义在必要参数后面。可选参数可以是 命名的 或 位置的。
定义函数时,使用 {参数1, 参数2, …} 来指定命名参数:
当你调用函数时,可以使用 参数名: 参数值 的形式来指定命名参数。例如:
~~~dart
void enableFlags({bool? bold, bool? hidden}) {
print(bold);
print(hidden);
}
enableFlags(bold: true, hidden: false);
~~~
虽然命名参数是可选参数的一种类型,但是你仍然可以使用 required 来标识一个命名参数是必须的参数,此时调用者必须为该参数提供一个值。例如:
~~~dart
const Scrollbar({Key? key, required Widget child})
~~~
### 可选的位置参数
使用 \[\] 将一系列参数包裹起来作为位置参数:
~~~dart
String say(String from, String msg, [String? device, String? device1="默认值"]) {
var result = '$from says $msg';
if (device != null) {
result = '$result with a $device .. $device1';
}
return result;
}
print(say('Bob', 'Howdy', '555', '5552142'));
// Bob says Howdy with a 555 .. 5552142
第一个参数赋值给 from, 第二个赋值给 msg,依次赋值
此例子中, 默认值加到 from和msg 会报错
~~~
### 函数是一级对象
可以将函数作为参数传递给另一个函数,也可以将函数赋值给一个变量。
~~~dart
void printElement(int element) {
print(element);
}
var list = [1, 2, 3];
// Pass printElement as a parameter.
list.forEach(printElement);
var loudify = (msg) => '!!! ${msg.toUpperCase()} !!!';
assert(loudify('hello') == '!!! HELLO !!!');
~~~
### 匿名函数
可以创建一个没有名字的方法,称之为 匿名函数、 Lambda 表达式 或 Closure 闭包。
## 类
对象的 成员 由函数和数据(即 方法 和 实例变量)组成。方法的 调用 要通过对象来完成,这种方式可以访问对象的函数和数据。
使用 ?. 代替 . 可以避免因为左边表达式为 null 而导致的问题:
~~~dart
// If p is non-null, set a variable equal to its y value.
var a = p?.y;
~~~
[dart 中文文档](https://dart.cn/samples)