無痛入手 C++:基礎教學6 - 迴圈

更新於 2024/05/12閱讀時間約 16 分鐘

迴圈 (loop) 是用來重複執行相同或相似行為的語法。

for 迴圈

基本架構

for 是最常被使用的迴圈語法,基本的架構如下:

// single statement​
for (初始式​; 判斷式; 運算式)
陳述;

// multiple statements​
for (初始式; 判斷式; 運算式) {
陳述1;
陳述2;
陳述3;
...
}

電腦遇到 for 的時候,會執行下列的流程:
1. 執行一次初始式 (通常包含變數的宣告與初始化)。
2. 如果判斷式的結果為真就執行陳述,為假的話則跳出 for 迴圈並繼續向下執行。
3. 若判斷式的結果為真,在執行完所有陳述以後,執行一次運算式,然後回到 2.。

下面的程式會讀入一個整數 x,並重複列印相同的文字 x 次:

int x = 0;
cin >> x;
for (int i = 0; i < x; ++i)
cout << "This is a statement in for loop\n";

我們用這個例子說明剛剛提到的流程。假設使用者輸入 2,當電腦遇到上面的 for 時:
1. 執行 int i = 0; 注意: 因為 int i 式宣告在 for 迴圈的小括號內,因此只有在這個 for 迴圈內可以使用,在這個 for 以外使用該變數 i 是不合法的行為。
2. 檢查判斷式 i < x; 是否為真。因為 0 < 2,判斷式為真,所以電腦會執行 for 中的陳述: cout << "This is a statement in for loop\n";
3. 執行完陳述以後,電腦會執行一次 ++i,所以變數 i 的值會從 0 變成 1,接著電腦便會回到 2. 重新進行判斷。

按照上述的邏輯,電腦會在第三次檢查 i < x; 的時候發現結果為假: 2 < 2,於是跳出 for 迴圈。因此在這個例子中,cout << "This is a statement in for loop\n"; 會被執行兩次。

注意1: 將 int i 初始化為 1 並調整一下判斷式,如: for (int i = 1; i <= x; ++i),也可以得到相同的結果 (why?)。不過在程式的世界裡,我們習慣用 0 當作開始,而不是 1,原因之後有機會再分享。

注意2: 在這個例子中,++ii++ 會得到相同的結果,事實上也可以寫成 i = i + 1。重點在於將 i 的值增加 1,只要能做到這點就可以。

注意3: 假如使用者輸入負數或 0 的話,判斷式在一開始就為假,所以電腦連一次陳述都不會執行。


在陳述中使用 induction variable

在上述的例子當中 int i 稱為 induction variable (我不知道中文怎麼翻QQ)。可以在 for 的陳述中使用 induction variable 來做到更複雜的事情。

下面的程式會讀入一個整數,並印出所有小於或等於該數的正整數:

int x = 0;
cin >> x;
for (int i = 1; i <= x; ++i)
cout << i << '\n';

因為我們要印出正整數,所以將 int i 初始化為 1,其他的部分就跟上個例子是相同的概念。

也就是說,for 不僅能重複執行相同的陳述,我們還能使用 induction variable 在每次的迴圈 (iteration) 中給予陳述不同的數值。事實上,大部分的問題都需要在陳述中使用 induction variable。

int i 就跟宣告一般的變數一樣,只要符合命名規範,變數名稱可以隨便取。i 只是一個簡單的代稱用來表示 induction variable (應該啦,我猜的)。懶惰的程式設計師們也經常使用: j, k, n, m 來命名。


更多範例

為了幫助大家熟悉 for 的使用方式,以下是更多的範例。
1. 由小到大印出所有大於等於 0 且小於等於 x 的偶數:

for (int i = 0; i <= x; i += 2)
cout << i << '\n';
  1. 由小到大印出所有小於 x 且為 2 的次方數的整數:
for (int i = 1; i < x; i *= 2)
cout << i << '\n';
  1. 由小到大印出所有平方數小於 x 的正整數:
for (int i = 1; i * i < x; ++i)
cout << i << '\n';
  1. 由大到小印出所有小於 x 的正整數 i,且 x + i 為 3 的倍數:
for (int i = x - 1; i > 0; --i) {
// if statement
if ((x + i) % 3 == 0)
cout << i << '\n';
}


巢狀迴圈

