探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南

探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南

Overview of Content

在這篇文章中,我們將深入探討 Dart 語言的各個方面,首先,我們會帶領您認識 Dart 語言,了解其程式特性和常用庫

接著,會詳細介紹 Dart 變數宣告的方法,包括 Objectvardynamic 以及 finalconst 的使用

隨後,我們將深入分析 Dart 的七種數據類型,揭示其中的陷阱和特殊用法,如 String 類型的代碼點與代碼單元(這些知識對避免常見錯誤至關重要),以及 Runes 類型的細節

最後,我們會講解各種操作符,從類型判斷到安全操作符,幫助您更好地掌握 Dart 語言

寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀

個人程式分享時比較注重「縮排」,所以可能不適合手機的排版閱讀,建議切換至「電腦版」、「平板版」視窗看


認識 Dart 語言

Dart 是單線程(單執行序),GC 回收使用 CMS 機制,也就是使用分代回收機制 (新生代、老年代)

以下會以 Java 語言進行比較,提及與 Java 的差異,Dart 與 Groovy 也有部分相像


// 先來個 Hello World 起手
void main() {
    print('Hello World');
}

● 使用指令運行,該指令存在於 Flutter SDK 的資料夾內,將該路徑加入環境變數,重啟 Android Studio 就可以在終端機下達指令 dart xxx.dart

Dart 指令路徑: ${flutter_sdk}\bin\cache\dart-sdk\bin


Dart 語言、程式特性

Dart 語言的特性

A. 所有的東西都是物件,甚至包含 Java 的基礎類型 (eg. int)

B. Dart 是弱類型語言,不必指定數據類型

C. 指定數據類型、const 常量可以提高運行速度

D. Dart 是 AOT (Ahead Of Time) 編譯,在安裝前會先進行預編譯,編譯成本地代碼 (Dart to java or object-c)

E. Dart 也可以 JIN (Just In Time) 編譯

F. 以 60fps 運行的流暢動畫和轉場,可以在沒有鎖的時候進行物件分配 & 垃圾回收,Dart 避免了線程的搶佔 (線程的特性與 Java 不同)

G. 代碼即是布局,不須另外使用另一個語言建構 (Android 使用 Xml 建構)

Dart 程式特性

A. 統一的程序入口 main(),這一點如同 C、Java 語言

B. 並沒有 publicprotectedprivate 的參數訪問限制概念,但 私有可以變量可以使用下滑線 ( _ ) 來表示


int hello;

int _hello; // 私有

C. 支持異步處理 anync/ await

D. 所有函數都有函數返回值! 如果沒有返回值默認返回為 null

Dart 常用庫

● Dart 中常見的第三方庫有如下表所示:

庫名描述
dart:async異步編成支持,提供 Future & Stream
dart:collection針對 dart:core 提供更多的集合支持
dart:convert不同類型(JSONUTF-8) 的字符轉換
dart:core基礎核心庫
dart:html網頁開發用到的庫
dart:io文件讀取的 IO
dart:math函數、隨機算法等等
dart:svgSVG 動畫

Dart 變數宣告

Dart 變數有幾個特色

A. Dart 可使用「中文」命名 (但不建議)

B. Dart 語言中所有的東西都是物件,並沒有基礎類型的概念

宣告變數:Object / var / dynamic

● 一般我們在宣告類別時必須寫清楚類型,而 dart 可以直接使用 var 作為類型宣告 (Dart 的編碼風格),在 Java 10kotlin 中也有 var

宣告方式解釋Example
類型宣告如同一般 Java 宣告,必須寫清楚是哪種類型的數據(之後會一一介紹 Dart 的類型)Person person = new Person();
Object任意類型Object person = new Person();
var自動推導類型 (若是不清楚就要考慮少用),並且==推倒後就不可以在更改類型==var person = new Person();
dynamic運行時確定數據類型,相對來說會慢一些,在沒有初始化時默認為 nulldynamic person = new Person();

其中較為特別的是 dynamic 類型它不在編譯期間定義,它在「運行期間才決定類型」;以下是不同宣告方式的範例


void main() {
  print('Hello World');

  // 使用 Object 宣告
  Object o1 = 1;
  o1 = "1";

    
  // 使用 var 宣告
  var v1 = 1;
  //v1 = "1";   // v1 已定型不可再改(自動推導)

    
  // 使用 var 宣告
  var v2;
  v2 = 1;
  v2 = "1";     // 兩個完全不同物件,v2 類似於 const 指標

    
  // 使用 dynamic 宣告
  dynamic d1 = 1;    // 與 Object 差異是 dynamic 是在運行時確定
  d1 = "1";

  Object name1 = 'Alien';
  var name2 = 'Pan';
  dynamic name3 = 'Kyle';

  print("$name1, $name2, $name3");
}

