一文了解Dart語法

Flutter程式設計指南發表於2019-01-31

前言

Flutter應用程式使用Dart語言開發,Dart是物件導向程式語言,由Google於2011年推出,目前最新版本是2.0,為了更好的使用Flutter進行應用開發,本文將詳細介紹Dart語言的語法和特性。

重要概念

在學習Dart之前,先要了解以下Dart相關概念:

  1. 能夠放在變數中的所有內容都是物件,每個物件都是一個類的例項。甚至於數字、函式和null值都是物件,並且所有物件都繼承自Object類。

  2. Dart是強型別語言,但型別標識是可選的,因為Dart可以推斷型別。如果要明確說明不需要任何型別,可以使用特殊型別dynamic標識。

  3. Dart支援泛型,如List或List(任何型別的物件列表)。

  4. Dart支援頂級函式(例如main函式),以及繫結到類或物件的函式(分別是靜態方法和例項方法)。函式內部也可以建立函式(巢狀函式或本地函式)。

  5. Dart支援頂級變數,以及繫結到類或物件的變數(分別是靜態變數和例項變數)。

  6. 與Java不同,Dart沒有關鍵字public、protected和private。如想設定私有變數或函式,則變數和函式名以下劃線(_)開頭。

  7. 識別符號可以以字母或下劃線(_)開頭,後跟這些字元加數字的任意組合。

  8. Dart有兩個表示式(具有執行時值)和語句(不具有)。 例如,條件表示式條件? expr1:expr2的值為expr1或expr2。 將其與if-else語句進行比較,該語句沒有任何值。 語句通常包含一個或多個表示式,但表示式不能直接包含語句。

  9. Dart工具可以報告兩種問題:警告和錯誤。警告只是表明您的程式碼可能無法正常工作,但它們不會阻止您的程式執行。 錯誤可以是編譯時或執行時。 編譯時錯誤會阻止程式碼執行; 執行時錯誤導致程式碼執行時引發異常。

關鍵字

任何語言都有關鍵字,關鍵字是在程式設計時不能使用作為識別符號的單詞。Dart的關鍵字如下:

一文了解Dart語法

編碼時應避免使用以上單詞作為識別符號,如果有必要,可以使用帶有上標的單詞作為識別符號:

  • 帶有上標1的單詞是上下文關鍵字,僅在特定位置有含義,它們在任何地方都是有效的;
  • 帶有上標2的單詞是內建識別符號,它們在大多數地方是有效的,但不能用作為類和型別名稱或作為一個匯入字首;
  • 帶有上標3的單詞是與Dart1.0釋出後新增的非同步支援相關的有限的保留字元,不能在任何標記為async,async * 或sync * 的任何函式體中使用await和yield作為識別符號。

變數

變數的定義

1.可以使用var來定義變數,變數的型別可以通過變數值推斷出來
var name = "hi"; //String型別
var age = 18; //int型別
var high = 1.70; //double型別
複製程式碼

如上變數定義後其型別已經確定,不可再將其他型別的值賦給變數。

var name = "hi"; //String型別
name = 3; //此處編譯器會報錯,name被定義賦值之後已經是一個String型別,不可再賦值int型別值
複製程式碼
2.也可以使用特定型別來定義變數
String name = "bruce"; //String型別
int age = 18; //int型別
複製程式碼
3.如果變數不限於單個型別,則可以使用dynamic或Object來定義變數
dynamic value = 18;
print("value = $value");
value = "bruce";
print("value = $value");
value = 3.5;
print("value = $value");
  
Object val = 18;
print("val = $val");
val = "bruce";
print("val = $val");
val = 3.5;
print("val = $val");
複製程式碼

輸出結果為

value = 18
value = bruce
value = 3.5
val = 18
val = bruce
val = 3.5
複製程式碼

變數的預設值

由於前文關於Dart的一些概念中說到過,能夠放在變數中的所有內容都是物件,所以如果一個變數沒有初始化值,那它的預設值就為null。

int value1;
print("value1 = $value1");
bool value2;
print("value2 = $value2");
var value3;
print("value3 = $value3");
dynamic value4;
print("value4 = $value4");
複製程式碼

輸出結果為

value1 = null
value2 = null
value3 = null
value4 = null
複製程式碼

final 和 const

如果不打算更改變數,可以使用final或者const。一個final變數只能被設定一次,而const變數是編譯時常量,定義時必須賦值。

