[Flutter]flutter基礎(chǔ)之Dart語言基礎(chǔ)(三)

一茶宵、函數(shù)

函數(shù)用于將代碼結(jié)構(gòu)化垢夹,將復(fù)雜的問題簡單化,實(shí)現(xiàn)根據(jù)功能拆分程序刀崖,使得代碼可以實(shí)現(xiàn)復(fù)用惊科。

Dart 中的入口函數(shù)為 main ,無論 main 函數(shù)放在哪都會從此開始執(zhí)行代碼亮钦。關(guān)于 main 函數(shù)的結(jié)構(gòu)在第一篇文章中已經(jīng)做了說明馆截,這里不再贅述。Dart 中一且皆對象,因此蜡娶,函數(shù)也是對象混卵,為 Function 類型對象。

Dart 中提供了很多常用的函數(shù)庫窖张,通過這些函數(shù)能很方便的實(shí)現(xiàn)一些通用功能幕随,在開發(fā)過程中可以直接使用。但是很多功能還需要開發(fā)人員自己實(shí)現(xiàn)宿接。

Dart 中函數(shù)可以嵌套定義赘淮。

1. 自定義基本函數(shù)

一個(gè)函數(shù)的完成格式如下:

返回值 函數(shù)名(參數(shù)列表) {
    函數(shù)體
}

比如定義一個(gè)實(shí)現(xiàn)兩個(gè)數(shù)相加的函數(shù),如下:

num addFunction(num a, num b) {   
  return a + b;
}

其中睦霎,第一個(gè) num 為返回值類型梢卸,addFunction 為函數(shù)名,括號內(nèi)為函數(shù)的參數(shù)列表副女,這里是兩個(gè) num 類型的 ab 蛤高。因?yàn)?num 類型是 intdouble 的父類,所以這個(gè)函數(shù)可以完成整型和浮點(diǎn)型的相加操作碑幅。函數(shù)定義好后戴陡,需要在 main 函數(shù)中調(diào)用才會執(zhí)行,如下:

main(){
  num sum = addFunction(10, 20);
  print(sum);  //輸出 30
}
     
num addFunction(num a, num b) {
  return a + b;
}

調(diào)用函數(shù)并定義一個(gè) num 類型的變量 sum 來接收函數(shù)的返回值沟涨。調(diào)用函數(shù)時(shí)恤批,傳入的參數(shù)類型應(yīng)與函數(shù)的參數(shù)類型和數(shù)量相對應(yīng),否則會報(bào)錯(cuò)拷窜。

前面第一篇文章在介紹 main 函數(shù)時(shí)就說過开皿,在 Dart 中,函數(shù)的返回值類型是可以省略的篮昧,其實(shí),參數(shù)類型也是可以省略的笋妥,Dart 會根據(jù)調(diào)用時(shí)傳入的參數(shù)類型來自動推斷數(shù)據(jù)類型懊昨。而返回值類型,如果函數(shù)有返回值則根據(jù)返回值類型進(jìn)行推斷春宣,如無返回值酵颁,則推斷為 Null ,而非 void 月帝。

main(){
  var sum = addFunction(10, 20);
  print(sum);  //輸出 30
}
     
addFunction(a, b) {
  return a + b;
}

需要注意的是躏惋,如不定義參數(shù)類型,需要在函數(shù)體內(nèi)做好邊緣處理嚷辅,防止類型錯(cuò)誤而拋出異常簿姨。也需要確定傳入的參數(shù)在函數(shù)內(nèi)是否有意義,如函數(shù)處理功能與參數(shù)類型無關(guān),或無法處理扁位,則沒有任何意義准潭。

Dart 中,如果函數(shù)體內(nèi)只有一個(gè)表達(dá)式域仇,可以寫成如下形式:

main(){
  num sum = addFunction(10, 20);
  print(sum);  //輸出 30
}

//只有一個(gè)表達(dá)式刑然,簡寫成如下形式,參數(shù)類型等依然是可以省略的
addFunction(num a, num b)=> a + b;

其中 => expr 語法為 {return expr;}暇务,此語法可稱為箭頭語法泼掠。

因?yàn)?Dart 中函數(shù)也是對象(Function)對象,所以可以將函數(shù)賦值給變量或作為參數(shù)進(jìn)行傳遞垦细。

main(){
  var iSum = addFunction;
  num sum = iSum(10, 20);
  print(sum);
}
     
addFunction(num a, num b)=> a + b;

函數(shù)類型也可作為參數(shù)和返回值類型择镇,如下:

void main(){
  var res = printFunction(printInfo);
  print(res.runtimeType);   //輸出  () => dynamic
}

printInfo() {
  print("這是一個(gè)函數(shù)");
}

Function printFunction(Function s) {
  s();    //執(zhí)行函數(shù) 輸出 這是一個(gè)函數(shù)
  return s;
}
2.可選參數(shù)函數(shù)

Dart 中,函數(shù)可以具有兩種類型的參數(shù):必須參數(shù)和可選參數(shù)蝠检。當(dāng)兩種類型的參數(shù)都存在時(shí)沐鼠,應(yīng)先列出必須參數(shù),然后列出可選參數(shù)叹谁∷撬螅可選參數(shù)又分為命名參數(shù)和位置參數(shù)。使用時(shí)焰檩,可以單獨(dú)使用命名參數(shù)或位置參數(shù)憔涉,但是兩者不能同時(shí)在一個(gè)函數(shù)參數(shù)中使用。

前面定義的函數(shù)中都為必須參數(shù)析苫,在調(diào)用函數(shù)時(shí)必須傳遞兜叨。而可選參數(shù)是指,在進(jìn)行函數(shù)調(diào)用時(shí)衩侥,可以傳入此參數(shù)国旷,也可以不傳入此參數(shù)。

命名參數(shù)

命名參數(shù)是在定義函數(shù)時(shí)茫死,將命名參數(shù)放在{}中跪但,如下:

personInfo({String sex, int age}) {
    print("性別:$sex 年齡:$age");
}

