Dart函式、類和運算子-處理資訊

宋魚000發表於2019-09-23

程式語言雖然千差萬別,但歸根結底,它們的設計思想無非就是回答兩個問題:

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
複製程式碼
operator 是 Dart 的關鍵字,與運算子一起使用,表示一個類成員運算子函式。在理解時,我們應該把 operator 和運算子作為整體,看作是一個成員函式名。

相關文章