Dart的語法詳解系列篇(二)-- 類與函數

版權聲明:本文為博主原創文章,未經博主允許不得轉載。http://www.lxweimin.com/p/44ae73a58ebc

轉載請標明出處:
http://www.lxweimin.com/p/44ae73a58ebc
本文出自 AWeiLoveAndroid的博客


本文首發在公眾號Flutter那些事,未經允許,嚴禁轉載。

前言
Flutter1.0穩定版昨晚的終于發布了。我們為此感到高興。對于開發者來說,有了穩定版相當于一個定心丸。本文主要介紹Fllutter1.0的一些功能和相關工具。


Flutter系列博文鏈接 ↓:

工具安裝:

Flutter基礎篇:

Flutter進階篇:

Dart語法系列博文鏈接 ↓:

Dart語法基礎篇:

Dart語法進階篇:


本文代碼同步發布在Github: https://github.com/AweiLoveAndroid/Flutter-learning/tree/master/projects/dart_demo

上一篇主要講了數據類型、運算符、流程語句等,由于文字太多,我就把剩下的內容分開寫一篇文章。
這一篇我們講Dart的類與函數,內容較多,希望大家可以耐心看完。我也是花了很長時間研究的。喜歡的就點個贊,打個賞吧。感謝大家支持。


八、Dart的類與函數

Dart是一種面向對象的語言,具有類和基于mixin的繼承。每個對象都是一個類的實例,所有類都來自Object。 基于Mixin的繼承意味著雖然每個類(除了Object)只有一個超類,但是類體可以在多個類層次結構中重用。

(一)類的分類

(1)普通類

1).Dart使用class關鍵字表示一個類,對象具有由函數和數據(分別為方法和實例變量)組成的成員。

我們通過new關鍵字來創建一個類對象,然后使用(.)調用類里面的變量或者普通(實例,非靜態)函數(注:函數和方法意思是一樣的。),以便訪問該對象的函數和數據。例如類Test里面有一個普通函數tests(),我們可以使用new Test().tests();來調用這個tests()函數,在Dart2里面創建對象時可以省略new關鍵字。

例如:

class Test{
  void tests(){}
}
void main(){
  new Test().tests();
}
2).要在運行時獲取對象的類型,可以使用Object類的runtimeType屬性,該屬性返回一個Type對象。

如果沒有指明具體類型,Dart具有類型推斷機制,會自動推斷變量類型。

示例如下:

  var a = 10;
  var b = 10.0;
  var c = '10';
  var d = true;
  var e = [12.5,13.1];
  var f = {3:'5',5:'11'};
  var t = new Test();// 這里就直接使用上面那個Test類 不再去重復寫這個類了
  print('a 的類型是: ${a.runtimeType}'); // a 的類型是: int
  print('b 的類型是: ${b.runtimeType}'); // b 的類型是: double
  print('c 的類型是: ${c.runtimeType}'); // c 的類型是: String
  print('d 的類型是: ${d.runtimeType}'); // d 的類型是: bool
  print('e 的類型是: ${e.runtimeType}'); // e 的類型是: List<double>
  print('f 的類型是: ${f.runtimeType}'); // f 的類型是: _InternalLinkedHashMap<int, String>
  print('t 的類型是: ${t.runtimeType}'); // t 的類型是: Test
class Test{}
3). Dart和Java一樣,使用extends關鍵字,表示一個類繼承另一個類。

使用@override注解聲明你要重寫的函數,在這個函數內部可以使用super調用重寫的這個父類的函數。

實例如下:

  class Test {
    void test() {/*這里省略方法內部的邏輯操作*/}
  // 其他邏輯
}

class TestChild extends Test {
  @override  //@override標注在test()函數上面 表示test()函數是重寫父類的。
    void test() {
      super.test();// 調用父類的test()函數
      /*這里省略方法內部的邏輯操作*/
    }
    // 其他邏輯
}

@override是元數據。元數據注解以字符開頭@,后跟對編譯時常量(如deprecated)的引用或對常量構造函數的調用。元數據可以出現在庫,類,typedef,類型參數,構造函數,工廠,函數,字段,參數或變量聲明之前以及導入或導出指令之前。您可以使用反射在運行時檢索元數據。
所有Dart代碼都有兩個注解:@deprecated@override
以下是使用@deprecated 注解的示例:

class Television {
  /// _Deprecated: Use [turnOn] instead._
  @deprecated
  void activate() {
    turnOn();
  }

  // Turns the TV's power on.
  void turnOn() {
    //...
  }
}

您可以定義自己的元數據注釋。

這是一個定義帶有兩個參數的@todo注釋的示例:

library todo;

class Todo {
  final String who;
  final String what;
  const Todo(this.who, this.what);
}

// 以下是使用@todo注釋的示例:
import 'todo.dart';
@Todo('seth', 'make this do something')
void doSomething() {
  print('do something');
}
4).靜態變量

使用static關鍵字修飾的類范圍的變量。
靜態變量(類變量)對于類范圍的狀態和常量很有用。靜態變量在使用之前不會初始化。

class Test {
  static const num = 16;
  // ···
}

void main() {
  print(Test. num); // 16
}
5).重寫操作符

您可以重寫下表中顯示的運算符。

操作符名稱
< + | []
> / ^ []=
<= ~/ & ~
>= * << ==
- % >>

注意:!=不是可重寫的運算符。表達式e1 != e2!(e1==e2)的語法糖。
以下是一個重寫+和-運算符的類的示例:

void main(){
  final a = Testoperator(2, 3);
  final b = Testoperator(2, 2);
  var num1 = Testoperator(4, 5);
  var num2= Testoperator(0,1);
  print(a + b == num1); // true
  print(a - b == num2); // true
}

class Testoperator {
  final int x, y;

  Testoperator(this.x, this.y);

  Testoperator operator +(Testoperator o) => Testoperator(x + o.x, y + o.y);
  Testoperator operator -(Testoperator o) => Testoperator(x - o.x, y - o.y);

  // Override hashCode using strategy from Effective Java, Chapter 11.
  @override
  int get hashCode {
    int result = 17;
    result = 37 * result + x.hashCode;
    result = 37 * result + y.hashCode;
    return result;
  }

  // 如果重寫了 hashCode,應該重寫==操作符。
  @override
  bool operator ==(dynamic other) {
    if (other is! Testoperator) return false;
    Testoperator person = other;
    return (person.x == x &&
        person.y == y);
  }
}
6).noSuchMethod()

要在代碼嘗試使用不存在的方法或實例變量時檢測或做出反應,您可以重寫noSuchMethod()

void main() {
  TestMethod test = new TestMethod();
  dynamic f = test.foo;
  // Invokes `Object.noSuchMethod`, not `TestMethod.noSuchMethod`, so it throws.
  f(42);
}

class TestMethod {
// 除非你重寫noSuchMethod,否則使用不存在的成員會導致NoSuchMethodError
  // Unless you override noSuchMethod, using a
  // non-existent member results in a NoSuchMethodError.
  @override
  void noSuchMethod(Invocation invocation) {
    print('You tried to use a non-existent member: ' +
        '${invocation.memberName}');
  }
dynamic foo();
}

你不能調用未實現的方法,除非以下的某一條是true:

  • 1.接收處有靜態類型dynamic
  • 2.接收處定義了一個未實現的方法(abstract也是OK的)的靜態類型dynamic,接收器的動態類型的實現與類noSuchMethod() 中的實現不同Object。
    有關更多信息,請參閱非正式 noSuchMethod轉發規范

(2)抽象類

1).使用abstract修飾符定義抽象類(無法實例化的類)。抽象類對于定義接口非常有用,通常還有一些實現。如果希望抽象類看起來是可實例化的,請定義工廠構造函數。

抽象類通常有抽象方法。這是一個聲明具有抽象方法的抽象類的示例:

// 此類聲明為abstract,因此無法實例化
abstract class Test {
  //定義構造函數,字段,方法...
    
  // 抽象方法
  void test();
}
2).隱式接口

每個類都隱式定義一個接口,該接口包含該類的所有實例成員及其實現的任何接口。如果要在不繼承B實現的情況下創建支持B類API的A類,則A類應實現B接口。
一個類通過在implements子句中聲明它們然后提供接口所需的API來實現一個或多個接口。
例如:

void main() {
  print(sayHello(Person('李四'))); // 你好 張三. 我是 李四.
  print(sayHello(PersonImpl())); // 你好 張三  你知道我是誰嗎?
}

// Person類 隱式接口包含hello()
class Person {
  // 在接口中,但是僅在此庫中可見。
  final _name;

  // 不在接口中,因為這是一個構造函數
  Person(this._name);

  // 在接口中
  String hello(String who) => '你好 $who. 我是 $_name.';
}

// Person接口的實現
class PersonImpl implements Person {
  get _name => '';

  String hello(String name) => '你好 $name  你知道我是誰嗎?';
}

String sayHello(Person person) => person.hello('張三'); 

一個類也可以實現多個接口,例如:
class ZhangSan implements Run,Life {
  //...
}
class Run {}
class Life {}

(3)可調用的類(Callable Class)

更多詳情可以查看Dart官網: 在Dart中模擬函數
要允許像函數一樣調用Dart類,請實現該call()方法。
在下面的示例中,Test類定義了一個call()方法,它接受三個字符串并連接它們,用空格分隔每個字符串,并附加一個感嘆號。

void main() {
    var test = new Test();
    var result = test(166.6665,"Flutter真好玩",672);
print("$result");// 666.666 Flutter真好玩 666
}
class Test {
    // 必須是call函數
    call(double a, String b, int c) => '${a*4} ${b} ${c-6}';
}

(4)枚舉類型

1.使用enum關鍵字聲明枚舉類型:

例如:enum Color { red, green, blue }

2). 枚舉中的每個值都有一個index getter,它返回枚舉聲明中值的從零開始的位置。例如,第一個值具有索引0,第二個值具有索引1。
assert(Color.red.index == 0);
assert(Color.green.index == 1);
assert(Color.blue.index == 2);
3).要獲取枚舉中所有值的列表,請使用枚舉values常量。
List<Color> colors = Color.values;
assert(colors[2] == Color.blue);
4).您可以在switch語句中使用枚舉,如果您不處理所有枚舉值,您將收到警告。
var aColor = Color.blue;
switch (aColor) {
  case Color.red:
    print('Red');
    break;
  case Color.green:
    print('Green');
    break;
  default: // 你沒有這個 你會看到一個警告
    print(aColor); // 'Color.blue'
}
5).枚舉類型的限制條件:
  • 1.你不能在子類,mixin 或者實現枚舉。
  • 2.你不能顯式實例化枚舉。

(5)mixin

更多關于mixin的資料,可以查看 Dart2.1 mixin規范

Mixins是一種在多個類層次結構中重用類代碼的方法。

1).要實現 mixin,請創建一個擴展Object的類,并且不聲明構造函數。除非您希望mixin可用作常規類,否則請使用mixin關鍵字而不是class。