調(diào)用方式為根據(jù)參數(shù)名傳入?yún)?shù),格式為:paramName:value 峦萎,如下:

main(){
  personInfo();   //不傳參數(shù) 輸出 性別:null 年齡:null
  personInfo(sex:"男"); //傳入性別 輸出 性別:男 年齡:null
  personInfo(age:20);   //傳入年齡 輸出 性別:null 年齡:20
  personInfo(sex:"男", age: 20); //傳入性別 年齡 輸出 性別:男 年齡:20
  personInfo(age: 20, sex:"男"); //傳入性別 年齡 輸出 性別:男 年齡:20 
}
     
personInfo({String sex, int age}) {
    print("性別:$sex 年齡:$age");
}

命名參數(shù)是根據(jù)指定的參數(shù)名稱尋找對應(yīng)的參數(shù)屡久,確定其類型,所以傳入的順序可以不定爱榔。

雖然命名參數(shù)是可選參數(shù)被环,但是也可以強(qiáng)制提供該參數(shù),使用關(guān)鍵字 @required 详幽。如下:

import 'package:meta/meta.dart';

main(){
  personInfo(name: "張三");
  personInfo();   //此處調(diào)用會報(bào)警告
}

personInfo({String sex, int age, @required String name}) {
    print("性別:$sex 年齡:$age 姓名:$name");
} 

使用 @required 標(biāo)注需要導(dǎo)入包 package:meta/meta.dart 筛欢。 package 并不是 Dart SDK 默認(rèn)提供的,需要添加依賴或直接安裝到,這在后面會講到悴能。有興趣的可以參考:https://pub.dev/packages/meta#-installing-tab-

根據(jù)官方文檔的說明揣钦,使用 @required 注釋的參數(shù)為必須提供的參數(shù),這里也進(jìn)行了測試漠酿,在 VSCode 環(huán)境下冯凹,.dart 文件運(yùn)行的情況下,不提供也是可以的炒嘲,但是會報(bào)警告信息宇姚,如下:

The parameter 'name' is required. .dart(missing_required_param)

并且當(dāng)使用 @required 標(biāo)注以后,在調(diào)用函數(shù)時(shí)夫凸,會自動帶上所修飾的關(guān)鍵字浑劳。

位置參數(shù)

位置參數(shù)是表示某個(gè)位置的參數(shù)是可選的,需將參數(shù)放在 [] 中夭拌,如下:

main(){
  personInfo("張三");   //輸出 姓名:張三 性別:null 年齡:null
  personInfo("張三", null, 20);  //輸出 姓名:張三 性別:男 年齡:20
  personInfo("張三", "男", 20);  //輸出 姓名:張三 性別:null 年齡:20
}

personInfo(String name, [String sex, int age]) {
    print("姓名:$name 性別:$sex 年齡:$age");
} 

sexage 為位置可選參數(shù)魔熏,name 為必須參數(shù)。當(dāng)有多個(gè)位置參數(shù)時(shí)鸽扁,如果想提供的參數(shù)不在位置參數(shù)的前面的位置蒜绽,在 Dart 中并沒有提供忽略參數(shù)的方法,可以將位置參數(shù)的排列順序做改變或者提供 null 參數(shù)桶现,并在函數(shù)體內(nèi)做處理即可躲雅。

3. 可選參數(shù)的默認(rèn)值