// 函數入參,未聲明就是動態類型 dynamic,如下
void testPrint(a) {
}

void testPrint2(dynamic a) {
}

不同語言的 var 所造成的差異(編譯期間)

● 在 JS 中也有 var 這種宣告方式,但 JS 是「動態類型語言」(python 也是),可在賦予值後重新設定不同的類型


// JS
var k = "Hello JS"
k = 3;        // okay

● Dart 則是「靜態類型語言」,在賦值時就被定義(自動推倒)


// Dart 在賦值時就被定義類型
var k = "Hello Dart";
k = 10;       // Err !!!


var j;    // 尚未被定義就是 null 也就是 object
j = "Hello Dart";
j = 20;      // Okay !!!

final & const 描述

● Java 中我們可以看到 finalC/C++ 中可以看到 const 描述,其實被這兩者描述過後,該變數都會變為不可再修改的值 (轉為常數),而 final、const 仍然存在著差異

描述作用時範例
final運行期間 確定final String FINAL_NAME = "Apple"
const編譯期間 確定const String CONST_NAME = "Banana"

void main() {
  final String FINAL_NAME = "Apple";
  const String CONST_NAME = "Banana";

  //const String TEMP = FINAL_NAME; //  Err: 運行時確定
  final String TEMP_2 = CONST_NAME; // okay
}

class MyClass {
  final String TAG_1 = "MyClass";
  // const String TAG_2 = "MyClass";    // Err: 如同 C++ 的 const 概念使用
  // const 定義在類中,必須使用 static 描述 -> static const 
  static const String TAG_3 = "MyClass";
}

● 運行時 final 不可以賦予給編譯時常量 const,因為 const 必須編譯期就確定,而相反操作過來就可以,因為 final 運行時再確認即可

所以被 const 描述的稱之為常量,它的效能往往會比被 final 描述的更好,因為它在編譯過後就被確定類型

const & final 不可以跟 var 一起使用

Dart 數據類型:7 個類型

● Dart 是屬於 強類型語言,並且 沒有基礎數據類型 (Java 有 8 大基礎數據),intlong...都是實體的物件,下圖是 dart 語言的 int 類

● Dart 有內置的七種類型 (7 個仍是類不是基礎類型)

內置類介紹 & Java 比較範例
num分為整數、浮點數int、double
String儲存大小為 UTF-16,可用單、雙引號,並可嵌套使用 (省去跳脫字元 )String str = 'A' or "B"
bool如同 Java 使用bool y = false;
List<E>不使用 ArrayList,可直接使用 index 取值,增加數值使用 addList list = new List(1); var list = List(1);
Map<K, V>一樣是 Key & Value 相對,取值方式可以使用中括號 []中括號內是放置 key 並非 indexMap map = ['A': 1, 'B': 2]
RunesUnicode 字符,將 32 位的 Unicode 編碼轉為字符串var a = '\u{1f9f99}'
Symbol可以看做 C/C++ 的宏,編譯時的常量Symbol

num 類型

num 所有數的父類,有 int & double 兩個子類 (但是並無 shortfloatlong... 其他數據類型)

graph TB num --> int num --> double

● Dart 與 Java 語言對於數字類型的看待、差異

A. Dart 只有 int(整) & double(浮點數),而沒有 float、short...

B. 數字類型佔用字節數(byte)的差異:int 在 java 中占用 4 個字節,dart 則會依照平台而改變,屬於動態改變自節數


// Dart
int i = 2;
print('int cost byte: ${i.bitLength}');    // 可看當前需要多少個 bit

String 類型:String 陷阱 | 代碼點、代碼單元

● String 類對於高級語言來說是個大家都通認的類型,這邊就不過多介紹,主要來比較一下 Dart 與 Java 語言對於 String 類型不同的地方

A. 基礎差異:總體來說比起 Java 使用起來更加的自由

● Dart 對於字串的解釋方式可以使用「字符串插值」(String Interpolation),不再像是 Java 只能透過 + 號來串接字串

● 可以混用單、雙引號來表達字串

