C語言中的遞歸函數,我覺得好難懂,這正常嗎?你們覺得難嗎?

小虎哥奇異故事


我也這麼覺得哈哈,我當初學習 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()的參數和局部變量。

具有這種性質的數據結構稱為堆棧或棧(Stack)。每個函數調用的參數和局部變量的存儲空間(圖裡的一個小方框)稱為一個棧幀(Stack Frame)。系統為每個程序的運行預留了棧空間,函數調用時就在這個棧空間裡分配棧幀,函數返回時就釋放棧幀。

可以看出,寫 C 語言遞歸函數最重要的就是一定要定義好“基礎條件”,不然函數就會永遠調用下去,知道系統資源耗盡程序崩潰為止。遞歸和循環是等價的,用循環能做的事用遞歸都能做,反之亦然,事實上有的編程語言(如某些LISP)只有遞歸而沒有循環。

計算機硬件能做的所有事情就是數據存取、運算、測試和分支、循環(或遞歸),在計算機上運行的高級語言寫的程序當然也不可能做到更多的事情,雖然高級語言有豐富的語法特性,但也只是為做這些事情提供一些方便。那麼,為什麼計算機是這樣設計的?為什麼想到計算機需要具有這幾種功能,而不是更多或者更少?這些要歸功於早期的計算機科學家,例如Alan Turing,他們在計算機還沒有誕生的年代從數學理論上為計算機的設計指明瞭方向。

歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。


IT劉小虎


你覺得難懂,是正常的。

遞歸函數屬於數據結構與算法中的知識範疇,這部分內容不僅是軟件人員的基礎,同時又是難點,它需要軟件學習人員具有一定的數學水平,而且是高等數學水平,而且這部分知識中的代碼思想都非常精簡,所以難看懂就很正常了。

最好的辦法是,不要僅僅只看書本的內容,還要經常動手編碼練一練,這樣能加快理解,並且不會遺忘。


Gfilsxin


不是c語言的遞歸難懂,而是遞歸本身比較不好理解,但你只要參透那個點,就雲開月明瞭,但還是建議在有可能的條件下不要使用遞歸,尤其遞歸深度較深的時候。


又喝多了


有for或while循環不就行了?


IT與隱私安全


所有的語言都可以使用遞歸,遞歸和循環是等價的,只不過實現方式不同而已。

一個等價的例子

求1到10累加:

  • 循環 用For循環在循環體內做累加計算,終止條件是 控制變量>10
  • 遞歸 累加計算和增1計算做遞歸公式,遞歸條件 控制變量<=10

遞歸的優缺點

遞歸的代碼簡潔複雜度低 遞歸在處理複雜嵌套時,具備了循環無法比擬的優勢。

  • 遞歸的內存使用效率略高 遞歸使用棧的空間,隨著循環的進行,前面遞歸函數不能結束後面的遞歸函數不斷增加,棧空間增加,但到後期,遞歸函數開始完結,棧空間會迅速釋放。相比之下,循環體主要使用堆空間,循環過程中堆空間不斷增加,循環結束後不會立即釋放堆空間。
  • 遞歸容易引起棧內存的溢出 由於遞歸函數是動態申請棧空間,通過編譯和靜態代碼解析,無法發現內存的溢出的問題。因此,遞歸對程序員的技術能力要求較高。
  • 理論上遞歸的執行速度略快 這是由於棧的讀寫速度要高於堆。

日衝信息 黃


遞歸,就是某函數在內部再次調用了自身,包括直接調用和間接調用。

一,遞歸函數,必須有退出條件,否則程序必定崩潰,而不是無限循環。

二,遞歸函數需要注意遞歸層數不能太大,每次遞歸調用都會有壓棧操作,要佔用棧空間,當棧滿了,會溢出,破壞數據,函數無法返回,程序崩潰。


分享到:


相關文章: