一、函數(shù)
函數(shù)用于將代碼結(jié)構(gòu)化,將復(fù)雜的問題簡單化,實(shí)現(xiàn)根據(jù)功能拆分程序,使得代碼可以實(shí)現(xiàn)復(fù)用。
Dart 中的入口函數(shù)為 main ,無論 main 函數(shù)放在哪都會(huì)從此開始執(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 類型的 a 和 b 。因?yàn)?num 類型是 int 和 double 的父類,所以這個(gè)函數(shù)可以完成整型和浮點(diǎn)型的相加操作。函數(shù)定義好后,需要在 main 函數(shù)中調(diào)用才會(huì)執(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),否則會(huì)報(bào)錯(cuò)。
前面第一篇文章在介紹 main 函數(shù)時(shí)就說過,在 Dart 中,函數(shù)的返回值類型是可以省略的,其實(shí),參數(shù)類型也是可以省略的,Dart 會(huì)根據(jù)調(diào)用時(shí)傳入的參數(shù)類型來自動(dòng)推斷數(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ù)??蛇x參數(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)用會(huì)報(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)提供的,需要添加依賴或直接安裝到,這在后面會(huì)講到。有興趣的可以參考:https://pub.dev/packages/meta#-installing-tab-
根據(jù)官方文檔的說明,使用 @required 注釋的參數(shù)為必須提供的參數(shù),這里也進(jìn)行了測試,在 VSCode 環(huán)境下,.dart 文件運(yùn)行的情況下,不提供也是可以的,但是會(huì)報(bào)警告信息,如下:
The parameter 'name' is required. .dart(missing_required_param)
并且當(dāng)使用 @required 標(biāo)注以后,在調(diào)用函數(shù)時(shí),會(huì)自動(dòng)帶上所修飾的關(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");
}
sex 和 age 為位置可選參數(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)不會(huì)出現(xiàn)問題,但是無法進(jìn)行調(diào)用,定義在全局則會(huì)報(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í),會(huì)報(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ù)");
};
使用 dynamic 或 Object 修飾變量。正常使用如下:
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ù)等。而閉包會(huì)對其使用的變量進(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ù)會(huì)對外部變量做一次拷貝,所以對于閉包函數(shù),變量是一直存在的。
二、類
Dart 是一種有類和基于 Mixin 的繼承的面向?qū)ο蟮恼Z言,一切皆對象。每個(gè)對象都是一個(gè)類的實(shí)例,所有類都繼承自 Object 。Dart 只支持單繼承,不支持多重繼承,但是可以通過混合 Mixin 實(shí)現(xiàn)多重繼承的特性。支持?jǐn)U展方法,擴(kuò)展方法可以在不更改類或創(chuàng)建子類的情況下向類中添加功能的方法。類中封裝了屬性和方法,屬性用來存儲(chǔ)類數(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í)例方法 study 和 running ,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í)候,會(huì)調(diào)用類的構(gòu)造方法,這是大多數(shù)編程語言的特性,Dart 中也不列外。Dart 的類在實(shí)例化的時(shí)候也會(huì)調(diào)用構(gòu)造方法,上述的代碼都沒有進(jìn)行構(gòu)造函數(shù)的定義,構(gòu)造函數(shù)是一種特殊的函數(shù),如果開發(fā)者沒有提供構(gòu)造函數(shù)(如上代碼,都沒有提供構(gòu)造函數(shù)),編譯器會(huì)提供默認(rèn)的構(gòu)造函數(shù),也就是說無論開發(fā)者是否書寫構(gòu)造函數(shù),在類的創(chuàng)建時(shí)都會(huì)調(diào)用構(gòu)造函數(shù),只是如果沒有提供構(gòu)造函數(shù),則會(huì)調(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)用任何方法,但是依然會(huì)打印函數(shù) Student 中的內(nèi)容,Student 就是默認(rèn)的構(gòu)造函數(shù)。構(gòu)造函數(shù)的函數(shù)名必須和類名相同,所謂默認(rèn)的構(gòu)造函數(shù)就是與類名相同的無參函數(shù),創(chuàng)建類的對象時(shí)會(huì)默認(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ù),分別為:newName 和 newGrade ,其實(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í)候會(huì)調(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í),會(huì)調(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)指定返回值類型,可以返回當(dāng)前對象或其他類型。如不添加任何返回值,則會(huì)報(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)于此會(huì)在后續(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)變量)在使用前不會(huì)被初始化。在類方法中無法訪問非靜態(tài)成員,非靜態(tài)方法中可以訪問靜態(tài)成員。
您可以使用靜態(tài)方法作為編譯時(shí)常量。例如,您可以將靜態(tài)方法作為參數(shù)傳遞給常量構(gòu)造函數(shù)。
5. setters 與 getters
所有的實(shí)例變量都會(huì)生成一個(gè)隱式的 getter 方法,非最終實(shí)例變量也會(huì)生成隱式的 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)造方法,否則會(huì)報(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),Student 類 eat 方法修改如下:
@override
void eat() {
super.eat();
print("$name 在吃蘋果");
}
這樣,父類與子類的實(shí)現(xiàn)會(huì)同時(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í),類本身需要重寫接口類中的所有聲明的方法,否則會(huì)報(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 在吃面包");
}
}
//動(dòng)物類
class Animal {
void drinking() {
print("喝水");
}
}
增添了動(dòng)物類(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)行定義,如下:
//動(dòng)物類
mixin Animal {
void drinking() {
print("喝水");
}
}
此時(shí)在創(chuàng)建 Animal 類的實(shí)例則會(huì)報(bào)錯(cuò),并且使用 mixin 定義的類也不能被繼承(使用 extends 繼承)。mixin 定義的類本身可以繼承其他類,此時(shí)使用 on 關(guān)鍵字繼承,如下:
class Description {
void des(){
print("描述");
}
}
//動(dòng)物類
mixin Animal on Description {
void drinking() {
print("喝水");
}
}
9. noSuchMethod
上面說過,在非抽象類中只聲明不實(shí)現(xiàn)方法是不被允許的,會(huì)報(bào)錯(cuò)。當(dāng)一個(gè)抽象類被繼承或被實(shí)現(xiàn)接口后,也需要實(shí)現(xiàn)抽象類中的所有方法,否則也會(huì)報(bào)錯(cuò)。但是有時(shí)候在抽象類中定義的方法并不需要全部實(shí)現(xiàn),此時(shí),可以選擇重寫 noSuchMethod 方法。重寫此方法后,在編譯階段就不會(huì)報(bào)錯(cuò)。而在運(yùn)行階段,如果調(diào)用了未實(shí)現(xiàn)的方法則會(huì)調(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); //注釋掉此行代碼,否則依然會(huì)拋出異常
}
}
abstract class asStudent {
void study();
void test();
}