● 使用 '''""" 可以包裹換行的字串

● 並且可以透過在字串前添加 r 來告訴 Dart 該字串是「元數據」(不需要解譯跳脫字符等等)


void testString() {
  String s1 = "Apple";
  // 使用 ${} 可直接引用其他字串,若是單個詞可省略大括號 {}
  String s2 = "This is $s1";
  print(s2);
  String s2_1 = 'This is $s1';  // 單、雙引號都可引用,groovy 單引號則不行
  print(s2_1);

  // 傳統跳脫字元 `\`,並且可以如同 Java 使用 `+` 號
  String s3 = "This is \"${s1 + "!"}\" ";
  print(s3);
  // 嵌套使用
  String s4 = 'This is "${s1 + "!"}" ';
  print(s4);

  // 三個單引號 or 雙引號可以使用換行字串
  String s5 = """
  Hello
  World
  EveryBody
  """;
  print(s5);
  String s6 = '''
  Apple
  Banana
  Car
  ''';
  print(s6);

  // 原始字 raw -> r""
  String s7 = r"\n";  // 跳脫字元不起作用
  String s8 = "\n";
  print(s7);


  String rType = r"\n";   // r 可輸出元形 "\\n"
  print(rType);
}

B. 特殊字元差異

Java 每個 String 大小不可超過 2 個 byte,若超過則必須分開(這是因為 Java 目前採用 UCS-2 編碼)


// Java
void main() {
  String str = "\uA388\uA388";

  println(str);
}

Dart 則可以超過 2 byte,超過只需使用 {} 即可 (若未超過則不用)


// Dart
void main() {
  var clapping = '\u{1f44f}';
  print("$clapping");

  var test = '\u1f44f';  // 超過未使用  其實就是 '\u1f44' + 字串 f
  print("$test");

  var test2 = '\uf44f';  // 未超過
  print("$test2");

}

● Dart String 字串長度的陷阱(關鍵字: 代碼點 & 代碼單元)

代碼單元:String#length 只是大部分是「字符長度」,但並不完全代表代碼單元數量… 假設字符串是以 UTF-16 編碼存儲的(Dart、Java 就是如此),一個代碼單元是 16 位的整數

代碼點:Unicode 編碼中每個字符對應的一個整數值,也就是儘管超過程式語言的代碼編制長度,也會算成是一個整數值

範例如下


void main() {

  String testLen = "\u5566\u7788";

  print("${testLen.length}");

}

bool 類型

● Dart 的 bool 類型與 Java 並無太大的差異(Java use boolean),同樣使用 true/false… 這邊只需要特別注意 對於 Dart 來說 bool 仍是物件,而對於 Java 來講它只是基礎類型


void testBool() {
    bool flag = false;

    String msg = flag ? "Hello" : "World";

    print(msg);
}

List 類型

● Dart 的 List 如同 Array 可以使用下標(index)取值

以下我們會特別加入 const 的使用,並與 C/C++ 比較 (有相似之處);

const 如果放在宣告物件的描述,會讓該物件的引用、內容皆不可修改

● 但如果 const 只放在建立物件實例的描述,則該物件的引用可以修改,而物件的實例內容則不能修改!


void testList() {
// 創建方法 1
  List list1 = new List();
// 創建方法 2,省略 new
  List list2 = List();
// 創建方法 3
  List list3 = [0,1,2,3];
// 下標 index 取值
  print("list3[1]: ${list3[1]}");

  list3.add(44);
  print("list3[4]: ${list3[4]}");

  // 1. const 修飾引用 & 內容
  const List list4 = [1,3,5,7,9];
//  list4.add(123);     Err: 編譯期間會錯誤
//  list4[3] = 10;      Err: 內容也會被修飾
//  list4 = list3;      Err: 引用指標也會被修飾

  // 2. const 修飾內容
  List list5 = const [9,7,5,3,1];
  print(list5);
  list5 = list4;        // 指標未被修飾,可以修改引用指標
//  list5[0] = 333;     Err: const 修飾內容
  // list5.add(55);    Err: const 修飾內容
  print(list5);
}

● 我們可以使用 C/C++ 的 const 修飾指標來表達相同的功效,這樣會看的更加清晰(當然這是對於學過 C/C++ 的人來說會看出兩者個相同之處)


#include <stdio.h>

int main()
{

    int a = 10;     int aa = 20;
    int * b = &a;   // 一般指標
    printf("b = %i\n", *b);

    const int * c = &a; // const 修飾內容
    // *c = 20; Err 內容不可改
    printf("c = %i\n", *c);
    c = &aa;    // 指標可改
    printf("change, c = %i\n", *c);

    int * const d = &a;     // const 修飾指標
    *d = 20;    // 內容可改
    printf("d = %i\n", *d);
    // d = &aa;    // 指標不可改
    printf("change, d = %i\n", *d);


    const int* const e = &a; // const 修飾指標 & 內容

    printf("e = %i\n", *e);

    return 0;
}

Map 類型

● Map 使用跟 List 相似,也可以使用 const 修改(這裡就不特別說明 const 描述的位置造成的差異),特點是 可以使用 [] 自動拓展,不需要像是 Java 使用 put() 函數做添加


void testMap() {

  // Map 創建方法 1
  Map map1 = new Map();
  // Map 創建方法 2
  Map map2 = Map();
  // Map 創建方法 3
  Map map3 = {'A': 1, 'B': 2, 'C': 3};

  print("map3[A]: ${map3['A']}");
  print("map3[B]: ${map3['B']}");
  print("map3[C]: ${map3['C']}");

  const Map map5 = {'A': 1};
//  map5['A'] = 3;
  var map6 = const {'A': 1};

  // 直接"自動"拓展 !!!
  map3['D'] = 4;
  print("map3[D]: ${map3['D']}");

}

Runes 類型:代碼點、代碼單元

● Dart 的 Runes 類型是個特別的類型:

Dart 特別分出兩個單詞:代碼點代碼單元

單詞說明
代碼點 codePointCount可以取出目前輸入數的數量,它不會按照字元的 Byte 數量來解釋字串長度
代碼單元 codeUnits經過編碼後才返回的 Byte 數量

void testRunes() {
  // \u{1f44f} 是超過 2Byte 的數據
  String str = "\u{1f44f}";
  print("String: $str, ${str.length}");

  var r = "\u{1f44f}";
  print("var: $r");

  var clapping = '\u{1f44f}'; // 超過 unsigned 2 Byte 需要使用大括號 {} 
  print(clapping); //👏
  // 16位代碼單元
  print(clapping.codeUnits); // 超過 16 位[55357, 56399]
  // 獲得完整的 32 位代碼單元
  print(clapping.runes.toList()); //輸出 10 進位 [128079]
  
  // 取得 codePointCount
  print("Code point count: ${clapping.runes.length}");

//fromCharCode 根據字節碼(Byte)創建字符串
  print( String.fromCharCode(128079));
  print( String.fromCharCodes(clapping.runes));
  print( String.fromCharCodes([55357, 56399]));
  print( String.fromCharCode(0x1f44f));

  Runes input = new Runes(
      '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
  print("Runes: " + String.fromCharCodes(input));
}

image

● Java 如何儲存超過 2Byte 的字串?

以往 Java String 類型無法放置超過 2Byte 的數據,如果要儲存就要使用 \u 開頭的方式來表達,範例如下


public static void main(String[] args) {
    String over2Byte = "\uD83D\uDC4F";

    System.out.println("length: " + over2Byte.length());
}

String#length() 方法是在 UCS-2Unicode 的其中一種)編碼下所返回的單元(Byte)數量

image

Symbol 類型

● Symbol 標示符號,其概念類似於 C/C++ 的 define 宏概念,它可以用來定義常數;Dart 語言在使用 Symbol 類型時要在常數前加上 # 定義;範例如下…


void testSymbol() {
  Symbol symbol1 = #Hello;

  switch(symbol1) {
    case #Hello:
      print(symbol1);
      break;

    case #World:
      print("World");
      break;
  }

  Symbol symbol2 = new Symbol("Book");
  print("#Book == symbol2: ${#Book == symbol2}");
}


Dart 操作符

操作符同樣是比較與 Java 相異、沒有的部分,至於與 Java 相同的操作符就不另外介紹

類型判斷型

● Dart 的類型判斷可以使用下表的幾個關鍵字

關鍵字解釋 & 比較Java 使用Dart 使用
as類型轉換,Java 使用括號強制轉型int b = (int)a;int b = a as int;
is類型判定,Java 使用 instanceof 判斷if(a instanceof Person)if(a is instanceof)
is!反向判定,注意 ! ++使用在後面++if(!(a instanceof Person))if(a is! instanceof)

void testDecide() {
  int a = 10;
  double b = 11.11;

  // as
  // int & double 無法互轉
  //print("b as int: ${b as int}");

  // is
  if(a is int) {
    print("a is int");
  }

  // is!
  if(b is! int) {
    print("b is not int");
  }
}

賦值操作符 ??=

● 在 Dart 中以下這些操作符號 =+=-=\=*= 都可以使用,而 有一個較特別的是 ??= 操作符,它會先判斷該物件是否為 null,若為 null 則賦值

以 Java、Dart 兩種語言來比較:


// 使用 Dart 表達… 若 b == null 則將 value 賦予 b,若有值則不改變
b ??= value;    


// 使用 Java 表達… 等同於上 ??= 操作符
if(b == null) {
    b = value;
}

操作符 ??= 使用範例如下:


void printMessage(String? msg) {
  msg ??= "No message";
  print(msg);
}

void main() {
  printMessage('Hello world');

  printMessage(null);
}

條件表達式 ??

● 除了基礎的三元表達式 condition ? todo1 : todo2,Dart 還多了一種 ?? 表達,它會先執行第一個敘述,若第一個敘述反為 null 則執行第二個敘述

以 Java、Dart 兩種語言來比較:


// 使用 Dart 表達… 先執行 expr1 如果為 null ,則換執行 expr2 返回
expr1 ?? expr2


// 使用 Java 表達… 
int function() {
    if(expr1 == null) {
        return expr2
    }
    return expr1;
}

操作符 ?? 使用範例如下:


void printMessage2(String? msg) {
  print(msg ?? "No message");
}

void main() {
  printMessage2('Hello world');

  printMessage2(null);
}

安全操作符 .?

● Dart 提供了安全操作符 .?若物件為 null 則直接返回 null,並不會往下執行函數

以 Java、Dart 兩種語言來比較:


// 使用 Dart 表達… 如果 expr1 不為 null,則執行 method 方法
expr1?.method();


// 使用 Java 表達… 功能同上
if(expr1 != null) {
    expr1.method();
}

操作符 .? 使用範例如下:


void printMessage3(String? msg) {
  final length = msg?.length;

  print('message len: $length');
}


void main() {
  printMessage3('using operation `.?`');

  printMessage3(null);
}

級聯操作符 ..

● 操作符號是 ..,在同一個物件上可以連續調用該物件的函數、成員,可以避免創建臨時變量 (可以使用 C++ 理解,必須透過複製建構函數創造臨時物件進行賦值)

這種操作尤其可以使用在「建造者 builder 模式


void testLink() {

  MyBuilder builder = MyBuilder(); // 可省略 new 關鍵字

  builder..setName("Alien")..setId(123)..setPhone('456')..show();
}

class MyBuilder {
  String? _name;
  int? _id;
  String? _phone;


  void setName(String name) {
    _name = name;
    // 一般 Java 製作 builder 模式就必須返回此類 this 物件
  }

  void setId(int id) {
    // 使用級聯則不用
    _id = id;
  }

  void setPhone(String phone) {
    // 可接續使用
    _phone = phone;
  }

  void show() {
    print('name($_name}), id($_id), phone($_phone)');
  }
}

解構元素 ...

● Dart 可以透過 ... 符號來「解構列表」的元素,將其轉為個別的元素,範例如下


void main() {
  var message = ['Hello', 'World', '123'];

  print(['你好', '世界', '456', ...message]);
}

更多的 Flutter/Dart 語言相關文章

了解 Flutter 如何在跨平台開發中佔據重要地位,掌握快速上手的技巧與項目建置流程,開啟你的跨平台開發之旅!

探索跨平台與 Flutter 技術的未來:從認識到 Flutter 專案建置 | 3 種跨平台

Dart 語言基礎

探討 Dart 語言:宣告、數據類型、操作符 | 從基礎到應用指南

快速掌握 Dart 語言的核心概念,包括變數宣告、數據類型及操作符,為 Flutter 開發奠定扎實基礎。

Dart 函數與方法、異常處理、引用庫 | Java 比較

深入了解 Dart 的函數與異常處理特性,並與 Java 的處理方式進行比較,幫助你跨語言切換更加順暢。

深入解析 Dart 語言:命名慣例、類特性、建構函數與抽象特性

學習 Dart 類的設計邏輯及命名慣例,深入探索抽象類與 Mixin 的強大應用場景。

深入探索 Dart 的併發與異步處理:從 Isolate 到 Event Loop 的全面指南 | Future、Stream

徹底搞懂 Dart 的併發與異步處理,掌握 Isolate 與 Event Loop 的運行機制,助你提升應用效能!

深入 Flutter 框架

深入解析 Flutter Navigator:常見錯誤、解決方法與路由跳轉技巧、動畫

從常見問題到自定義解決方案,學會如何利用 Navigator 實現路由跳轉與流暢動畫效果。

深入理解 Flutter 中的數據共享:從普遍方案到 InheritedWidget | 3 種方案

探討數據共享的最佳實踐,了解 InheritedWidget 等三種主要方案,幫助你優化應用結構。

深入解析 Flutter 三顆樹:Widget、Element 與 RenderObject 完整指南

拆解 Flutter 的內部結構,全面了解 Widget、Element 和 RenderObject 之間的關係,提升你的 Flutter 開發技能!

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響