前面有提到 Dart 是 Flutter 所使用的核心語言。因此在我們投入 App 開發前,先來了解一下 Dart 的一些基礎語法吧!
跟許多程式語言相同,Dart 也有推出類似 Playground 的線上編譯器,名叫 DartPad。您可以盡情在裡面嘗試 Dart 的語法。DartPad 傳送門
在談論變數型態前,我們先來討論幾個宣告變數的修飾字。
var
我們可以使用 var
來進行變數宣告,Dart 會自動的針對給定的值來推定型態,並且該型態一旦推論完後,便不得再更改
// name 已經自動推論型態為 String
var name = 'Micro Jordan';
// 此時會報錯,因為嘗試將 Int 型態的值存入 String 的變數中
name = 1;
dynamic
與 Object
Object
是所有型態的根類別,所以每種型態都是 Object
的子類別。也就意味著我們可以將任何值放入以 Object
宣告的變數。dynamic
是 Dart 中的動態型別,不為在編譯時進行型別檢查,也就意味著可以自由的變換型態。
// Object 因為是根類別,所以什麼型態都能接受
Object obj1 = "I'm an object";
obj1 = 1;
// dynamic 則是自動變化,現在是 String
dynamic obj2 = "I'm an dynamic";
// 現在又變成 Int
obj2 = 1;
// 此時 Dart 會標註此行錯誤,原因是 Object 並沒有提供 length 這個屬性
print(obj1.length);
// 由於是 dynamic 讓編譯器跳過檢查,不過執行的時候就會真的出錯了
print(obj2.length);
final
與 const
有時我們會希望宣告某個值為常數,這時我們可以考慮使用 final
或 const
。兩者皆為把某值設為常數的語法,差距在於 const
設定的值在編譯時會直接被替換為某值,因此要使用 const
時請確保你要設定的值在程式執行前就已經確定好;相反的 final
雖然也是設定變數,但可以是在運算後得到的結果。
// 假設我們在根據資料筆數計算總頁數
// 每頁固定 10 筆資料,這在編譯前便可以確定,因此宣告為 const
const pageSize = 10;
// 但由於資料筆數不固定,因此 totalPage 無法在編譯前就先行確定,需要經過運算 total / pageSize 才可以得出總頁數
// 因此宣告為 final
final totalPage = 20;
null-safety
null
代表著「無」,在 Dart 中用於標示某個變數或對象的值不存在,並且可以被賦值給任何型態的變數。Null safety
強制區分了 null
與 non null
的值。在 Dart 預設中所有類型皆預設為 non null
,這也意味不能接收 null
值。若需要使用空值,則必須將該類型加入 ?
修飾,來表示該變量為 nullable
。透過這樣的分別能使程式碼更加安全而減少出現預期外的錯誤。
舉例來說,當你預期取得的為一個 int
變數但卻獲得 null
時,程式就會在執行時產生 runtime error
。因此正確應將該變數宣告為 int?
。
截至寫文章的今天 Dart 的版本為 3.1.1
,自從 3.0
版本開始, Dart 強制採用 null-safety
,這可以非常有效的避免寫出執行時產生難以預料結果的程式碼。
我們透過例子來說明
// 在未有 null safety 前,此函式的傳入型態可能是 null 或 non null
bool isEmpty(String string) {
// 當 string 為 null 時,會因為沒有 length 屬性而出現 runtime error
return string.length == 0;
}
main() {
isEmpty(null);
}
但有了 null safety 後便可以更有彈性的去調用這個函式
// String? 表示該值可為 String 或 null 皆可
bool isEmpty(String? string) {
if (string != null) {
return string.length == 0;
}
return false;
}
void main() {
isEmpty(null);
}
在 Dart 中,與其他語言相同也提供了幾種常見基本資料型態,以下將一一介紹:
Dart 的數字型態定義為 num
class,底下有兩個子型態分別為
int
- 整數型態,至於整數的範圍則要取決於執行的平台double
- 64 bit 的雙精度浮點數double
是 num
的一種 ✅double
也是 Object
中的一種 ✅int
是 double
的一種 ❌// 在 dartpad 中試試吧!
// 宣告變數方式與多數語言相同
//[變數型態] [變數名稱] = [變數值];
int a = 1; // 標註實際型態
num b = 1; // 標註父類別型態
Object c = 1; // 標註祖父(?)類別型態
print(a.runtimeType);
print(b.runtimeType);
print(c.runtimeType);
// 以上三個變數在執行時都會被認定為 int 型態
// 這時候 dartpad 應該會自動把 a 用紅色底線標著
// 因為不能將 double 的值放到 int 變數中
a = 1.2;
// 這裡就不會跳錯,因為 double 也屬於 num 的子類別,因此可以自由的轉型
b = 1.2;
// 此外 Dart 也提供了一種自動推論型態的變數宣告方式
var d = 1;
// 這樣其實 Dart 也能自動的判別出 x 的型態為 int
print(d.runtimeType);
// 但是謹記!! var 會自動推論型態,一但給定型態後便無法再進行轉換
d = 1.2;
Dart 的字串是以 UTF-16 編碼為單位,用單引號或雙引號括起來的都可以是字串。
String contest = '鐵人賽';
String name = "Micro Jordan";
// 串接字串也很簡單
print('2023 ' + contest + ' ' + name); // 2023 鐵人賽 Microjordan
// 也可以這樣做,就不用一直加來加去
print('2023 $contest $name');
// 若要運算則必須寫成 ${expression},例如:
int year = 2021;
print('${year + 2} $contest $name');
僅有 true
或 false
兩種值,用於條件判斷式
String message = "Hello Flutter";
// 在 Flutter 中與多數程式語言一樣使用 "==" 作為判斷前後是否相符的運算子
// 所以這行看的順序是先看 message 變數是否與 "Hello Flutter" 的字串相符,並將 true / false 放入 isGreeting 變數中
bool isGreeting = message == "Hello Flutter";
// true
print(isGreeting);
Unicode 是國際標準的字符集,編碼了世界上大多數的文字。因此無論是數字、英文字母、中文字甚至是表情符號 emoji 都可以對應要一組編碼,並且只要採用 Unicode 標準的皆通用。
前面有提到 Dart 的 String 型態使用的 UTF-16 為編碼單位,我們這裡並不細講編碼的細節,我們只需要知道 UTF-16 有編碼的極限,也就意味著有些字符並無法被容納在單個 UTF-16 編碼之中。
俗稱🐎:一個便當吃不夠,可以吃兩個!!一個 UTF-16 字元裝不下,可以用第二個來裝。
在 Dart 中定義了 runes
的 class,讓我們可以看到每個字元的編碼
var string = '鐵人賽';
print(string.runes.toList()); // [37941, 20154, 36093]
print(string.length); // 3
var emoji = '😊';
print(emoji.runes.toList()); // [128522]
print(emoji.length); // 2
從上述雖然我們得到了 😊 符號的編碼,但由於一個 UTF-16字元裝不完,需要兩個才能完整的表示該字元。也因此在印出字串長度時,明明是一個字元卻會得到 2 這個結果。
所以這時候就需要用到 Flutter 官方提供的 characters
套件,使用 character API
就可以正常的印出正確的長度拉。
import 'package:characters/characters.dart';
void main() {
var emoji = '😊';
print(emoji.runes.toList()); // [128522]
print(emoji.length); // 2
print(emoji.characters.length); // 1
}
Symbol 是一種代表符號的特殊類型,通常用於識別 Dart 程式碼中的符號名稱。但因為並非常用的資料型態,因此我們就先略過。
我們上面介紹了數種變數型態,都圍繞在單一物件的範疇,下部分我們會談談 Dart 中將多個單一物件包在一起時有哪些東西可以使用。
需要 Dart version >= 3.0 才支援
Records 是可將多個物件放入一個單一物件,並且大小是固定的。
// 宣告方式
var ithome = {2023, name: 'Micro Jordan', language: 'Flutter', '鐵人賽'};
// 取用每個變數
print(ithome.$1); // 因為 2023 並沒有名稱可以調用,所以用 $1 表示取用第 1 個匿名的值
print(ithome.name); // Microjordan
print(ithome.language); // Flutter
print(ithome.$2); // 取用第 2 個匿名的值
我們也可以標註 record 的型態
// 宣告 phone 是一個由 (String, int) 組成的 record
(String, int) phone1;
phone1 = ('iPhone', 14);
// 使用加上 name 讓給定的值到對應的 name 時就要加上 {} 才能宣告
({String model, int generation}) phone2;
phone2 = (model: 'iPhone', generation: 14);
與多數的程式語言相同,Dart 也有提供陣列的使用,也與多數語言相同,陣列統一是從 index 為 0 的地方開始存放。
// 宣告陣列的方式
var nums = [1, 2, 3, 4];
// 此時由於是 var,因此會自動推論為 List<int> 型態,也就是存放整數型態的陣列
// 陣列的操作
nums[0] = 20; // 可直接更動某值 [20, 2, 3, 4]
nums.add(2); // 新增元素至陣列尾端 [20, 2, 3, 4, 2]
nums.remove(2); // 刪除第一個值為 2 的元素 [20, 3, 4, 2]
nums.removeAt(0); // 刪除 index 為 0 的元素 [3, 4, 2]
nums.insert(2, 3333); // 在 index 為 2 的地方插入 3333. [3, 4, 3333, 2]
在 Dart 所定義的 List 中其實有分兩種,一種為固定長度;另一種是可變長度的。預設是採用可變長度的 List,也因此可以使用 add
、remove
這些變動陣列長度的 API。
https://api.dart.dev/stable/3.1.1/dart-core/List-class.html
Sets 是物件的集合,並且內容不重複。換句話說就是同樣的內容,只會在 Set 中看到一次,如果有第二個相同的內容要加入時,Set 會判別到相同內容而略過操作。
在 Dart 中其實提供多種 Set
的實作,內容不重複的精神是一樣的,不過在於實現的資料結構不同而使的有不同特性。
Set
實作,內容會按照存放的順序而依序存放。self-balancing binary tree
實作,在保證存放速度的同時,也使存放的內容得以有序的存放。// 因為 HashSet / SplayTreeSet 在 collection 中,因此要先引入
import 'dart:collection';
void main() {
var set1 = <String>{'Zebra', 'Ant', 'Ballon', 'Candy'};
print(set1); // 按照輸入順序輸出
var set2 = HashSet<String>();
set2.addAll({'Zebra', 'Ant', 'Ballon', 'Candy'});
print(set2); // 原則上是無序輸出,存放順序是按照 hashCode 來決定,所以這裡看不出來無序的部分
var set3 = SplayTreeSet<String>();
set3.addAll({'Zebra', 'Ant', 'Ballon', 'Candy'});
print(set3); // 按照字母的順序輸出,因為字母順序是 A -> B -> C -> Z
// set 的基本操作
set1.add('Banana'); // 加入元素
set1.remove('Zebra'); // 刪除元素
print(set1.contains('Banana')); // 是否包含元素
print(set1.length); // set 的長度
}
Map
這個資料型態跟字典一樣,我們會預期查找字典時,一個單字對應到的是一個解釋。因此 Map
在設定時需要同時給定 Key
(單字) 和 Value
(對應的解釋),並且 Key
是唯一且不重複的。
// 定義 dictionary 變數為 Map 型態,且 Key 為 String / Value 也為 String
var dictionary = Map<String, String>();
// 加入元素至 Map 中
dictionary['apple'] = '平果';
dictionary['banana'] = '香蕉';
// 直接修改 Key 對應的 Value
dictionary['apple'] = '蘋果';
因為 Map 中的 Key 是唯一且不重複的,聽起來跟 Set
的特性很像對吧!所以正是因為如此,Map 也提供了三種 Map 的實作:
1. LikedHashMap
2. HashMap
3. SplayTreeMap
細節我們也不談,總之就大同小異拉~
因為篇幅的關係我們沒辦法一一的講述每個型態底下各自的方法,畢竟我們的目的的實戰,而不是翻譯官方文件。簡單總結一下今天的內容:
- 要充分瞭解各修飾字的用途
1. int - 整數
2. double - 倍精度浮點數,也就是可用來表示小數點
3. String - 字串,用單引號或雙引號刮起來的都是
4. Boolean - 布林值,用於判斷式
5. Records - 固定長度可存放多個物件的單一物件,然後要 Dart version >= 3 才支援
6. Lists - 陣列,長度是可變動的
7. Sets - 集合,用於存放不重複的內容
8. Map - 字典,需要一對 key / value 來表示,key 值不得重複
今天的內容如果是有程式基礎的人來看應該很快就可以瀏覽過去,未來 2 ~ 3 天也是著重在 Dart 語法相關的介紹,畢竟工欲善其事,必先利其器,先打好對這個語言的基礎絕對不是壞事拉 XD
明天我們會講解關於迴圈、判斷式、函式的相關內容。明天見囉~