計算機最擅長做的事情就是“重復”——像兒童一樣不厭其煩地重復做一件事,而且重復的速度很快,可以在1毫秒內列出你的全部Facebook好友。
本章將學習如何用有限的幾個塊來編寫可以重復執行的程序,而不必反復拷貝粘貼同一段代碼;還將學習與列表有關的操作,如給電話號碼列表中的每個號碼發送一條短信,以及為列表項排序。通過學習,你將了解到如何用循環塊來有效地簡化程序。

## **控制程序的執行:分支及循環**
在前幾章中,我們學習了用一組事件處理程序來定義應用中的行為:事件以及對事件做出響應的函數。在這些響應函數中,程序通常不是按照線性的順序執行,有些程序塊只能在滿足某些條件時才能執行。
重復塊是程序的另一種非線性運行方式。就像if及ifelse塊讓程序產生分支一樣,重復塊讓程序循環執行,換句話說,在執行完一組指令后,重新跳回到這組指令的起點并再次運行,如圖20-1所示。在應用的運行過程中,內部的計數器會跟蹤即將執行的下一步操作,因此,對于整個事件處理程序來說,從頭至尾的每一步操作都在程序計數器的監控之下(有條件地)完成。程序計數器隨著這些重復執行的塊循環,不斷地重復這些功能。

**圖 20-1 讓程序循環執行的重復塊**
在App Inventor中有兩種類型的重復塊:foreach及while.foreach,其作用是對列表中的每一項實施某些特定的操作,如,向電話號碼列表中的每個號碼發送一條短信。
塊while的應用比foreach要普遍,while塊中的程序塊會一直重復運行,直到某個條件不再滿足。while塊可用于數學公式的計算,如求n個連續自然數的和,或求n的階乘,此外,while也可以用于同時處理兩個列表;foreach每次只能處理一個列表。
## **使用foreach對列表實施迭代**
在第18章里,我們討論了一個“隨機撥號”應用。這種隨機撥打朋友電話的方式有時能撥通,但如果你有一個像我這樣的朋友,這種呼叫卻不總是能得到應答。可以采取另一種方式,給所有列表中的朋友發短信說“想你”,然后看誰最先回復你(或許還有更令人愉快的方式!)。
這個應用可以通過點擊一次按鈕向多個朋友發送短信,最簡單的方法是,先寫好發給一個人的代碼塊,然后拷貝粘貼并修改接收人的電話號碼,如圖20-2所示。

**圖 20-2 拷貝并粘貼向不同號碼發送短信的塊**
如果只有少量的塊,用這種“強力”的拷貝粘貼方式也還說得過去,但是像朋友列表這樣的數據表會時常變化,而你不希望每次添加或刪除一個電話號碼,都要動手去修改程序。
塊foreach提供了一個更好的解決方案,可以定義一個包括所有電話號碼的列表變量phoneNumberList,然后用foreach塊將發送一次短信的塊包圍起來,從而實現群發功能,如圖20-3所示。

