官术网_书友最值得收藏!

3.7 循環(huán)結(jié)構(gòu)

循環(huán)結(jié)構(gòu)是程序中一種很重要的編程構(gòu)造,特點(diǎn)是在給定條件成立時(shí),反復(fù)執(zhí)行某程序段,直到條件不成立為止。給定的條件稱為循環(huán)條件,反復(fù)執(zhí)行的程序段稱為循環(huán)體。Java語言提供了多種循環(huán)語句,可以組成各種不同形式的循環(huán)結(jié)構(gòu)。

3.7.1 while語句

while語句的一般形式為:

     while(表達(dá)式) 語句

其中,表達(dá)式是循環(huán)條件,語句為循環(huán)體。如果循環(huán)體包含一個(gè)以上的語句,則必須用{}括起來,組成復(fù)合語句。

while語句的執(zhí)行流程是:計(jì)算表達(dá)式的值,當(dāng)值為真時(shí),執(zhí)行循環(huán)體語句。其執(zhí)行過程如圖3.14所示。

圖3.14 while語句的執(zhí)行流程框圖

例3.5 擴(kuò)充例3.3分?jǐn)?shù)統(tǒng)計(jì)程序的功能,使該程序可以計(jì)算并顯示平均分?jǐn)?shù)。

分析:如果還像例3.3一樣為類添加字段,每輸入一個(gè)分?jǐn)?shù)就累加到總分?jǐn)?shù)字段并且分?jǐn)?shù)個(gè)數(shù)也增加1,可以實(shí)現(xiàn)平均分?jǐn)?shù)的計(jì)算功能。但是,如果統(tǒng)計(jì)的指標(biāo)比較多,例如還要計(jì)算標(biāo)準(zhǔn)差等,則類中的字段變得繁多,程序也顯得不夠簡練。事實(shí)上可以采用這樣的思路:用戶輸入的所有合法分?jǐn)?shù)都存放在文本區(qū)域jTextAreaNums中,可以通過該組件的getText()方法將所有分?jǐn)?shù)拼合成的字符串取出來,然后從該字符串中解析出各個(gè)分?jǐn)?shù),從而實(shí)現(xiàn)平均分?jǐn)?shù)的計(jì)算功能。此分?jǐn)?shù)字符串的格式是“89\n65\n46\n100\n73\n92”,其中的字符’\n’是轉(zhuǎn)義字符——換行符,使用String類的以下兩個(gè)方法可以從中解析出單個(gè)分?jǐn)?shù)。

1. substring(int beginIndex, int endIndex)

此方法返回一個(gè)新字符串,它是此字符串的一個(gè)子字符串。該子字符串是從指定的beginIndex處開始直到索引endIndex?1處的字符。例如,"89\n65\n46\n100\n73\n92".substring(0, 2)返回"89"。

2. indexOf(int ch, int fromIndex)

該方法返回在此字符串中第一次出現(xiàn)指定字符ch的索引,從指定的索引fromIndex處開始搜索。在此String對象表示的字符序列中,如果帶有值ch的字符的索引不小于fromIndex,則返回第一次出現(xiàn)該值的索引。如果此字符串中fromIndex或之后的位置沒有這樣的字符出現(xiàn),則返回?1。如果fromIndex的值為負(fù)或0,則搜索整個(gè)字符串;如果大于或等于此字符串的長度,則返回?1。被搜索的字符ch是Unicode字符,即可以是漢字或其他語言字符。例如,"89\n65\n46\n100\73\92". indexOf('\n', 0)返回2。

分?jǐn)?shù)字符串可能包含多個(gè)分?jǐn)?shù),需要反復(fù)提取每一個(gè)分?jǐn)?shù),因此采用循環(huán)結(jié)構(gòu)。

解:按照以下步驟操作。

(1)右擊chap03項(xiàng)目的“源包”節(jié)點(diǎn),在快捷菜單中選擇“新建”|“Java包”菜單項(xiàng),包名輸入“book.loopdemos”,單擊“完成”按鈕。

(2)右擊chap03項(xiàng)目的“源包”|ifdemos包下的IfElseIfDemo.java節(jié)點(diǎn),在快捷菜單中選擇“復(fù)制”菜單項(xiàng)。