例如:

// 聲明mixin
// 專家
mixin Expert {
  // 發現和解決難題
  bool solveProblems = false;
  // 精通數據結構和算法
  bool dataStructureAndAlgorithms = false;
  // 會架構設計
  bool architectureDesign = false;
  // 性能優化
  bool performanceOptimization = false;
  // 熟練掌握計算機系統
  bool computerSystem = false;

  void develop() {
    // 娛樂節目
    if (solveProblems) {
      print('發現和解決難題');
    }
    if (dataStructureAndAlgorithms) {
      print('精通數據結構和算法');
    }
    if (architectureDesign) {
      print('會架構設計');
    }
    if (performanceOptimization) {
      print('熟練掌握性能優化');
    }
    if (computerSystem) {
      print('熟練掌握計算機系統');
    }
  }
}

// 特性

// 有效率的
mixin Efficient {
  void getEfficientAttrs() {
    print('有效率的');
  }
}

// 和藹的
mixin Kind {
  void getKindAttrs() {
    print('和藹的');
  }
}

// 軟件架構師
class SoftwareArchitect {
  SoftwareArchitect() {
    print('軟件架構師');
  }
}
2).要使用 mixin,請使用with關鍵字后跟一個或多個mixin名稱。以下示例顯示了兩個使用mixins的類。
void main() {
  // 姓名:張三
  // 發現和解決難題
  // 精通數據結構和算法
  // 會架構設計
  ACompanySoftwareArchitect architect1 = new ACompanySoftwareArchitect('張三');
  architect1.develop();
  print('====');
  // 姓名:李四
  // 發現和解決難題
  // 精通數據結構和算法
  // 會架構設計
  // 熟練掌握性能優化
  // 熟練掌握計算機系統
  // 有效率的
  // 和藹的
  BCompanySoftwareArchitect architect2 = new BCompanySoftwareArchitect('李四');
  architect2.develop();
  architect2.getEfficientAttrs();
  architect2.getKindAttrs();
}

// 使用mixin
// A公司的軟件架構師,繼承自軟件架構師,擁有專家的特性。
class ACompanySoftwareArchitect extends SoftwareArchitect with Expert {
  String name;
  ACompanySoftwareArchitect(String name) {
    this.name = name;
    print('姓名:' + name);
    solveProblems = true;
    dataStructureAndAlgorithms = true;
    architectureDesign = true;
  }

  @override
  void develop() {
    super.develop();
  }
}

//  B公司的軟件架構師,繼承自軟件架構師,
class BCompanySoftwareArchitect extends SoftwareArchitect
    with Expert, Efficient, Kind {
  String name;

  BCompanySoftwareArchitect(String name) {
    this.name = name;
    print('姓名:' + name);
    solveProblems = true;
    dataStructureAndAlgorithms = true;
    architectureDesign = true;
    performanceOptimization = true;
    computerSystem = true;
  }
}
3).要指定只有某些類型可以使用mixin。例如,所以你的mixin可以調用它沒有定義的方法, 用于on指定所需的超類。

mixin SoftwareDeveloper on ACompanySoftwareArchitect{}


(二)泛型

如果您查看基本數組類型的API文檔 List,您會看到該類型實際上是List<E><...>表示法將List標記為 泛型(或參數化)類型 - 具有正式類型參數的類型。按照慣例,大多數類型變量都有單字母名稱,例如E,TSKV`.

(1)為什么使用泛型?

類型安全通常需要泛型,但它們比僅允許代碼運行有更多好處:

1).正確指定泛型類型可以生成更好的代碼。

如果您希望列表只包含字符串,則可以將其聲明為List<String>(將其讀作“字符串列表”)。這樣一來,工具可以檢測到將非字符串分配給列表可能是一個錯誤。
例子:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
// 報錯 The argument type 'int' can't be assigned to the parameter type 'String'.
names.add(42); 
2).您可以使用泛型來減少代碼重復。

泛型允許您在多種類型之間共享單個接口和實現,同時仍然利用靜態分析。
例如:創建了一個用于緩存對象的接口:

abstract class ObjectCache {
  Object getByKey(String key);
  void setByKey(String key, Object value);
}

您發現需要此接口針對字符串的做一個緩存,因此您需要創建另一個接口:

abstract class StringCache {
  String getByKey(String key);
  void setByKey(String key, String value);
}

如果還有其他更改,就要寫很多接口。

class Test1 extends ObjectCache {
  @override
  Object getByKey(String key) {
    return 'object cache';
  }

  @override
  void setByKey(String key, Object value) {
    return null;
  }
}

class Test2 extends StringCache {
  @override
  String getByKey(String key) {
    return 'String cache';
  }

  @override
  void setByKey(String key, String value) {
    return null;
  }
}

泛型可以省去創建所有這些接口的麻煩。你可以創建一個帶有類型參數的接口。
示例如下:T是一個占位符,您可以將其視為開發人員稍后定義的類型。

abstract class Cache<T> {
  T getByKey(String key);
}

使用泛型以前的調用方式

print(new Test1().getByKey('123'));
print(new Test2().getByKey('456'));

(2)使用集合文字

listmap文字可以參數化。參數化文字就像你已經看到的文字一樣,除了你在開始括號之前添加 <type>(對于list)或 <keyType, valueType>(對于map)。
以下是使用類型文字(typed literals)的示例:

var numbers = <String>['11', '22', '33'];
var pages = <String, String>{
  'index.html': 'Homepage',
  'store.html': 'Store',
  'mine.html': 'Mine'
};

(3)使用帶有構造函數的參數化類型

要在使用構造函數時指定一個或多個類型,請將類型放在類名稱后面的尖括號<...>中。

例如:

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
var nameSet = Set<String>.from(names);

以下代碼創建一個具有整數的key和View類型的value的map:

var views = Map<int, View>();

(4)泛型集合及其包含的類型

Dart的泛型類型是具體的。也就說,它們在運行時會會攜帶類型信息。

var names = List<String>();
names.addAll(['Seth', 'Kathy', 'Lars']);
print(names is List<String>); // true
print(names.runtimeType); // List<String>

注意:相反,Java中的泛型使用擦除,這意味著在運行時刪除泛型類型參數。在Java中,您可以測試對象是否為List,但您無法測試它是否是List<String>

(5)限制參數類型

實現泛型類型時,您可能希望限制其參數的類型。你可以在<>里面使用extends。
例如:

abstract class SomeBaseClass {
    // 其他操作
}

class Foo<T extends SomeBaseClass> {
  String toString() {
      return "Instance of Foo<$T>";
  }
}

class Extender extends SomeBaseClass {
     //其他操作
}

現在可以使用SomeBaseClass或它的任何子類作為泛型參數。

例如:

void main() {
    var someBaseClassFoo = Foo<SomeBaseClass>();
    var extenderFoo = Foo<Extender>();
    print(someBaseClassFoo.toString());// Instance of Foo<SomeBaseClass>
    print(extenderFoo.toString());// Instance of Foo<SomeBaseClass>
}

也可以不指定泛型參數。
例如:

var foo = Foo();

//等同于print(foo.toString());

print(foo);// Instance of Foo<SomeBaseClass>

如果指定任何非SomeBaseClass類型會導致錯誤。
例如:var foo = Foo<Object>;

(6)使用泛型方法

新版本的Dart的泛型方法,允許在方法和函數上使用類型參數。(但它同樣適用于實例方法,靜態方法,頂級函數,本地函數甚至lambda表達式。)
例如:
    T first<T>(List<T> data) {
      // 做一些初始工作或錯誤檢查...
      T tmp = data[0];
  // 做一些額外的檢查或處理...
  return tmp;
}

在first(<T>)上的的泛型類型參數,允許你在以下幾個地方使用類型參數T:

  • 1). 在函數的返回類型(T)中
  • 2). 在參數類型(List<T>)中
  • 3). 在局部變量的類型(T tmp)

泛型方法可以聲明類方法(實例和靜態)以相同的方式獲取泛型參數。

class Test {
  static int f<M, N>(int x) => 3;
  int m<M, N>(int y) => 5;
}

泛型方法也適用于函數類型參數,本地函數和函數表達式。
// 作為參數

void functionTypedParameter<T>(T callback){}

// 聲明一個本地泛型函數本身

void  localFunction(){
   T itself<T>(T thing) => thing;
}

// 將泛型函數表達式綁定到局部變量。

void functionExpression(){
   var lambda =  <T>(T thing) => thing;
}

使用泛型方法:

void main() {
      List<String> data = ["張三","李四","王五"];
      var result = first(data);
      print(result);
}

(三)函數

(1)普通函數

Dart是一種真正的面向對象的語言,所以即使是函數也是對象,函數屬于Function類型。可以通過函數指定變量或者把函數作為其他函數的參數。

1)函數的簡寫。

1.對于只有一個表達式的函數,可以簡寫。

例如flutter新建工程里面的main.dart,找到里面的runApp函數,可以使用 =>這樣的箭頭函數去操作,如下所示:
操作前:

  void main(){
    runApp(new MyApp());
  }

操作后:(main.dart文件里面默認使用的是==>箭頭函數)

void main() => runApp(new MyApp());

【注意:】main函數是程序的入口,不管是純Dart代碼,還是Flutter項目,或者其他語言,基本都是main函數是入口函數。

2.返回值為void時,可以省略void關鍵字(開發中不建議這么做)。

函數的返回值可以是void,也可以是null,也可以是具體對象。如果沒有指定返回值,則該函數返回的是null。例如flutter新建工程里面的main.dart_incrementCounter()函數,可以省略關鍵字void,如下所示:
操作前:

void _incrementCounter(){
  //...
}

操作后:

_incrementCounter(){
  //...
}

我們使用assert(_incrementCounter()==null);測試一下,發現程序運行正常,可以看出該函數返回值為null

【注意】函數屬于Function類型,可以通過斷言assert(XXX is Funtion);判斷出結果,返回值必須是具體類型或者省略,如果返回值寫為void,編譯器有錯誤提示。
舉例如下:

void testMethod (){
  //...
}

例如我們:assert(testMethod () is Function);//這時候編譯器會報錯。

2)普通參數與可選參數

Dart的函數最好玩的就是這個可選參數了,就是可以聲明多個參數,使用時可以調用其中的某一個或者多個參數,與參數位置無關。

1.可選參數的基本使用

可選參數的定義方式:{參數1,參數2,,...},使用方式:函數名(paramName1: value1, paramName2: value2, paramName3: value3...);

下面我們來看一個簡單的示例對比一下普通函數和可選參數:

操作前:

// 工作:地址、公司名、工資、工作時長、公司人數
void work(
  String address, 
  String cpompany, 
  double money, 
  String workTime,
  int workerNumbers) {
  //TODO:... 
}

使用:

void main() {
  // 缺一個參數都會報錯
  work('hangzhou','XXCompany',1000000.00,'9:00-5:00',500);
}

操作后:

void work2({
  String address, 
  String cpompany, 
  double money, 
  String workTime,
  int workerNumbers}) {
  //TODO:... 
}

使用:

void main() {
  //你隨意使用其中的參數都是可以的,例如我使用了其中的參數1,參數4和參數5
  work2(address:'hangzhou', workTime:'9:00-5:00', workerNumbers:500);
}

2.可選參數默認的值

可以使用 = 為任意的可選參數設置默認值,默認值必須是編譯時常量,如果沒有提供默認值,則默認值為null。
例如下例就是給參數1和參數2設置了默認值:

void work3({
  String address = 'hangzhou', 
  String cpompany = ' XXCompany ', 
  double money, 
  String workTime,
  int workerNumbers}) {
    //TODO:... 
}

3.普通函數參數為list或者map的默認值

如果普通函數的參數是一個匿名List集合(也叫數組),也可以使用 = 設置默認值,數組不能被包含在可選參數里面。

例如:

void work4(
    String address,
    [String cpompany = 'XXCompany',
    double money,
    String workTime,
    int workerNumbers]) {
  //TODO:...
}

4.可變參數為list或者map的默認值

可變參數可以是顯示聲明的List集合或者map,但是list或者map的值比如是const修飾。

舉例如下:

void work5({
    List<int> list = const [10, 20, 30],
    Map<String, String> gifts = const {
     'cpompany':'XXCompany',
     'money':'50000',
     'workTime':'9:00-5:00',
    }}) {
  //TODO:...
}
3)函數作為一個參數傳給另一個函數

這個就類似于java里面的回調功能。例如flutter新建工程里面的main.dart,我們看看這段代碼就知道了:

// 函數作為參數傳給另一個函數
void main() {
    // 例如main.dart里面FloatingActionButton的onPressed參數引入了一個_incrementCounter()函數
    // floatingActionButton: new FloatingActionButton(onPressed: _incrementCounter,), 
}

void _incrementCounter() {
//   setState(() {
//     _counter++;
//   });
}
4)匿名函數

我們還是以flutter新建工程里面的main.dart為例,我們看看這里的setState函數,這里面的參數是一個(){}。小括號里面沒有參數,我們去看看setState源碼你會發現它的參數是一個Function,這里沒有傳入任何參數。這里面其實就是一種匿名函數的用法。

void _incrementCounter() {
  setState(() {
    _counter++;
  });
}

再比如常見的list.foreach用法也會用到匿名函數,例如下例中的forEach()我們這里寫的是無類型參數的匿名函數item,forEach 源碼是:forEach (void f(E element)),它的參數是一個函數。

List list = [10, 7, 23];
list.forEach((item) {
  print('$item');
});

以上語句可以簡寫成:list.forEach((item) => print('$item'));

5)函數作用域

Dart是一種具有語法范圍的語言,變量的范圍是靜態確定的,只需通過代碼布局來確定。通過花括號向外查看,可以確定變量是否在范圍內。
以下是一個嵌套函數的例子,每個作用域級別上都有變量,變量作用域為函數內部,外部無法訪問。我們可以看看日志就清楚了:

// main函數里面可以輸出topLevel和insideMain的值。
// myFunction函數里面可以輸出topLevel、insideMain和insideFunction的值。
// nestedFunction函數里面可以輸出topLevel、insideMain、insideFunction和insideNestedFunction的值。
bool topLevel = true;
void main() {
  var insideMain = true;
  void myFunction() {
    var insideFunction = true;
    void nestedFunction() {
      var insideNestedFunction = true;
      print('topLevel\r');
      print(topLevel);
      print('insideMain\r');
      print(insideMain);
      print('insideFunction\r');
      print(insideFunction);
      print('insideNestedFunction\r');
      print(insideNestedFunction);
    }
    // print('topLevel\r');
    // print(topLevel);
    // print('insideMain\r');
    // print(insideMain);
    // print('insideFunction\r');
    // print(insideFunction);
    // 調用函數nestedFunction
    nestedFunction();
  }
  // 調用函數myFunction
  myFunction();
  // print('topLevel\r');
  // print(topLevel);
  // print('insideMain\r');
  // print(insideMain);
}
6)閉包

當函數定義和函數表達式位于另一個函數的函數體內。而且這些內部函數可以訪問它們所在的外部函數中聲明的所有局部變量、參數和聲明的其他內部函數。當其中一個這樣的內部函數在包含它們的外部函數之外被調用時,就會形成閉包。

  • a. 內部函數為有參數的匿名函數示例:
void main() {
  var result = test();
  print(result(2.0));//結果為:12.56
}

Function test(){
  const PI = 3.14;
  return (double r) => r * r * PI;
}
  • b. 內部函數為無參數的匿名函數示例:
void main() {
  var result2 = test2();
  print(result2());//結果為:3.14
}

Function test2() {
  const PI = 3.14;
  return () => PI;
}
7)等價函數
//函數也是對象

void topMethod() {} // 一個頂級函數

class Demo {
  static void staticMethod() {} //一個靜態方法
  void caseMethod() {} //實例方法
}

void main() {
  var compareVar;

  // 比較頂級的函數
  compareVar = topMethod;
  print(topMethod == compareVar);

  // 比較靜態方法
  compareVar = Demo.staticMethod;
  print(Demo.staticMethod == compareVar);

  // 比較實例方法
  var demo1 = Demo(); // Demo類的實例1
  var demo2 = Demo(); // Demo類的實例2
  var y = demo2;
  compareVar = demo2.caseMethod;

  //這些閉包指向同一個實例demo2,所以它們相等。
  print(y.caseMethod == compareVar);

  //這些閉包是指不同的實例,所以他們不平等。
  print(demo1.caseMethod != demo2.caseMethod);
}
8)函數別名

在Dart中,函數是對象,就像字符串一樣,數字是對象。一個類型定義,或功能型的別名,給出了一個函數類型聲明字段時,您可以使用和返回類型的名稱。當函數類型分配給變量時,typedef會保留類型信息。

以下代碼,它不使用typedef:我們可以看到funs是一個函數,但它是哪一種類型的函數?不是很清楚。

class Demo {
  Function funs;
  Demo (int f(Object a, Object b)) {
    funs = f;
  }
}

int test(Object a, Object b) => 0;

void main() {
  Demo demo = Demo(test);
  // funs是一個函數,但它是哪一種類型的函數?
  print(demo.funs is Function); // true
}

可以使用typedef給函數取個別名,這一點讓我想起了C語言里面的函數指針。接下來使用typedef改造一下,我們將代碼更改為使用顯式名稱并保留類型信息,開發人員和工具都可以使用該信息。

  1. 給Function取一個別名叫做TypedefFuns
typedef TypedefFuns = int Function(Object a, Object b);
  1. Demo類里的構造方法使用這個別名
class Demo {
  TypedefFuns funs;
  Demo(this.funs);
}
  1. 使用Demo類,傳入一個函數。這里給Demo類傳入了一個函數test。如果想知道然后判斷demo.funs屬于哪一種類型。
int test(Object a, Object b) => 0;

void main() {
  Demo demo = Demo(test);
  print(demo.funs is Function); // true
  print(demo.funs is Demo); // false
}

目前:typedef僅限于函數類型。

因為typedef只是別名,Dart提供了一種檢查任何函數類型的方法。
例如:

typedef TypedefFuns2<T> = int Function(T a, T b);
int test2(int a, int b) => a - b;
void main() {
  print(test2 is TypedefFuns2<int>); // True
}
9)getter 和 setter

gettersetter是提供對象屬性的讀寫訪問權限的特殊方法。所有實例變量都生成一個隱式getter方法。非final實例變量也會生成隱式setter方法。使用get和set關鍵字通過實現getter和setter來創建其他屬性。

  • 1.使用getter和setter,可以從實例變量開始。

例如:

void main() {
  var rect = Rectangle(3, 4, 20, 15);
  print(rect.left == 3); // true
  rect.right = 12;
  print(rect.left == -8); // true
}

class Rectangle {
  num left, top, width, height;

  Rectangle(this.left, this.top, this.width, this.height);

  num get right => left + width;
  set right(num value) => left = value - width;
  num get bottom => top + height;
  set bottom(num value) => top = value - height;
}

注意: 無論是否明確定義了getter,增量(++)等運算符都以預期的方式工作。為避免任何意外的副作用,只需調用一次getter,將其值保存在臨時變量中。

  • 2.實例變量的隱式getter和setter方法

所有實例變量都生成一個隱式getter方法。非final實例變量也會生成隱式setter方法。

例如:

class Point {
    num x;
    num y;
}

void main() {
    var point = Point();
    point.x = 4; // Use the setter method for x.
    assert(point.x == 4); // Use the getter method for x.
    assert(point.y == null); // Values default to null.
}

如果初始化聲明它的實例變量(而不是構造函數或方法),則在創建實例時設置該值,該實例在構造函數及其初始化列表執行之前。

例如:

class Point2 {
  num x = 10;
  num y = 5;
  Point2 p = new Point2();//p在構造函數之前執行
  Point2(){}
}
void main() {
    var point2 = Point2();
    point2.x = 4; // 
}
10)抽象方法

實例,gettersetter方法可以是抽象的,定義一個接口,但將其實現留給其他類。抽象方法只能存在于抽象類中。要使方法抽象,請使用分號(;)而不是方法體。

abstract class Test {
  //定義實例變量和方法...

//定義一個抽象方法
void doSomething(); 
}

class TestImpl extends Test {
  // 抽象方法的實現
  void doSomething(){
    // 具體的實現...
  }
}
11)靜態方法

靜態方法:使用static關鍵字修飾的方法,也叫類方法,它不對實例進行操作,因此無權訪問this

void main() {
  print(Point.area(5, 4));// 10
}
class Point {
  static num area(num x, num y) {
    return (x * y)/2;
  }
}

注意:對于常用或廣泛使用的實用程序和功能,請考慮使用頂級函數而不是靜態方法。您可以使用靜態方法作為編譯時常量。例如,您可以將靜態方法作為參數傳遞給常量構造函數。

(2)構造函數

通過創建一個與其類同名的函數來聲明構造函數(另外,還有一個額外的標識符,如命名構造函數中所述)。

1).最常見的構造函數形式,即生成構造函數,創建一個類的新實例。

例如:

class Test {
  int x, y;

  Test(int x, int y) {
    this.x = x;//this.x指向的是當前Test類里面的變量int x
    this.y = y;
  }
}
void main(){
  // 使用Test構造函數創建Test對象
  var test = new Test(5, 15);
}
2).Dart具有語法糖,可以將構造函數參數賦值給實例變量。
class Test {
  num x, y;
  
  // 構造函數運行之前設置x和y
  Test(this.x, this.y) {
    print('x:$x, y:$y');
  }
}

如果沒有內容體,可以使用簡寫形式,示例如下:
class Test {
  num x, y;
  
  // 構造函數運行之前設置x和y
  Test(this.x, this.y);
}
3).默認構造函數(空參構造)

如果您未聲明構造函數,則會為您提供默認構造函數。默認構造函數沒有參數,并在超類中調用無參數構造函數。

注意:如果定義了空參構造,再去寫實參構造,會報錯(這一點和java不一樣)。

  class Test{
    // 如果不寫 默認就是空參構造
  Test(){}
}
4).命名構造函數

Java可以做到方法重載(也就是:多個同名不同參數構造函數),但是Dart不可以這么做。Dart提供了命名構造。使用命名構造函數為類實現多個構造函數或提供更多的解釋說明。
例如:

class Test{
  num x, y;

  // 命名構造
  Test.help() {
    x = 5;
    y = 10;
    print('x=${x}, y = ${y}');
  }
}
void main(){
  Test.help();// 調用命名構造函數
}

構造函數不是繼承的,也就是說超類的命名構造函數不會被子類繼承。如果希望使用超類中定義的命名構造函數創建子類,則必須在子類中實現該構造函數。
例如:Test類里面有一個Test.help()命名構造,它的子類是TestChild,如果使用new TestChild.help()就會報錯,因為構造函數不能繼承。只有在TestChild類里面寫一個TestChild.help()命名構造函數,才可以使用該命名構造。

示例如下:

void main() {
  // 先執行Test類的空參構造, 再執行TestChild類的空參構造。
new TestChild();
  // 調用時會報錯
  //new TestChild.help();
}

class Test{
  var x, y;
  Test(){
    print('這是 Test 類的空參構造');
  }
// 命名構造不能被繼承
  Test.help(){
    x = 5;
    y = 10;
    print('Test.help() 命名函數 x=${x}, y = ${y}');
  }
}

class TestChild extends Test{
  var x, y;
  TestChild(){
    print('這是 TestChild 類的空參構造');
  }
  // 加上與父類相同的命名構造就不會錯 注釋了就會報錯
  // TestChild.help(){
  //   x = 3;
  //   y = 2;
  //   print('TestChild.help() 命名函數 x=${x}, y = ${y}');
  // }
}
5).構造函數不是繼承的

子類不從其超類繼承構造函數。聲明沒有構造函數的子類只有默認(無參數,無名稱)構造函數。

6).構造函數調用流程

默認情況下,子類中的構造函數調用超類的無參構造函數。超類的構造函數在構造函數體的開頭被調用。如果 還使用初始化列表,則在調用超類之前執行。
執行順序如下:
初始化列表 -> 超類的無參數構造函數 -> 主類的無參數構造函數,
超類必須要有一個空參構造,如果超類沒有未命名的無參數構造函數,則必須手動調用超類中的一個構造函數。在冒號(:)之后,在構造函數體(如果有)之前指定超類構造函數。
例如下面的示例:TestChild類和其超類Test類。

運行結果為:

Test 空參構造
TestChild 有參構造
面積為:6.0

示例代碼:

void main() {
  var result = new TestChild.area(3, 4);
  print('面積為:${result.area}');
}

class Test {
  num width;
  num height;
  num area;

  // 必須加上空參構造,如果注釋掉 它的子類會報錯
  Test() {
    print('Test 空參構造');
  }

  Test.area(width, height)
      : width = width,
        height = height,
        area = width * height {
    print('Test 有參構造');
  }
}

class TestChild extends Test {

  num width;
  num height;
  num area;

  TestChild() {
    print('TestChild 空參構造');
  }

  TestChild.area(num width, num height)
    : area = (width * height)/2 {
    print('TestChild 有參構造');
  }
}

注意事項:

    1. 在調用構造函數之前會計算超類構造函數的參數,所以參數可以是一個表達式。
void main(){
  new TestChild();
}

class Test{
  static String data;
  Test() {
    print('Test 空參構造,data: $data');
  }

  Test.area(String datas) {
    print('Test.area 命名函數,data: $datas');
  }
}

class TestChild extends Test {
  // 參數可以是一個表達式
  TestChild() : super.area(getDefaultData()) {
    print('TestChild 空參函數 調用父類的命名構造');
  }

  static String getDefaultData(){
    return 'TestChild的數據 getDefaultData';
  }
}

執行結果為:

  Test.area 命名函數,data: TestChild的數據 getDefaultData
  TestChild 空參函數 調用父類的命名構造
  • 2.超類構造參數不能使用this關鍵字。例如:參數可以調用靜態方法,但是不能調用實例方法。
    例如上例中的Test.area,修改一下就會報錯:
Test.area(this.width, this.height)
       : width = width,
         height = height,
         area = width * height {
print('Test 有參構造');
}
7).重定向構造函數

有時構造函數的唯一目的是重定向到同一個類中的另一個構造函數。重定向構造函數的主體是空的,構造函數調用出現在冒號(:)之后。

示例如下:

void main() {
  var result = new Test(4, true, '數字', 10);
  print('abcd分別是:${result.a},${result.b},${result.c},${result.d}');
}

class Test {
  num a;
  bool b;
  String c;
  num d;
  // 主構造函數
  Test(this.a, this.b, this.c, this.d);

  // 委托給主構造函數
  Test.test1(num x,bool y) : this(x, y,'', 0);
  Test.test2(num a,bool b, String c) : this(a, b, c, 0);
  Test.test3(num a,bool b, String c,num d) : this(a, b, c, d);
}

結果是:abcd分別是:4,true,數字,10

8).常量構造函數
  • 1.如果您的類生成永遠不會更改的對象,則可以使這些對象成為編譯時常量。為此,請定義const構造函數并確保所有實例變量都是final。要使用const構造函數創建編譯時常量,請將const關鍵字放在構造函數名稱之前。
    示例代碼:
void main() {
  var result = new Test(4, 10);
  print('x=${result.x}, y:=${result.y}'); // x=4, y=10
  print(Test.origin.x); // 5
  print(Test.origin.y); // 1

  var a = const Test(1, 1);
  var b = const Test(1, 1);
// 他們是相同示實例
  print(a == b); // true

class Test {
  static final Test origin = const Test(5, 1);
  final num x;
  final num y;
  const Test(this.x, this.y);
}
  • 2.常量上下文中的const構造函數

在常量上下文中,您可以省略const構造函數或文字之前的內容。
常量上下文,可以簡單的理解為:const后面包裹的語句一定是連續的一個整體,例如聲明一個list或者map。
例如,查看此代碼,該代碼創建一個const的map:

// 這里有很多const關鍵字
const pointAndLine = const {
  'point': const [const ImmutablePoint(0, 0)],
  'line': const [const ImmutablePoint(1, 10), const ImmutablePoint(-2, 11)],
};
class ImmutablePoint{
  const ImmutablePoint(int a, int b);
}

您可以省略除const關鍵字的第一次使用之外的所有內容:

// 只有一個const, 它建立了常量上下文
const pointAndLine2 = {
  'point': [ImmutablePoint(0, 0)],
  'line': [ImmutablePoint(1, 10), ImmutablePoint(-2, 11)],
};

如果一個常量構造函數在常量上下文之外,并且在沒有常量的情況下被調用,則會創建一個非常量對象:

var a = const ImmutablePoint(1, 1); //創建一個常量
var b = ImmutablePoint(1, 1); // 不創建常量
assert(!identical(a, b)); // 不是同一個實例
9).工廠構造函數

factory是在實現不總是創建其類的新實例的構造函數時使用關鍵字。例如,工廠構造函數可能從緩存中返回實例,或者它可能返回子類型的實例。

class Test{
  final String name;
  static Map<String, Test> _cache = new Map<String, Test>();
  factory Test(String name) {
    if (_cache.containsKey(name)) {
      return _cache[name];
    } else {
      final symbol = new Test._internal(name);
      _cache[name] = symbol;
      return symbol;
    }
  }
  Test._internal(this.name);
  void test(){
    print('調用了test()');
  }
}
void main(){
  var a = new Test('abc');
  var b = new Test('abc');
  // 檢查兩個是否引用的相同的對象
  print(identical(a, b)); // true
  new Test('abc').test();
}

運行結果:

true
調用了test()

說明:

  • 1.工廠構造函數無權訪問this
  • 2.可以創建子類的實例(例如:取決于傳遞的參數)。
  • 3.返回緩存的實例而不是新的實例。
  • 4.可以使用new關鍵字,也可以不使用。(上例中可以這樣寫:Test('abc').test())
  • 5.工廠構造函數沒有初始化列表(沒有 :super())

以下是Dart的工廠函數實現的單例:

// factory實現的單例
class Singleton {
  factory Singleton() => const Singleton._internal_();
  const Singleton._internal_();
}

void main() {
  print(new Singleton() == new Singleton());
  print(identical(new Singleton(), new Singleton()));
}

(3)初始化列表

1).可以在構造函數體運行之前初始化實例變量,用逗號分隔初始化。

例如:

有參構造的初始化:

class Test1 {
  var x, y;
  Test1(var x, var y)
      : x = x,
        y = y {
    print('Test1 有參構造初始化');
  }
}

命名構造的初始化:

class Test2{
  var x,y;
  Test2.from(Map<String, num> json)
      : x = json['x'],
       y = json['y'] {
    print('Test2.from(): ($x, $y)');
  }
}
2). 在實際應用開發中,可以使用assert在初始化列表用來校驗輸入參數。

例如:

有參構造使用assert校驗參數:

class Test1 {
  var x, y;

  Test1(var x, var y) : assert(x >= 0) {
    print('Test1(): ($x, $y)');
  }

}

命名構造使用assert校驗參數:

class Test2 {
  var x, y;
  Test2.withAssert(this.x, this.y) : assert(x >= 0) {
    print('Test2.withAssert(): ($x, $y)');
  }
}
3).如果沒有更多實際操作內容,可以簡寫。

有參構造使用assert校驗參數的簡寫形式:

class Test1 {
  var x, y;

  Test1(var x, var y) : assert(x >= 0);

}

命名構造使用assert校驗參數的簡寫形式:

class Test2{
var x, y;
  Test2.withAssert(this.x, this.y)  : assert(x >= 0);
}
4).如果要把構造的初始化和assert校驗同時使用,可以采用這種方式:

使用assert簡寫,然后在內容體里面執行初始化操作。

class Test1{
  var x, y;
  Test1(var x, var y) : assert(x > 0){
    this.x = x;
    this.y = y;
    print('Test1 有參構造初始化');
  }
}
5).設置final字段,初始化程序時更方便。
import 'dart:math';

// 設置final字段,初始化程序時更方便

void main() {
  var p = new Test1(4, 3);
  print('長方形的對角線長度:${p.hypotenuse}');
}

class Test1 {
  final num width;
  final num height;
  final num hypotenuse;

  Test1(width, height)
      : width = width,
        height = height,
        hypotenuse = sqrt(width * width + height * height);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373