## 空安全
版本 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)