因?yàn)樵谡{(diào)用函數(shù)時(shí),默認(rèn)參數(shù)不是必須提供骡和,所以當(dāng)調(diào)用者未提供指定的可選參數(shù)時(shí)相赁,Dart 可以為可選參數(shù)設(shè)置一個(gè)默認(rèn)值,當(dāng)調(diào)用函數(shù)時(shí)慰于,未提供指定參數(shù)钮科,則使用默認(rèn)值做為指定值。默認(rèn)值必須是編譯時(shí)常量婆赠,如果不提供默認(rèn)值跺嗽,默認(rèn)值為 null

命名參數(shù)的默認(rèn)值

main(){
  personInfo();                                         //輸出 姓名:null 年齡:20
  personInfo(name: "張三");                    //輸出 姓名:張三 年齡:20
  personInfo(name: "張三", age: 28); //輸出 姓名:張三 年齡:28
}

personInfo({String name, int age = 20}) {
    print("姓名:$name 年齡:$age");
} 

位置參數(shù)的默認(rèn)值

main(){
  personInfo();             //輸出 姓名:null 年齡:20
  personInfo("張三");        //輸出 姓名:張三 年齡:20
  personInfo("張三", 28);  //輸出 姓名:張三 年齡:28
}

personInfo([String name, int age = 20]) {
    print("姓名:$name 年齡:$age");
} 

PS:也可使用 List , Map , Set 等作為默認(rèn)值页藻,如下:

personInfo({List a = const [], Map b = const {}, Set c = const {}}) {
   print("$a $b $c");
} 
4. 匿名函數(shù)

一般定義函數(shù)都是有函數(shù)名的,如上面所示的代碼植兰,但是并不是所有函數(shù)都有名字份帐。Dart 中沒有名字的函數(shù)稱為匿名函數(shù)。匿名函數(shù)可以直接賦值給變量來通過變量進(jìn)行函數(shù)調(diào)用楣导,也可以創(chuàng)建自執(zhí)行的函數(shù)和閉包废境。匿名函數(shù)也可以有參數(shù)或無參數(shù)。

var fun = (){
  print("匿名無參函數(shù)");
};

如上匿名函數(shù)如果定義在其他函數(shù)體內(nèi)則相當(dāng)于將匿名函數(shù)賦值給局部變量,定義在全局則相當(dāng)于賦值給全局變量噩凹,如果如下匿名函數(shù)未賦值給變量巴元,定義在其他函數(shù)體內(nèi)不會出現(xiàn)問題,但是無法進(jìn)行調(diào)用驮宴,定義在全局則會報(bào)錯(cuò):

(){
   print("匿名函數(shù)");
};

但可以創(chuàng)建自執(zhí)行匿名函數(shù)逮刨,如下:

main(){
  (){
    print("匿名函數(shù)");
   }();  //輸出 匿名函數(shù)
}

帶參數(shù)的自執(zhí)行匿名函數(shù):

main(){
  (int a){
    print("匿名函數(shù) $a");
   }(10);  //輸出 10
}

當(dāng)在全局區(qū)定義匿名函數(shù)并賦值給一個(gè)變量時(shí),會報(bào)警告堵泽,如下:

main(){
}

var fun = (){      //()處報(bào)警告
  print("匿名無參函數(shù)");
};

//警告內(nèi)容如下
//The type of the function literal can't be inferred because the literal has a block as its body.
//Try adding an explicit type to the variable.dart(top_level_function_literal_block)

警告的意思是無法推斷出變量的類型修己,此時(shí)可以做如下修改即可消除警告:

main(){
}

dynamic fun = (){
  print("匿名無參函數(shù)");
};

使用 dynamicObject 修飾變量。正常使用如下:

main(){
  var sum = addFunction(10, 20);
  print(sum);   //輸出 30
}

dynamic addFunction = (num a, num b) => a + b; 

在函數(shù)體內(nèi)定義匿名函數(shù):

void main(){
  dynamic addFunction = (num a, num b) => a + b; 
  var sum = addFunction(10, 20);
  print(sum);
}

此時(shí)迎罗,只能在函數(shù)體內(nèi)使用睬愤,使用前需要定義,注意順序纹安。

5. 閉包

閉包也是函數(shù)對象尤辱,無論變量還是函數(shù)等都有其使用范圍,也就是作用域厢岂,當(dāng)出了作用域后將無法繼續(xù)使用對象或函數(shù)等光督。而閉包會對其使用的變量進(jìn)行拷貝,即使出了其作用域依然可以被函數(shù)內(nèi)部訪問咪笑。

main(){
  var fun1 = addFunction(5);
  var result = fun1(10);
  print(result);

}

//閉包函數(shù)可帽,函數(shù)內(nèi)嵌套函數(shù),并且內(nèi)部函數(shù)作為外部函數(shù)的返回值
addFunction(num a) {
  return (num b) => a + b;
}

上述實(shí)現(xiàn)了閉包窗怒,如果在閉包函數(shù)內(nèi)定義一個(gè)變量映跟,相對于函數(shù)內(nèi)部的函數(shù)來說,在外層函數(shù)定義的變量對于內(nèi)部函數(shù)來說就類似全局的概念扬虚,內(nèi)部函數(shù)會對外部變量做一次拷貝努隙,所以對于閉包函數(shù),變量是一直存在的辜昵。

二荸镊、類

Dart 是一種有類和基于 Mixin 的繼承的面向?qū)ο蟮恼Z言,一切皆對象堪置。每個(gè)對象都是一個(gè)類的實(shí)例躬存,所有類都繼承自 Object 。Dart 只支持單繼承舀锨,不支持多重繼承岭洲,但是可以通過混合 Mixin 實(shí)現(xiàn)多重繼承的特性。支持?jǐn)U展方法坎匿,擴(kuò)展方法可以在不更改類或創(chuàng)建子類的情況下向類中添加功能的方法盾剩。類中封裝了屬性和方法雷激,屬性用來存儲類數(shù)據(jù),方法用來描述類行為告私。

1. 定義類

Dart 中使用關(guān)鍵字 class 定義類屎暇,如下:

class Student {
  String name;
  int grade;
}

上述定義了一個(gè)學(xué)生類,并在其中定義了姓名和年級屬性驻粟,姓名和年級屬于實(shí)例屬性(實(shí)例變量)根悼,可以在類內(nèi)部使用,也可以將類實(shí)例化出的對象使用點(diǎn)語法來調(diào)用格嗅,未初始化的實(shí)例變量默認(rèn)值為 null番挺,如下:

main(){
  var student = new Student();
  student.name = "hike";
  student.grade = 1;
  print("姓名:${student.name} 班級:${student.grade}");  //輸出 姓名:hike 班級:1
}

class Student {
  String name;
  int grade;
}

其中的 new 關(guān)鍵字是可以省略的。也可以使用級聯(lián)運(yùn)算符屯掖,如下:

main(){
  var student = Student()
  ..name = "hike"
  ..grade = 1;
  print("姓名:${student.name} 班級:${student.grade}"); //輸出 姓名:hike 班級:1
}

class Student {
  String name;
  int grade;
}
2. 實(shí)例方法

實(shí)例方法與上述實(shí)例屬性一樣玄柏,在類內(nèi)定義好的實(shí)例方法需要實(shí)例化的類來調(diào)用,方法就是函數(shù)贴铜,如下:

main(){
  var student = new Student();
  student.name = "hike";
  student.grade = 1;
  student.study();

  Student student1 = Student();
  student1.running("mach", 5);
  print("學(xué)生的名字為${student1.name} ${student1.grade}年級");
}

class Student {
  String name;
  int grade;

  void study() {
    print("${grade}年級的學(xué)生${name}在學(xué)習(xí)");
  }

  void running(String name, int grade) {
    this.name = name;
    this.grade = grade;
    print("${this.grade}年級的學(xué)生${this.name}在跑步");
  }
}

上面在學(xué)生類中定義了兩個(gè)實(shí)例方法 studyrunning 粪摘,study 為無參數(shù)方法,running 為有參方法绍坝。在類內(nèi)定義的方法可以直接使用類內(nèi)定義的實(shí)例屬性徘意,但是如果方法的參數(shù)名稱與類內(nèi)的屬性名稱相同,在做賦值操作時(shí)轩褐,應(yīng)在屬性前通過 this 關(guān)鍵字使用點(diǎn)語法調(diào)用椎咧,this 代表當(dāng)前類的實(shí)例對象,如方法running 把介。

3. 構(gòu)造方法

