程式語言雖然千差萬別,但歸根結底,它們的設計思想無非就是回答兩個問題:
1、如何表示資訊;
2、如何處理資訊;
函式
函式是一段用來獨立地完成某個功能的程式碼。函式是物件型別,它的型別叫做Function。這意味著函式也可以被定義為變數,甚至可以被定義為引數傳遞給另一個函式。
bool isZero(int number) => number == 0;
void printInfo(int number, Function check) => print("$number is Zero: ${check(number)}");
複製程式碼
如果函式體只有一行表示式,就可以像JavaScript語言那樣用箭頭函式來簡化這個函式
一個函式中可能需要傳遞多個引數。如何讓這類函式的引數宣告變得更加優雅、可維護,同時降低呼叫者的使用成本?
C++與Java的做法是,提供函式的過載,即提供同名但引數不同的函式。但Dart認為過載會導致混亂,因此從設計之初就不支援過載,而是提供了可選命名引數和可選引數。
bool isZero(int number) => number == 0;
void printInfo(int number, Function check) => print("$number is Zero: ${check(number)}");
// 要達到可選命名引數的用法,那就在定義函式的時候給引數加上{}
void enableFlagsA({bool bold, bool hidden}) => print("$bold, $hidden");
// 定義可選命名引數時增加預設值
void enableFlagesB({bool bold = true, bool hidden = false}) => print("$bold, $hidden");
// 可忽略的引數在函式定義時用[]符號指定
void enableFlagesC(bool bold, [bool hidden]) => print("$bold, $hidden");
// 定義可忽略引數時增加預設值
void enableFlagesD(bool bold, [bool hidden = false]) => print("$bold, $hidden");
複製程式碼
類
類是特定型別的資料和方法的集合,也是建立物件的模版。
類的定義及初始化
Dart是物件導向的語言,每個物件都是一個類的例項,都繼承自頂層型別Object。
Dart中並沒有public、protected、private這些關鍵字,只要在宣告變數與方法時,在前面加上_
即可作為private方法使用,如果不加_
,則預設為public。不過_
的限制範圍並不是類訪問級別的,而是庫訪問級別。
class Point {
num x, y;
static num factor = 0;
// 語法糖,等同於在函式體內:this.x = x; this.y = y;
Point(this.x, this.y);
void printInfo() => print('($x,$y)');
static void printZValue() => print('$factor');
}
複製程式碼
有時候類的例項化需要根據引數提供多種初始化方法。除了可選命名引數和可選引數之外,Dart還提供了命名建構函式的方式,使得類的例項化過程語義更清晰。
此外,與C++類似,Dart支援初始化列表。在建構函式的函式體真正執行之前,還有機會給例項變數賦值,甚至重定向至另一個建構函式。
class Point {
num x, y, z;
Point(this.x, this.y) : z = 0; // 初始化變數z
Point.bottom(num x) : this(x, 0); // 重定向建構函式
void printInfo() => print('($x, $y, $z)');
}
var p = Point.bottom(100);
p.printInfo(); // 輸出(100,0,0)
複製程式碼
複用
在物件導向的程式語言中,將其他類的變數與方法納入本類中進行復用的方式一般有兩種:繼承父類和介面實現。
在Dart中,可以對同一個父類進行繼承或介面實現:
- 繼承父類意味著,子類由父類派生,會自動獲取父類的成員變數和方法實現,子類可以根據需要覆寫建構函式及父類方法;
- 介面實現則意味著,子類獲取到的僅僅是介面的成員變數符號和方法符號,需要重新實現成員變數,以及方法的宣告和初始化,否則編譯器會報錯。
class Point {
num x = 0, y = 0;
void printInfo() => print('($x, $y)');
}
// Vector 繼承自 Point
class Vector extends Point {
num z = 0;
@override
void printInfo() => print('($x,$y,$z)'); // 覆寫了printInfo實現
}
// Coordinate 是對 Point 的介面實現
class Coordinate implements Point {
num x = 0, y = 0; // 成員變數需要重新宣告
void printInfo() => print('($x, $y)'); // 成員函式需要重新宣告實現
}
var xxx = Vector();
xxx
..x = 1
..y = 2
..z = 3; // 級聯運算子,等同於 xxx.x = 1; xxx.y = 2; xxx.z = 3;
xxx.printInfo(); // 輸出(1,2,3)
var yyy = Coordinate();
yyy
..x = 1
..y = 2; // 級聯運算子,等同於 yyy.x = 1; yyy.y = 2;
yyy.printInfo(); // 輸出 (1,2)
print(yyy is Point); // true
print(yyy is Coordinate); // true
複製程式碼
除了繼承和介面實現之外,Dart還提供了另一種機制來實現類的複用,即“混入"(Mixin),混入鼓勵程式碼重用,可以被視為具有實現方法的介面。不僅可以解決Dart缺少對多重繼承的支援問題,還能夠避免由於多重繼承可能導致的歧義(菱形問題)。
要使用混入,只需要with關鍵字即可。
class Coordinate with Point {
}
var yyy = Coordinate();
yyy
..x = 1
..y = 2; // 級聯運算子,等同於 yyy.x = 1; yyy.y = 2;
yyy.printInfo(); // 輸出 (1,2)
print(yyy is Point); // true
print(yyy is Coordinate); // true
複製程式碼
可以看到,通過混入,一個類裡可以以非繼承的方式使用其他類中的變數與方法。
運算子
Dart和絕大多數程式語言的運算子一樣,除外,Dart多了幾個額外的運算子,用於簡化處理變數例項缺失(即null)的情況。
- ?. 運算子:假如Point類有printInfo()方法,p是Point的一個可能為null的例項。那麼,p呼叫成員方法的安全程式碼,可以簡化為p?.printInfo(),表示p為nul的時候跳過,避免丟擲異常。
- ??= 運算子:如果a為null,則給a賦值value,否則跳過。這種用預設值兜底的賦值語句在Dart中我們可以用a ??= value表示。
- ?? 運算子:如果a不為null,返回a的值,否則返回b。在Java或者C++中,我們需要通過三元表示式(a != null)? a:b 來實現這種情況。而在Dart中,這類程式碼可以簡化為 a ?? b。
在Dart中,一切都是物件,就連運算子也是物件成員函式的一部分。
對於系統的運算子,一般情況下只支援基本資料型別和標準庫中提供的型別。而對於使用者自定義的類。如果想要支援基本操作,比如比較大小、相加相減等,則需要使用者自己來定義關於這個運算子的具體實現。
Dart提供了類似C++的運算子覆寫機制,使得我們不僅可以覆寫方法,還可以覆寫或者自定義運算子。
class Vector {
num x, y;
Vector(this.x, this.y);
// 自定義相加運算子,實現向量相加
Vector operator + (Vector v) => Vector(x + v.x, y + v.y);
// 覆寫相等運算子,判斷向量相等
bool operator == (dynamic v) => x == v.x && y == v.y;
}
final x = Vector(3,3);
final y = Vector(2,2);
final z = Vector(1,1);
print(x==(y+z)); // 輸出 true
複製程式碼