for 本身也是陳述的一種,所以 for 的陳述也可以包含另一個 for,這個結構稱為巢狀 (跟巢狀條件判斷的概念是一樣的)。以下的程式會依序把兩個 for 迴圈的 induction variable 印出來,猜猜看會印出什麼樣的結果:

for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 5; ++j)
cout << i << " " << j << '\n';
}

在外層 for 的每次迴圈 (iteration) 中,內層 for 的 j 都會從 0 加到 4,接著就會回到外層 for 進行運算式 ++i 和條件判斷 i < 3,若結果為真的話就再執行一次內層的 for。依此類推,會印出 0~2 和 0~4 的所有排列組合:

0 0
0 1
0 2
0 3
0 4
1 0
1 1
1 2
1 3
1 4
2 0
2 1
2 2
2 3
2 4

注意: induction variable 就跟一般的變數一樣,差別在於宣告在 for 的小括號內的時候,只有這個 for 裡面的陳述能使用 (所以上述程式的內層 for 能夠使用上層 for 的 i)。如果不小心把內層 for 的 induction variable 也取名叫 i,就會把外層 for 的 i 覆蓋掉 (詳細說明請看下一篇文章)。

我們也可以利用外層 for 的 induction variable 來設定內層 for 的判斷式,舉例來說,以下的程式會用 * 印出邊長為 n 的直角三角形:

for (int i = 0; i < n; ++i) {
for (int j = 0; j <= i; ++j)
cout << '*';
cout << '\n';
}

上述程式的邏輯是這樣的:
1. 外層 for 的每次迴圈都會印出一列 *,所以在迴圈結束的時候 (第 4 行) 要換行。
2. 內層 for 會決定每一列要印幾個 *。因為第 i 列需要印出 i + 1 個 *,而且 i 介於 0~(n - 1),因此我們將 j 設定為 0~i,因為 i - 0 + 1 = i + 1。

以下的程式會用類似的邏輯,印出倒過來的直角三角形:

for (int i = 0; i < n; ++i) {
for (int j = n - 1; j >= i; --j)
cout << '*';
cout << '\n';
}

我們只修改了內層迴圈: 在這個例子中,第 i 列需要印出 n - i 個 *,而且 i 介於 0~(n-1),因此我們將 j 設定為 (n - 1)~i 的遞減形式,因為 (n - 1) - i + 1 = n - i。

觀察上面兩個直角三角形的例子,可以發現設計 for 迴圈的步驟大致是:
1. 依照問題設計每層 for 要做到的事情。
2. 將要做的事情定義成公式 (有必要的話可以使用 induction variable 和其他變數)。
3. 設計 for 的初始式、判斷式、運算式來滿足 2. 定義的公式。


continue 和 break

for 中可以使用 continuebreak 這兩個特殊的陳述。

continue:
電腦執行到 continue 後會忽略後面尚未執行的陳述,直接跳至下一個迴圈,重新進行條件判斷。下面的程式會印出所有介於 0 ~ n 之間的偶數,提供了兩種寫法做比較: 1. 利用條件判斷印出偶數和 2. 利用 continue 跳過奇數不印

// method 1
for (int i = 0; i <= n; ++i) {
if (i % 2 == 0)
cout << i << '\n';
}

// method 2
for (int i = 0; i <= n; ++i) {
if (i % 2 == 1)
continue;
cout << i << '\n';
}


break:
電腦執行到 break 後會立即跳出目前所在的 for。下面的程式讓使用者輸入 N 個數字,如果使用者輸入偶數,它會印出該數字然後停止執行:

// input 10 numbers
for (int i = 0; i < 10; ++i) {
int num;
cin >> num;
if (num % 2 == 0) {
cout << num << " is even!";
break;
}
}

假如沒有 break 的話,電腦會把後續使用者輸入的偶數全部印出來。
注意: 上述的 if 有超過一個陳述,所以要用大括號包起來,否則電腦會認為 break 在 if 外面,導致 for 在第一個迴圈就會因為 break 的關係跳出去。

如果使用在巢狀迴圈中的話,break 只會跳出目前所屬的 for。以下面的程式為例:

for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 10; ++j) {
cout << i << ' ' << j << '\n';
if (j == 3)
break;
}
}

內層 for 的 j 一旦加到 3 就會跳出去,因此會得到以下輸出:

0 0
0 1
0 2
0 3
1 0
1 1
1 2
1 3
2 0
2 1
2 2
2 3