類在進(jìn)行實(shí)例化時(shí)候勤讽,會調(diào)用類的構(gòu)造方法,這是大多數(shù)編程語言的特性拗踢,Dart 中也不列外脚牍。Dart 的類在實(shí)例化的時(shí)候也會調(diào)用構(gòu)造方法,上述的代碼都沒有進(jìn)行構(gòu)造函數(shù)的定義巢墅,構(gòu)造函數(shù)是一種特殊的函數(shù)诸狭,如果開發(fā)者沒有提供構(gòu)造函數(shù)(如上代碼,都沒有提供構(gòu)造函數(shù))君纫,編譯器會提供默認(rèn)的構(gòu)造函數(shù)驯遇,也就是說無論開發(fā)者是否書寫構(gòu)造函數(shù),在類的創(chuàng)建時(shí)都會調(diào)用構(gòu)造函數(shù)蓄髓,只是如果沒有提供構(gòu)造函數(shù)妹懒,則會調(diào)用默認(rèn)的構(gòu)造函數(shù),默認(rèn)構(gòu)造函數(shù)也屬于常規(guī)構(gòu)造函數(shù)双吆。構(gòu)造函數(shù)參數(shù)也支持可選參數(shù)方式眨唬。

常規(guī)構(gòu)造函數(shù)

默認(rèn)構(gòu)造,如下:

main(){
  var student = new Student();   //輸出 這是默認(rèn)的構(gòu)造函數(shù)
}

class Student {
  String name;
  int grade;

  Student(){
    print("這是默認(rèn)的構(gòu)造函數(shù)");
  }

  void study() {
    print("${grade}年級的學(xué)生${name}在學(xué)習(xí)");
  }

  void running(String name, int grade) {
    this.name = name;
    this.grade = grade;
    print("${this.grade}年級的學(xué)生${this.name}在跑步");
  }

上述代碼好乐,我們只創(chuàng)建了 Student 類的對象,并沒有調(diào)用任何方法,但是依然會打印函數(shù) Student 中的內(nèi)容姑原,Student 就是默認(rèn)的構(gòu)造函數(shù)徽曲。構(gòu)造函數(shù)的函數(shù)名必須和類名相同,所謂默認(rèn)的構(gòu)造函數(shù)就是與類名相同的無參函數(shù)反璃,創(chuàng)建類的對象時(shí)會默認(rèn)調(diào)用此函數(shù)昵慌。我們也可以改寫默認(rèn)的構(gòu)造函數(shù),給默認(rèn)的構(gòu)造函數(shù)添加參數(shù)淮蜈,如下:

main(){
  var student = new Student("hike", 3);
  student.study();
  student.running();
}

class Student {
  String name;
  int grade;

  Student(String newName, int newGrade){
    name = newName;
    grade = newGrade;
  }

  void study() {
    print("${grade}年級的學(xué)生${name}在學(xué)習(xí)");
  }

  void running() {
    print("${this.grade}年級的學(xué)生${this.name}在跑步");
  }

上述代碼斋攀,將默認(rèn)的構(gòu)造函數(shù)添加了兩個(gè)參數(shù),分別為:newNamenewGrade 梧田,其實(shí)參數(shù)名和屬性名可以完全相同淳蔼,就如前面所寫的 running 一樣,只不過需要添加 this 關(guān)鍵字裁眯,指明所屬對象鹉梨。修改如下:

 Student(String name, int grade){
    this.name = name;
    this.grade = grade;
  }

在 Dart 中有一種更為便捷的實(shí)現(xiàn)方法,可以完全省略構(gòu)造函數(shù)體內(nèi)的賦值過程穿稳,修改如下:

Student(this.name, this.grade);

如此實(shí)現(xiàn)存皂,同樣是實(shí)現(xiàn)構(gòu)造函數(shù)的賦值過程,提供構(gòu)造函數(shù)的好處逢艘,可以在創(chuàng)建對象的時(shí)候就提供參數(shù)旦袋,省去了創(chuàng)建對象的賦值過程,當(dāng)然也可以在構(gòu)造函數(shù)內(nèi)或非構(gòu)造函數(shù)內(nèi)對屬性做默認(rèn)賦值埋虹,但是如果不提供構(gòu)造函數(shù)猜憎,賦的默認(rèn)值就無法通過便捷的方式修改,不提供默認(rèn)值則默認(rèn)值為 null 搔课。值得注意的是胰柑,一旦對默認(rèn)的構(gòu)造函數(shù)(與類同名無參的函數(shù))做了修改,原本提供的默認(rèn)構(gòu)造函數(shù)(與類同名無參的函數(shù))便無法繼續(xù)使用爬泥,如下:

main(){
  // var student =  new Student();   //錯(cuò)誤
  var student = new Student("hike", 3);
  student.study();
  student.running();
}

class Student {
  String name;
  int grade;

  Student(this.name, this.grade);

  void study() {
    print("${grade}年級的學(xué)生${name}在學(xué)習(xí)");
  }

  void running() {
    print("${this.grade}年級的學(xué)生${this.name}在跑步");
  }
}

Dart 中不支持函數(shù)重載柬讨,構(gòu)造函數(shù)也是如此。所以如果想創(chuàng)建多個(gè)提供不同參數(shù)的構(gòu)造函數(shù)便無法實(shí)現(xiàn)袍啡。但是 Dart 中提供了其他的方式實(shí)現(xiàn)同樣的功能踩官,就是命名構(gòu)造函數(shù)。

命名構(gòu)造函數(shù)

命名構(gòu)造函數(shù)就是通過為構(gòu)造函數(shù)起一個(gè)別名的方式顯示構(gòu)造函數(shù)的功能境输,實(shí)現(xiàn)方法如下:

main(){

  var student1 = new Student("hike", 3);
  student1.study();
  student1.running();

  Map studentMap = {
    "name" : "Mary",
    "grade" : 2
  };
  var student2 = new Student.fromMap(studentMap);
  student2.study();
  student2.running();

  var student3 = new Student.otherStudent(student1);
  student3.study();
  student3.running();
}

class Student {
  String name;
  int grade;

