我的上一個回答介紹了C語言的 “遞迴函式”,一行一行利用遞迴寫出了求 n! 的C語言程式並分析了它的執行流程。
其實,每次遞迴呼叫都是在重複做同樣一件事,都是計算 n x (n-1)!。當然了,雖說是“同樣一件事”,還是略有不同的(n的值每次都不同),所以稱呼其為“迭代”更恰當一點。
計算機特別擅長處理重複迭代的工作,這也是我們人類使用計算機的原因之一,因為人類最不擅長,也不喜歡重複迭代的工作。有了計算機,程式設計師透過程式設計告訴計算機怎樣做就可以了。
雖然迭代用遞迴可以解決,但是C語言的迴圈語句更符合我們人類的使用習慣,用起來更習慣,我們先來看看 C語言中的 while 語句。它的語法為:
到達 while 語句時,程式會判斷“條件表示式”的真假,若假則跳過 while 語句塊。若真,則執行 while 語句塊裡的內容,到達語句塊末尾時,程式會回到“條件表示式”處,再次判斷真假。
現在知道了 while 迴圈語句的用法,我們來用它計算 n 的階乘,C語言程式碼可以如下寫:
上面的C語言程式碼和之前利用遞迴求階乘的程式碼,從某種程度上來說,是等價的。我們仍然以 factorial(3) 為例,說說這段C語言程式碼的執行流程。
程式第一次到達 while 處,n=3,顯然大於 0,於是 result=1 x 3,接著 n=2;回到 while 處,n 依然大於 0,於是 result = 1 x 3 x 2;接著 n=1,回到 while 處,n 依然大於 0,於是 result = 1 x 3 x 2 x 1,接著 n = 0;回到 while 處,0 不大於 0,於是跳過 while 語句,factorial 函式返回 result = 6。
很多程式設計師習慣稱呼 n 為迴圈變數,因為它控制著迴圈體是迴圈還是結束。
我在上一個回答中提到“遞迴和迴圈是常常是等價的”,這裡就是一個例子。
但是要注意的是,遞迴和迴圈解決問題的思路不一樣,用遞迴解決階乘問題靠的是遞推關係n!=n·(n-1)!,用迴圈解決這個問題則更像是把這個公式展開了:n!=n·(n-1)·(n-2)·…·3·2·1。
在整個遞迴呼叫過程中,雖然分配和釋放了很多變數,但是所有的變數都只在初始化時賦值,沒有任何變數的值發生過改變,而上面的迴圈程式則是透過對n和result這兩個變數多次賦值來達到同樣目的的。
既然“遞迴和迴圈常是等價的”,而遞迴函式如果寫的不恰當就會造成無限遞迴,導致程式最後崩潰,那對應的,while 迴圈語句如果寫的不恰當,也會造成無限迴圈,程式設計師們常常稱其為“死迴圈”。
造成 while 語句死迴圈的原因很簡單,只要 while 的條件表示式不可能為假,程式跳不出 while 迴圈,就會導致C語言程式陷入“死迴圈”。
上面的C語言程式碼例子中,正整數 n 不斷減 1,最後 n 必定會等於 0 的,因此 n>0 有為假的時刻,所以不會導致死迴圈。
但是,如果不小心把 n = n-1 這條語句漏掉了,那程式永遠都不會跳出 while 迴圈體了。
不過與無限遞迴不同,程式一般不會因為死迴圈崩潰,而是會“卡死”在死迴圈處。所以,在使用 while 迴圈語句之前,要確保 while 的條件表示式有機會為假,除非,你故意希望有一個死迴圈。
不過,有時候死迴圈並不是那麼一目瞭然的,例如下面這個著名的 3x+1 問題:
迴圈體所做的事情是:如果n是偶數,就把n除以2,如果n是奇數,就把n乘3加1。一般的迴圈變數要麼遞增要麼遞減,可是這個例子中的n一會兒變大一會兒變小,最終會不會變成1呢?
可以找個數試試,例如一開始n等於7,每次迴圈後n的值依次是:7、22、11、34、17、52、26、13、40、20、10、5、16、8、4、2、1。最後n確實等於1了。
讀者可以再試幾個數都是如此,但無論試多少個數也不能代替證明,目前世界上還無人能證明。
我的上一個回答介紹了C語言的 “遞迴函式”,一行一行利用遞迴寫出了求 n! 的C語言程式並分析了它的執行流程。
其實,每次遞迴呼叫都是在重複做同樣一件事,都是計算 n x (n-1)!。當然了,雖說是“同樣一件事”,還是略有不同的(n的值每次都不同),所以稱呼其為“迭代”更恰當一點。
計算機特別擅長處理重複迭代的工作,這也是我們人類使用計算機的原因之一,因為人類最不擅長,也不喜歡重複迭代的工作。有了計算機,程式設計師透過程式設計告訴計算機怎樣做就可以了。
C語言中的 while 迴圈語句雖然迭代用遞迴可以解決,但是C語言的迴圈語句更符合我們人類的使用習慣,用起來更習慣,我們先來看看 C語言中的 while 語句。它的語法為:
while(條件表示式){ 語句;}到達 while 語句時,程式會判斷“條件表示式”的真假,若假則跳過 while 語句塊。若真,則執行 while 語句塊裡的內容,到達語句塊末尾時,程式會回到“條件表示式”處,再次判斷真假。
現在知道了 while 迴圈語句的用法,我們來用它計算 n 的階乘,C語言程式碼可以如下寫:
上面的C語言程式碼和之前利用遞迴求階乘的程式碼,從某種程度上來說,是等價的。我們仍然以 factorial(3) 為例,說說這段C語言程式碼的執行流程。
程式第一次到達 while 處,n=3,顯然大於 0,於是 result=1 x 3,接著 n=2;回到 while 處,n 依然大於 0,於是 result = 1 x 3 x 2;接著 n=1,回到 while 處,n 依然大於 0,於是 result = 1 x 3 x 2 x 1,接著 n = 0;回到 while 處,0 不大於 0,於是跳過 while 語句,factorial 函式返回 result = 6。
很多程式設計師習慣稱呼 n 為迴圈變數,因為它控制著迴圈體是迴圈還是結束。
C語言的迴圈和遞迴我在上一個回答中提到“遞迴和迴圈是常常是等價的”,這裡就是一個例子。
但是要注意的是,遞迴和迴圈解決問題的思路不一樣,用遞迴解決階乘問題靠的是遞推關係n!=n·(n-1)!,用迴圈解決這個問題則更像是把這個公式展開了:n!=n·(n-1)·(n-2)·…·3·2·1。
把公式展開了理解會更直觀一些,所以有些時候迴圈程式比遞迴程式更容易理解。在整個遞迴呼叫過程中,雖然分配和釋放了很多變數,但是所有的變數都只在初始化時賦值,沒有任何變數的值發生過改變,而上面的迴圈程式則是透過對n和result這兩個變數多次賦值來達到同樣目的的。
再來說說使用 while 的注意事項既然“遞迴和迴圈常是等價的”,而遞迴函式如果寫的不恰當就會造成無限遞迴,導致程式最後崩潰,那對應的,while 迴圈語句如果寫的不恰當,也會造成無限迴圈,程式設計師們常常稱其為“死迴圈”。
造成 while 語句死迴圈的原因很簡單,只要 while 的條件表示式不可能為假,程式跳不出 while 迴圈,就會導致C語言程式陷入“死迴圈”。
上面的C語言程式碼例子中,正整數 n 不斷減 1,最後 n 必定會等於 0 的,因此 n>0 有為假的時刻,所以不會導致死迴圈。
但是,如果不小心把 n = n-1 這條語句漏掉了,那程式永遠都不會跳出 while 迴圈體了。
不過與無限遞迴不同,程式一般不會因為死迴圈崩潰,而是會“卡死”在死迴圈處。所以,在使用 while 迴圈語句之前,要確保 while 的條件表示式有機會為假,除非,你故意希望有一個死迴圈。
不過,有時候死迴圈並不是那麼一目瞭然的,例如下面這個著名的 3x+1 問題:
迴圈體所做的事情是:如果n是偶數,就把n除以2,如果n是奇數,就把n乘3加1。一般的迴圈變數要麼遞增要麼遞減,可是這個例子中的n一會兒變大一會兒變小,最終會不會變成1呢?
可以找個數試試,例如一開始n等於7,每次迴圈後n的值依次是:7、22、11、34、17、52、26、13、40、20、10、5、16、8、4、2、1。最後n確實等於1了。
許多世界難題都是這樣的:描述無比簡單,連小學生都能看懂,但證明卻無比困難。讀者可以再試幾個數都是如此,但無論試多少個數也不能代替證明,目前世界上還無人能證明。