左邊的數字是 i 的值 (0 ~ 2),右邊的則是 j 的值 (0 ~ 3)。


for 的變形

for 的初始式、判斷式、運算式其實可以是空的,如:

for (;;) {
cout << "infinite loop...\n";
}

這是一個合法的 for 迴圈 (只是沒什麼用)。 判斷式是空的話,電腦會當成是真,所以上述的 for 會無止盡的執行 cout << "infinite loop...\n";
下面的程式會請使用者持續輸入整數,直到使用者輸入奇數,程式會印出該數以及它是第幾個被輸入的數字,然後停止執行:

int number;
for (int i = 0;; ++i) {
cin >> number;
if (number % 2 == 0) {
cout << "Get an even number " << number << " at " << i + 1 << "th input.";
break;
}
}


有時候我們會故意把 for 的初始式和運算式放在小括號外。這可以用於當 induction variable 會在 for 外面被使用的時候,或是運算式很複雜,很難全部塞在小括號內的時候。

下面的程式會印出小於 x 且為 2 的次方數的整數中最大的數 (其實就是要找前面更多範例第 2 題中最後一個被印出來的數):

int i = 1;
// for with empty body
for (; i < x; i *= 2) {}
cout << i / 2 << '\n';

注意: 最後要把 i 除以 2,因為這時候的 i 剛因為條件判斷為假所以跳出迴圈,表示此時它是大於等於 x 的最小的 2 的次方數。也就是 i 比我們預期的還多乘了一次 2,所以要除以 2 把它的值修正回來。

以下的寫法也可以得到一樣的結果:

int number = 1;
for (int i = 1;; ++i) {
if (number * 2 > x)
break;
number *= 2;
}
cout << number << '\n';

它們的差異在於第一種寫法直接用 induction varibale 來計算答案,而第二種寫法則是單純拿 induction variable 來計數,另外又宣告了 number 來計算答案。

第一種寫法不只是比較簡潔而已,當 x 的值很大的時候,第一種寫法的效能 (電腦執行程式所需要花費的時間) 會遠遠好於第二種寫法
可以透過觀察發現,兩種寫法都會在所求的值達到一個上限後跳出迴圈,但第一種寫法的 induction variable 一次會乘以 2,而第二種則是一次加 1,從這邊就可以理解為何第一種寫法會快很多。

我們之後會談到更多關於程式效能的議題。

總結

  1. 迴圈 (loop) 是用來重複執行相同或相似行為的語法。其中 for 迴圈是最常被使用的,包含了初始式、判斷式、運算式,以及想要重複執行的陳述。
  2. 可以在陳述中使用 induction variable,使得陳述在每次的迴圈得到不一樣的結果。
  3. 可以在 for 迴圈中使用更多的 for,也可以使用 if、else if、else 等陳述。
  4. 可以利用 continue 直接進入下一個迴圈,或利用 break 跳出目前的 for。
  5. 初始式、判斷式、運算式不一定要放在 for 的小括號內。


習題

  1. 讓使用者輸入 N 個整數,印出這 N 個整數的和。
  2. 讓使用者輸入 N 個整數,印出這 N 個數當中最大的數。
  3. 讓使用者輸入 N 個整數,印出所有 3 和 5 的倍數。寫兩個版本: 一個使用 continue 一個不使用。
  4. 持續讓使用者輸入兩個整數: x 和 y,分別代表二維空間中 x 座標和 y 座標的位置,當使用者輸入一個和 (-3, 8) 距離超過 5 的點 (x, y) 時,印出該點的座標並結束執行。
  5. 讓使用者輸入兩個正整數 n 和 m,用 * 印出上底為 n 、下底為 m、高為 m - n + 1 的梯形。
  6. 讓使用者輸入兩個偶數 n 和 m,分別代表兩個正方形的邊長,用 * 印出較大的那個正方形,但中間要留一個小正方形面積大小的空白。
  7. 同 6. 且讓使用者額外輸入一個座標 (x, y),以大正方形的左上角作為原點,小正方形的左上角要位於使用者輸入的座標 (x, y)。若小正方形的任何一邊會超出大正方形的話,請使用者重新輸入一組新的座標。







