Dart基礎(chǔ)語法
一、選擇開發(fā)工具
首先需要安裝Dart SDK(https://www.dart-china.org/t/topic/480#heading--manual-install),SDK安裝完成后,選擇一款編輯工具。這里就先只推薦兩款工具 VS Code ,Android Studio。工具下載完成后,安裝dart插件,這個時候就可以編寫dart代碼了。先新建一個hello.dart文件,然后在文件中輸入下方的代碼,點擊運行按鈕就可以執(zhí)行代碼。
// 在dart中,main方法是代碼運行的入口方法,一切都是從main方法開始的。。
void main (){
// 點擊IDE運行按鈕 在打印臺中會打印出 hello Dart。 接下來的例子都是在該方法下執(zhí)行的(除特殊說明)。
print('hello Dart');
}
二、常用的數(shù)據(jù)類型
num num是數(shù)字類型的父類,包含了兩個子類 int和double。
-
String String可以用 '' 或 "" 來創(chuàng)建,也可以使用''' 或者"""來創(chuàng)建可變行的字符串。
eg.num與String的一些常用操作
// String -> num int one = int.parse('1'); print(one + 2); // 輸出 3 double類型同理 int one = int.parse('hello'); print(one + 2); // 報錯 // int -> String String oneStr = 1.toString(); print(oneStr); // 輸出 1 // double -> String String oneStr = 3.1455.toStringAsFixed(2); print(oneStr); // 截取兩位小數(shù), 輸出3.15 四舍五入 String twoStr = 3.toStringAsFixed(2); print(twoStr); // 輸出3.00 // 可以使用${表達(dá)式}將表達(dá)式的值放入字符串中。如果表達(dá)式是標(biāo)識符,{}可省略 String oneStr = 'hello'; String twoStr = "dart"; print('$oneStr $twoStr'); //輸出 hello dart print('$oneStr ${twoStr.toLowerCase()}'); //輸出 hello dart print('$oneStr $twoStr.toLowerCase()'); //輸出 hello dart.toLowerCase() -
bool Dart是強bool類型檢查。
只有兩個對象具有bool類型:true和false,它們都是編譯時常量
-
if(非bool類型) 報錯,assert( 非bool類型)報錯,也就是說需要bool類型的,如果我們用int 等非bool類型的值都會報錯。
if (1){} // 報錯
-
List 列表
var list_1 = new List(); // 非固定長度 new可以省略 // 添加元素 list_1.add('1'); // 添加多個元素 list_1.addAll(['1', '2']); // 獲取list長度 print(list_1.length); // 輸出2 // 獲取list最后一個元素 print(list_1.last); //輸出 2 // 獲取list第一個元素 print(list_1.first); // 輸出 1 // 查詢某位置的元素,index從0開始 print(list_1[0]); // 輸出 1 // 根據(jù)某個元素獲取對應(yīng)的index print(list_1.indexOf("1")); // 輸出 0 // 刪除元素 list_1.removeAt(0); // 通過索引刪除 list_1.remove("1"); // 通過對象刪除 如果有兩個 "1" 那么會刪除第一個 list_1.removeLast(); // 刪除最后一個元素 list_1.removeRange(start,end); // 刪除范圍內(nèi)的數(shù)據(jù) list_1.removeWhere((item) => item.length > 6);// 刪除元素長度大于6的元素 這種寫法可見下面箭頭函數(shù)的詳解 list_1.clear(); // 清除所有元素 /*注意*/ List list_2 = new List(2); // 固定長度 list_2.add('1'); // 報錯 報錯信息 Cannot add to a fixed-length list // 另外一種定義方式 List list_3 = ['1','2','3']; print(list_3); // 輸出 [1,2,3]
-
Set 集合 Set是沒有順序且不能重復(fù)的集合,所以不能通過索引去獲取值
Set set_1 = new Set(); Set set_2 = new Set(2); // 報錯 set沒有固定長度的定義 set_1.add('1'); set_1.add('2'); set_1.add('1'); print(set_1); // 輸出 {1, 2} // 可以和list一樣通過contains來判斷是否包含某個元素 print(set_1.contains("1")); // 輸出 true set_1.addAll(['b','c']); print(set_1); // 輸出 {1, 2, b, c}
-
Map 映射是無序的鍵值對。鍵和值都可以是任何類型的對象。
// 常用的兩種定義方式 Map map_1 = Map(); Map map_2 = {"key_1":"value_1","key_2":"value_2"}; print(map_1); // 輸出 {} print(map_2); // 輸出 {key_1: value_1, key_2: value_2} // 賦值 map_1["1"] = "one"; map_1["2"] = "two"; print(map_1); // 輸出 {1: one, 2: two} map_1["1"] = "first"; print(map_1); // 輸出 {1: first, 2: two} map_1[1] = "one"; // 在map中key必須要保持唯一 value可以相同 key類型可以為任意其他類型 print(map_1); // 輸出 {1: one, 2: two, 1: one} // 常用api map_1.remove(1); // 刪除key為1的元素 map_1.containsKey(1); // 判斷是否存在key為1的元素
三、函數(shù)/方法
-
內(nèi)置函數(shù)/方法
print("hello Dart");這個是常用的將數(shù)據(jù)打印到控制臺的方法,這是系統(tǒng)提供給我們的內(nèi)置函數(shù),可以直接調(diào)用。
-
自定義方法
自定義方法的基本格式:
返回類型 方法名稱 (參數(shù)1,參數(shù)2,...){
? 方法體
? return 返回值;
}
int getMaxCount(int a, int b){ if (a > b){ return a; } return b; } print(getMaxCount(2, 1)); // 輸出 2 // 在dart中 返回值和參數(shù)類型是可以省略的 getMinCount(a,b){ if (a < b){ return a; } return b; } print(getMinCount(2, 1)); // 輸出 1
-
方法的傳參
// 可選參數(shù) String printUserInfo(String username,[int age]){ // username和age表示是形參 其中age表示可選參數(shù) return "姓名:$username 年齡:$age"; } print(printUserInfo("張三",30)); // 張三 和 30 表示實參 print(printUserInfo("張三")); // 這里可以不用傳入age的參數(shù),如果age不是可選參數(shù)的話,那么age是必須要傳的 // 默認(rèn)參數(shù) String printUserInfoDefalut(String username,[int age,String sex = "男"]){ return "姓名:$username 年齡:$age 性別:$sex"; } print(printUserInfoDefalut("李四",30)); // 輸出 姓名:李四 年齡:30 性別:男 /*注意 對于可選參數(shù)的傳遞是按照參數(shù)的順序進(jìn)行的 print(printUserInfoDefalut("李四","女")); 這里會報錯。雖然sex和age都是可選參數(shù),但是當(dāng)對一個形參賦值時,會默認(rèn)選擇第一個。這里可選參數(shù)的第一個形參是age int類型的,這里傳遞一個"女"是string類型的,所以會報錯。 */ // 命名參數(shù) String printUserInfoName(String username,{int age,String sex = "男"}){ return "姓名:$username 年齡:$age 性別:$sex"; } print(printUserInfoDefalut("李四",age:30,sex:"男")); // 輸出 姓名:李四 年齡:30 性別:男 // 這樣我們通過指定參數(shù)名稱實現(xiàn)我們想要傳遞的參數(shù)。這個比較常用。 // 將方法作為參數(shù) fun_1(){ print("方法1"); } fun_2 (Function fun){ // fun方法其實就是一個Function對象 fun(); } fun_2(fun_1); // 輸出 方法1
-
箭頭函數(shù)
// 遍歷數(shù)組 List list = ["h","e","l","l","0"]; // 正常寫法 list.forEach((element){ print(element); }); // 箭頭函數(shù)的方式 箭頭后跟的方法體只能是一行 這只是一種簡寫的方式 list.forEach((element)=>print(element)); list.forEach((element) => { print(element) // 這里不能寫分號,也是只能執(zhí)行一行代碼 }); List list_1 = [1,3,5,1,3,7]; // 這也是數(shù)組的一種遍歷并處理的方式 List list_2 = list_1.map((element){ if(element >3){ return element*3; } return element; }).toList(); print(list_2); List list_3 = list_1.map((element)=>element>3?element*3:element).toList(); print(list_3);
-
自執(zhí)行與自調(diào)用
//實際上自執(zhí)行方法我們可以理解為一個匿名函數(shù)的自調(diào)用 ((){})();// 格式 將一個匿名函數(shù)用()包起來 然后執(zhí)行 ((int n){ print(n); })(12); //輸出12 // 方法可以自己調(diào)用自己 int sum = 1; fn(n){ sum*=n; if (n == 1){ return; } fn(n-1); } fn(5); // 這樣通過方法的自調(diào)用可以實現(xiàn)5的階乘
-
閉包
全局變量特點:全局變量常駐內(nèi)存,會污染全局。
局部變量特點:不常駐內(nèi)存會被垃圾回收機(jī)制回收,不會污染全局。
-
閉包:常駐內(nèi)存,不會污染全局。寫法:函數(shù)嵌套函數(shù),并return內(nèi)層函數(shù)。
//閉包 fun(){ // 雖然這里定義的是一個局部變量,但是通過閉包的寫法就不會被垃圾回收機(jī)制回收,且不會污染全局 int a = 1; return (){ a++; print(a); }; } Function b = fun(); // 因為fun方法返回的就是一個方法,所以下面直接調(diào)用b方法。 b(); b(); b(); // 輸出 2 3 4
四、語法規(guī)則
-
Dart中一切皆對象,都是繼承自O(shè)bject類。所以在Dart中不存在基本數(shù)據(jù)類型,我們定義的任何一個對象,如果不將其賦值的話,那么這個對象就是一個null對象。
int a; int b = 0; print(a); // 輸出 null print(b); // 輸出 0
-
Dart是動態(tài)型語言,如果沒有指定其類型時,則默認(rèn)時dynamic類型。在運行時會自動推導(dǎo)出具體的類型。
var str = 'shsh'; str = 1; // 報錯 -
運算符及修飾符
static:用于修飾類成員變量,這個變量是屬于類的,通過類名直接調(diào)用,而不是通過對象調(diào)用。這個與java類似。非靜態(tài)方法可以訪問靜態(tài)成員以及非靜態(tài)成員。靜態(tài)方法無法訪問非靜態(tài)成員,也無法調(diào)用非靜態(tài)方法。
final:用于修飾變量,表示單賦值(single-assignment),使用final修飾的變量必須進(jìn)行初始化,一旦被賦值之后,不能夠再次被賦值,否則編譯會報錯。
-
const:與final有一點類似,即只能被賦值一次。但是其修飾的對象有一定的限制。const修飾的對象的狀態(tài)是完全可以在編譯期間就能確定的,并且是不可變的。
const n = 1+2;// 可行 因為這個是在編譯期間我們就知道n = 3的 const list = new List(); // 不可行 const list_1 = [1,2,3]; // 可行 list_1 = [3,4]; // 不可行
-
常用的運算符和表達(dá)式
//加減乘除 int a = 10; int b = 3; print(a/b); // 3.3333333333333335 不用判斷除數(shù)和被除數(shù)的類型,默認(rèn)返回double類型 print(a~/b); // 3 整除取整 print(a*b); // 30 print(a+b); // 13 print(a-b); // 7 print(a%b); // 1 取余 //as 定型 Person per = new Person(); // 我們定義一個Person類,關(guān)于類的定義下文會介紹 // 如果per是Person類型的話 那么就給Person的name屬性賦值,如果不是Person類型則不賦值 并 (per as Person).name = "張三"; // is 如果對象是指定的類型 那么就返回true 否則就返回false if (per is Person){ per.name = "張三"; } /// 注意:以上代碼不相等。如果per為空或不為Person,第二個示例(帶is)什么也不做;第一個(帶有as)拋出異常。 // ??與三目運算符 String str_1; String str_2 = "張三"; print(str_1??str_2); // 輸出 張三 print((str_1 is! Null)?str_1:str_2); // 輸出張三 在dart中 關(guān)于條件的判斷必須是bool類型 // 級聯(lián)符號 可以避免創(chuàng)建臨時變量的繁瑣步驟 Person() ..name ="張三"; // 等價于 Person per = new Person(); per.name = "張三"; // ?. 有條件的成員屬性訪問 Person per; // 這里per就是null // 如果直接調(diào)用成員會報錯 per.name = "張三"; // 報錯 per?.name = "張三"; // 不會報錯
五、類與對象
-
類的創(chuàng)建
// 與上面的例子不同,類的定義需要與mian方法同級 class Person{ String name; // 屬性 void study (){ print("學(xué)習(xí)"); } // 方法 } void main (){ // 實例化,也就是創(chuàng)建對象 Person per = new Person(); // per就是一個對象 per.name = "張三"; print("${per.name}"); // 輸出 張三 調(diào)用對象的屬性 per.study(); // 輸出 學(xué)習(xí) 調(diào)用方法 }
-
類的構(gòu)造函數(shù)
-
默認(rèn)構(gòu)造函數(shù)。每個類都有一個默認(rèn)的構(gòu)造函數(shù),默認(rèn)是不用寫的,如果需要在構(gòu)造函數(shù)做操作的話,則重寫一下默認(rèn)構(gòu)造函數(shù)。
class Person{ String name; //構(gòu)造參數(shù)名與類名要相同 Person(){ // 無參 print("我需要在這里做一些操作"); } // 初始化列表 person():name = "張三"{ // 在執(zhí)行構(gòu)造函數(shù)運行之前 初始化一些實例變量 } } class Student{ String name; Student(String name){ // 有參數(shù) 參數(shù)必傳 this.name = name; } void printStudentInfo (){ print("學(xué)生的姓名:${this.name}"); } } class Coder{ String name; Coder(this.name); // 簡寫的有參構(gòu)造函數(shù) 參數(shù)必傳 void printCoderInfo (){ print("程序員的姓名:${this.name}"); } } class Engineer{ String name; Engineer({this.name}); // 簡寫的有參構(gòu)造函數(shù) 參數(shù)非必傳 void printEngineerInfo (){ print("工程師的姓名:${this.name}"); } } void main (){ // 實例化,也就是創(chuàng)建對象 Person per = new Person(); // 輸出 我需要在這里做一些操作。 // 報錯 因為我們修改了默認(rèn)的構(gòu)造函數(shù),所以之前的無參構(gòu)造函數(shù)就不能使用了。 // Student stu = new Student(); Student stu_1 = new Student('張三'); stu_1.printStudentInfo(); Coder coder = Coder("張三"); coder.printCoderInfo(); Engineer eng = new Engineer(); eng.printEngineerInfo(); // 輸出 工程師的姓名:null Engineer eng_1 = new Engineer(name: "張三"); eng_1.printEngineerInfo(); // 輸出 工程師的姓名:張三 }
-
-
命名構(gòu)造函數(shù)。類是可以有多個構(gòu)造函數(shù)的,通過不同的命名來實現(xiàn)不同功能的構(gòu)造函數(shù)
class Engineer{ String name; String hairStyle; //發(fā)型 Engineer({this.name,this.hairStyle = "茂密的頭發(fā)"}); // 簡寫的有參構(gòu)造函數(shù) 參數(shù)非必傳 Engineer.seniorEngineer(String name){ //命名構(gòu)造函數(shù) this.name = name; this.hairStyle = "禿了"; } void printEngineerInfo (){ print("工程師的姓名:${this.name} 發(fā)型:${this.hairStyle}"); } } void main (){ Engineer eng_1 = Engineer(name:"張三"); eng_1.printEngineerInfo(); Engineer eng_2 = Engineer.seniorEngineer("李四"); eng_2.printEngineerInfo(); }
-
類中的setter和getter方法
class Rect{ num height; num width; Rect(this.height,this.width); // getter方法 get area{ return this.height*this.width; } // setter方法 set areaHeight(num height){ this.height = height; } } void main(){ Rect re = Rect(10,10); print("面積:${re.area}"); // 輸出 面積:100 re.areaHeight = 5; print("面積:${re.area}"); // 輸出 面積:50 }
-
類的繼承
子類使用 extends 關(guān)鍵字來繼承父類
子類會繼承父類中可見你的屬性和方法,但是不會繼承構(gòu)造函數(shù)
-
子類能復(fù)寫父類的方法 getter和setter
class Person{ String name; num age; Person(this.name,this.age); void printPersonInfo(){ print("姓名:${this.name} 年齡:${this.age}"); } void work(){ print("我正在工作"); } } class Coder extends Person{ // 因為Person是重寫了構(gòu)造函數(shù),所以這里也需要寫一下與父類關(guān)聯(lián)的構(gòu)造函數(shù) // 如果父類有多個構(gòu)造函數(shù) 則需要選一個自己需要的 Coder(String name,num age,String grade):super(name,age){ // 通過代碼可以看出,這么寫的目的是當(dāng)我們在實例化Coder時也會將值傳遞給父類 // grade 是子類的屬性,所以不用將參數(shù)傳遞給父類 this.grade = grade; } String grade; void printCoderInfo(){ super.work(); // 調(diào)用父類的方法 print("姓名:${this.name} 年齡:${this.age} 等級:${this.grade}"); } @override // 重寫父類方法 void work() { print("我正在寫代碼"); } } void main(){ Coder code = Coder("zhangsan",20,"初級碼農(nóng)"); code.printCoderInfo(); // 輸出 姓名:zhangsan 年齡:20 等級:初級碼農(nóng) code.work(); // 輸出 我正在寫代碼 }
-
抽象類。Dart中的抽象類主要是用于定義標(biāo)準(zhǔn),子類可以繼承抽象類,然后實現(xiàn)抽象類的接口。
抽象類通過abstract關(guān)鍵字來定義。
抽象方法不能用abstract聲明,我們將沒有方法體的方法稱為抽象方法。
如果子類繼承抽象類必須實現(xiàn)里面的抽象方法。
如果把抽象類當(dāng)作接口實現(xiàn)的話必須得實現(xiàn)抽象類里定義的所有屬性和方法。
抽象類不能被實例化,子類可以。
如果要實現(xiàn)抽象方法約束子類的話我們需要用extends繼承抽象類。
-
如果只是把抽象類作為一個標(biāo)準(zhǔn)的話我們需要用implements實現(xiàn)抽象類。
abstract class Person{ String name; int age; Person(this.name,this.age); work(); // 沒有實現(xiàn)方法體 這是一個抽象方法 抽象方法是必須要在子類中實現(xiàn)的 eat(){ print('吃飯'); } } class Coder extends Person{ Coder(String name, int age) : super(name, age); @override work(){ print("${this.name}在搬運代碼,他今年已經(jīng)${this.age}了"); } } /* 在dart中普通類和抽象類都可以作為接口被實現(xiàn) 使用關(guān)鍵字 implements dart的接口會將普通類或者是抽象類中的屬性或者方法都重寫一遍,所以我們一般采用抽象類實現(xiàn)接口。 因為只有抽象類才可以創(chuàng)建抽象方法。 */ class Engineer implements Person{ // 這里就不用像繼承一樣需要實現(xiàn)父類的構(gòu)造方法。 @override int age; @override String name; @override eat() { print("工程師在吃飯"); } @override work() { print("${this.name}在搞算法,他今年才${this.age}"); } } void main (){ Coder code = Coder("張三", 30); code.work(); // 輸出 張三在搬運代碼,他今年已經(jīng)30了 Engineer eng = Engineer() ..name = "李四" ..age = 20 ..work(); // 輸出 李四在搞算法,他今年才20 }
-
多接口和mixins
-
通過implements的方式,可以實現(xiàn)多接口。
abstract class A{ String a; printA(); } abstract class B{ String b; printB(); } class C implements A,B{ @override String a; @override String b; @override printA() { // TODO: implement printA return null; } @override printB() { // TODO: implement printB return null; } } // 這樣C類的對象就可以同時實現(xiàn) A B的的抽象方法
-
-
通過mixins實現(xiàn)多繼承,Dart是不支持多繼承,是無法實現(xiàn)類似C++的多繼承的功能。
class A{ // mixins 不能有顯示的構(gòu)造方法 其實這個也好理解 因為使用mixins繼承多個類的話,它也不知道用什么構(gòu)造方法 只能使用默認(rèn)的了 // A.withName(){ // // } a(){ print('A.a()'); } } class B{ a(){ print('B.a()'); } b(){ print('B.b()'); } } class C{ a(){ print('C.a()'); } b(){ print('C.b()'); } c(){ print('C.c()'); } } class D extends A with B,C{ int count; // 可以定義自己的屬性 } void main (){ var d = new D(); d.a(); // 輸出 C.a() 默認(rèn)調(diào)用最后一個方法 } /*mixins的方式組合的類 是必須要繼承object的 A B C 就是不能繼承與任何的類 除了默認(rèn)的object 如果想實現(xiàn)類似繼承的效果 我們可以使用接口的方式*/
六、泛型
-
泛型方法
T getData<T>(value){ return value; } print(getData("xingming")); // 輸出 xingming print(getData(20)); // 輸出 20 print(getData<String>(20)); // 報錯 因為指定的是string類型 但是傳入的是int類型 // 通過泛型方法我們就可以不用區(qū)分傳入的參數(shù)類型和輸出的參數(shù)類型。 var names = List<String>(); names.addAll(['1', '2', '3']); names.add(42); // 報錯
-
泛型類
通過指定類的泛型類型,來確定輸入的數(shù)據(jù)類型是否正確
void main (){ LogList loglist = LogList<int>(); // 確定泛型類型為int類型,后續(xù)類中的類型判斷就是int類型 loglist.addData(1); loglist.addData("1"); // 報錯 loglist.printList(); LogList loglist_1 = LogList(); // 沒有確定泛型類型 loglist_1.addData(1); loglist_1.addData("1"); // 沒有報錯 loglist_1.printList(); } class LogList<T>{ List list = List<T>(); void addData(T value){ list.add(value); } void printList(){ for (int i = 0;i<list.length;i++){ print(list[i]); } } }
-
泛型接口
void main (){ B b = B<int>(); b.pirntA(1); //b.pirntA("12"); // 報錯 } abstract class A<T>{ pirntA(T value); } class B<T> implements A<T>{ @override pirntA(T value) { print(value); } }
七、結(jié)尾
? 上面簡單的介紹了一些Dart語言的基礎(chǔ)語法知識,關(guān)于Dart中更深層次的內(nèi)容,我會在以后的文章中再慢慢的剖析。關(guān)注好享家技術(shù)團(tuán)隊公眾號,閱讀更多精彩技術(shù)文章。