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