avatar-img
3會員
14內容數
程式設計 & 電腦系統 & 系統軟體
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
鏟薯員的窩 的其他內容
<iostream> ​在之前的文章有提到過,<iostream> 是專門處理程式的輸入 (input) 以及輸出 (output) 的函式庫。輸入輸出的對象是以電腦作為主角: 輸入指的是「把資料給電腦」,輸出指的是「從電腦那邊取得資料」。 在這個系列的文章中,程式輸入指的都是從鍵盤輸入資料給電
電腦只做一件事情: 運算。 我們所看到的任何酷酷的應用: 不論是網頁動畫、遊戲特效、甚至是 AI 說的話,全部都 是由電腦的運算結果組合而成的。 首先我們來梳理一下各個名詞之間的關聯: 1. 運算分成兩個部分: 運算子 (運算的名稱,如: 加法) 和運算元 (運算的對象,如: 8)。運算就是對資
<iostream> ​在之前的文章有提到過,<iostream> 是專門處理程式的輸入 (input) 以及輸出 (output) 的函式庫。輸入輸出的對象是以電腦作為主角: 輸入指的是「把資料給電腦」,輸出指的是「從電腦那邊取得資料」。 在這個系列的文章中,程式輸入指的都是從鍵盤輸入資料給電
電腦只做一件事情: 運算。 我們所看到的任何酷酷的應用: 不論是網頁動畫、遊戲特效、甚至是 AI 說的話,全部都 是由電腦的運算結果組合而成的。 首先我們來梳理一下各個名詞之間的關聯: 1. 運算分成兩個部分: 運算子 (運算的名稱,如: 加法) 和運算元 (運算的對象,如: 8)。運算就是對資
你可能也想看
Google News 追蹤
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Grass 是由一家去中心化人工智慧領域的初創公司Wynd Network,在2023年6月所推出的去中心化網路資源共享平台。
期許未來的自己能幫助更多對英文恐懼或排斥的孩子看到更多這個世界裡頭的有趣與價值。為了達到這個目標,用心準備每一堂課,在課堂中好好陪伴每一位學生學習是現在的我能努力做好的事。
Thumbnail
論語裡孔子曾說過:「未知生,焉知死?」過了五十歲之後,我反而有一種「先知死,而後生」的體悟。 最近因為一些因素,對於「死亡」的歷程必須先行預習,所以連續看了兩部日本電影:「無痛離世」跟「山中靜夫的最後尊嚴」。同樣聚焦在癌症末期的主題,同樣超級寫實的演繹了病人在世間最後辛苦的時光,讓我可以先預習癌末
Thumbnail
透過Google Authenticator轉移帳戶的功能,可以一次性提取所有TOTP密鑰,加快整合TOTP到Bitwarden的過程。整合完成後,不論是輸入帳號密碼或是輸入TOTP認證碼,都只需要Bitwarden即可搞定,在安全性和方便性取得平衡。在方便性和安全性之間取得平衡,是資安永遠的課題。
Thumbnail
在另一次快速的截肢手術中,李斯頓雖然饒過病人的睪丸,卻意外切斷助理的兩根手指。後來病人與助理雙雙死於壞疽,而一名在旁觀看這場手術的人,看見李斯頓匆忙揮舞手術刀,刀子戳破了外套,還以為李斯頓被戳死,因此嚇得休克,一命嗚呼。在麻醉劑出現之前的年代,手術就是這麼危險。
Thumbnail
  為了未來就業穩定,目前打算以成為公股銀行行員的目標邁進!打算花半年至一年的時間考取Fit(金融基測),再慢慢考一些有的沒的法定考試(好痛苦...)   先說我報名證基會2023/7/7的紙筆考試,大概從2023/6/27開始準備,準備時長10天左右。但我不能說自己是非常認真準備的學生,主要原因是