  Student(this.name, this.grade);

  Student.fromMap(Map studentJson) {
    name = studentJson["name"];
    grade = studentJson["grade"];
  }

  Student.otherStudent(Student stu){
    name = stu.name;
    grade = stu.grade;
  }

  void study() {
    print("${grade}年級的學(xué)生${name}在學(xué)習(xí)");
  }

  void running() {
    print("${this.grade}年級的學(xué)生${this.name}在跑步");
  }
}

通過以上可以看出蔗牡,命名構(gòu)造函數(shù)格式為 構(gòu)造函數(shù)名.別名(參數(shù)列表) 颖系。別名也稱作標(biāo)識符,只要不同就可以了辩越,很簡單嘁扼,其他的語法規(guī)則都與以前介紹的相同。

構(gòu)造函數(shù)初始化列表

構(gòu)造函數(shù)的初始化列表用來在構(gòu)造函數(shù)主體執(zhí)行之前初始化實(shí)例變量黔攒,初始化列表可以進(jìn)行賦值操作趁啸,也可以使用表達(dá)式,對于計(jì)算某個(gè)值的最終結(jié)果很方便督惰。初始化列表使用 : 分隔不傅。

main(){
  var student1 = new Student("hike", 3, 89, 99.4);
  student1.study();
  student1.running();
}

class Student {
  String name;
  int grade;
  double score;
  double English;
  double math;

  Student(this.name, this.grade, english, math): score = english + math {
    print("${grade}年級的${name}的英語和數(shù)學(xué)的總分為:$score");
  }

  void study() {
    print("${grade}年級的學(xué)生${name}在學(xué)習(xí)");
  }

  void running() {
    print("${this.grade}年級的學(xué)生${this.name}在跑步");
  }
}

代碼中新增了總分?jǐn)?shù) score 、英語分?jǐn)?shù) english 和數(shù)學(xué)分?jǐn)?shù) math 赏胚,在構(gòu)造函數(shù)中使用初始化列表在構(gòu)造函數(shù)主體執(zhí)行之前計(jì)算出了總分?jǐn)?shù)访娶。

初始化列表右側(cè)(: 右側(cè))不能使用 this 關(guān)鍵字。在開發(fā)期間栅哀,可以在初始列表中加入 assert 來驗(yàn)證輸入的正確性震肮。

重定向構(gòu)造函數(shù)

通常,在創(chuàng)建對象的時(shí)候會調(diào)用一個(gè)構(gòu)造函數(shù)(無論是默認(rèn)構(gòu)造函數(shù)還是自定義的構(gòu)造函數(shù))留拾,在這過程中戳晌,調(diào)用哪個(gè)構(gòu)造函數(shù)就執(zhí)行哪個(gè)構(gòu)造函數(shù)的函數(shù)體(函數(shù)內(nèi)容)。在 Dart 中痴柔,可以定義重定向構(gòu)造函數(shù)沦偎,定義重定向構(gòu)造函數(shù)的目的在于,當(dāng)調(diào)用重定向構(gòu)造函數(shù)時(shí)咳蔚,會調(diào)用重定向函數(shù)指定的構(gòu)造函數(shù)方法豪嚎,以實(shí)現(xiàn)某種目的。在定義Dart 中的重定向函數(shù)時(shí)谈火,重定向函數(shù)主題必須為空(不能有函數(shù)體)侈询,目標(biāo)函數(shù)放在 : 后面,使用如下:

main(){
  var student1 = new Student("hike", 3);
  student1.study();
  student1.running();

  var student2 = Student.formGradeOne("Lucy");
  student2.study();
}

class Student {
  String name;
  int grade;

  Student(this.name, this.grade);

  //重定向構(gòu)造函數(shù)
  Student.formGradeOne(String name):this(name, 1);

  void study() {
    print("${grade}年級的學(xué)生${name}在學(xué)習(xí)");
  }

  void running() {
    print("${this.grade}年級的學(xué)生${this.name}在跑步");
  }
}

上述代碼定義了重定向構(gòu)造函數(shù) Student.formGradeOne 糯耍,目標(biāo)構(gòu)造函數(shù)為 Student 扔字,這個(gè)重定向構(gòu)造函數(shù)實(shí)現(xiàn)了一個(gè)定義班級為1的學(xué)生,只需要提供學(xué)生的姓名就可以直接創(chuàng)建班級為1的學(xué)生温技,這樣定義提供了一種便捷的創(chuàng)建方式革为,實(shí)際調(diào)用的構(gòu)造函數(shù)為目標(biāo)構(gòu)造函數(shù)(Student)。

PS:重定向函數(shù)的參數(shù)名稱不用與目標(biāo)函數(shù)的參數(shù)名稱相同舵鳞,但類型必須對應(yīng)震檩。

常量構(gòu)造函數(shù)

如果希望通過同一個(gè)類實(shí)例化出來的對象為同一對象(對于無參構(gòu)造函數(shù)創(chuàng)建的對象為同一對象,對于有參構(gòu)造函數(shù)蜓堕,當(dāng)傳入相同的數(shù)據(jù)時(shí)創(chuàng)造出的為同一對象)抛虏,就可以使用常量構(gòu)造函數(shù)博其,Dart 中,使用 const 關(guān)鍵字修飾的構(gòu)造函數(shù)為常量構(gòu)造函數(shù)嘉蕾,在使用常量構(gòu)造函數(shù)的類中贺奠,實(shí)例屬性必須使用 final 修飾,且常量構(gòu)造函數(shù)不能有函數(shù)體错忱。非常量構(gòu)造函數(shù)創(chuàng)建對象如下:

main(){
  var student1 = new Student("hike", 3);
  var student2 = new Student("hike", 3);
  bool isSame = identical(student1, student2);
  print(isSame);   //輸出 false
}

class Student {
  String name;
  int grade;

  Student(this.name, this.grade);
}

