本文收錄于dart入門潛修系列教程豌鸡。
創(chuàng)作不易段标,轉(zhuǎn)載還請(qǐng)備注涯冠。
控制流語句
所謂控制流語句就是能夠改變程序執(zhí)行流程的語句逼庞,比如if else、switch赛糟、for、assert語句等等璧南,dart為我們提供了和其他語言一樣豐富的控制流語法吨瞎,羅列如下:
—if else: 條件判斷語句
—for循環(huán):循環(huán)遍歷語句
—while 和 do-while語句: 循環(huán)遍歷語句
—break:跳出當(dāng)前循環(huán)體
—continue:跳過循環(huán)中的本次執(zhí)行
—switch 和 case: 條件匹配語句
—assert:斷言
下面一一來看一下上面的語句的用法穆咐。
if-else
dart中的if-else寫法同其他語言一致,但是需要注意的是对湃,只有boolean值才能作為if-else中的條件!if-else示例如下:
void main() {
int i = 1;
if (i > 0) {
print("i > 0");
} else if (i < 0) {
print("i < 0");
} else {
print("i == 0");
}
}
我們無法像下面這樣使用if-else語句:
void main() {
int i = 0;
if (!i) {//這里i不是boolean值拍柒,僅僅是個(gè)對(duì)象,所以不能這么使用
print(i);
}
}
for
for是用于迭代遍歷的語句拆讯,典型的場(chǎng)景是用于遍歷lists养叛。示例如下:
var lists = [1, 2, 3];
for (int i = 0; i < lists.length; i++) {
print(i);//打印'1 2 3'
}
注意,for循環(huán)中的變量i的作用域就只在for循環(huán)體內(nèi)部可以訪問弃甥,無法像javascript那樣可以在外部訪問,也就是說dart嚴(yán)格限制了作用域淆攻,這有助于避免一些未知錯(cuò)誤。
dart還提供了forEach方法嘿架,該方法可以用于遍歷具有迭代屬性的對(duì)象,來看個(gè)例子:
class Person {//聲明了一個(gè)Person類
String name;
}
//測(cè)試方法
void main() {
//生成3個(gè)Person對(duì)象
Person p1 = Person();
Person p2 = Person();
Person p3 = Person();
p1.name="張三";
p2.name="李四";
p3.name="王五";
//列表顯然具備迭代屬性
var lists = [p1, p2, p3];
//這里我們采用forEach方法遍歷lists中的元素耸彪,并打印name值
lists.forEach((person) => print(person.name));//打印 '張三 李四 王五'
}
此外,對(duì)于可迭代的對(duì)象蝉娜,dart還提供了“增加for循環(huán)”for-in,如下所示:
var lists = [1, 2, 3];//lists是一個(gè)可迭代的對(duì)象
for(var item in lists){//可以用for in循環(huán)進(jìn)行遍歷
print(item);//打印'1 2 3'
}
上面多次提到可迭代的對(duì)象蜀肘,那么什么是可迭代的對(duì)象稽屏?在dart中,可迭代對(duì)象就是指實(shí)現(xiàn)了Iterable類狐榔,并提供Iterator迭代器的對(duì)象。下面我們來闡述下如何定義自己的可迭代對(duì)象薄腻。由于這些涉及到面向?qū)ο蟛糠值闹R(shí)收捣,如果看不太明白庵楷,可以暫時(shí)略過。
import 'dart:collection';
//定義了一個(gè)Person類尽纽,這個(gè)是我們要遍歷的元素類型
class Person {
String name;
Person(String name){
this.name = name;
}
}
//我們需要提供一個(gè)迭代器,用于遍歷Person弄贿,我們必須實(shí)現(xiàn)該接口中定義的“抽象元素”
class PersonIterator implements Iterator<Person> {
//簡(jiǎn)單起見,這里并沒有提供對(duì)外設(shè)置入口差凹,暫時(shí)寫死
var lists = [Person("張三"), Person("李四")];
//元素其實(shí)索引
var index = -1;
//必須實(shí)現(xiàn)Iterator接口中的current成員的getter方法
//用于返回當(dāng)前遍歷的元素對(duì)象
get current => lists[index];
//必須實(shí)現(xiàn)moveNext方法
bool moveNext() {
index++;
return index < lists.length;
}
}
//實(shí)現(xiàn)IterableBase侧馅,IterableBase實(shí)現(xiàn)了Iterable抽象類
class Persons extends IterableBase<Person> {
//這里我們需要提供一個(gè)迭代器,for-in遍歷的時(shí)候會(huì)獲取該迭代器
@override
final Iterator<Person> iterator = PersonIterator();
}
//測(cè)試方法
void main() {
//我們可以使用for-in來遍歷Persons類型的對(duì)象馁痴。
for(var person in Persons()){
print(person.name);//打印'張三 李四'
}
}
上面簡(jiǎn)單演示了如何自定義一個(gè)具有迭代屬性的對(duì)象,同時(shí)也演示了dart中迭代器中用法弥搞。對(duì)于上述代碼需要注意以下幾點(diǎn)點(diǎn):
迭代類必須要提供一個(gè)迭代器(本例中即PersonIterator),這個(gè)迭代器同時(shí)需要實(shí)現(xiàn)兩個(gè)方法渠旁,一個(gè)是表示當(dāng)前元素current的getter方法(即get current => lists[i];),另一個(gè)則是尋找下一個(gè)元素并確認(rèn)是否還有剩余元素的moveNext方法顾腊。
對(duì)于迭代器的實(shí)現(xiàn)須遵循以下步驟:首先,迭代器的默認(rèn)指向位置位于第一個(gè)元素之前杂靶,所以我們定義了var index = -1,表示初始化索引為第一個(gè)元素的前一個(gè)元素(其實(shí)就是不存在)吗垮。其次,moveNext方法的含義是烁登,首先尋找下一個(gè)元素,并返回是否還有下一個(gè)元素的判斷饵沧,如果有則返回true,否則返回false狼牺。所以,這里我們先對(duì)moveNext進(jìn)行了index++運(yùn)算是钥,然后返回是否還存在該元素的判斷掠归。最后悄泥,current的getter方法返回的就是當(dāng)前元素值,所以我們只需簡(jiǎn)單返回即可码泞。
最后,我們需要實(shí)現(xiàn)抽象類Iterable,這個(gè)類包含了forEach方法领铐,可用于forEach遍歷。這里我們采用的是繼承IterableBase的方式绪撵,因?yàn)镮terableBase本身繼承了自Iterable類。
當(dāng)然音诈,這個(gè)迭代器只是演示案例,沒有任何擴(kuò)展性细溅,如果想實(shí)現(xiàn)類似于系統(tǒng)庫list那樣具有高擴(kuò)展性、健壯性的迭代機(jī)制喇聊,還需要做一些工作恍风。這里只需明白其原理即可誓篱。
while和do-while
這兩條也是存在于眾多語言中的遍歷語句,while是先判斷條件再?zèng)Q定是否執(zhí)行循環(huán)體窜骄,而do-while則是先執(zhí)行一次循環(huán)體,然后結(jié)合條件判斷決定是否繼續(xù)執(zhí)行下一次循環(huán)體邻遏,二者示例如下:
//定義了一個(gè)整型變量i,其值為2
int i = 2;
//while循環(huán)遍歷党远,這里顯然i不小于2,所以while
//循環(huán)體根本不會(huì)執(zhí)行
while (i < 2) {
print(i++);
}
//do-while循環(huán)沟娱,因?yàn)橄葓?zhí)行一次while循環(huán)體,
//然后再判斷i是否小于2济似,所以會(huì)打印 2
do {
print(i++);
} while (i < 2);
break和continue
break和continue都是用于改變代碼執(zhí)行流程的語句,break用于跳出當(dāng)前循環(huán)體砰蠢,而continue則是跳過循環(huán)體的當(dāng)前執(zhí)行,繼續(xù)執(zhí)行下一次循環(huán)台舱,示例如下:
var lists = [1, 3, 2, 3];//定義了一個(gè)lists
//break示例,這里當(dāng)遇到lists中的偶數(shù)元素即停止打印
for (int i = 0; i < lists.length; i++) {
if (lists[i] % 2 == 0) {
break;
}
print(lists[i]);//打印'1 3'
}
//continue示例竞惋,這里的意思是跳過lists中的偶數(shù)元素
for (int i = 0; i < lists.length; i++) {
if (lists[i] % 2 == 0) {
continue;
}
print(lists[i]);//打印'1 3 3'
當(dāng)然對(duì)于可迭代對(duì)象柜去,我們還可以利用其提供的便利方法來達(dá)到上面代碼的目的拆宛,如下所示:
//定義一個(gè)Person類
class Person {
int age;
String name;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
//測(cè)試方法main
void main() {
//包含有年齡不同的三個(gè)對(duì)象
var lists = [Person("張三", 20), Person("李四", 21), Person("王五", 22)];
//我們可以使用where方法嗓奢,進(jìn)行條件判斷浑厚,
//這里我們過濾掉 age<= 21歲的Person
lists.where((person) => person.age > 21)
.forEach((person) => print(person.name));//打印'王五'
}
上面代碼用到了where方法,where的方法定義如下所示;
//where方法的定義
Iterable<E> where(bool test(E element)) => new WhereIterable<E>(this, test);
//WhereIterable的定義
WhereIterable(this._iterable, this._f);
由此可見钳幅,where實(shí)際上是結(jié)合了外部的判斷條件以及內(nèi)部的迭代器完成了條件過濾。
在dart內(nèi)置的list中贡这,where是以mixin的方式提供的(后面會(huì)有文章闡述mixin機(jī)制),位于ListMixin中盖矫,而ListMixin最終實(shí)現(xiàn)了可迭代的抽象類Iterable,在該類中定義了where方法辈双,所以任何實(shí)現(xiàn)迭代功能的對(duì)象责掏,都可以使用該方法進(jìn)行條件過濾湃望。比如我們同樣可以使用where方法來顧慮我們自己定義的可迭代對(duì)象,如下所示:
//Person類
class Person {
int age;
String name;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
//我們自己實(shí)現(xiàn)的迭代器证芭,用于迭代Persons對(duì)象
class PersonIterator implements Iterator<Person> {
var lists = [Person("張三", 22), Person("李四", 20), Person("王五", 22)];
var index = -1;
get current => lists[index];
bool moveNext() {
index++;
return index < lists.length;
}
}
//可迭代對(duì)象類Persons,
class Persons extends IterableBase<Person> {
@override
final Iterator<Person> iterator = PersonIterator();
}
//測(cè)試方法
void main() {
var persons = Persons();
persons.where((person) => (person.age > 21))
.forEach((person) => print(person.name));//打印' 張三 王五'
}
switch和case
dart中的switch-case機(jī)制同其他語言一樣废士,都是作為條件匹配存在的語句,不過dart對(duì)switch-case做了一些健壯性控制官硝,先來看一個(gè)最基本的switch-case使用案例:
void main() {
//假設(shè)我們默認(rèn)有三種顏色,red氢架、yellow、green
String color = "red";//可以通過改變此值來觀察不同的打印結(jié)果
//現(xiàn)在我們想根據(jù)不同的顏色做不同的事情
//就可以使用switch-case語句岖研,
switch (color) {
case "red":
print("red");
break;
case "yellow":
print("yellow");
break;
default:
print("green");
}
}
在使用switch-case的時(shí)候,必須要使用break及時(shí)終止剩余代碼的執(zhí)行,在有些語言中硬纤,如果不指定break,會(huì)繼續(xù)執(zhí)行下一個(gè)case語句筝家,直到遇到break或者default為止,而dart對(duì)其進(jìn)行了控制溪王,如下所示:
void main() {
String color = "red";
switch (color) {
case "red":
print("red");//!!!注意這里,我們沒有顯示使用break莹菱,dart編譯器會(huì)報(bào)錯(cuò)!!!
case "yellow":
print("yellow");
break;
default:
print("green");
}
}
但是移国,如果多個(gè)case確實(shí)對(duì)應(yīng)了同樣的執(zhí)行邏輯道伟,該怎么辦迹缀?
很簡(jiǎn)單蜜徽,dart很人性化的給我們考慮到了這種情況,只要case分支中拘鞋,沒有具體的執(zhí)行邏輯,那么dart是允許你共用同一個(gè)case邏輯 的盆色,比如下面的代碼:
void main() {
String color = "red";
switch (color) {
case "red"://注意這里灰蛙,我們沒有任何實(shí)現(xiàn)
case "yellow":
print("red or yellow");
break;
default:
print("green");
}
}
上面代碼執(zhí)行過后隔躲,會(huì)打印red or yellow,這就是dart對(duì)switch-case所做的健壯處理宣旱。
但是,可能又有朋友說了响鹃,我現(xiàn)在的需求確實(shí)是要執(zhí)行兩個(gè)不同case下的不同邏輯驾霜!那么买置,此時(shí)該怎么做强霎?
其實(shí)這個(gè)需求很怪異忿项,因?yàn)閟witch-case的本意表達(dá)更多的是“單條件”匹配,而現(xiàn)在這種情況意思就是要多條件匹配轩触,然而dart針對(duì)這種情況還是提供了一種機(jī)制,即結(jié)合continue來做脱柱,示例如下:
void main() {
String color = "red";//注意這里的匹配條件是red
switch (color) {
case "red"://匹配,按道理可以結(jié)束了
print("red");
continue anotherColor;//但是這里卻有個(gè)continue語句榨为,指向了anotherColor標(biāo)簽
anotherColor://anotherColor標(biāo)簽實(shí)際上執(zhí)行了case "yellow"這個(gè)匹配case
case "yellow":
print("yellow");
break;
default:
print("green");
}
}
上面代碼執(zhí)行完后,打印如下:
red
yellow
這個(gè)也是dart語言中關(guān)于switch-case的一個(gè)特性随闺。
上面闡述了dart中switch-case的各種判斷場(chǎng)景,那么switch的入?yún)㈩愋投伎梢允悄男?/p>
dart中的swith支持多種常見的入?yún)㈩愋途乩郑热缯土渚洹⒆址⒑薄⒕幾g時(shí)常量、枚舉等等笨使。switch語句在進(jìn)行匹配的時(shí)候卿樱,會(huì)調(diào)用了相應(yīng)的==操作符來進(jìn)行匹配比較硫椰,當(dāng)然,前提是比較的兩個(gè)對(duì)象必須具備同一個(gè)類型(連子類型都不行)靶草。
assert
assert即斷言蹄胰,是很多語言都提供的一種檢測(cè)機(jī)制奕翔,它是用來判斷是否符合執(zhí)行邏輯的語句裕寨,示例如下:
void main() {
String str = null;
assert(str != null);
print(str.length);
}
上面代碼會(huì)直接中斷執(zhí)行流程,拋出一個(gè)斷言異常派继,因?yàn)閟tr != null為非真值宾袜。如果此時(shí)str不為null,則程序會(huì)正常執(zhí)行驾窟。
我們會(huì)發(fā)現(xiàn)庆猫,很多時(shí)候在生產(chǎn)的代碼中都寫有大量的斷言,這個(gè)在不符合斷言條件的時(shí)候顯然也會(huì)拋出異常绅络,那為什么還要這么寫月培?實(shí)際上嘁字,斷言默認(rèn)只在開發(fā)環(huán)境中生效,發(fā)布到生產(chǎn)環(huán)境中的斷言是會(huì)被忽略的杉畜。因此纪蜒,斷言實(shí)際上起到了在開發(fā)環(huán)境調(diào)試程序、測(cè)試程序的目的此叠。
可能會(huì)有朋友說纯续,上面的代碼我不用assert不照樣拋出異常嗎?確實(shí)杆烁,不用的話也會(huì)拋出空指針這種異常寂恬!但是我們還是需要嘗試從兩個(gè)角度來理解斷言:1. 空指針異常實(shí)際上是被動(dòng)拋出的,是程序執(zhí)行過程中報(bào)出的錯(cuò)誤闰挡,更多的是給程序員看的举娩;而斷言則更多的是用戶側(cè)主動(dòng)定義的行為析校,除了程序員,測(cè)試開發(fā)人員也可以利用斷言機(jī)制來來測(cè)試系統(tǒng)铜涉,斷言可以清楚的給出我們異常的原因是什么智玻。2. 上面只是一個(gè)演示例子,實(shí)際上斷言處理的更多的是邏輯層面上的測(cè)試芙代,這個(gè)也是最重要的一點(diǎn)吊奢。比如下面代碼:
//這里提供一個(gè)極其簡(jiǎn)單的元轉(zhuǎn)分的實(shí)現(xiàn)(注意這里并沒有考慮溢出問題)
//僅僅是為了說明assert的用法
int yuan2fen(int yuan) {
//這里的入?yún)uan顯然可能為負(fù)數(shù),但這個(gè)不符合現(xiàn)實(shí)邏輯
//所以我們進(jìn)行了一層斷言纹烹,保證金額>0;
assert(yuan >= 0, "金額錯(cuò)誤!");
return yuan * 100;
}
//測(cè)試方法
void main() {
print(yuan2fen(100));//正確页滚!打印'10000'
print(yuan2fen(-1));//錯(cuò)誤铺呵!拋出異常'Failed assertion: line 15 pos 10: 'yuan >= 0': 金額錯(cuò)誤!'
}
上面代碼演示了斷言的一種應(yīng)用場(chǎng)景裹驰,如果沒有斷言,當(dāng)我們輸入的數(shù)字為負(fù)數(shù)時(shí)片挂,程序不會(huì)拋出任何異常,進(jìn)而會(huì)產(chǎn)生錯(cuò)誤的金額值音念。加上斷言后沪饺,則在開發(fā)、測(cè)試期間就能發(fā)現(xiàn)類似的錯(cuò)誤闷愤。
由上述代碼可知整葡,assert還可以接收第二個(gè)參數(shù):即錯(cuò)誤描述信息,用于在斷言不符合需求的情況下肝谭,提示給用戶的描述性文案掘宪。這樣可以很明了的知道發(fā)生了什么錯(cuò)誤。