Thumbnail
大綱: 1.股利的三大迷思 2.投資中第1~3級思考 3.大部分的人虧損的原因 4.兩個最有效率的投資策略 5.單筆投資與定期定額的差別。 6.長期投資成功的關鍵是什麼? 7.已實現和未實現報酬的差異。 8.股票賣出的三大理由。 9.投資必勝九大金律。 讀書心得分成兩篇,上篇的連結 本文PODCAS
Thumbnail
大綱: 1.適合誰閱讀 2.股市秤重機 3.風險管理的兩大誤區 4.長期投資的七大優勢 5.股利是雙面刃 【無痛致富】,去年10/26號上市。作者是佛里曼投資顧問團隊(Freeman Publications),總部位於英國倫敦,提供個人專業的投資理財顧問服務,將最複雜的投資策略以易於理解的語言傳達
Thumbnail
水晶洞功效: 水晶洞又稱作是風水石,裡面充滿漂亮的晶牙、水晶花,彼此能量互相震動凝聚磁場!能夠聚財納福、避邪擋煞、吉祥平安。且晶洞本身就是源源不絕的發電廠,可以幫其他水晶淨化、消磁。 迷你晶洞擺放在辦公桌: 在工作上有貴人幫助、專注辦公、防止小人陷害、被老闆讚許、加薪升官的機會! 迷你晶洞擺放在正財
Thumbnail
*合作聲明與警語: 本文係由國泰世華銀行邀稿。 證券服務係由國泰世華銀行辦理共同行銷證券經紀開戶業務,定期定額(股)服務由國泰綜合證券提供。   剛出社會的時候,很常在各種 Podcast 或 YouTube 甚至是在朋友間聊天,都會聽到各種市場動態、理財話題,像是:聯準會降息或是近期哪些科
Grass 是由一家去中心化人工智慧領域的初創公司Wynd Network,在2023年6月所推出的去中心化網路資源共享平台。
期許未來的自己能幫助更多對英文恐懼或排斥的孩子看到更多這個世界裡頭的有趣與價值。為了達到這個目標,用心準備每一堂課,在課堂中好好陪伴每一位學生學習是現在的我能努力做好的事。
Thumbnail
論語裡孔子曾說過:「未知生,焉知死?」過了五十歲之後,我反而有一種「先知死,而後生」的體悟。 最近因為一些因素,對於「死亡」的歷程必須先行預習,所以連續看了兩部日本電影:「無痛離世」跟「山中靜夫的最後尊嚴」。同樣聚焦在癌症末期的主題,同樣超級寫實的演繹了病人在世間最後辛苦的時光,讓我可以先預習癌末
Thumbnail
透過Google Authenticator轉移帳戶的功能,可以一次性提取所有TOTP密鑰,加快整合TOTP到Bitwarden的過程。整合完成後,不論是輸入帳號密碼或是輸入TOTP認證碼,都只需要Bitwarden即可搞定,在安全性和方便性取得平衡。在方便性和安全性之間取得平衡,是資安永遠的課題。
Thumbnail
在另一次快速的截肢手術中,李斯頓雖然饒過病人的睪丸,卻意外切斷助理的兩根手指。後來病人與助理雙雙死於壞疽,而一名在旁觀看這場手術的人,看見李斯頓匆忙揮舞手術刀,刀子戳破了外套,還以為李斯頓被戳死,因此嚇得休克,一命嗚呼。在麻醉劑出現之前的年代,手術就是這麼危險。
Thumbnail
  為了未來就業穩定,目前打算以成為公股銀行行員的目標邁進!打算花半年至一年的時間考取Fit(金融基測),再慢慢考一些有的沒的法定考試(好痛苦...)   先說我報名證基會2023/7/7的紙筆考試,大概從2023/6/27開始準備,準備時長10天左右。但我不能說自己是非常認真準備的學生,主要原因是
Thumbnail
大綱: 1.股利的三大迷思 2.投資中第1~3級思考 3.大部分的人虧損的原因 4.兩個最有效率的投資策略 5.單筆投資與定期定額的差別。 6.長期投資成功的關鍵是什麼? 7.已實現和未實現報酬的差異。 8.股票賣出的三大理由。 9.投資必勝九大金律。 讀書心得分成兩篇,上篇的連結 本文PODCAS
Thumbnail
大綱: 1.適合誰閱讀 2.股市秤重機 3.風險管理的兩大誤區 4.長期投資的七大優勢 5.股利是雙面刃 【無痛致富】,去年10/26號上市。作者是佛里曼投資顧問團隊(Freeman Publications),總部位於英國倫敦,提供個人專業的投資理財顧問服務,將最複雜的投資策略以易於理解的語言傳達
Thumbnail
水晶洞功效: 水晶洞又稱作是風水石,裡面充滿漂亮的晶牙、水晶花,彼此能量互相震動凝聚磁場!能夠聚財納福、避邪擋煞、吉祥平安。且晶洞本身就是源源不絕的發電廠,可以幫其他水晶淨化、消磁。 迷你晶洞擺放在辦公桌: 在工作上有貴人幫助、專注辦公、防止小人陷害、被老闆讚許、加薪升官的機會! 迷你晶洞擺放在正財