上述為非常量構(gòu)造函數(shù)創(chuàng)建的對象,identical 方法用來檢測兩個(gè)對象是否指向同一對象挂据。雖然參數(shù)都相同以清,且都是同一類的實(shí)例化對象,但是他們并不是同一個(gè)對象崎逃。

使用常量構(gòu)造函數(shù)方法如下:

main(){
  const student1 = Student("hike", 3);
  const student2 = Student("hike", 3);
  print(identical(student1, student2));   //輸出 true
}

class Student {
  final String name;
  final int grade;

  const Student(this.name, this.grade);
}

通過常量構(gòu)造函數(shù)實(shí)例化的對象掷倔,必須使用 const 做修飾,不能使用 new 關(guān)鍵字个绍。

工廠構(gòu)造函數(shù)

Dart 中支持工廠構(gòu)造函數(shù)勒葱,工廠構(gòu)造函數(shù)與普通構(gòu)造函數(shù)的區(qū)別在于,工廠構(gòu)造函數(shù)使用 factory 關(guān)鍵字修飾巴柿,并且有自己的返回值凛虽。以上使用的構(gòu)造函數(shù)可以歸納為普通構(gòu)造函數(shù)(默認(rèn)構(gòu)造與命名構(gòu)造),在普通構(gòu)造函數(shù)內(nèi)不能有明確返回值广恢,即便返回當(dāng)前對象類型凯旋,其操作是由 Dart 來完成的。而工廠構(gòu)造需要開發(fā)者手動指定返回值類型钉迷,可以返回當(dāng)前對象或其他類型至非。如不添加任何返回值,則會報(bào)警告(VSCode開發(fā)環(huán)境)糠聪。使用工廠構(gòu)造創(chuàng)建單利的方式如下:

main(){
  var bmwCar1 = new BmwCar(2);
  var bmwCar2 = new BmwCar(1);
  bool isSame = identical(bmwCar1, bmwCar2);
  print(isSame);                //輸出 true
  print("${bmwCar1.carId}  ${bmwCar2.carId}");   //輸出 2  2
}

class BmwCar {
  int carId;
  static BmwCar student;

  factory BmwCar(int carId) {
   if(student == null) {
     student = new BmwCar._fromCarId(carId);
   }
   return student;
 }

  BmwCar._fromCarId(this.carId);
}

上面的代碼荒椭,無論傳入的為何種參數(shù),創(chuàng)建出的新對象都為同一對象舰蟆。Dart 中下劃線(_)開頭的變量為私有變量趣惠,私有變量只能在定義它們的庫中使用(一個(gè) .dart 文件就是一個(gè)庫文件),關(guān)于此會在后續(xù)的關(guān)于庫的文章中詳細(xì)介紹夭苗。此外信卡,工廠方法也可以從一個(gè)緩存返回實(shí)例,官方的例子就是如此题造,可以參考傍菇。

4. 類屬性(靜態(tài)屬性) 與 類方法(靜態(tài)方法)

以上定義的屬性和方法都是實(shí)例屬性和實(shí)例方法,即必須通過類實(shí)例化以后的對象來進(jìn)行調(diào)用界赔。Dart 中也提供了類屬性和類方法丢习,即不用實(shí)例化類牵触,直接通過類名就可以直接調(diào)用的屬性和方法。Dart 中使用 static 來修飾類屬性和類方法咐低,如下:

main(){
  Student.name = "hike";
  Student.grade = 2;
  Student.study();
}

class Student {
  static String name;
  static int grade;
  
  static study(){
    print("${grade}班級的${name}在學(xué)習(xí)");
  }
}

類屬性和類方法直接使用類來調(diào)用揽思,不能使用實(shí)例化的對象來操作。因?yàn)轭惙椒o法通過實(shí)例化的對象調(diào)用见擦,所以
不能在類方法中使用 this 關(guān)鍵字钉汗。類屬性(靜態(tài)變量)在使用前不會被初始化。在類方法中無法訪問非靜態(tài)成員鲤屡,非靜態(tài)方法中可以訪問靜態(tài)成員损痰。

您可以使用靜態(tài)方法作為編譯時(shí)常量。例如酒来,您可以將靜態(tài)方法作為參數(shù)傳遞給常量構(gòu)造函數(shù)卢未。

5. setters 與 getters

所有的實(shí)例變量都會生成一個(gè)隱式的 getter 方法,非最終實(shí)例變量也會生成隱式的 setter 方法堰汉。getter 方法用來獲取屬性辽社,setter 方法用來設(shè)置屬性。當(dāng)我們使用實(shí)例變量通過點(diǎn)運(yùn)算符調(diào)用屬性時(shí)翘鸭,調(diào)用的就是getter或setter方法滴铅。如果一個(gè)屬性值并非最終變量,就可以通過setter方法來進(jìn)行定義矮固,在其他語言中失息,有些也叫計(jì)算屬性。

main(){;
  var student = Student();
  student.calculate = 60;
  print(student.calculate);
}

class Student {
  int baseCredit;

  //獲取總學(xué)分
  int get calculate {
    return baseCredit + 20;
  }

  //輸入平時(shí)得分
  set calculate(int value) {
    baseCredit = value + 10;
  }
}

上述例子档址,設(shè)置了一個(gè)計(jì)算屬性 (calculate) 盹兢,set 方法用來提供學(xué)生平時(shí)的表現(xiàn)得分,并在表現(xiàn)分基礎(chǔ)上加10分守伸,get 方法用來計(jì)算學(xué)生的最終得分绎秒,為在基礎(chǔ)分基礎(chǔ)上加20分為學(xué)生的最終得分。下面是一個(gè)更為直觀的例子:

main(){
  var network = Network();
  network.strURL = "http://www.baidu.com";
  print(network.strURL);
}

class Network {
  String _strURL;

  String get strURL {
    return _strURL;
  }

  void set strURL(String urlString) {
    _strURL = urlString;
  }
}
6. 類的繼承