(3)右擊book.loopdemos包名節(jié)點(diǎn),在快捷菜單中選擇“粘貼”|“重構(gòu)復(fù)制”菜單項(xiàng),新名稱輸入“WhileDemo”,單擊“重構(gòu)”按鈕。

(4)在WhileDemo窗體中將“清除”按鈕移到文本區(qū)域左側(cè);在文本區(qū)域下方創(chuàng)建“平均分”按鈕并將其變量名修改為jButtonAvg;在該按鈕右邊創(chuàng)建文本字段組件jTextFieldAvg,并設(shè)置其editable屬性值為False、columns屬性值為12、text屬性值為空。完成后的界面如圖3.15所示。

圖3.15 例3.5分?jǐn)?shù)統(tǒng)計(jì)程序界面設(shè)計(jì)視圖

(5)單擊“平均分”按鈕,在屬性窗口中單擊“事件”標(biāo)簽,單擊actionPerformed行右端的…按鈕,在對話框中單擊“添加”按鈕,新建處理程序的名稱輸入“calcAvg”,單擊“確定”按鈕,再次單擊“確定”按鈕。

(6)自動(dòng)切換到源代碼視圖后,在calcAvg方法體中輸入以下代碼段。

(7)在clearNums方法中添加語句“jTextFieldAvg.setText("");”。

完成上述操步驟作后運(yùn)行程序,可以正確計(jì)算顯示平均分?jǐn)?shù)。

3.7.2 do-while語句

while循環(huán)首先計(jì)算循環(huán)條件,有可能第一次循環(huán)時(shí)循環(huán)條件就是false,則循環(huán)體一次都不執(zhí)行。如例3.5中沒有輸入分?jǐn)?shù)或只輸入一個(gè)分?jǐn)?shù),則循環(huán)控制變量to的值為?1,循環(huán)條件為false,循環(huán)體不會(huì)執(zhí)行。但是,有時(shí)希望循環(huán)體先執(zhí)行一次,然后判斷是否繼續(xù)下次循環(huán),此時(shí)就應(yīng)該使用do-while循環(huán)。

此循環(huán)語句的執(zhí)行流程是:它先執(zhí)行循環(huán)中的語句,然后再判斷表達(dá)式是否為真(true),如果為真則繼續(xù)循環(huán);如果為假(false),則終止循環(huán)。因此,do-while循環(huán)至少要執(zhí)行一次循環(huán)語句。其執(zhí)行過程可用圖3.16表示。

圖3.16 do-while語句的執(zhí)行流程框圖

分析例3.5分?jǐn)?shù)統(tǒng)計(jì)程序中calcAvg方法的源代碼,發(fā)現(xiàn)循環(huán)體中的關(guān)鍵語句之一“to = numStr.indexOf('\n', from);”在while循環(huán)語句之前就執(zhí)行過一次,且to是循環(huán)控制變量。這就是說,無論循環(huán)何時(shí)結(jié)束,此語句必須執(zhí)行一次。因此,在calcAvg方法中采用do-while循環(huán)更合適。修改后calcAvg方法體中的程序段如下。

可見,采用do-while循環(huán)結(jié)構(gòu)后,程序似乎變得簡潔了。

3.7.3 for語句

在Java程序設(shè)計(jì)中經(jīng)常會(huì)遇到使一組語句迭代執(zhí)行指定次數(shù)的問題,Java語言提供了for語句來完成“計(jì)數(shù)”循環(huán)。for語句使用也最為靈活,完全可以取代while語句。for語句的一般形式為:

     for(表達(dá)式1; 表達(dá)式2; 表達(dá)式3)
         語句

for語句的執(zhí)行流程如圖3.17所示。它的執(zhí)行過程是:①先求解表達(dá)式1;②求解表達(dá)式2,若其值為true(真),則執(zhí)行for語句循環(huán)體內(nèi)的語句,然后轉(zhuǎn)至③執(zhí)行;若其值為false(假),則循環(huán)結(jié)束,轉(zhuǎn)到for語句下面的語句執(zhí)行;③求解表達(dá)式3;④轉(zhuǎn)至②執(zhí)行。

圖3.17 for語句的執(zhí)行流程框圖

for語句最簡單的應(yīng)用形式是計(jì)數(shù)循環(huán),也是最容易理解的形式,語句如下:

     for(循環(huán)變量賦初值; 循環(huán)條件; 循環(huán)變量增量)
        語句

循環(huán)變量賦初值是一個(gè)賦值語句,用來給循環(huán)控制變量賦初始值;循環(huán)條件是一個(gè)關(guān)系表達(dá)式,它決定什么時(shí)候退出循環(huán);循環(huán)變量增量用來定義循環(huán)控制變量每循環(huán)一次后按什么方式變化。這三個(gè)部分之間用“;”分開。例如:

     for(i=1; i<=100; i++)
         sum=sum+i;

先給i賦初值1,判斷i是否小于或等于100,若是則執(zhí)行循環(huán)體語句,之后值增加1。再重新判斷,直到i>100,結(jié)束循環(huán)。顯然,此for語句與下面的while等價(jià):

事實(shí)上,for語句與while語句的這種等價(jià)關(guān)系具有一般性。

for循環(huán)中的“表達(dá)式1(循環(huán)變量賦初值)”“表達(dá)式2(循環(huán)條件)”和“表達(dá)式3(循環(huán)變量增量) ”都是選擇項(xiàng),即表達(dá)式可以省略,但“;”不能省略。由此,可以形成for語句的以下靈活運(yùn)用方式。

(1)省略“表達(dá)式1(循環(huán)變量賦初值)”,表示不對循環(huán)控制變量賦初值。

(2)省略“表達(dá)式2(循環(huán)條件)”。但應(yīng)特別注意:若在循環(huán)體內(nèi)不做其他處理,則會(huì)形成死循環(huán)——循環(huán)一直進(jìn)行。例如,“for(i=1; ; i++) sum = sum+1 ;”就是死循環(huán)。

(3)省略“表達(dá)式3(循環(huán)變量增量)”,則不對循環(huán)控制變量進(jìn)行操作。這時(shí)應(yīng)該在語句體中加入修改循環(huán)控制變量的語句,使循環(huán)控制變量向著使循環(huán)結(jié)束的方向靠近。例如:

     for(i=1;i<=100;) {
         sum=sum+i;
         i++;  // 變量i向著101靠近
     }

(4)省略“表達(dá)式1(循環(huán)變量賦初值)”和“表達(dá)式3(循環(huán)變量增量)”。例如:

     for(; i<=100; ) {
         sum = sum + 1;
         i++;
     }

(5)三個(gè)表達(dá)式都可以省略。例如,“for(; ; )語句”,此種結(jié)構(gòu)應(yīng)在“語句”部分創(chuàng)造循環(huán)結(jié)束的條件。

(6)表達(dá)式1可以是設(shè)置循環(huán)變量的初值的賦值表達(dá)式,也可以是其他表達(dá)式。例如,“for(sum=0;i<=100;i++) sum = sum + i;”。

(7)表達(dá)式1和表達(dá)式3既可以是一個(gè)簡單表達(dá)式也可以是逗號表達(dá)式。例如,“for(sum=0,i=1;i<=100;i++)sum=sum+i;”;又如,“for(i=0,j=100;i<=100;i++,j--)k=i+j;”。

逗號運(yùn)算符“,”可以放在多個(gè)表達(dá)式之間形成逗號表達(dá)式,使各個(gè)表達(dá)式依次求值,逗號表達(dá)式的值是其中最后一個(gè)表達(dá)式的值。逗號運(yùn)算符“,”只用于for語句。

如果for語句的循環(huán)控制變量在該語句塊之外不再有用,可以在for語句的第一個(gè)表達(dá)式之處直接聲明其類型,例如:“for(int i=1;i<=100;i++) sum+=i;”。但是,語句“for(int i=1,sum=0;i<=100;i++) sum+=i;”,在之后不能訪問變量sum,如接著執(zhí)行語句“system.out.println("sum="+sum);”會(huì)發(fā)生找不到變量sum的錯(cuò)誤。

for語句盡管有這么多靈活用法,但一般還是應(yīng)該按照其正常形式使用,不應(yīng)人為增加程序的復(fù)雜性。應(yīng)該遵循一條不成文規(guī)矩:for語句的三個(gè)表達(dá)式應(yīng)該對同一個(gè)計(jì)數(shù)變量進(jìn)行初始化、檢測和更新。

還應(yīng)注意,如果for語句的表達(dá)式2檢測浮點(diǎn)數(shù),盡量不要檢測相等,而是檢測其誤差在一定范圍內(nèi)。例如,“for(double x=0; x!=10; x+=0.1) …”則可能形成死循環(huán)——由于誤差,變量x永遠(yuǎn)不會(huì)等于10。

Java 10可以使用保留字var在for語句的第一個(gè)表達(dá)式中定義循環(huán)控制變量,該變量的類型由Java 10根據(jù)給其所賦值的類型推斷。例如語句“for(var i='a';i<='z';i++)system.out.println(i);”則會(huì)分行輸出26個(gè)英文字母。但此處var一次只能定義一個(gè)推斷類型變量,語句“for(var i=1,sum=0;i<=100;i++) ……”是不允許的。

例3.6 設(shè)計(jì)一個(gè)階乘計(jì)算程序,使程序能夠計(jì)算任意自然數(shù)的階乘。

分析:從階乘的計(jì)算公式n! = 1×2×…×(n?1)×n來看,是一個(gè)迭代相乘的操作,且迭代次數(shù)是n次,可以采用計(jì)數(shù)循環(huán)完成:

     long fact = 1;
     for(int i=1; i<=n; i++)
         fact *= i;

但是,運(yùn)行程序發(fā)現(xiàn),當(dāng)n取比較大的值時(shí),計(jì)算結(jié)果超出long類型的范圍,顯然并不能滿足計(jì)算任意自然數(shù)階乘的要求。在3.1.2節(jié)介紹過BigInteger類可以表示和處理不可變的任意精度的整數(shù),當(dāng)然可以處理大整數(shù)運(yùn)算。查閱該類的API文檔,找到以下兩個(gè)可以用于本例的方法。

valueOf(long val):返回其值等于指定long類型值的BigInteger對象,且可以使用類名直接調(diào)用。

multiply(BigInteger val):返回當(dāng)前BigInteger對象與val對象乘積的BigInteger,也就是執(zhí)行大整數(shù)相乘運(yùn)算。

顯然,使用這兩個(gè)方法,采用上述for循環(huán)就可以實(shí)現(xiàn)任意自然數(shù)階乘的計(jì)算。

解:按以下步驟操作。

(1)右擊book.loopdemos包名節(jié)點(diǎn),在快捷菜單中選擇“新建”|“JFrame窗體”菜單項(xiàng),類名輸入“ForDemo1”,單擊“完成”按鈕。

(2)在窗體中設(shè)計(jì)如圖3.18所示GUI界面。其中,文本字段組件jTextFieldFact存放和顯示階乘值,columns屬性值設(shè)置為20,editable屬性值為false。

圖3.18 階乘計(jì)算程序設(shè)計(jì)界面

(3)單擊“=”按鈕,在屬性窗口中單擊“事件”標(biāo)簽,單擊actionPerformed行右端的…按鈕,在對話框中單擊“添加”按鈕,新建處理程序的名稱輸入“calcFact”,單擊“確定”按鈕,再次單擊“確定”按鈕。

(4)自動(dòng)切換到源代碼視圖后,在calcFact方法體中輸入以下代碼段。

(5)使用與步驟(3)相同的操作,為“清除”按鈕添加actionPerformed事件處理方法clearAll,并在該方法體中輸入以下程序代碼。

     jTextFieldNum.setText("");
     jTextFieldFact.setText("");

(6)使用與步驟(3)相同的操作,為“退出”按鈕添加actionPerformed事件處理方法calcExit,并在該方法體中輸入語句“System.exit(0);”。

完成上述步驟后,運(yùn)行程序,輸入45,單擊“=”按鈕,顯示階乘值為119622220865480194561963161495657715064383733760000000000,顯然遠(yuǎn)遠(yuǎn)超出long類型范圍,程序能夠按照題意運(yùn)行。

循環(huán)語句的使用還有一種常見的情況是在一個(gè)循環(huán)語句內(nèi)包含另外一個(gè)循環(huán)語句,分別稱為外循環(huán)和內(nèi)循環(huán)語句,這種用法稱為循環(huán)的嵌套。甚至在內(nèi)循環(huán)語句中還可能包含一條循環(huán)語句,從而構(gòu)成多重嵌套循環(huán)結(jié)構(gòu)。本書后面章節(jié)將介紹嵌套循環(huán)的例子。

3.7.4 循環(huán)中的跳轉(zhuǎn)

循環(huán)中的跳轉(zhuǎn)是指在循環(huán)條件仍然成立時(shí)強(qiáng)制改變循環(huán)流程的操作,主要包括強(qiáng)制結(jié)束循環(huán)和強(qiáng)制開始下一次循環(huán)兩種情況。

1. break語句

Java語言中break語句除了用于switch語句之外,還可以用于do-while、for、while循環(huán)語句中使程序終止循環(huán)而執(zhí)行循環(huán)后面的語句。通常break語句總是與if語句連在一起,使循環(huán)在滿足特定條件時(shí)跳出循環(huán)。在循環(huán)語句中使用break語句的一般形式如下。

其執(zhí)行流程如圖3.19所示。

對于以下常見的計(jì)算自然數(shù)階乘的程序段,利用break語句可以在階乘值超出long類型范圍時(shí)直接結(jié)束計(jì)算階乘的for循環(huán)。

圖3.19 while循環(huán)體內(nèi)使用break語句的執(zhí)行流程框圖

Java語句還提供了一個(gè)帶標(biāo)號的break語句。Java程序中的標(biāo)號是一個(gè)后面加冒號“:”的標(biāo)識符,一般可以在循環(huán)語句、if語句或任何塊語句前面加標(biāo)號。當(dāng)執(zhí)行“break標(biāo)號;”語句時(shí),執(zhí)行流程直接跳轉(zhuǎn)到標(biāo)號語句塊之后。例如:

2. continue語句

continue語句的作用是跳過循環(huán)體中剩余的語句而強(qiáng)行執(zhí)行下一次循環(huán)。continue語句只用在for、while、do-while等循環(huán)體中,常與if條件語句一起使用以加速循環(huán)。在循環(huán)語句中使用break語句的一般形式如下。

其執(zhí)行過程如圖3.20所示。

圖3.20 while循環(huán)體內(nèi)使用continue語句的執(zhí)行流程框圖

continue語句也可以帶有標(biāo)號,使流程跳轉(zhuǎn)到標(biāo)號標(biāo)記的語句塊首部執(zhí)行。

例3.7 設(shè)計(jì)一個(gè)Java GUI程序,提供文本框讓用戶輸入一個(gè)正整數(shù)n,并將2~n的全部素?cái)?shù)顯示在文本區(qū)域。

分析:如果一個(gè)整數(shù)不能被除了1和它自身之外的任何整數(shù)整除,這個(gè)整數(shù)就是素?cái)?shù)。顯然,所有的偶數(shù)都不是素?cái)?shù);最小的素?cái)?shù)是2;對一個(gè)奇數(shù)m,如果除以3~m/2的任一整數(shù)的余數(shù)為0即不是素?cái)?shù),反之m即為素?cái)?shù)。

可以采用for循環(huán)對2~n的奇數(shù)逐一測試。for循環(huán)內(nèi)部嵌套一個(gè)循環(huán),測試當(dāng)前整數(shù)是否為素?cái)?shù)。

解:按照以下步驟操作。

(1)右擊chap03項(xiàng)目的“源包”|ifdemos包下的IfMaxDemo.java節(jié)點(diǎn),在快捷菜單中選擇“復(fù)制”菜單項(xiàng)。

(2)右擊book.loopdemos包名節(jié)點(diǎn),在快捷菜單中選擇“粘貼”|“重構(gòu)復(fù)制”菜單項(xiàng),新名稱輸入“LoopsDemo”,單擊“重構(gòu)”按鈕。修改窗體LoopsDemo的title屬性值為“素?cái)?shù)查找程序”。

(3)在設(shè)計(jì)視圖下刪除LoopsDemo窗體中的“最大整數(shù)”標(biāo)簽組件及文本字段jTextFieldMax組件;將“確定”和“清除”按鈕移到靠近窗體底邊框位置;修改jLabel1組件的text屬性值為“2——”、jLabel2組件的text屬性值為“所有素?cái)?shù):”。修改后的程序GUI如圖3.21所示。

圖3.21 查找素?cái)?shù)程序GUI設(shè)計(jì)視圖

(4)切換到“源”視圖,刪除LoopsDemo類中的“int maxNum=Integer.MIN_VALUE;”語句,刪除addNumber方法中的所有語句但保留方法首尾行,在該方法體中輸入以下語句。

(5)刪除clearNums方法中的后兩行語句。

     jTextFieldMax.setText("");
     maxNum = Integer.MIN_VALUE;

完成上述步驟后運(yùn)行程序,可以找出指定范圍內(nèi)的所有素?cái)?shù)(見圖3.22)。程序中對于2~n的偶數(shù),使用continue語句直接跳過(見程序代碼中的注①);對于某個(gè)數(shù)m,如果能被3~m/2的任意數(shù)整除,則使用break語句直接退出內(nèi)層循環(huán)體(見程序代碼中的注②)。

圖3.22 例3.7素?cái)?shù)查找程序運(yùn)行結(jié)果

3.7.5 遞歸方法

對于階乘計(jì)算問題,除了例3.6給出的迭代相乘方法外,還有遞歸求解方法。即n!可以分解為n×(n?1)!,即變?yōu)榍蠼?n?1)!問題;(n?1)! = (n?1)×(n?2)!,從而變?yōu)榍蠼?n?2)!的問題;以此類推,當(dāng)最終變?yōu)榍蠼?!的問題時(shí),可以直接得到結(jié)果1(見圖3.23)。然后逆序得到各步結(jié)果,即用1替換1!代入2×1!得到2!結(jié)果2;用2替換2!代入3×2!得到3!結(jié)果6;用6替換3!代入4×3!得到4!結(jié)果24;以此類推,直到用(n?1)!結(jié)果計(jì)算出n?。ㄒ妶D3.24)。

遞歸是一種非常有用的解題方法,其基本思路是:首先尋找一種方法將原問題分解為規(guī)模較小的同類問題,而這種分解方法又可以應(yīng)用于前面分解得到的較小問題上,從而將問題逐步降解為同類較小的問題。當(dāng)問題降解為最小規(guī)模或足夠小的規(guī)模時(shí),可以直接得到結(jié)果。第二階段,利用足夠小的規(guī)模問題的結(jié)果獲得較大規(guī)模問題的結(jié)果,進(jìn)一步得到更大規(guī)模問題的結(jié)果,直至得到原始問題的結(jié)果。

圖3.23 求解5!的遞歸過程

圖3.24 求解5!的逆推過程中間截圖

恰當(dāng)?shù)乩眠f歸方法可以降低某些問題的解決難度,同時(shí)也使解題思路更加清晰,程序邏輯更為簡單。例如,在JShell中可以輸入如圖3.25所示的Java程序片段實(shí)現(xiàn)圖3.23和圖3.24的遞歸算法。

圖3.25 JShell中n!計(jì)算程序片段

可以采用遞歸方法解決的問題必須符合以下三個(gè)條件。

(1)問題可以轉(zhuǎn)化為一個(gè)新問題,而新問題解決方法與原問題相同,且新問題為原問題的有規(guī)律遞增或遞減。

(2)可以通過轉(zhuǎn)化過程使問題得到解決。

(3)必須有一個(gè)明確的結(jié)束遞歸的條件,使問題在有限步內(nèi)得到解決。

事實(shí)上,某些問題只有使用遞歸方法才能設(shè)計(jì)計(jì)算機(jī)程序來解決。此外,Java語言利用遞歸方法解決的問題遞歸層數(shù)不能太多,否則會(huì)造成JVM出錯(cuò)。

主站蜘蛛池模板: 鹿泉市| 林周县| 九寨沟县| 沙雅县| 博野县| 黄大仙区| 额敏县| 五台县| 建阳市| 林甸县| 梁山县| 中宁县| 华安县| 凤城市| 息烽县| 香格里拉县| 邮箱| 桦川县| 镶黄旗| 昭平县| 贵南县| 东乌珠穆沁旗| 卓资县| 辉南县| 高邮市| 清流县| 萍乡市| 华安县| 襄城县| 信宜市| 牟定县| 礼泉县| 正宁县| 独山县| 张家界市| 商丘市| 砚山县| 项城市| 嫩江县| 乌海市| 习水县|