**圖 20-3 使用foreach塊對列表中的每一項執行同一套指令**
上述代碼可以解讀為:
對于phoneNumberList列表中的每一項(電話號碼),設置Texting對象的PhoneNumber屬性為列表中的項,并發送該條短信。
對于foreach塊,一個必須的參數是一個列表,它所要處理的列表,將列表插入“in list”參數插槽。此時,從phoneNumberList變量的初始化塊中拖出“get global phoneNumberList”塊,并插入“in list”插槽,以便為即將發送的短信提供電話號碼列表。
foreach塊的第一行使用了foreach自帶的占位符變量,在默認情況下,變量名為item,你可以修改它,也可以就用默認值,該變量代表了列表中正在被處理的當前項。
foreach中的所有塊都將對列表中的每一項執行同樣的操作,其中的占位符變量(例子中的phoneNumber)始終保存的是當前正被處理的項。如果列表中有三項,則foreach中包含的塊將被執行三次,這些塊可以說是從屬于foreach塊,或處于foreach塊的內部,這些內部塊執行到最后一行時,我們所說的程序計數器將要循環回第一行。
### **循環過程詳細分析**
我們來詳細地分析一下foreach塊的運行機制,因為理解循環是編程的基礎。當點擊TextGroupButton時,觸發事件處理程序,首先執行的是“set Texting1.Message to”塊,要將短信內容設置為“想你...”,這個塊只執行一次。
然后開始執行foreach塊。在foreach內部塊開始執行前,占位符變量item被設置為列表phoneNumberList的第一項(111-1111),這一步是自動完成的,代替了你自己使用select list item來調出列表項。在完成將列表中的第一項賦給item之后,foreach內部的塊開始第一次運行,Texting1.PhoneNumber屬性被設為item的值(111-1111),并發出短信。
當運行到foreach中的最后一行時(Texting1.SendMessage塊),程序將循環會到foreach的首行,并自動將列表中的下一項(222-2222)設為變量item的值,然后重復操作foreach內部的兩個塊,即發送短信“想你...”到號碼222-2222。然后程序再次循環會首行,并將item的值設為列表中的第三項(333-3333),并執行第三次重復操作,第三次發送短信。
由于列表中最后一項,即本例子中的第三項已經被處理完畢,因此foreach循環到此結束,程序將跳出循環,這意味著程序計數器將繼續下移來處理foreach下面的塊。在本例中,foreach之后沒有塊,因此整個事件處理程序結束。
### **書寫可維護的代碼**
在最終用戶看來,使用foreach的方法還是“強力”的拷貝粘貼法,在最終結果上并無分別,但從程序員的角度來看,foreach方法讓代碼有更好的可維護性,即使數據(電話號碼列表)是動態輸入的,程序也可以適用。
可維護軟件指的是可以很容易地對軟件進行修改,而不會引入程序的漏洞。使用foreach方法,一旦需要修改短信接收人,只需要修改列表變量,而絲毫不需要修改程序的邏輯(事件處理程序)。相反,采用強力的方法,如果需要添加新的接收人,則需要在事件處理程序中添加新的塊。任何時候,只要你改動了程序的邏輯,都會冒帶來漏洞的風險。
更重要的是,即便電話列表是動態的,即,不僅是程序員,最終用戶也可以向列表中添加新的號碼,foreach方法也能奏效。在我們的例子中只有三個固定的號碼,而且號碼直接寫在了代碼中,與此相比,采用動態數據的應用,其信息來源可能是最終用戶,或其他來源。如果你要重新設計應用,讓最終用戶來輸入電話號碼,你就必須使用foreach方法,因為在你寫程序的時候,根本無法知道會有哪些號碼,因此也就無從采用強力的拷貝粘貼法。
### **foreach的第二個例子:顯示列表**
顯示列表項最簡單的方式就是將列表變量插入Label的Text屬性,如圖20-4所示。