繼承是類的重要特性尼摹,子類可以通過繼承的方式對父類進(jìn)行擴(kuò)展和使用父類中定義的屬性和方法见芹,也可以對父類中的方法進(jìn)行重寫以實(shí)現(xiàn)自己需要的功能。Dart 中使用 extends 關(guān)鍵字實(shí)現(xiàn)繼承:

main(){;
  Student student = Student();
  student.name = "hike";
  student.age = 20;
  student.eat();   //輸出  hike 在吃面包
  student.study(); //輸出  hike 在學(xué)習(xí)
}

class Student extends Person {
  void study(){
    print("$name 在學(xué)習(xí)");
  }
}

//基類(父類)
class Person {
  String name;
  int age;

  void eat(){
    print("$name 在吃面包");
  }
}

這里定義了一個(gè)基類 Person 蠢涝,并定義了姓名和年齡屬性玄呛,還有一個(gè)吃的方法,學(xué)生也是人類和二,Person 類定義的屬性和方法學(xué)生類也同樣應(yīng)該有徘铝,所以通過直接繼承 Person 類的方式,就可以直接使用 Person 類中的屬性和方法,Student 類也有自己的學(xué)習(xí)方法惕它。有一點(diǎn)值得注意怕午,構(gòu)造方法是無法繼承的,如果父類中存在非默認(rèn)構(gòu)造方法淹魄,子類在繼承時(shí)必須使用調(diào)用父類構(gòu)造方法郁惜,否則會報(bào)錯(cuò)。如下:

main(){;
  Student student = Student("hike", 20);
  student.eat();
  student.study();
}

class Student extends Person {
  //必須實(shí)現(xiàn)
  Student(String name, int age) : super(name, age);

  void study(){
    print("$name 在學(xué)習(xí)");
  }
}

//基類(父類)
class Person {
  String name;
  int age;

  //非默認(rèn)構(gòu)造函數(shù)
  Person(this.name, this.age);
  void eat(){
    print("$name 在吃面包");
  }
}

Student(String name, int age) : super(name, age); 調(diào)用父類構(gòu)造方法的格式與重定向構(gòu)造函數(shù)格式相同甲锡,只是將 this 關(guān)鍵字換成了 super 兆蕉。 super 除了用在此處之外,主要用于在子類中調(diào)用父類的方法缤沦,修改 study 方法如下:

void study(){
  super.eat();
  print("$name 在學(xué)習(xí)");
}

子類除了能調(diào)用父類方法外恨樟,還可以對父類方法進(jìn)行重寫,如下疚俱,重寫 eat 方法:

main(){;
  Student student = Student("hike", 20);
  student.eat();    //輸出 hike 在吃蘋果
  student.study();  //輸出 hike 在學(xué)習(xí)
}

class Student extends Person {
  //必須實(shí)現(xiàn)
  Student(String name, int age) : super(name, age);
  
  //重寫父類方法
  @override
  void eat() {
    print("$name 在吃蘋果");
  }

  void study(){
    print("$name 在學(xué)習(xí)");
  }
}

//基類(父類)
class Person {
  String name;
  int age;

  //非默認(rèn)構(gòu)造函數(shù)
  Person(this.name, this.age);
  void eat(){
    print("$name 在吃面包");
  }
}

通過 @override 標(biāo)注方法為重寫方法,此標(biāo)注可以省略缩多。此刻呆奕,輸出的為在吃蘋果,而并非父類的吃面包衬吆,證明本類已經(jīng)覆蓋了父類的方法實(shí)現(xiàn)梁钾。如果想在覆蓋父類方法的同時(shí),保留父類方法的實(shí)現(xiàn)逊抡,可以在本類的覆蓋實(shí)現(xiàn)中通過 super 調(diào)用父類的實(shí)現(xiàn)姆泻,Studenteat 方法修改如下:

@override
void eat() {
  super.eat();
  print("$name 在吃蘋果");
}

這樣,父類與子類的實(shí)現(xiàn)會同時(shí)執(zhí)行冒嫡。

7. 抽象類 與 抽象方法

抽象類是無法被實(shí)例化的類(無法直接通過抽象類創(chuàng)建對象)拇勃。抽象類常用于定義通用接口,通用接口用來提供給符合條件的類使用孝凌。所謂接口就是抽象類中只定義不實(shí)現(xiàn)的方法方咆,這些方法被稱為抽象方法。在非抽象類中是不能只定義不實(shí)現(xiàn)方法主體功能的蟀架。Dart 中使用 abstract 關(guān)鍵字定義抽象類瓣赂,如下:

abstract class asStudent {
  void study();
  void test();
}

上述代碼定義了一個(gè)抽象學(xué)生類,類中定了 study()test() 兩個(gè)方法片拍,這兩個(gè)方法就是抽象方法煌集,抽象方法只能在抽象類中定義。類 asStudent 為抽象類捌省。這個(gè)類不能直接實(shí)例化苫纤,只能通過繼承或?qū)崿F(xiàn)接口的方式使用。

接口實(shí)現(xiàn)方式通過關(guān)鍵字 implements 實(shí)現(xiàn),如下:

main(){;
  Student student = Student();
  student.study();
  student.test();
}

class Student implements asStudent {

  @override
  void study(){
    print("學(xué)習(xí)");
  }

  @override
  void test() {
    print("考試");
  }
}

abstract class asStudent {
  void study();
  void test();
}

也可以同時(shí)實(shí)現(xiàn)多個(gè)接口抽象類方面,使用 , 分割即可放钦。當(dāng)一個(gè)類實(shí)現(xiàn)接口類時(shí),類本身需要重寫接口類中的所有聲明的方法恭金,否則會報(bào)錯(cuò)操禀,實(shí)現(xiàn)多個(gè)接口,則需要將多個(gè)接口中聲明的方法全部實(shí)現(xiàn)横腿。

通過繼承的方式實(shí)現(xiàn)颓屑,使用關(guān)鍵字 extends ,如下:

main(){;
  Student student = Student();
  student.study();
  student.test();
}

class Student extends asStudent {

  @override
  void study(){
    print("學(xué)習(xí)");
  }