// Person類
class Person {
  static const desc = "This is a Person class"; //必須定義時賦值,否則編譯時報錯
  final name;
  Person(this.name); //物件初始化時賦值一次
}

// 定義一個Person物件
Person p = Person("Bruce"); //建立物件時設定一次name
print("p.name = ${p.name}"); //可正常輸出 p.name = Bruce
p.name = "haha"; //編譯器報錯
複製程式碼

內建型別

Dart語言支援以下型別

  • numbers

    包含int和double兩種型別,沒有像Java中的float型別,int和double都是num的子型別。

  • strings

    Dart的字串是一系列UTF-16程式碼單元。建立方法如下:

String str1 = "hello"; //可以使用單引號或雙引號
print("str1 = $str1");
String str2 = """Hi,Bruce
This is Xiaoming.
"""; //使用帶有單引號或雙引號的三重引號可以建立多行字串
print("str2 = $str2");
複製程式碼

輸出結果為

str1 = hello
str2 = Hi,Bruce
  This is Xiaoming.
複製程式碼
  • booleans

    Dart有一個名為bool的型別,只有兩個物件具有bool型別:true和false,他們都是編譯時常量。

  • lists

    和其他程式語言常見的集合一樣,Dart中使用的集合是陣列或有序的物件組。Dart中陣列是List物件。

List arr = ["Bruce", "Nick", "John"];
print("arr = $arr");
複製程式碼
  • maps
Map map = {
"name": "Bruce",
"age": 18,
"high": 1.70
};

print("map = $map");
print("map['name'] = ${map['name']}");

var map1 = {
1: "hi",
2: "hello",
3: "yep"
};
print("map1 = $map1");
print("map1[1] = ${map1[1]}");
複製程式碼

輸出結果為

map = {name: Bruce, age: 18, high: 1.7}
map['name'] = Bruce
map1 = {1: hi, 2: hello, 3: yep}
map1[1] = hi
複製程式碼
  • runes

    符文是字串的UTF-32程式碼點。在字串中表示32位Unicode值需要特殊語法,常用方法是 \uXXXX,其中XXXX是4位十六進位制值,比如小心心(♥)是\u2665。要指定多於或少於4個十六進位制數字,請將值放在大括號中。 比如,微笑(?)是\u{1f600}

String smile = '\u{1f600}';
print("微笑:$smile");

Runes input = new Runes(
  '\u2665  \u{1f605}  \u{1f60e}  \u{1f47b}  \u{1f596}  \u{1f44d}');
print(String.fromCharCodes(input));
複製程式碼

輸出結果為

微笑:?
♥  ?  ?  ?  ?  ?
複製程式碼

函式

Dart是一種真正的面嚮物件語言,因此即使是函式也是物件並且具有型別Function。這意味著函式可以分配給變數或作為引數傳遞給其他函式。

定義方法

和絕大多數程式語言一樣,Dart函式通常的定義方式為

String getName() {
  return "Bruce";
}
複製程式碼

如果函式體中只包含一個表示式,則可以使用簡寫語法

String getName() => "Bruce";
複製程式碼

可選引數

Dart函式可以設定可選引數,可以使用命名引數也可以使用位置引數。

命名引數,定義格式如 {param1, param2, …}

// 函式定義
void showDesc({var name, var age}) {
  if(name != null) {
    print("name = $name");
  }
  if(age != null) {
    print("age = $age");
  }
}

// 函式呼叫
showDesc(name: "Bruce");

// 輸出結果
name = Bruce
複製程式碼

位置引數,使用 [] 來標記可選引數。

// 函式定義
void showDesc(var name, [var age]) {
  print("name = $name");
  
  if(age != null) {
    print("age = $age");
  }
}

// 函式呼叫
showDesc("Bruce");

// 輸出結果
name = Bruce
複製程式碼

預設值

函式的可選引數也可以使用 = 設定預設值

// 函式定義
void showDesc(var name, [var age = 18]) {
  print("name = $name");
  
  if(age != null) {
    print("age = $age");
  }
}

// 函式呼叫
showDesc("Bruce");

// 輸出結果
name = Bruce
age = 18
複製程式碼

main函式

和其他程式語言一樣,Dart中每個應用程式都必須有一個頂級main()函式,該函式作為應用程式的入口點。

函式作為引數

Dart中的函式可以作為另一個函式的引數。

// 函式定義
void println(String name) {
  print("name = $name");
}

void showDesc(var name, Function log) {
  log(name);
}

// 函式呼叫
showDesc("Bruce", println);

// 輸出結果
name = Bruce
複製程式碼

匿名函式

// 函式定義
void showDesc(var name, Function log) {
  log(name);
}

// 函式呼叫,匿名函式作為引數
showDesc("Bruce", (name) {
    print("name = $name");
  });

// 輸出結果
name = Bruce
複製程式碼

巢狀函式

Dart支援巢狀函式,也就是函式中可以定義函式。

// 函式定義
void showDesc(var name) {
  print("That is a nested function!");
  
  //函式中定義函式
  void println(var name) {
    print("name = $name");
  }
  
  println(name);
}

// 函式呼叫
showDesc("Bruce");

// 輸出結果
That is a nested function!
name = Bruce
複製程式碼

運算子

Dart中使用到的運算子如下表格

Description Operator
一元后綴 expr++ expr-- () [] . ?.
一元字首 -expr !expr ~expr ++expr --expr
乘除操作 * / % ~/
加減操作 + -
移位 << >>
按位與 &
按位異或 ^
按位或 |
比較關係和型別判斷 >= > <= < as is is!
等判斷 == !=
邏輯與 &&
邏輯或 ||
是否null ??
條件語句操作 expr1 ? expr2 : expr3
級聯操作 ..
分配賦值操作 = *= /= ~/= %= += -= <<= >>= &= ^= |= ??=

下面就對一些對於Java或Objective-C來說未使用過的運算子通過程式碼來做個介紹。

  • ?.的使用
//定義類
class Person {
  var name;
  Person(this.name);
}

// 呼叫
Person p;
var name = p?.name; //先判斷p是否為null,如果是,則name為null;如果否,則返回p.name值
print("name = $name");

// 輸出結果
name = null
複製程式碼
  • ~/的使用
// 程式碼語句
var num = 10;
var result = num ~/ 3; //得出一個小於等於(num/3)的最大整數
print("result = $result");

// 輸出結果
result = 3
複製程式碼
  • as的使用,as用來做型別轉化
// 類定義
class Banana {
  var weight;
  Banana(this.weight);
}

class Apple {
  var weight;
  Apple(this.weight);
}

// 呼叫
dynamic b = Banana(20);
(b as Banana).weight = 20; // 正常執行
print("b.weight = ${(b as Banana).weight}");
(b as Apple).weight = 30; // 型別轉換錯誤,執行報錯
print("b.weight = ${(b as	Apple).weight}");

//輸出結果
b.weight = 20
Uncaught exception:
CastError: Instance of 'Banana': type 'Banana' is not a subtype of type 'Apple'
複製程式碼
  • is的使用
// 函式和類程式碼定義
getFruit() => Banana(20); // 獲取一個水果物件

class Banana {
  var weight;
  Banana(this.weight);
}

class Apple {
  var color;
  Apple(this.color);
}

// 呼叫
var b = getFruit();
if(b is Apple) { //判斷物件是否為Apple類
  print("The fruit is an apple");
} else if(b is Banana) { //判斷水果是否為Banana類
  print("The fruit is a banana");
}

// 輸出結果
The fruit is a banana
複製程式碼
  • ??的使用
// 操作程式碼塊
String name;
String nickName = name ?? "Nick"; //如果name不為null,則nickName值為name的值,否則值為Nick
print("nickName = $nickName");
  
name = "Bruce";
nickName = name ?? "Nick"; //如果name不為null,則nickName值為name的值,否則值為Nick
print("nickName = $nickName");
  
// 輸出結果
nickName = Nick
nickName = Bruce 
複製程式碼
  • ..的使用,級聯操作允許對同一個物件進行一系列操作。
// 類定義
class Banana {
  var weight;
  var color;
  Banana(this.weight, this.color);
  
  void showWeight() {
    print("weight = $weight");
  }
  
  void showColor() {
    print("color = $color");
  }
}

// 呼叫
Banana(20, 'yellow')
    ..showWeight()
    ..showColor();
    
// 輸出結果
weight = 20
color = yellow
複製程式碼

控制流語句