**圖 20-4 列表的簡單顯示方法:將列表直接插入label**
這樣做的結果是,列表項在label中顯示為一行,項之間以空格分隔,整個列表被一對括號包圍:(111-1111 222-2222 333-3333)。
這些號碼可能顯示為多行或單行,取決于號碼的多少。最終用戶能看到這個數據,也可能將它們當做電話號碼的列表,但這樣的顯示方式很不美觀。通常會將列表項分行顯示或用逗號分隔。
為了適當地顯示列表,需要將每個列表項轉換為一段帶格式的單獨的文本。文本對象通常有字母、數字、標點符號組成,但也可能包含特殊的控制字符,它們對應一些不可見的字符,如tab被表示為\t(更多關于控制字符的內容,請查閱文本表示的統一碼[Unicode]標準:[http://www.unicode.org/standard/standard.html](http://www.unicode.org/standard/standard.html))。
為了逐行顯示我們的電話號碼列表,需要一個換行符“\n”。當“\n”出現在一段文本中,意味著“到下一行來顯示后面的東西”。因此文本對象“111-1111\n222-2222\n333-3333”將顯示為:
> 111-1111
> 222-2222
> 333-3333
要構造出這樣的文本對象,需要用到foreach塊,將每個列表項附加換行符后再添加到PhoneNumberLabel.Text屬性中,如圖20-5所示。

**圖 20-5 使用foreach處理列表:在每個列表項后添加換行符**
我們來跟蹤一下這些塊的作用。在第15章中討論過在程序運行過程中跟蹤變量及屬性變化的相關內容,在foreach塊中,我們考慮每一次迭代之后的值,所謂一次迭代,就是foreach循環執行一次。
在foreach之前,PhoneNumberLabel的Text屬性被初始化為空文本;從foreach開始,程序會自動將列表的第一項賦給占位符變量phoneNumber。然后將PhoneNumberLabel.Text、\n、phoneNumber連接起來之后,再將其設為PnoneNumberLabel.Text的屬性值。這樣,在完成foreach的第一次迭代后,相關的變量值如表20-1所示。
**表20-1 第一次foreach迭代之后的變量值**
| phoneNumber | PhoneNumberLabel.Text |
| --- | --- |
| 111-1111 | \n111-1111 |
此時已經是foreach內的最后一行,程序進入第二次迭代,下一個列表項(222-2222)被設為占位符變量phoneNumber的值,并重復執行foreach內部的塊:將PhoneNumberLabel.Text的原值(\n111-1111)與“\n”及phoneNumber(此時是222-2222)連接起來。第二次迭代后,變量及屬性值如表20-2所示。
**表20-2 第二次foreach迭代之后的變量值**
| phoneNumber | PhoneNumberLabel.Text |
| --- | --- |
| 222-2222| \n111-1111\n222-2222 |
列表中的第三項被設為phoneNumber的值,第三次重復運行foreach內部的塊,在完成最后一次迭代后,最終結果如表20-3所示。
**表20-3 第三次foreach迭代之后的變量值**
| phoneNumber | PhoneNumberLabel.Text |
| --- | --- |
| 333-3333 | \n111-1111\n222-2222\n333-3333 |
三次迭代完成之后,label包含了所有的電話號碼,文本變得很長,在foreach執行完成后,PhoneNumberLabel.Text的顯示如下:
> 111-1111
> 222-2222
> 333-3333
## **用while實現迭代**
循環塊while的使用比foreach要稍顯復雜,但while塊的優勢在于它的通用性:foreach可以遍歷一個列表,而while可以為循環設定任意的條件。隨便舉個例子,假設你想給電話號碼表中每隔一個人發短信,foreach則做不到,但while中可以將每次循環中index的遞增值設為2。
在第18章中,條件測試的結果將返回一個值:true或false,在while-do塊中也包含了一個想if塊一樣的條件測試。如果while測試的結果為true,程序會執行while內部的塊,然后返回并再次進行條件測試。只要測試結果為true,while內部的塊就會重復運行。當測試值為false時,程序將跳出循環(如同foreach中一樣)并繼續執行while下面的塊。
### **使用while同步處理兩個列表**
關于while的更具啟發性的例子中,涉及到了一種常見的情形,即,需要同步處理兩個列表。例如,在總統測試(第10章)應用中,有兩個分別存放問題和答案的列表,以及一個變量index來跟蹤當前的問題序號。為了同時顯示問題-答案對,需要同步遍歷兩個列表,并從兩個列表中獲取序號為index的項。foreach只允許遍歷一個列表,但在while循環中,則可以使用index從每個列表中抓取對應的項。圖20-6中顯示了用while塊逐行顯示問題-答案對的方法。

**圖 20-6 使用while循環逐行顯示問題-答案對**
由于用while替代了foreach,因而需要直接初始化index、檢查是否到達列表結尾、在每次循環中選擇各個列表中對應的項,并使得index遞增。
### **使用while做公式計算**
這里是使用while循環的另一個例子:與列表無關的重復操作。想想看,圖20-7中的塊在做什么?高水平?要想弄清楚,就要跟蹤每一個塊(關于程序跟蹤的更多內容見第15章),隨著程序的進展,跟蹤每個變量的值。

**圖 20-7 你能說出這些塊的功能嗎?**
當變量number的值小于或等于變量N時,while中的塊將重復執行。在這個應用中,N值等于最終用戶在界面上的文本框(NTextBox)中輸入數字,假設用戶輸入3。當程序運行到while塊時,程序中的變量如表20-4所示。
**表20-4 程序運行到while塊時,各個變量的值**
| N | number | tota |
| --- | --- | --- |
| 3 | 1 | 0 |
在第一次循環中,while塊詢問:number值小于或等于(≤)N 嗎?第一次詢問得到的結果是true,于是執行while中的塊:total值等于它現在的值(0)加上number(1),number值遞增1。第一次while循環之后,各變量的值如表20-5所示。
**表20-5 while中的塊完成第一次循環使用,各個變量的值**
| N | number | total |
| --- | --- | --- |
| 3 | 2 | 1 |
第二次循環中,繼續測試“number≤N”,結果仍然是true(2≤3),因而while內部的塊再次運行。total值等于它自身(1)加上number(2),number繼續遞增。第二次迭代完成時,各變量的值如表20-6所示。
**表20-6 兩次循環結束時,各個變量的值**
|N | number | total |
| --- | --- | --- |
| 3 | 3 | 3 |
程序再次返回到條件測試,這次的結果仍然是true(3≤3),于是while內的塊第三次運行。現在total值為它自身(3)加上number(3),結果為6;number遞增到4,如表20-7所示。
**表20-7 三次循環之后各個變量的值**
| N | number | total |
| --- | --- | --- |
| 3 | 4 | 6 |
在完成第三次迭代之后,程序再次返回測試“number≤N”,或“4≤3”,此時結果為false,因此while內部的塊不再執行,事件處理程序完成。
現在該知道這些塊的作用了吧?它們在做一個最基本的數學運算:數字計算。每當用戶輸入數字,程序就給出從1到N的自然數的和,這里的N就是輸入的數。在這個例子中,我們假設用戶輸入了3,因此加和的結果是6;如果用戶輸入4,最后的結果為10。
## **小結**
計算機擅長于做重復的事情。想象一下所有的銀行賬戶都要做利息的累計核算,所有計算學生平均績點的成績處理,以及日常生活中計算機所做的各種無計其數的重復的工作。
App Inventor 提供了兩種用于循環操作的塊。foreach塊適合于針對列表中的每一項實施一組相同的操作。與那些具體的數據相比,foreach更適合于處理抽象的列表,其編碼更具可維護性,尤其是對于動態數據來說,foreach是必需的。
與foreach相比,while則更為通用:既可以處理單個列表,也可以同步處理兩個列表,還能進行公式計算。在執行while循環時,只要條件測試結果為真,while內部的塊就會順次執行;在內部塊運行完成后,程序將返回并重新進行條件測試,直到測試結果為false,則循環結束。
- 簡介
- 序言
- 前言
- 第 1 章 Hello 貓咪
- 第 2 章 油漆桶
- 第 3 章 打地鼠
- 第 4 章 開車不發短信
- 第 5 章 瓢蟲快跑
- 第 6 章 巴黎地圖旅游
- 第 7 章 安卓,我的車在哪?
- 第 8 章 總統測驗
- 第 9 章 木琴
- 第 10 章 出題及答題
- 第 11 章 廣播中心
- 第 12 章 遙控機器人
- 第 13 章 亞馬遜掌上書店
- 第 14 章 理解應用的結構
- 第 15 章 軟件工程與應用調試
- 第 16 章 應用中的存儲
- 第 17 章 創建動畫應用
- 第 18 章 程序中的決策:條件塊
- 第 19 章 數據列表編程
- 第 20 章 循環
- 第 21 章 定義過程
- 第 22 章 數據庫
- 第 23 章 傳感器
- 第 24 章 與Web API通信