謝邀。
我也這么覺得哈哈,我當初學習 C 語言時,覺得最難的就是“遞歸”了,比指針還難理解(C 語言中的指針,很多人都認為難以理解)。
那什么是“遞歸”呢?我有一天翻詞典時,看到詞典這么解釋一個詞:
驚人的:用來形容驚人的形容詞。
這要么是惡搞,要么就是玩笑。然而在數學上確實有很多概念是用自己定義的,舉個例子:n 的階乘等于 n 乘以 n-1 的階乘,并且 0 的階乘等于 1。咋一看,似乎它并沒有說清楚什么是階乘,但是這樣的描述,卻足以讓人知道怎樣計算階乘。例如計算 4 的階乘:
4! = 4 x 3! = 4 x 3 x 2! = 4 x 3 x 2 x 1! = 4 x 3 x 2 x 1 x 0! = 4 x 3 x 2 x 1 x 1 = 24并不用細究階乘到底是什么,只需要按照定義去計算即可,當然,這種定義方式必須要有一個“基礎條件”,比如階乘的“基礎條件”就是 0! = 1。如果沒有“基礎條件”,階乘只會無限往下推,沒有盡頭。
C 語言中,什么是遞歸函數?說了半天階乘,就是為“遞歸”做鋪墊的,如果一個概念需要用到自身,我們就稱它的定義是遞歸的。那顯然,遞歸函數一定是調用了自身的函數,這么說有點虛,來看看實例吧,下面用 C 語言計算 n 的階乘。我們已經知道,遞歸最重要的就是“基礎條件”了,我們先把階乘的基礎條件寫好:
上面的代碼實現了 0 的階乘等于 1,那如果 n 大于 0 呢?按照階乘的定義,應該是 n x fatorial(n-1),用代碼實現就是:
這就用 C 語言實現了計算 n 的階乘。factorial 函數調用了自己,所以 factorial 是遞歸函數。事實上,不僅僅是直接調用自己,間接調用自己也屬于遞歸函數。比如,A 調用了函數 B,函數 B 又調用了 A,那 A 也是遞歸函數。
那,遞歸函數是怎么執行的呢?為了方便解釋,我們在 factorial 函數的else 部分加幾個局部變量:
這里以 factorial(3) 為例,用實線箭頭表示調用,用虛線箭頭表示返回,右邊的框表示在調用和返回過程中各函數調用的局部變量的變化情況。
我們看圖右邊表示存儲空間的框的變化過程,隨著函數調用的層層深入,存儲空間的一端逐漸增長,然后隨著函數的層層退出,存儲空間的這一端又逐漸縮短,這是一種具有特定性質的數據結構。
它的特性就是只能在某一端增長或縮短,并且每次訪問參數和局部變量時只能訪問這一末端的單元,而不能訪問內部的單元,比如當factorial(2)的存儲空間位于末端時,只能訪問它的參數和局部變量,而不能訪問factorial(3)和main()的參數和局部變量。
具有這種性質的數據結構稱為堆?;驐#⊿tack)。每個函數調用的參數和局部變量的存儲空間(圖里的一個小方框)稱為一個棧幀(Stack Frame)。系統為每個程序的運行預留了棧空間,函數調用時就在這個??臻g里分配棧幀,函數返回時就釋放棧幀。
可以看出,寫 C 語言遞歸函數最重要的就是一定要定義好“基礎條件”,不然函數就會永遠調用下去,知道系統資源耗盡程序崩潰為止。遞歸和循環是等價的,用循環能做的事用遞歸都能做,反之亦然,事實上有的編程語言(如某些LISP)只有遞歸而沒有循環。
計算機硬件能做的所有事情就是數據存取、運算、測試和分支、循環(或遞歸),在計算機上運行的高級語言寫的程序當然也不可能做到更多的事情,雖然高級語言有豐富的語法特性,但也只是為做這些事情提供一些方便。那么,為什么計算機是這樣設計的?為什么想到計算機需要具有這幾種功能,而不是更多或者更少?這些要歸功于早期的計算機科學家,例如Alan Turing,他們在計算機還沒有誕生的年代從數學理論上為計算機的設計指明了方向。歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。