Dart中的控制流語句和其他語言一樣,包含以下方式

  • if and else
  • for迴圈
  • while和do-while迴圈
  • break和continue
  • switch-case語句

以上控制流語句和其他程式語言用法一樣,switch-case有一個特殊的用法如下,可以使用continue語句和標籤來執行指定case語句。

var fruit = 'apple';
switch (fruit) {
  case 'banana':
    print("this is a banana");
    continue anotherFruit;
      
  anotherFruit:
  case 'apple':
    print("this is an apple");
    break;
}

// 輸出結果
this is an apple
複製程式碼

異常

Dart的異常捕獲也是使用try-catch語法,不過與java等語言稍有不同

// 定義一個丟擲異常的函式
void handleOperator() => throw Exception("this operator exception!");

// 函式呼叫
try {
  handleOperator();
} on Exception catch(e) {
  print(e);
} finally { // finally語句可選
  print("finally");
}

// 輸出結果
Exception: this operator exception!
finally
複製程式碼

Dart是一種物件導向的語言,具有類和基於mixin的繼承。同Java一樣,Dart的所有類也都繼承自Object。

建構函式

Dart的建構函式同普通函式一樣,可以定義無參和有參,命名引數和位置引數,可選引數和給可選引數設定預設值等。Dart的建構函式有以下幾個特點:

  1. 可以定義命名建構函式
  2. 可以在函式體執行之前初始化例項變數
  3. 子類不從父類繼承建構函式,定義沒有建構函式的子類只有無參無名稱的建構函式
  4. 子類定義建構函式時預設繼承父類無參建構函式,也可繼承指定有引數的建構函式;

命名建構函式和函式體執行前初始化例項變數

// 類定義
class Tree {
  var desc;
  
  // 命名建構函式
  Tree.init() {
    desc = "this is a seed";
  }
  
  // 函式體執行之前初始化例項變數
  Tree(var des) : desc = des;
}

// 建構函式呼叫
Tree t = Tree.init();
print("${t.desc}");

Tree t1 = Tree("this is a tree");
print("${t1.desc}");

// 輸出結果
this is a seed
this is a tree
複製程式碼

建構函式繼承

// 類定義
class Fruit {
  Fruit() {
    print("this is Fruit constructor with no param");
  }
  
  Fruit.desc(var desc) {
    print("$desc in Fruit");
  }
}

class Apple extends Fruit {
  Apple():super() {
    print("this is Apple constructor with no param");
  }
  
  // 預設繼承無參建構函式
  Apple.desc(var desc) {
    print('$desc in Apple');
  }
}

// 建構函式呼叫
Apple();
Apple.desc("say hello");
  
// 輸出結果
this is Fruit constructor with no param
this is Apple constructor with no param
this is Fruit constructor with no param
say hello in Apple
複製程式碼

mixin繼承

mixin是一種在多個類層次結構中重用類程式碼的方法。

// 類定義
class LogUtil {
  void log() {
    print("this is a log");
  }
}

class Fruit {
  Fruit() {
    print("this is Fruit constructor with no param");
  }
}

class Apple extends Fruit with LogUtil {
  Apple():super() {
    print("this is Apple constructor with no param");
  }
}

// 呼叫
Apple a = Apple();
a.log(); //可執行從LogUtil繼承過來的方法

// 輸出結果
this is Fruit constructor with no param
this is Apple constructor with no param
this is a log
複製程式碼

泛型

Dart同Java一樣,也支援泛型。

// 類定義
class Apple {
  var desc;
  Apple(this.desc);
  
  void log() {
    print("${this.desc}");
  }
}

class Banana {
  var desc;
  Banana(this.desc);
  
  void log() {
    print("${this.desc}");
  }
}

class FruitFactory<T> {
  T produceFruit(T t) {
    return t;
  }
}

// 呼叫
FruitFactory<Banana> f = FruitFactory<Banana>();
Banana b = f.produceFruit(Banana("a banana"));
b.log();
  
FruitFactory<Apple> f1 = FruitFactory<Apple>();
Apple a = f1.produceFruit(Apple("an apple"));
a.log();
  
// 輸出結果
a banana
an apple  
複製程式碼

寫在最後

本文主要針對Dart不同於其他程式語言的一些語法特性進行了分析和舉例,相信讀過文字之後大家會對Dart語法有個很系統的瞭解,後邊我們就可以開啟Flutter應用開發之旅了。

相關文章