一茶宵、函數(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
類型的 a
和 b
蛤高。因?yàn)?num
類型是 int
和 double
的父類,所以這個(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");
}
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)不會出現(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ù)");
};
使用 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ù)等光督。而閉包會對其使用的變量進(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í)例方法 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í)候勤讽,會調(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ù),分別為: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í)候會調(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)姆泻,Student
類 eat
方法修改如下:
@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();
}