今天主要會說明 Dart 各種變數宣告的方法及 Dart 的基本型別。
dart主要有四種方式宣告變數
分別為 const
var
type
final
const a = 10;
final b = 10;
var c = 123;
string d = '123'
首先 const
及 final
這兩個宣告方式就跟 JS 的 const
一樣是用於宣告一個「不可變的數值」。
亦即這之後不能將這些變數重新賦值。那為什麼還有分 const
及 final
呢?最主要的差異是const
更為嚴格,它代表的是「編譯時的常數」,什麼意思?
int getNumber(a){
const b = a;
return b;
}
void main() {
int a = 10;
getNumber(a);
}
這邊會看到我在 getNumber
裡使用 const
宣告b 將a的值給b ,但這無法通過 Dart的編譯器。因為 const
的是要在編譯期間就已經是有數值的常數。所以const a = b ;
這種直到runtime才完成初始化的事情是不被允許的。
所以 const
較常用地方是一些**「永遠不變的數值」**像是 const pi = 3.14;
或者const textColor = Color.fromARGB(255, 66, 165, 245)
而final
就會比較接近於 js裡const的用法,就是在接下來的runtime這個數值都會是immutable。
var
就可以想成是 JS 的 let
就是可以被變更的變數。但最大的差異就是Dart在宣告完後就會進行型別推斷(Type inference)意即如果 var b = 10
後不能在進行 b='123'
了(可以想像成ts一樣),因為在初始化後就會將 b的type推斷為int
了所以不能將string
給b了
而直接用型別宣告的方式就跟 var
差不多一樣了就不詳細介紹了,就差別是一個是交由dart 推斷型別一個是我們自己宣告型別。
但其實在變數宣告以及初始化還有一些細節,這部分就留到之後提到「null safety」時再來說明。
Dart 有以下基本型別
int
, double
String
bool
List
Set
Map
Runes
Symbol
Null
基本的 int
(整數)、 double
(浮點數)、 String
(字串)、bool
(布林)就不多做介紹。
以下只稍微介紹一下比較常用到的 List
、 Set
、 Map
而List
就是其他語言中的陣列(Array)在Dart中的最基礎形式如下:
final listA = [1,2,3,4]
在Dart裡的List有提供其他建構子(constructor) .filled
及 . generate
都是可以用來動態產生list的建構子:
final listB = List.filled(3, 1); // [0, 0, 0]
final listC = List.generate(3, (index) => index); // [0, 1, 2]
差異是 filled
每一個element都是同一個reference而 generate
不是。
listB[0].add(1);
listC[0].add(1);
print(listB); // [[1], [1], [1]]
print(listC); // [[1], [], []]
以及其中有一個控制這個List是不是可變長度的named parameters growable
的預設值不一樣。但詳細就不贅述了有興趣的讀者可以到官方的API文件閱讀。
定義為「沒有索引值且不可重複的集合」
我們可以用 {}
來做初始化並用逗號分隔每一個元素:
final setA = {0, 1, 2, 3, 4};
print(setA); // {0, 1, 2, 3, 4}
也可以利用 Set.from
放入一個可迭代的值來產生 Set
final listX = [0, 1, 0, 0, 1, 2, 1, 3, 4, 5, 6, 7];
final setB = Set.from(listX);
print(setB); //{0, 1, 2, 3, 4, 5, 6, 7}
因為 Set
裡並沒有存放索引值,所以我們無法直接存取特定位置的值。但因為 Dart 底層實作的關係,其實還是有將 Set
的順序存入,也因此我們迭代時是會跟初始化時的順序一樣:
setB.forEach((element) {
print(element);
});
// 0
// 1
// ...
// 7
Map
就是有key-value型式的資料結構,而且key不能重複,也因為了有了key所以我們有辦法直接存取Map
。
final mapA = {
'a': 1,
'b':2,
'c':3
};
mapA['a'] // 1
當然Map
也有提供其他的constructor:
final mapB = Map.fromIterable([1, 2, 3, 4]);
// {1: 1, 2: 2, 3: 3, 4: 4}
final valueList = [0, 1, 2];
final keyList = ['z', 'x', 'c'];
final mapC = Map.fromIterables(keyList, valueList);
// {z: 0, x: 1, c: 2}
當然這些資料結構還有其他API可以介紹,但我覺得還是等到之後實際有用到時在一起介紹好了,有興趣的讀者可以先查閱Dart API的文件。
泛型(generic)最簡單的解釋大概就是型別有了參數。
通常都是使用 <>
來實作,像是List的實作是 List<E>
而這個E就是我們可以傳入的型別:
final intList = <int>[1, 2, 3, 4];
final stringList = <String>['1', '2', '3', '4'];
而當我們使用了不一樣的型別時就會跳出 Error,像是我在 List<String>
裡放入一個 int
就會跳出以下錯誤:
當然我們也可以運用到其他地方像是 Class
或者 Fucntion
上
E ientityFunc<E>(E e) => e;
class A<T> {
T? value;
}
print(ientityFunc<int>(2)); // 2
final a = A<int>();
a.value = 'string'; // 這行會出錯因為 A傳入的是 int type
而關於型別有一些進階應用像是利用 typedef
對Function的型別做更進一步的抽象
typedef Identity<E> = E Function(E e);
這行的意思就是我定義了一個型別叫做Identity,而這個型別代表的意義就是他是一個會回傳type E
的 Function
且只有一個type E
的參數。
class Utils<T> {
Identity<T> ientity = (x) => x;
}
final utils = Utils<int>();
print(utils.ientity(1));
我們可以從vs code中看到因為我們傳入了 int
所以 utils.ientity
也變成有一個 int
參數且會回傳 int
的 Function
這次的內容稍微有點雜亂,這類比較基礎的語法在沒有實際範例來看總會有一種「到底什麼時候會用到這些語法」的感覺。
今天的程式碼有放到github上,有興趣的讀者可以clone下來跑跑看。
https://github.com/zxc469469/dart-playground/tree/Day04/type
明天會開始講到 Dart/Flutter 裡最常用到的東西:class
參考資料