計算機最擅長做的事情就是“重復(fù)”——像兒童一樣不厭其煩地重復(fù)做一件事,而且重復(fù)的速度很快,可以在1毫秒內(nèi)列出你的全部Facebook好友。
本章將學(xué)習(xí)如何用有限的幾個塊來編寫可以重復(fù)執(zhí)行的程序,而不必反復(fù)拷貝粘貼同一段代碼;還將學(xué)習(xí)與列表有關(guān)的操作,如給電話號碼列表中的每個號碼發(fā)送一條短信,以及為列表項排序。通過學(xué)習(xí),你將了解到如何用循環(huán)塊來有效地簡化程序。
在前幾章中,我們學(xué)習(xí)了用一組事件處理程序來定義應(yīng)用中的行為:事件以及對事件做出響應(yīng)的函數(shù)。在這些響應(yīng)函數(shù)中,程序通常不是按照線性的順序執(zhí)行,有些程序塊只能在滿足某些條件時才能執(zhí)行。
重復(fù)塊是程序的另一種非線性運行方式。就像if及ifelse塊讓程序產(chǎn)生分支一樣,重復(fù)塊讓程序循環(huán)執(zhí)行,換句話說,在執(zhí)行完一組指令后,重新跳回到這組指令的起點并再次運行,如圖20-1所示。在應(yīng)用的運行過程中,內(nèi)部的計數(shù)器會跟蹤即將執(zhí)行的下一步操作,因此,對于整個事件處理程序來說,從頭至尾的每一步操作都在程序計數(shù)器的監(jiān)控之下(有條件地)完成。程序計數(shù)器隨著這些重復(fù)執(zhí)行的塊循環(huán),不斷地重復(fù)這些功能。
圖 20-1 讓程序循環(huán)執(zhí)行的重復(fù)塊
在App Inventor中有兩種類型的重復(fù)塊:foreach及while.foreach,其作用是對列表中的每一項實施某些特定的操作,如,向電話號碼列表中的每個號碼發(fā)送一條短信。
塊while的應(yīng)用比foreach要普遍,while塊中的程序塊會一直重復(fù)運行,直到某個條件不再滿足。while塊可用于數(shù)學(xué)公式的計算,如求n個連續(xù)自然數(shù)的和,或求n的階乘,此外,while也可以用于同時處理兩個列表;foreach每次只能處理一個列表。
在第18章里,我們討論了一個“隨機撥號”應(yīng)用。這種隨機撥打朋友電話的方式有時能撥通,但如果你有一個像我這樣的朋友,這種呼叫卻不總是能得到應(yīng)答。可以采取另一種方式,給所有列表中的朋友發(fā)短信說“想你”,然后看誰最先回復(fù)你(或許還有更令人愉快的方式?。?。
這個應(yīng)用可以通過點擊一次按鈕向多個朋友發(fā)送短信,最簡單的方法是,先寫好發(fā)給一個人的代碼塊,然后拷貝粘貼并修改接收人的電話號碼,如圖20-2所示。
圖 20-2 拷貝并粘貼向不同號碼發(fā)送短信的塊
如果只有少量的塊,用這種“強力”的拷貝粘貼方式也還說得過去,但是像朋友列表這樣的數(shù)據(jù)表會時常變化,而你不希望每次添加或刪除一個電話號碼,都要動手去修改程序。
塊foreach提供了一個更好的解決方案,可以定義一個包括所有電話號碼的列表變量phoneNumberList,然后用foreach塊將發(fā)送一次短信的塊包圍起來,從而實現(xiàn)群發(fā)功能,如圖20-3所示。
圖 20-3 使用foreach塊對列表中的每一項執(zhí)行同一套指令
上述代碼可以解讀為:
對于phoneNumberList列表中的每一項(電話號碼),設(shè)置Texting對象的PhoneNumber屬性為列表中的項,并發(fā)送該條短信。
對于foreach塊,一個必須的參數(shù)是一個列表,它所要處理的列表,將列表插入“in list”參數(shù)插槽。此時,從phoneNumberList變量的初始化塊中拖出“get global phoneNumberList”塊,并插入“in list”插槽,以便為即將發(fā)送的短信提供電話號碼列表。
foreach塊的第一行使用了foreach自帶的占位符變量,在默認情況下,變量名為item,你可以修改它,也可以就用默認值,該變量代表了列表中正在被處理的當(dāng)前項。
foreach中的所有塊都將對列表中的每一項執(zhí)行同樣的操作,其中的占位符變量(例子中的phoneNumber)始終保存的是當(dāng)前正被處理的項。如果列表中有三項,則foreach中包含的塊將被執(zhí)行三次,這些塊可以說是從屬于foreach塊,或處于foreach塊的內(nèi)部,這些內(nèi)部塊執(zhí)行到最后一行時,我們所說的程序計數(shù)器將要循環(huán)回第一行。
我們來詳細地分析一下foreach塊的運行機制,因為理解循環(huán)是編程的基礎(chǔ)。當(dāng)點擊TextGroupButton時,觸發(fā)事件處理程序,首先執(zhí)行的是“set Texting1.Message to”塊,要將短信內(nèi)容設(shè)置為“想你...”,這個塊只執(zhí)行一次。
然后開始執(zhí)行foreach塊。在foreach內(nèi)部塊開始執(zhí)行前,占位符變量item被設(shè)置為列表phoneNumberList的第一項(111-1111),這一步是自動完成的,代替了你自己使用select list item來調(diào)出列表項。在完成將列表中的第一項賦給item之后,foreach內(nèi)部的塊開始第一次運行,Texting1.PhoneNumber屬性被設(shè)為item的值(111-1111),并發(fā)出短信。
當(dāng)運行到foreach中的最后一行時(Texting1.SendMessage塊),程序?qū)⒀h(huán)會到foreach的首行,并自動將列表中的下一項(222-2222)設(shè)為變量item的值,然后重復(fù)操作foreach內(nèi)部的兩個塊,即發(fā)送短信“想你...”到號碼222-2222。然后程序再次循環(huán)會首行,并將item的值設(shè)為列表中的第三項(333-3333),并執(zhí)行第三次重復(fù)操作,第三次發(fā)送短信。
由于列表中最后一項,即本例子中的第三項已經(jīng)被處理完畢,因此foreach循環(huán)到此結(jié)束,程序?qū)⑻鲅h(huán),這意味著程序計數(shù)器將繼續(xù)下移來處理foreach下面的塊。在本例中,foreach之后沒有塊,因此整個事件處理程序結(jié)束。
在最終用戶看來,使用foreach的方法還是“強力”的拷貝粘貼法,在最終結(jié)果上并無分別,但從程序員的角度來看,foreach方法讓代碼有更好的可維護性,即使數(shù)據(jù)(電話號碼列表)是動態(tài)輸入的,程序也可以適用。
可維護軟件指的是可以很容易地對軟件進行修改,而不會引入程序的漏洞。使用foreach方法,一旦需要修改短信接收人,只需要修改列表變量,而絲毫不需要修改程序的邏輯(事件處理程序)。相反,采用強力的方法,如果需要添加新的接收人,則需要在事件處理程序中添加新的塊。任何時候,只要你改動了程序的邏輯,都會冒帶來漏洞的風(fēng)險。
更重要的是,即便電話列表是動態(tài)的,即,不僅是程序員,最終用戶也可以向列表中添加新的號碼,foreach方法也能奏效。在我們的例子中只有三個固定的號碼,而且號碼直接寫在了代碼中,與此相比,采用動態(tài)數(shù)據(jù)的應(yīng)用,其信息來源可能是最終用戶,或其他來源。如果你要重新設(shè)計應(yīng)用,讓最終用戶來輸入電話號碼,你就必須使用foreach方法,因為在你寫程序的時候,根本無法知道會有哪些號碼,因此也就無從采用強力的拷貝粘貼法。
顯示列表項最簡單的方式就是將列表變量插入Label的Text屬性,如圖20-4所示。
圖 20-4 列表的簡單顯示方法:將列表直接插入label
這樣做的結(jié)果是,列表項在label中顯示為一行,項之間以空格分隔,整個列表被一對括號包圍:(111-1111 222-2222 333-3333)。
這些號碼可能顯示為多行或單行,取決于號碼的多少。最終用戶能看到這個數(shù)據(jù),也可能將它們當(dāng)做電話號碼的列表,但這樣的顯示方式很不美觀。通常會將列表項分行顯示或用逗號分隔。
為了適當(dāng)?shù)仫@示列表,需要將每個列表項轉(zhuǎn)換為一段帶格式的單獨的文本。文本對象通常有字母、數(shù)字、標點符號組成,但也可能包含特殊的控制字符,它們對應(yīng)一些不可見的字符,如tab被表示為\t(更多關(guān)于控制字符的內(nèi)容,請查閱文本表示的統(tǒng)一碼[Unicode]標準:http://www.unicode.org/standard/standard.html)。
為了逐行顯示我們的電話號碼列表,需要一個換行符“\n”。當(dāng)“\n”出現(xiàn)在一段文本中,意味著“到下一行來顯示后面的東西”。因此文本對象“111-1111\n222-2222\n333-3333”將顯示為:
111-1111
222-2222
333-3333
要構(gòu)造出這樣的文本對象,需要用到foreach塊,將每個列表項附加換行符后再添加到PhoneNumberLabel.Text屬性中,如圖20-5所示。
圖 20-5 使用foreach處理列表:在每個列表項后添加換行符
我們來跟蹤一下這些塊的作用。在第15章中討論過在程序運行過程中跟蹤變量及屬性變化的相關(guān)內(nèi)容,在foreach塊中,我們考慮每一次迭代之后的值,所謂一次迭代,就是foreach循環(huán)執(zhí)行一次。
在foreach之前,PhoneNumberLabel的Text屬性被初始化為空文本;從foreach開始,程序會自動將列表的第一項賦給占位符變量phoneNumber。然后將PhoneNumberLabel.Text、\n、phoneNumber連接起來之后,再將其設(shè)為PnoneNumberLabel.Text的屬性值。這樣,在完成foreach的第一次迭代后,相關(guān)的變量值如表20-1所示。
表20-1 第一次foreach迭代之后的變量值
phoneNumber | PhoneNumberLabel.Text |
---|---|
111-1111 | \n111-1111 |
此時已經(jīng)是foreach內(nèi)的最后一行,程序進入第二次迭代,下一個列表項(222-2222)被設(shè)為占位符變量phoneNumber的值,并重復(fù)執(zhí)行foreach內(nèi)部的塊:將PhoneNumberLabel.Text的原值(\n111-1111)與“\n”及phoneNumber(此時是222-2222)連接起來。第二次迭代后,變量及屬性值如表20-2所示。
表20-2 第二次foreach迭代之后的變量值
phoneNumber | PhoneNumberLabel.Text |
---|---|
222-2222 | \n111-1111\n222-2222 |
列表中的第三項被設(shè)為phoneNumber的值,第三次重復(fù)運行foreach內(nèi)部的塊,在完成最后一次迭代后,最終結(jié)果如表20-3所示。
表20-3 第三次foreach迭代之后的變量值
phoneNumber | PhoneNumberLabel.Text |
---|---|
333-3333 | \n111-1111\n222-2222\n333-3333 |
三次迭代完成之后,label包含了所有的電話號碼,文本變得很長,在foreach執(zhí)行完成后,PhoneNumberLabel.Text的顯示如下:
111-1111
222-2222
333-3333
循環(huán)塊while的使用比foreach要稍顯復(fù)雜,但while塊的優(yōu)勢在于它的通用性:foreach可以遍歷一個列表,而while可以為循環(huán)設(shè)定任意的條件。隨便舉個例子,假設(shè)你想給電話號碼表中每隔一個人發(fā)短信,foreach則做不到,但while中可以將每次循環(huán)中index的遞增值設(shè)為2。
在第18章中,條件測試的結(jié)果將返回一個值:true或false,在while-do塊中也包含了一個想if塊一樣的條件測試。如果while測試的結(jié)果為true,程序會執(zhí)行while內(nèi)部的塊,然后返回并再次進行條件測試。只要測試結(jié)果為true,while內(nèi)部的塊就會重復(fù)運行。當(dāng)測試值為false時,程序?qū)⑻鲅h(huán)(如同foreach中一樣)并繼續(xù)執(zhí)行while下面的塊。
關(guān)于while的更具啟發(fā)性的例子中,涉及到了一種常見的情形,即,需要同步處理兩個列表。例如,在總統(tǒng)測試(第10章)應(yīng)用中,有兩個分別存放問題和答案的列表,以及一個變量index來跟蹤當(dāng)前的問題序號。為了同時顯示問題-答案對,需要同步遍歷兩個列表,并從兩個列表中獲取序號為index的項。foreach只允許遍歷一個列表,但在while循環(huán)中,則可以使用index從每個列表中抓取對應(yīng)的項。圖20-6中顯示了用while塊逐行顯示問題-答案對的方法。
圖 20-6 使用while循環(huán)逐行顯示問題-答案對
由于用while替代了foreach,因而需要直接初始化index、檢查是否到達列表結(jié)尾、在每次循環(huán)中選擇各個列表中對應(yīng)的項,并使得index遞增。
這里是使用while循環(huán)的另一個例子:與列表無關(guān)的重復(fù)操作。想想看,圖20-7中的塊在做什么?高水平?要想弄清楚,就要跟蹤每一個塊(關(guān)于程序跟蹤的更多內(nèi)容見第15章),隨著程序的進展,跟蹤每個變量的值。
圖 20-7 你能說出這些塊的功能嗎?
當(dāng)變量number的值小于或等于變量N時,while中的塊將重復(fù)執(zhí)行。在這個應(yīng)用中,N值等于最終用戶在界面上的文本框(NTextBox)中輸入數(shù)字,假設(shè)用戶輸入3。當(dāng)程序運行到while塊時,程序中的變量如表20-4所示。
表20-4 程序運行到while塊時,各個變量的值
N | number | tota |
---|---|---|
3 | 1 | 0 |
在第一次循環(huán)中,while塊詢問:number值小于或等于(≤)N 嗎?第一次詢問得到的結(jié)果是true,于是執(zhí)行while中的塊:total值等于它現(xiàn)在的值(0)加上number(1),number值遞增1。第一次while循環(huán)之后,各變量的值如表20-5所示。
表20-5 while中的塊完成第一次循環(huán)使用,各個變量的值
N | number | total |
---|---|---|
3 | 2 | 1 |
第二次循環(huán)中,繼續(xù)測試“number≤N”,結(jié)果仍然是true(2≤3),因而while內(nèi)部的塊再次運行。total值等于它自身(1)加上number(2),number繼續(xù)遞增。第二次迭代完成時,各變量的值如表20-6所示。
表20-6 兩次循環(huán)結(jié)束時,各個變量的值
N | number | total |
---|---|---|
3 | 3 | 3 |
程序再次返回到條件測試,這次的結(jié)果仍然是true(3≤3),于是while內(nèi)的塊第三次運行。現(xiàn)在total值為它自身(3)加上number(3),結(jié)果為6;number遞增到4,如表20-7所示。
表20-7 三次循環(huán)之后各個變量的值
N | number | total |
---|---|---|
3 | 4 | 6 |
在完成第三次迭代之后,程序再次返回測試“number≤N”,或“4≤3”,此時結(jié)果為false,因此while內(nèi)部的塊不再執(zhí)行,事件處理程序完成。
現(xiàn)在該知道這些塊的作用了吧?它們在做一個最基本的數(shù)學(xué)運算:數(shù)字計算。每當(dāng)用戶輸入數(shù)字,程序就給出從1到N的自然數(shù)的和,這里的N就是輸入的數(shù)。在這個例子中,我們假設(shè)用戶輸入了3,因此加和的結(jié)果是6;如果用戶輸入4,最后的結(jié)果為10。
計算機擅長于做重復(fù)的事情。想象一下所有的銀行賬戶都要做利息的累計核算,所有計算學(xué)生平均績點的成績處理,以及日常生活中計算機所做的各種無計其數(shù)的重復(fù)的工作。
App Inventor 提供了兩種用于循環(huán)操作的塊。foreach塊適合于針對列表中的每一項實施一組相同的操作。與那些具體的數(shù)據(jù)相比,foreach更適合于處理抽象的列表,其編碼更具可維護性,尤其是對于動態(tài)數(shù)據(jù)來說,foreach是必需的。
與foreach相比,while則更為通用:既可以處理單個列表,也可以同步處理兩個列表,還能進行公式計算。在執(zhí)行while循環(huán)時,只要條件測試結(jié)果為真,while內(nèi)部的塊就會順次執(zhí)行;在內(nèi)部塊運行完成后,程序?qū)⒎祷夭⒅匦逻M行條件測試,直到測試結(jié)果為false,則循環(huán)結(jié)束。
更多建議: