Java programing Java programing

Java 程式設計上課教材

Java 程式設計上課教材 > 課程內容 > 第 7 堂課 - 迴圈與迴圈條件及應用

第 7 堂課 - 迴圈與迴圈條件及應用

上次更新日期 2018/08/21

前一小節介紹了 switch 與 while 的簡易迴圈,並且使用了相當有趣的亂數功能。不過,其實迴圈有很多的條件需要設定, 此外,也可能需要透過跳出迴圈或者是離開單一迴圈,以及巢狀迴圈等特別的功能!接下來,讓我們來玩一玩這些迴圈的重要功能!

  • 7.0: 英打練習與上週作業執行
  • 7.1: while 迴圈控制: break, continue, System.exit(0)
  • 7.2: 格式化輸出
  • 7.3: do...while 迴圈
  • 7.4: for 迴圈與應用
  • 7.5: 巢狀迴圈
  • 7.6: 參考資料
  • 7.7: 課後練習

7.0: 英打練習與上週作業執行

資訊科技的技術,如果不是老闆,生產力人員 (就是你我) 通常不會只使用手持式裝置,至少會使用到各式鍵盤。因此,打字就非常重要, 好的打字速度帶你上天堂,差的打字速度讓你呆成糖 (蟲的台語,慢吞吞之意)!關於打字練習的重要注意事項請參考第 0 堂課的說明, 同時,請花 10 分鐘完成第 0 堂課的英文打字練習。完成之後,請打開你的 notepad++ 軟體,輸入底下的字串喔!

public class demo { ↵
	public static void main (String args[]) { ↵
		int i = 1; ↵
		while (true) { ↵
			System.out.println ("累加值: " + i++); ↵
			if ( i >= 1000 ) { ↵
				System.out.println ("次數超過 1000 次,不再進行了"); ↵
				break; ↵
			} ↵
		} ↵
		System.out.println ("順利完成迴圈運作囉!"); ↵
	} ↵
} ↵

你不需要複製貼上,這單純是要讓你的打字速度變快!所以不要太緊張!練習就對了!沒練習完畢?沒關係,有空繼續練習即可!

  • 上週作業的執行

請從 class.vbird.tw 上面下載你上週的作業 java 文字檔,然後先行編譯成功後,等待老師過來查閱是否編譯成功與執行成功! 若執行成功,在你的作業成績會打 v !

7.1: while 迴圈控制: break, continue, System.exit(0)

上一堂課我們知道了 while 的迴圈用法,主要的語法就是『 while (條件) {動作...} 』這樣的格式,因此,最重要的還是在條件的達成上! 但是,如果條件一直無法達成了!?那麼就可能會變成所謂的無窮迴圈了!什麼是無窮迴圈?看一下底下的案例大概就清楚了。

  1. 分析問題:讓程式一直輸出 1, 2, 3... 一直到無窮大。其實只需要類似累加的條件,就可以直接印出來。
  2. 解決步驟:
    1. 宣告類別名稱為 unit07_1_1 (就是檔名)
    2. 開啟方法為 main 的程式
    3. 宣告一個名為 i 的整數型態變數,而且初始值給他為 1 即可。
    4. 建立一個 while 的迴圈,條件則永遠是 true 即可。
      • 以類似 System.out.println ("累加值: " + i++ ); 這樣的模式來印出累加的數值即可。
    5. 關閉 main
    6. 關閉類別
  3. 程式設計:主程式的迴圈部份大概是這樣:
    int i = 1;
    while (true) {
    	System.out.println ("累加值: " + i++);
    }
    
  4. 編譯、執行與測試:開始執行『 java unit07_1_1 』之後,程式就會一直輸出數值!如果都不動他,那程式就運作到天荒地老! 或者是運作到整數值的最大值,產生程式溢位之後才結束吧!

當發生程式進入無窮迴圈之後,該程式就無法自動停止。如果產生這樣的情況,你要關閉該程式,只要按下『 [ctrl]+c 』這樣的組合按鍵,就可以將該程式中斷了! 這隻程式還沒有很嚴重的影響到系統,畢竟只是單純的輸出,所以不會太耗費系統資源。如果是某些大型運算程式,產生這種無窮迴圈時, 那整個系統的資源被瘋狂應用的情況下,最終可能導致系統產生高熱而當機....

  • 讓整個 JVM 中斷的指令: System.exit(0)

因此,為了避免無窮迴圈的發生,因此,程式設計師可以在迴圈裡面設計一些跳脫的條件~舉例來說,如上所示,當迴圈次數超過 1000 次的時候,就讓他停下來。 除了寫在 while 的條件內部,也能夠在迴圈時增加額外的限制!例如將主程式的迴圈內,增加這一個設計:

int i = 1;
while (true) {
	System.out.println ("累加值: " + i++);
	if ( i >= 1000 ) {
		System.out.println ("次數超過 1000 次,不再進行了");
		System.exit(0);
	}
}

重新編譯之後再進行『 java unit07_1_1 』的執行,你就會發現當 i 等於 1000 的時候,整隻程式就結束了。但是,如果你想要在迴圈結束後, 再增加一個說明說已經結束了,這個時候的程式就會有點像這樣:

int i = 1;
while (true) {
	System.out.println ("累加值: " + i++);
	if ( i >= 1000 ) {
		System.out.println ("次數超過 1000 次,不再進行了");
		System.exit(0);
	}
}
System.out.println ("順利完成迴圈運作囉!");  // 在這裡新增說明迴圈已經結束

不過這樣的條件底下進行編譯,卻會出現如下的錯誤:

unit07_1_1.java:11: error: unreachable statement
                System.out.println ("順利完成迴圈運作囉!");
                ^
1 error

看起來語法都沒有問題,而且出現的錯誤很有趣,中文的解釋是『error: unreachable statement ==> 錯誤,無法到達的狀態!』啥鬼!? 這是因為 System.exit(0) 執行之後,該程式碼會讓整個 JVM 結束,由於整個 JVM 會被中斷掉,任何其他程式碼將無法執行, 因此,在該程式段之後的程式,就不會被執行!因此該段程式就會被告知無法被執行啊~你要想想怎麼辦才行!

  • 讓程式只離開迴圈段落的方法: break

所以,在迴圈裡面使用 System.exit(?) 應該是不對的!那該如何是好呢?最簡單的方法,其實就是透過中斷迴圈的 break 來處理比較妥當!所以, 同樣將 unit07_1_1.java 再次修改成底下的模樣:

int i = 1;
while (true) {
	System.out.println ("累加值: " + i++);
	if ( i >= 1000 ) {
		System.out.println ("次數超過 1000 次,不再進行了");
		break;
	}
}
System.out.println ("順利完成迴圈運作囉!");  // 在這裡新增說明迴圈已經結束

再次執行你就會發現迴圈執行完畢後,還會有額外的資訊告訴你了!現在你就知道 break 的功能了吧!所以未來你的程式越寫越長, 而執行某些迴圈需要加上額外的判斷時,要離開迴圈請務必記得 break 這個程式碼!

  • 終極密碼

有個遊戲名稱叫做『終極密碼戰』,其實就是先讓主持人 (系統) 隨機寫一個 1~100 的數字,然後讓所有參與者去猜數字~ 每個人心中要假設有個數字,而且要避過該數字,最後不小心猜中數字的人,就是失敗者~當然,也可以反過來,讓所有使用者去猜數字, 猜到數字的人就是贏了!看大家的規則怎麼寫這樣。那如果我們要用這樣的狀態來撰寫一個可以讓大家猜密碼的程式, 該如何處理呢?

  1. 分析問題:重點是要讓 (1)系統隨機產生一個數字 (2)讓使用者一直猜數字,直到猜中才離開程式!
  2. 解決步驟:因為要讓用戶填寫數值,所以當然就得要有互動!然後,使用者會猜幾次不知道,所以當然需要用迴圈來讓用戶一直輸入。此時,就得要加入條件判斷了!
    1. 使用終端機輸入,因此需要匯入 java.util.Scanner 套件
    2. 宣告類別名稱為 unit07_1_2 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 建立名為 input 的 Scanner 物件
    5. 先讓系統使用 Math.random() 的方式,隨機產生一個 1~100 間的數字 (參考前一章喔!),假設變數名稱為 syspass 且為整數型態
    6. 開始要讓使用者猜測密碼,使用者的密碼變數為 mypass,此時得要進入迴圈,且當 mypass != syspass 時,就會一直進行迴圈
      • 顯示讓使用者輸入密碼的字眼
      • 透過 nextInt() 取得使用者的密碼,並且分配為 mypass
      • 判斷 mypass 是否等於 syspass,若比較小,則提示『再增加一些』,若比較大則提示『再減少一些』, 若相等,就顯示『你猜對啦!』,並且離開迴圈
    7. 透過 System.out.println 顯示遊戲結束
    8. 關閉 main
    9. 關閉類別
  3. 程式設計:主要迴圈部份的設計會像這樣:
    while (syspass != mypass) {
    	System.out.print ("請輸入 0 ~ 100 的密碼數值: " );
    	mypass = input.nextInt();
    	if ( mypass < syspass ) {
    		System.out.println ("再增加一些");
    	} else if ( mypass > syspass ) {
    		System.out.println ("再減少一些");
    	} else {
    		System.out.println ("答對啦!就是這個數字啦!");
    		break;
    	}
    }
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit07_1_2
    請輸入 0 ~ 100 的密碼數值: 50
    再增加一些
    請輸入 0 ~ 100 的密碼數值: 70
    再減少一些
    請輸入 0 ~ 100 的密碼數值: 60
    再減少一些
    請輸入 0 ~ 100 的密碼數值: 55
    再減少一些
    請輸入 0 ~ 100 的密碼數值: 53
    再減少一些
    請輸入 0 ~ 100 的密碼數值: 51
    答對啦!就是這個數字啦!
    遊戲結束囉!
    

如果兩個以上的使用者,輪流去猜測這個密碼,就可以產生很多很好玩的遊戲了!

  • 使用 continue 讓程式運作速度加快

某些時刻,你可能會根據在迴圈內的判斷式來處理工作,但有的時候明明第一個工作已經判定不用繼續進行,那麼後續的工作就應該不要進行! 這樣的流程應該不是 break,因為 break 是跳離整個迴圈,而我們目前『只是想要跳出目前這次的迴圈』,『這次與整個』的差別喔! 那就使用『 continue 』吧!基本上, continue 與 break 的流程圖有點像這樣:

break 的流程 continue 的流程

上圖左側是 break 流程,右側是 continue 流程。在 break 的流程中,你會發現,break 將跳離整個迴圈,也就是說,如果迴圈後面還有其他的數值, 那就不會繼續運作。而 continue 則是跳脫該次迴圈運作,因此只有後續的『迴圈內任務-...』被略過一次,然後繼續下一個迴圈數值的運作。 一般剛剛開始學程式設計的朋友,可能不會使用 continue 的行為,如果是這樣,那麼原本不用進行的『迴圈內任務-...』就會被持續執行, 此時就會浪費些許資源。若使用 continue,就可以處理掉這個問題。

我們來讓使用者輸入一個數值,例如 100 好了。然後計算 1 到 100 之間的數值,若數值是 7 的倍數或尾數為 7 的時候,就顯示一個小老鼠符號 (@), 否則就將該數值在螢幕上面顯示出來!那該如何進行。這個在團康的時候很長玩!只是團康的時候常用的分母為 3 ,這裡我們用 7 來處理而已。

  1. 分析問題:倍數為 7 或尾數為 7 可以使用 N % 7 以及 N % 10 來確認餘數為 0 或為 7 即可
  2. 解決步驟:
    1. 需要匯入 java.util.Scanner 物件,讓使用者可以輸入數值
    2. 宣告類別名稱為 unit07_1_3 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 讓使用者輸入一個數值,並說明最好能夠大於 50 較佳。
    5. 開始進入迴圈,設定一個 i 整數型態數值,且透過 i++ 來每次累加 1,直到 i 大於使用者輸入值就結束
      • 判斷 i 是否能被 7 整除,或者是被 10 整除餘數為 7,若是,則 (1)輸出 @ 且 (2)透過 continue 進到下個數值
      • 印出此數值
    6. 透過 System.out.println 說明遊戲結束
    7. 關閉 main
    8. 關閉類別
  3. 程式設計:
    int i = 0;
    while ( i <= usernu ){
    	i++;
    	if ( i%7 == 0 || i%10 == 7) {
    		System.out.print ("@, ");
    		continue;
    	}
    	System.out.print (i + ", ");
    }
    System.out.println ("遊戲結束囉!");
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit07_1_3
    請輸入一個數值,最好大於 50 較佳。我會取消 7 的倍數或尾數為 7 的數值: 50
    1, 2, 3, 4, 5, 6, @, 8, 9, 10, 11, 12, 13, @, 15, 16, @, 18, 19, 20, @, 22, 23,
    24, 25, 26, @, @, 29, 30, 31, 32, 33, 34, @, 36, @, 38, 39, 40, 41, @, 43, 44, 4
    5, 46, @, 48, @, 50, 51, 遊戲結束囉!
    

上面的練習當然是比較無俚頭的~基本上,使用 if ... else 也能達到上述的作法!只是這邊提供比較多的思考!

在 unit07_1_2.java 的終極密碼戰猜測密碼的遊戲中,那個輸出的資訊每次都是 0~100 的數值,有點蠢。你能不能增加兩個變數,一個是 minpass 一個是 maxpass, 一開始的 minpass 是 1 而 maxpass 是 100。透過與使用者輸入的數值做比對,隨時更改 minpass 或 maxpass,取代掉『增加一些或減少一些』的字樣, 那比較符合我們所需要的遊戲界面。最終執行『 java unit07_1_2_new 』時,就可以顯示類似下列的遊戲流程:
C:\Users\dic\java>java unit07_1_2_new
請輸入 1 ~ 100 的密碼數值: 50
請輸入 51 ~ 100 的密碼數值: 70
請輸入 71 ~ 100 的密碼數值: 90
請輸入 71 ~ 89 的密碼數值: 80
請輸入 81 ~ 89 的密碼數值: 85
請輸入 81 ~ 84 的密碼數值: 83
請輸入 81 ~ 82 的密碼數值: 82
答對啦!就是這個數字啦!
遊戲結束囉!

7.2: 格式化輸出

回到剛剛上一小節的最後一個課堂練習,我們發現到整個遊戲輸出的資訊有點難看,就是沒有排列整齊!有點醜醜的。如果需要排列整齊的時候, 就需要所謂的『格式化輸出』 (format) 的動作了。一般來說,所謂的格式化輸出,就是透過一些我們所制定的格式,來讓整個輸出訊息排列較整齊的情況。 而我們常見的需要的格式化輸出有『字串數』、『整數長度』、『浮點數長度』等而已,其他的就先不予理會,底下的表格就是常見的格式了!

格式撰寫法輸出資料範例
%d十進位的整數值%4d --> " 200" (前面補空白)
%f具有小數的浮點數%5.2f --> " 3.14" (共 5 個數值,小數點下 2 個位數)
%s字串長度%10s --> " I'm VBird" (前面補空白)

至於處理的方案中,以『請輸入 1 ~ 100 的密碼數值: 』這樣的格式來說,基本上原始的程式碼是這樣設計的:

System.out.print ("請輸入 " + minpass + " ~ " + maxpass + " 的密碼數值: " );

用 + 號連結 5 個資料,而資料有字串、整數、字串、整數、字串這樣。那當我們要讓兩個整數使用 3 個位數來處理時,整個程式碼就應該要改成這個模樣:

System.out.printf ("%s%3d%s%3d%s", "請輸入 ", minpass, " ~ ", maxpass, " 的密碼數值: " );

也就是說,用 System.out.printf 的那個 printf 來取代原本的 print 或 println,然後整個括號內的第一個項目先設定格式, 之後看有幾個百分比符號 (%) 就需要外接幾個參數的意思。請將 unit07_1_2_new.java 另存新檔成為 unit07_2_1.java,並且修改上述的程式段, 重新編譯並執行 『java unit07_2_1』,就會出現類似底下的運作流程:

請輸入   1 ~ 100 的密碼數值: 50
請輸入   1 ~  49 的密碼數值: 25
請輸入  26 ~  49 的密碼數值: 37
請輸入  38 ~  49 的密碼數值: 45
請輸入  46 ~  49 的密碼數值: 48
答對啦!就是這個數字啦!
遊戲結束囉!

你會發現到整個輸出的訊息要漂亮的多了!這就是格式化輸出的優點了!那如果需要浮點數呢?舉例來說,整個浮點數的目的在算出 pi 的話, 我們知道 pi 的值應該是 3.14159... 這樣的數值,假設我要讓程式輸出 10 個數值,且整數為 2 位數,小數點以下則具有 8 位數,因此就會使用:『 %10.8f 』這樣的格式! 那個 10.8 的意思是,共有 10 個數字,其中 8 個數字是小數點以下位數的意思。

讓我們回到第二章的 2.7 小節,裡面有談到需要使用程式來計算 pi 值,簡單的計算式是:

pi = 4 x (1/1-1/3+1/5-1/7+1/9-1/11+1/13) = 計算的結果

當時我們只用到 13 ,因為還不會使用迴圈的緣故。現在,讓我們使用迴圈來計算這個 pi 值~因為分子都是 1 所以就不理他,至於分母呢,第一個是 2*1-1,第二個是 2*2-1,第三個是 2*3-1,第四個 2*4-1,所以總結在迴圈裡面就會變成『 2*i-1 』這樣的模式。然後還得要注意,i 是奇數時,要乘上 1,而 i 是偶數時,就得乘上 -1 才行。 所以,如果 pi 是在迴圈裡面的話,那麼上述計算式小括號裡面的資料就會變成

double i = 0;
double cross = 1;
double pi1 = 0;
while ( i <= 1000 ) {
	i++;
	if ( i % 2 == 1 ) {
		cross = 1;
	} else {
		cross = -1;
	}
	pi1 = pi1 + 1.0/(2.0*i-1)*cross;
}
double pi = 4 * pi1;

那最後我要輸出的訊息是『 3.14159... 』共 10 個數字時,該如何撰寫最後面的輸出碼?請自行處理,最終輸出的結果會有點像這樣:

C:\Users\dic\java>java unit07_2_2
PI = 3.14159365
上面的程式計算 pi 是有點蠢,因為 i <= 1000 實在很小,測試一下,至少也要 100000 才會比較準確。但是這個數值也太大了些! 所以,我們打算使用『 Math.pow(10,N) 』來處理,亦即當你輸入 8 時,其實是 108 這樣的數字,在使用上會比較方便。 同時,輸出的訊息中,位數也請使用上述的位數來處理! (你可以使用 vbformat = "%s%N+2.Nf" 之類的方式來處置, 然後將 vbformat 這個變數帶入到 System.out.printf 裡面來處理看看)。最終執行的結果會有點像這樣:
C:\Users\dic\java>java unit07_2_3
你要計算 pi 到幾位數 (ex> 6): 6
PI = 3.141594
C:\Users\dic\java>java unit07_2_3
你要計算 pi 到幾位數 (ex> 6): 7
PI = 3.1415928

上面的練習題確實還挺有趣的!大家可以測試一下!同時理解一下格式化輸出與迴圈呢!不過,不要輸入超過 8 (最大就到 8 吧),否則程式會算很久!

7.3: do...while 迴圈

除了單純的 while 迴圈之外,還有另外一種有趣的也跟 while 有關的迴圈,就是 do 的迴圈。他的語法是這樣的:

do {
	迴圈內的動作;
	...
} while (條件判斷);

你會發現 while 變成在區塊 ({}) 的後面了!取而代之的是 do 這個關鍵字。其實 do 與 while 迴圈幾乎是一模一樣! 差別在於『 do 的迴圈一定會進行過一次,然後再帶入 while 進行判斷 』的情況!如果單純是 while 的話,那麼會先進行條件判斷, 如果條件判斷沒有成功,則該迴圈就不會進行!這樣理解差異嘛?

請將 unit07_1_3.java 另存新檔為 unit07_3_1.java,請將 7 改成 3 之外,並且使用 do 迴圈來取代 while 迴圈。基本上,迴圈的主程式大概會變成這樣:

int i = 0;
do {
	i++;
	if ( i%3 == 0 || i%10 == 3) {
		System.out.print ("@, ");
		continue;
	}
	System.out.print (i + ", ");
} while ( i <= usernu );

就是位置的變化而已。基本上,你只要記得 while 應該就很夠用了!

7.4: for 迴圈與應用

只要是 while 方面的迴圈,如果涉及到固定的數值變化,就得要加上控制碼才行。同時, while 迴圈也能夠進行不同的條件判斷。那如果如同上面的許多練習, 大部分的迴圈就是數值的變化而已呢?我們如果想要設計固定數值的迴圈次數,似乎使用底下的 for 迴圈,會比 while 迴圈更方便設計也方便閱讀。

for (初始條件; 運算條件; 增值) {
	迴圈內的動作;
	...
}

現在,讓我們將 unit07_3_1.java 另存新檔為 unit07_4_1.java,然後將 do 迴圈以 for 迴圈來處理,主程式的部份會有點像這樣:

for (i=1; i<=usernu; i++) {
	if ( i%3 == 0 || i%10 == 3) {
		System.out.print ("@, ");
		continue;
	}
	System.out.print (i + ", ");
} 

你不用在程式碼裡面使用 i++ 做判斷,直接在 for 迴圈裡面進行增值的設計即可!至於判斷式則是兩個分號中間,也就是『 i <= usernu 』那一個設計! 這樣的程式碼看起來當然舒服很多!

  • 計算累加值

有時候我們需要計算累計值,例如 1+2+3+...+1000,假如不是透過快速計算式,而是直接透過真的加總來計算的話,那該如何使用 for 迴圈來處理?

  1. 分析問題:其實很簡單,就是類似 sum = sum + i,而 i 會持續長大這樣,最終印出 sum 就結束了
  2. 解決步驟:
    1. 匯入 java.util.Scanner,讓使用者輸入想要累加的數字
    2. 宣告類別名稱為 unit07_4_2 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 使用 for 迴圈,讓 i 初始值為 1 ,且運算條件為 i < 使用者輸入值,然後進入迴圈
      • 以 sum = sum + i 來累加數值囉
    5. 透過 System.out.println 來印出最終的結果
    6. 關閉 main
    7. 關閉類別
  3. 程式設計:
    int i = 0, sum = 0;
    for (i=1; i<=usernu; i++) {
    	sum = sum + i;
    } 
    System.out.println ("1+2+..+" + usernu + " = " + sum);
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit07_4_2
    請輸入一個整數,我會對他做累加: 101
    1+2+..+101 = 5151
    
與上面的練習類似的,請設定 unit07_4_3.java 的程式碼,讓使用者輸入數值後,最終輸出 1*2*3*...*N 的結果,亦即是 N 階層 (N!) 的結果! 最終計算的結果會有點像這樣:
C:\Users\dic\java>java unit07_4_3
請輸入一個整數,我會對他做乘暮: 10
1x2x3..x10 = 3628800

C:\Users\dic\java>java unit07_4_3
請輸入一個整數,我會對他做乘暮: 50
1x2x3..x50 = 0  <==為什麼會這樣?

C:\Users\dic\java>java unit07_4_3
請輸入一個整數,我會對他做乘暮: 30
1x2x3..x30 = 1409286144

上面的練習當中,思考一下,為什麼輸入 50 之後,系統會出現計算結果為 0 呢!在未來設計數值程式的時候,這點是相當重要的概念喔!

  • 持續計算 pi 值

除了 unit07_2_3.java 可以計算 pi 值之外,事實上還有另外一個可以收斂的 pi 值計算會比我們之前的計算值還要快速,如下圖所示,就是另外的一個計算式了:

計算 PI 值
  1. 分析問題:仔細分析一下上面的算式,我們會發現到分母是以 2 為一個單位去累加,然後,累加次數為奇數時,則乘上 +1,若為偶數則乘上 -1 這樣的情況。
  2. 解決步驟:
    1. 匯入 java.util.Scanner 這個套件,讓用戶來輸入一個位數
    2. 宣告類別名稱為 unit07_4_4 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 設定一個名為 input 的 Scanner 物物件。
    5. 顯示一個『請輸入一個位數』的單純顯示,然後讓使用者透過 input.nextDouble() 來取得一個位數值
    6. 給予 i, cross, pi 的宣告與初始值,其中 pi 初始值請設定為 3 (請看上面的算式)
    7. 開始進入迴圈,以 i 為變數,且起始值為 2 ,當 i <= Math.pow(10,位數值) 就持續運算,步階為 i=i+2,開始迴圈
      • 處理 cross 的數值,當 (int)(i/2)%2 == 1 成立,則 cross 為 1,否則則為 -1
      • 計算 pi = pi + 4/(i * (i+1) * (i+2)) * cross 這樣的數值 (就是上方的 pi 計算)
    8. 透過 System.out.println 將 pi 的值輸出
    9. 關閉 main
    10. 關閉類別
  3. 程式設計:主要的 for 迴圈大致上有點像這樣:
    for (i=2; i<=Math.pow(10,usernu); i=i+2) {
    	if ( (int)(i/2) % 2 == 1 ) cross = 1; else cross = -1;
    	pi = pi + 4 / (i * (i+1) * (i+2)) * cross;
    } 
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit07_4_4
    請輸入一個整數位數,我會計算 pi 的次數 (ex> 7): 3
    pi = 3.141592651601756
    
    C:\Users\dic\java>java unit07_4_4
    請輸入一個整數位數,我會計算 pi 的次數 (ex> 7): 5
    pi = 3.141592653589785
    
    C:\Users\dic\java>java unit07_4_4
    請輸入一個整數位數,我會計算 pi 的次數 (ex> 7): 7
    pi = 3.141592653589787
    

我們可以很輕鬆的就得到很正確的數值!帶入 3 次方就已經得到小數點下 7 位數的正確值了!相當快速!

7.5: 巢狀迴圈

過去我們曾經算過九九乘法表的單一數值,就是在 unit04_2_1.java 程式碼裡面設計的,讓使用者輸入一個 (2~9) 的值,然後我們將該值帶入 1~9 的乘積, 就得到該數值的乘法表。好笨~現在可以使用到迴圈了,當然很方便的就能夠計算出九九乘法表!只是,因為有兩個變數,因此,我們需要寫兩層迴圈, 假設九九乘法表是 X*Y=Z 的話, X 是一層迴圈,Y 則是包含在 X 內的另一層迴圈。由於有兩層包在一起的迴圈,因此就成為巢狀迴圈。若以 for 迴圈為例, 語法就會有點像這樣:

for (x=1; x<=9; x++) {
	for (y=1; y<=9; y++ ) {
		System.out.println (x + " x " + y + " = " + x*y );
	}
}
請撰寫一隻名為 unit07_5_1.java 的程式,將上述的程式碼帶入,編譯後執行,看看執行的成果。
  • 繪製 9x9 乘法表 (使用兩種格式)

上面的 unit07_5_1.java 執行成果有夠爛,竟然是一條長串輸出,這樣實在很難查閱乘法表的結果啊!假如我們需要的是如下的輸出狀態,那應該如何撰寫呢?

1x1= 1  2x1= 2  3x1= 3  4x1= 4  5x1= 5  6x1= 6  7x1= 7  8x1= 8  9x1= 9
1x2= 2  2x2= 4  3x2= 6  4x2= 8  5x2=10  6x2=12  7x2=14  8x2=16  9x2=18
....
1x9= 9  2x9=18  3x9=27  4x9=36  5x9=45  6x9=54  7x9=63  8x9=72  9x9=81
  1. 分析問題:如果要達成上述的輸出,我們得要知道『終端機的輸出,主要是從左而右,從上而下』這樣的格式! 所以,從上面的分析來看,第一橫列變化的是 XxY,就是那個 X 會變化!因此,若以之前 unit07_5_1.java 拿來修改的話, X 與 Y 的結構需要調整, 讓 Y 在外層而 X 在內層才可以。此外,不能用 System.out.println 了,得要使用 System.out.printf 搭配格式來處理才行!
  2. 解決步驟:
    1. 宣告類別名稱為 unit07_5_2 (就是檔名)
    2. 開啟方法為 main 的程式
    3. 進入第一層 for 迴圈, y 的設計
      • 進入第二層 for 迴圈, x 的設計
        • 開始處理輸出,包括資料輸出與乘法輸出
      • 記得要斷行
    4. 關閉 main
    5. 關閉類別
  3. 程式設計:兩層迴圈其實很簡單,但是格式化輸出比較需要注意!會有點像這樣:
    System.out.printf ("%1d%1s%1d%1s%2d%2s", x, "x", y, "=", x*y,"" );
    
  4. 編譯、執行與測試:開始測試執行流程。

上面是將所有的算式都列出來,如果想要簡化整個畫面的話,還有底下這種也算很常見的輸出組合:

   |   1   2   3   4   5   6   7   8   9  <==這一行 Y=0,結果輸出的是 X 的值
----------------------------------------  <==在 y=1 時,額外增加一個減號的輸出迴圈!
  1|   1   2   3   4   5   6   7   8   9  <==最左邊數值為 Y,右邊 9 個值為 XxY
  2|   2   4   6   8  10  12  14  16  18  <==同上,就一直處理 9 次就對了!
  3|   3   6   9  12  15  18  21  24  27
  4|   4   8  12  16  20  24  28  32  36
  5|   5  10  15  20  25  30  35  40  45
  6|   6  12  18  24  30  36  42  48  54
  7|   7  14  21  28  35  42  49  56  63
  8|   8  16  24  32  40  48  56  64  72
  9|   9  18  27  36  45  54  63  72  81

如果想要達成上述的輸出,可能還得要加上許多技術。同樣的,我們以 XxY 的角度來思考,當 X 為 0 的時候,就得要輸出 Y 的值,然後非為 0 的時候, 才是輸出 XxY 的值。至於當 Y 為 0 的時候,整個輸出的值則是 X 喔!所以整個迴圈可能會變成底下的模樣:

for (y=0; y<=9; y++) {
	if ( y == 1 ) {  // 第二行輸出減號,美化整個輸出畫面而已
		for ( z=1; z<=40;z++ ) {
			System.out.print ("-");
		}
		System.out.print ("\n");
	}
	for (x=0; x<=9; x++ ) {  // 這裡才是每一行的數值輸出
		if ( y == 0 && x == 0 ) {        // 第一行的第一個輸出
			System.out.printf ("%3s%1s", "","|");
		} else if ( y == 0 && x != 0 ) { // 第一行的後面 9 個輸出
			System.out.printf ("%4d", x);
		} else if ( y != 0 && x == 0 ) { // 第三行以後的第一個輸出
			System.out.printf ("%3d%1s", y,"|");
		} else {                         // 第三行以後的 9 個輸出
			System.out.printf ("%4d", x*y);
		}
	}
	System.out.print ("\n");
}

上述的資料如果會自己分析又看得懂,那就太棒啦!

  • 用 * 繪製聖誕樹

還記得以前剛剛碰觸程式時,程式能夠在終端機輸出一個聖誕樹,實在是讓人很開心啊!啥!?聖誕樹!對的,就如下所示, 醜醜一顆感覺像是樹的東西而已。不過,你能夠使用程式繪製出這樣的圖示嘛?

         *
        ***
       *****
      *******
     *********
    ***********
        ***
        ***
        ***
(看最大你可以作到多大!)

不過,在完成這個東西之前,我們先來測試比較簡單的東西。假設你要讓使用者輸入 X 與 Y 軸,然後透過以雙層迴圈的行為, 輸出一個矩形的星號圖示,該如何處理?

  1. 分析問題:就如同前面說的:『終端機的輸出是由左而右,由上而下』的情形,因此,會先輸出 X 軸,再由 Y 軸持續輸出 X 。
  2. 解決步驟:
    1. 因為要讓使用者輸入資料,所以匯入了 java.util.Scanner 物件
    2. 宣告類別名稱為 unit07_5_4 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 讓使用者輸入兩個整數型態的變數,分別設定為 xx 與 yy 好了。
    5. 開始 進入 for 的迴圈,以 y 的初始值 1 運算條件在 y <= yy 且步階為 1 的情況下
      • 開始進入 for 的迴圈,子 x 的初始值 1,運算條件 <== xx 且步階為 1
        • 每次只輸出一個 * ,且不會斷行
      • 執行一個斷行功能
    6. 關閉 main
    7. 關閉類別
  3. 程式設計:太簡單了!所以請自己寫
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit07_5_4
    請以空白隔開,分別輸入 X 與 Y 需要的 * 個數: 10 5
    **********
    **********
    **********
    **********
    **********
    
承上,寫一個可以繪製出類似底下的圖樣的直角三角形,讓使用者輸入一個數值來處理喔!
C:\Users\dic\java>java unit07_5_5
請輸入一個邊長,我來設計出直角三角形: 10
*
**
***
****
*****
******
*******
********
*********
**********
課後有一題要畫出倒三角形的,請仔細玩一玩!

現在讓我們來完成簡單聖誕樹吧!由聖誕樹的個數來思考,第一行 1 個,第二行 3 個,第三行 5 個,因此個數會是 2n-1 個,n 代表行數。 而總共有六行,所以最大有 2*6-1=11 個星星,所以中心點應該是在第 6 個位置上。而我們從『終端機的輸出是由左而右、由上而下』的型態來看, 所以結果是:

  • 第一行先輸出 5 個空白,然後輸出 1 個 *;
  • 第二行先輸出 4 個空白,然後輸出 3 個 *;
  • 第三行先輸出 3 個空白,然後輸出 5 個 *;
  • ....
  • 第六行先輸出『 6-行數 』個空白,然後輸出『 2x行數-1 』 個 *;

這樣就簡單了!我們就可以設計 unit07_5_6.java 的前半部,也就是樹葉的部份,大致上的程式碼會有點像這樣:

for (i=1;i<=6;i++ ) {                  // 這裡說的是行數
	for ( j=1; j<=6-i; j++ ) {     // 這裡則是空白的處理
		System.out.print(" ");
	}
	for ( k=1; k<=2*i-1; k++ ){    // 這裡是星號的處理
		System.out.print ("*");
	}
	System.out.println("");
}

接著下來則是樹幹的部份,樹幹主要給 3 個星星,這是固定的!然後,當然要是中心點 (6) 再少一個點,然後給予 3 個星星即可! 也就是說,空白的部份會到 6-2 的意思。然後我們給個 5 個高度好了!所以程式碼會有點像這樣:

for (i=1;i<=5;i++){                  // 意思是有 5 行的樹幹星星
	for (j=1;j<=6-2;j++){        // 中心點往左 2 格是空白
		System.out.print(" "); 
	}
	System.out.println("***");      // 印出三顆星星
}

最後將兩個資料組合在一起,執行後的結果就會變成這樣了:

C:\Users\dic\java>java unit07_5_6
     *
    ***
   *****
  *******
 *********
***********
    ***
    ***
    ***
    ***
    ***

課後有一題要處理讓使用者輸入樹葉的行數數量來畫圖的,請仔細玩一玩!

  • 計算某範圍內的質數有幾個

程式還可以拿來幫我們計算質數。所謂的『質數』指的是在整數的範圍內,只有 1 與本身才能整除。wiki 的說明是 『指在大於1的自然數中,除了1和該數自身外,無法被其他自然數整除的數』。那麼我們怎麼計算質數呢?可以使用所謂的『試除法』, 試除法的定義是:『給定一個合數n(這裡,n是待分解的正整數),試除法看成是用小於等於 n0.5 的每個質數去試除待分解的整數。如果找到一個數能夠整除除盡,這個數就是待分解整數的因子。

最簡單的說明就是,假設你要知道 n 是不是質數,那就是用 1, 2, 3, 4... 一直到 n0.5 去除 n, 若可以整除,那就代表不是質數,如果無法整除,那就代表 n 是質數。那你能不能知道 1~1000 之內有多少質數呢? 這就使用計算的方法來處理了。

  1. 分析問題:要找出一個範圍內的質數,一定要從 2 開始,因為 1 一定能整除啊!假設數值到 11 的時候,那麼 110.5 是 3.32,因此取 3 來進行除法, 11/2, 11/3 都無法整除,因此 11 就是質數。
  2. 解決步驟:
    1. 匯入 java.util.Scanner 套件
    2. 宣告類別名稱為 unit07_5_7 (就是檔名)
    3. 開啟方法為 main 的程式
    4. 建立名為 input 的物件
    5. 讓使用者輸入一個數值來進行質數的計算,假設為 xx 這個整式型態的變數名稱
    6. 進入 for 迴圈,x 初始值為 2,運算值 x <== xx,步階為 1
      • 設定 vbyes 為 1 ,亦即假設預設為質數之意
      • 計算最大除數,亦即 (int)Math.pow(x,0.5) 這個數值,設定為 yy 這個整數型態變數
      • 當 yy 大於等於 2 的時候,才開始後續動作
        • 開始 for 迴圈, y 初始值為 2,運算值為 yy,步階為 1
          • 如果 x 可以被 y 整除,就設定 vbyes = 0,且立刻 break 掉迴圈
      • 判斷 vbyes 是否為 1,若為 1 就顯示 x 的數值,否則就略過,繼續下個計算。
    7. 關閉 main
    8. 關閉類別
  3. 程式設計:主要的迴圈部份設計如下
    for ( x = 2; x <= xx; x++ ) {
    	vbyes = 1;
    	yy = (int)Math.pow(x,0.5);
    	if ( yy >= 2 ) {
    		for ( y = 2; y <= yy; y++ ) {
    			if ( x % y == 0 ) {
    				vbyes = 0;
    				break;
    			}
    		}
    	}
    	if ( vbyes == 1 ) System.out.print ( x + ", ");
    }
    
  4. 編譯、執行與測試:開始測試執行流程。
    C:\Users\dic\java>java unit07_5_7
    一個數值範圍,我將計算該範圍內的質數: 100
    2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73,
    79, 83, 89, 97,
    

基本上這個試除法就有很多可用的概念~包括那個 break 的功能可以跳脫內層迴圈~是相當有用的程式設計概念!

  • 計算最大公約數

我們說, 24 與 18 的最大公約數是 6,因為 24 = 6*4 而 18 = 6*3,所以公約數有 2, 3, 6,最大那個當然就是 6 囉。 那怎麼從程式裡面找出公約數呢?首先,我們來找一下因數好了!讓使用者輸入一個數,然後我們來找出每一個因數。那麼什麼是因數呢?很簡單, 就是可以將被除數整除的除數,就是囉!我們也知道,除數是從 N/2 開始往下到 2 來計算,假設你輸入的數值為 24 好了,那麼因數當然是從 12 (24/2) 開始求, 因為大於 12 的數值,將無法整除被除數!所以,現在讓我們來找出使用者輸入的數值的因數:

for ( i = 2; i <= n/2; i++ ) {
		if ( n % i == 0 ) System.out.println (i);
}

那如果要反向輸出呢?就將第一行改成『 for (i=n/2; i>=2; i--) 』即可!現在,讓使用者輸入兩個數,第一個數由大到小找出所有的因數,然後, 再用因數去除以第二個數,若第二個數可以被整除,那就是最大公約數了!最重要的是迴圈加上選擇而已!記得要透過 break 離開迴圈!

C:\Users\dic\java>java unit_07_5_9
用空白隔開,輸入 2 個數值,我來找出最大公約數: 24 18
最大公約數為: 6

7.6: 參考資料

7.7: 課後練習

先查閱 1.3 小節的介紹,了解雲端系統的登入、繳交資料檔名等等的設計,然後再繼續底下的習題。 最終要上傳的檔案有:

  • unit07-4070CXXX-你的名字.docx
  • unit07_toone.java (純文字檔)
  • unit07_fact.java (純文字檔)
  • unit07_triangle.java (純文字檔)
  • unit07_tree.java (純文字檔)
  1. 完成上課的習題內容回答:
    1. 在迴圈的中斷使用中,System.exit(0) 及 break 的差別在哪裡?
    2. while () {...} 迴圈與 do {..} while (); 迴圈的差別在哪裡?
    3. 如何產生一個 1~100 之間的亂數密碼 (整數型態)?
    4. 我需要輸出 3.141592 這樣的數值格式,使用 System.out.printf 內的格式要求應該怎麼寫?
    5. 為什麼在 int 的數值型態下,使用 50! 會出現錯誤?該如何訂正這個問題?
    6. 一般來說,終端機的資料輸出方向為何?
  2. 讓使用者輸入一個整數,然後循環進行:
    • 當數值為偶數,就除以 2
    • 當數值為奇數,就給予『 *3+1 』
    持續計算到該值等於 1 ,並且告知共計算幾次得到這個結果。當執行『 java unit07_toone 』時,會出現如下資料:
    C:\Users\dic\java>java unit07_toone
    輸入一個數值,最後這個數值經過計算會變 1 : 10
    1: 5
    2: 16
    3: 8
    4: 4
    5: 2
    6: 1
    
  3. 寫一隻程式,執行程式後,他會算出 1/1!+1/2!+1/3!+...+1/n! 的結果,其中 n 為使用者輸入的資料。當執行『 java unit07_fact 』時,會出現如下的執行成果:
    C:\Users\dic\java>java unit07_fact
    輸入一個數值,我來計算 1/1!+1/2!+1/3!+...+1/n!: 10
    1.7182818011463847
    
  4. 模仿 unit07_5_5.java 的程式執行結果,只是輸出的資料為倒三角形,如下所示:
    C:\Users\dic\java>java unit07_triangle
    請輸入一個邊長,我來設計出直角三角形: 10
    **********
    *********
    ********
    *******
    ******
    *****
    ****
    ***
    **
    *
    
  5. 模仿 unit07_5_6.java 的程式執行結果,只是不要使用 6 行,而是讓使用者自己選擇行數,但是行數必須大於等於 6 且小於等於 30 , 若小於 6 就以 6 執行,若超過 30 就以 30 執行。當執行『 java unit07_tree 』時,會有點像底下的模樣:
    C:\Users\dic\java>java unit07_tree
    請輸入聖誕樹的大小 (6~30): 15
                  *
                 ***
                *****
               *******
              *********
             ***********
            *************
           ***************
          *****************
         *******************
        *********************
       ***********************
      *************************
     ***************************
    *****************************
                 ***
                 ***
                 ***
                 ***
                 ***