iT邦幫忙

2021 iThome 鐵人賽

DAY 4
0
Modern Web

Flutter web 的奇妙冒險系列 第 4

Day04 | Dart基本介紹 - 變數宣告與基本型別

今天主要會說明 Dart 各種變數宣告的方法及 Dart 的基本型別。

變數宣告

dart主要有四種方式宣告變數

分別為 const var type final

const a = 10;
final b = 10;
var c = 123;
string d = '123' 

首先 constfinal 這兩個宣告方式就跟 JS 的 const 一樣是用於宣告一個「不可變的數值」。

亦即這之後不能將這些變數重新賦值。那為什麼還有分 constfinal 呢?最主要的差異是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 有以下基本型別

  • intdouble
  • String
  • bool
  • List
  • Set
  • Map
  • Runes
  • Symbol
  • Null

基本的 int (整數)、 double (浮點數)、 String (字串)、bool (布林)就不多做介紹。

以下只稍微介紹一下比較常用到的 ListSetMap

List

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文件閱讀。

Set

定義為「沒有索引值且不可重複的集合」

我們可以用 {} 來做初始化並用逗號分隔每一個元素:

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

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 就會跳出以下錯誤:

https://ithelp.ithome.com.tw/upload/images/20210917/20112906KUc3FvuRkQ.png

當然我們也可以運用到其他地方像是 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 EFunction 且只有一個type E 的參數。

class Utils<T> {
  Identity<T> ientity = (x) => x;
}

final utils = Utils<int>();
print(utils.ientity(1));

我們可以從vs code中看到因為我們傳入了 int 所以 utils.ientity 也變成有一個 int 參數且會回傳 intFunction

https://ithelp.ithome.com.tw/upload/images/20210917/201129065GdsLyzk9l.png


這次的內容稍微有點雜亂,這類比較基礎的語法在沒有實際範例來看總會有一種「到底什麼時候會用到這些語法」的感覺。

今天的程式碼有放到github上,有興趣的讀者可以clone下來跑跑看。
https://github.com/zxc469469/dart-playground/tree/Day04/type

明天會開始講到 Dart/Flutter 裡最常用到的東西:class


參考資料

  1. https://dart.dev/guides/language/language-tour#built-in-types
  2. https://api.dart.dev/stable/2.14.2/dart-core/dart-core-library.html

上一篇
Day 3 | Dart 基本介紹 - Dart vs JS
下一篇
Day05 | Dart基本介紹 - class、factory
系列文
Flutter web 的奇妙冒險30

尚未有邦友留言

立即登入留言