[C++ 入門] 2.1 程序流程:while & for 迴圈控制

我們現在會輸出一行字串,對吧??如果要輸出兩行呢??複製貼上,搞定。可是要是是 \(100, 1000\), 甚至是 \(n\) 次呢??電腦的專長除惹計算,還有做重複的事,這時我們可以運用迴圈。

while

while 的語法有點像 if:

while (condition)
{
    // ...
}

if 的不同在於 while 會重複執行大括弧內的陳述,直到 condition 為零。來看個範例吧 這個程式首先模擬丟擲一顆骰子,產生閉區間 \([1, 6]\) 內的偽隨機數為 val, 之後讓使用者輸入 n, 當 n != val 就讓使用者繼續猜。第二、八、九及十行前半是產生亂數的方法,可以先不理它。而第十行後半的 n = 0 很重要,它是一個變數初始化的作用,當我們宣告一個變數卻未賦予它值,存在一個極低的機率它的值剛好會是 val.

回到本文開頭的引子,如何寫出輸出 n 次字串的程式?? 還記得遞減運算子嗎??這裡我們應用一個小小技巧,對於一個正整數 n, 經過 n--, 其值會變為 \(0\). 但注意 n 最終的值會是 -1 原因是當 n 為零時還會進入 while(), 判斷的時候又會 --. 如果你現在一頭霧水,顯然還沒是沒有學好遞減運算子,不過也沒關係,等下用 for 迴圈改寫比較直觀易懂。

do-while

do-while 的語法如下:

do
{
    // ...
} while (condition);

while 的不同在於 do-while 會至少執行一次大括弧的內容。我們的擲骰子程式可以這樣改寫: 注意到因為我們在取 n 的值判斷 n != val 前必定執行過 cin >> n 賦值,因此第十行不予初始化也是安全的。

例題解析

  • GreenJudge c046
    • 這題要求小數點後前 n 位,就用迴圈模擬我們的長除法
  • GreenJudge c040
    • 一樣是模擬題,即使沒聽過輾轉相除法也沒關係,照題目說的就對惹

類題演練

for

for 也是迴圈流程,它做得到的事其實 while 也都做得到,某方面而言算是一種語法糖。

for (initial; condition; afterthought)
{
    // ...
}

其中,initial 是初始化的陳述,C++ 跟現代的 C 允許在此宣告變數;conditionwhile 一樣,變成 \(0\) 就會離開迴圈;而 afterthought 是每次執行完大括弧內陳述之後會做的運算。我們的輸出 n 次字串的程式 for ver.: 我們外加一個小功能,就是輸出這是第幾遍,順帶一提, for 中的 iindex 編號的意思。

0-indexed numbering

到這裡裡可能很納悶,我們的 for 怎麼不寫成 for (int i = 1; i <= n; i++)?? 其實這樣當然也對,但在程式設計中我們傾向使用 0-indexed numbering, 包括先前我們看過 int, long long 的值域的表達,往往都是用一個左閉右開的區間 \([a, b)\),是因為有這些好處:

  • 區間長度即為 \(b - a\), 沒有討人厭的國小植樹問題
  • 將區間一分為二時可以輕易地得到 \([a, \frac{a+b}{2})\), \([\frac{a+b}{2}, b)\), 不用再 \(+1\)
  • 還記得國二數學等差數列與級數嗎?? \(a_n = a_1 + (n - 1)d,\ S_n = \frac{n}{2}(2a_1 + (n - 1)d)\), 如果我們改用 0-indexed numbering, 公式變成 \(a_n = a_0 + nd,\ S_n = \frac{n+1}{2}(2a_0 + nd)\), 是不是漂亮許多??

歷史上 \(0\) 這個概念於人類文明中算是比較晚的突破性創新,至今我們甚至都還不是很習慣它。等到後面進入陣列與指標之後,相信會對 0-indexed numbering 更有感覺。發明最短路徑演算法的 Dijkstra 寫過一篇短文 可以看看。

例題解析

  • GreenJudge c004
    • 因為這題我們需要知道是迴圈第幾次,所以用 for 而非 while. 注意到這題的數字都擠在一起沒有空白,因此我們用讀入字元的方式,再減去 0 的字元值就可以轉回整數。

類題演練

break & continue

考慮下面的問題:

給定一數列 \(A\) 及一整數 \(K\), 求 \(K\) 最早出現的下標,保證 \(K \in A\), 注意 \(K\) 可能重複出現多次。

輸入的第一行有兩個整數 \(N, K\), 其中 \(N\) 為 \(A\) 的長度,接著一行有 \(N\) 個數字,分別是 \(A_0, A_1, …, A_{N-1}\). 試輸出一整數,為 \(K\) 最早出現的下標。

寫個迴圈讀 \(N\) 個數進來判斷是否等於 \(K\) 對我們而言不難,關鍵在於需要避免重複。我們可以使用 break; 語句跳離當前的迴圈: 我們在 switch-case 就遇過 break; 惹,而它也可以用在 while, for.

接著考慮另一個問題。小時候大家都會數數,不過在某些地方某些數字很敏感,需要被屏蔽。示範一下從 \(0\) 數到 \(99\), 並用 continue; 跳過敏感數字:

巢狀迴圈

if-else 相同,我們當然可以結合多個迴圈。注意巢狀迴圈執行順序,是由外而內,再由內而外。底下示範印出九九乘法表: 由於乘積有一位數也有兩位數,因此我們使用 <iomanip> 中的 setw() 來做排版。

類題演練

Online Judge 的輸入

在此之前我們做 Online Judge 的題目大多是只有一筆測資,但有些題目可能有一次多筆測資,我們就需要應用迴圈。底下列出兩個系列題讓大家感受一下,之後做題都要看清楚題目敘述。


comments powered by Disqus