  @override
  void test() {
    print("考試");
  }
}

abstract class asStudent {
  void study();
  void test();
}

此方式也同樣需要重寫父類中所有的方法耿焊。

8. Mixin

Dart 中不支持多重繼承揪惦,即一個(gè)子類只能繼承一個(gè)父類。如果想要實(shí)現(xiàn)多繼承的特性罗侯,就需要使用 Mixin 的特性器腋,使用關(guān)鍵字 with ,如下:

main(){;
  Student student = Student();
  student.name = "hike";
  student.eat();        //輸出 hike 在吃面包
  student.drinking();   //輸出 喝水
  student.study();          //輸出 hike 在學(xué)習(xí)
}

class Student extends Person with Animal {

  void study(){
    print("$name 在學(xué)習(xí)");
  }
}

//基類(父類)
class Person {
  String name;
  int age;

  void eat(){
    print("$name 在吃面包");
  }
}

//動物類
class Animal {
  void drinking() {
    print("喝水");
  }
}

增添了動物類(Animal)钩杰,并添加了一個(gè)喝水的方法纫塌,在 Student 繼承的基礎(chǔ)上使用 with 關(guān)鍵字后添加需要混合的類名,可以添加多個(gè)讲弄,使用逗號(,)進(jìn)行分割措左,這樣就可以使用新增添的類中的方法和屬性。

也可以使用如下寫法:

class resStudent = Student with Animal;

main(){;
  resStudent student = resStudent();
  student.name = "hike";
  student.eat();
  student.drinking();
  student.study();
}

class Student extends Person {

  void study(){
    print("$name 在學(xué)習(xí)");
  }
}

//基類(父類)
class Person {
  String name;
  int age;

  void eat(){
    print("$name 在吃面包");
  }
}

class Animal {
  void drinking() {
    print("喝水");
  }
}

需要注意的是避除,如果一個(gè)可做為 Mixin 類(使用在 with 后)怎披,不能有構(gòu)造方法,即便重寫默認(rèn)的構(gòu)造方法也不行瓶摆。但是在不提供構(gòu)造方法的前提下凉逛,可以創(chuàng)建該類的對象。也就是說雖然 Mixin 類雖然不能有構(gòu)造函數(shù)赏壹,但是可以被實(shí)例化鱼炒。如果不想作為 Mixin 類被實(shí)例化,可以使用 mixin 關(guān)鍵字替換 class 關(guān)鍵字進(jìn)行定義蝌借,如下:

//動物類
mixin Animal {
  void drinking() {
    print("喝水");
  }
}

此時(shí)在創(chuàng)建 Animal 類的實(shí)例則會報(bào)錯(cuò)昔瞧,并且使用 mixin 定義的類也不能被繼承(使用 extends 繼承)。mixin 定義的類本身可以繼承其他類菩佑,此時(shí)使用 on 關(guān)鍵字繼承自晰,如下:

class Description {
  void des(){
    print("描述");
  }
}

//動物類
mixin Animal on Description {
  void drinking() {
    print("喝水");
  }
}
9. noSuchMethod

上面說過,在非抽象類中只聲明不實(shí)現(xiàn)方法是不被允許的稍坯,會報(bào)錯(cuò)酬荞。當(dāng)一個(gè)抽象類被繼承或被實(shí)現(xiàn)接口后搓劫,也需要實(shí)現(xiàn)抽象類中的所有方法,否則也會報(bào)錯(cuò)混巧。但是有時(shí)候在抽象類中定義的方法并不需要全部實(shí)現(xiàn)枪向,此時(shí),可以選擇重寫 noSuchMethod 方法咧党。重寫此方法后秘蛔,在編譯階段就不會報(bào)錯(cuò)。而在運(yùn)行階段傍衡,如果調(diào)用了未實(shí)現(xiàn)的方法則會調(diào)用此方法深员,可以在此方法中做一些處理。

main(){;
  Student student = Student();
  student.study();     //輸出 學(xué)習(xí)
  student.test();        //此行調(diào)用將執(zhí)行 noSuchMethod 方法
}

class Student extends asStudent {
  @override
  void study(){
    print("學(xué)習(xí)");
  }

  @override
  noSuchMethod(Invocation invocation) {
    print("調(diào)用了未實(shí)現(xiàn)方法:${invocation.memberName}");  //輸出 調(diào)用了未實(shí)現(xiàn)方法:Symbol("test")
    // return super.noSuchMethod(invocation);  //注釋掉此行代碼蛙埂,否則依然會拋出異常
  }
}

abstract class asStudent {
  void study();
  void test();
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末倦畅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子绣的,更是在濱河造成了極大的恐慌叠赐,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屡江,死亡現(xiàn)場離奇詭異燎悍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)盼理,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來俄删,“玉大人宏怔,你說我怎么就攤上這事〕胍” “怎么了臊诊?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斜脂。 經(jīng)常有香客問我抓艳,道長,這世上最難降的妖魔是什么帚戳? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任玷或,我火速辦了婚禮,結(jié)果婚禮上片任,老公的妹妹穿的比我還像新娘偏友。我一直安慰自己,他們只是感情好对供,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布位他。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹅髓。 梳的紋絲不亂的頭發(fā)上舞竿,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音窿冯,去河邊找鬼骗奖。 笑死,一個(gè)胖子當(dāng)著我的面吹牛靡菇,可吹牛的內(nèi)容都是我干的重归。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼厦凤,長吁一口氣:“原來是場噩夢啊……” “哼鼻吮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起较鼓,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤椎木,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后博烂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體香椎,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年禽篱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了畜伐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡躺率,死狀恐怖玛界,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悼吱,我是刑警寧澤慎框,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站后添,受9級特大地震影響笨枯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜遇西,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一馅精、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粱檀,春花似錦硫嘶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽称近。三九已至,卻和暖如春哮塞,著一層夾襖步出監(jiān)牢的瞬間刨秆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工忆畅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衡未,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓家凯,卻偏偏與公主長得像缓醋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子绊诲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容