【 聲明:著作權,歡迎轉載,請勿用於商業用途。 聯絡信箱:feixiaoxing @163.com】
看過我前面部落格的朋友都清楚,函數調用主要依靠ebp和esp的堆棧互動來實現的。那麼遞迴呢,最主要的特色就是函數自己調用自己。如果一個函數調用的是自己本身,那麼這個函數就是遞迴函式。
我們可以看一下普通函數的調用怎麼樣的。試想如果函數A調用了函數B,函數B又調用了函數C,那麼在堆棧中的資料是怎麼儲存的呢?
函數A ^函數B | (地址遞減)函數C |
如果是遞迴函式呢,舉一個簡單的遞迴函式為例:
int iterate(int value){if(value == 1)return 1;return value + iterate(value -1);}
下面我們使用一個函數進行調用,看看會發生什麼情況?
void process(){int value = iterate(6);}
看看此時記憶體堆棧是什麼樣的?
iterate(int 1) line 96iterate(int 2) line 97 + 12 bytesiterate(int 3) line 97 + 12 bytesiterate(int 4) line 97 + 12 bytesiterate(int 5) line 97 + 12 bytesiterate(int 6) line 97 + 12 bytesprocess() line 102 + 7 bytesmain() line 108mainCRTStartup() line 206 + 25 bytesKERNEL32! 7c817067()
大家也看到了上面的代碼,遞迴函式和普通的函數也沒有什麼差別。除了自己調用本身之外,他就是一個普通的函數。那麼這個函數遞迴到什麼時候返回呢?這就是遞迴函式的關鍵了。我們看到iterate函數到1就停止了,所以上面的堆棧在(value == 1)即return。所以一個遞迴函式最關鍵的部分就是兩點:(1)遞迴策略;(2)函數出口。
看到這裡,大家可能感到遞迴函式不過如此,事實上也是這樣。但是,還有一點大家需要牢記在心,遞迴的深度是我們必須考慮的一個問題。只有遞迴深度在一個可控的範圍內,那麼整個遞迴過程都是可控的。那什麼時候不可控呢?那就是遞迴深度超過了一定的數字?這個數字和具體的線程堆棧長度有關?等到堆疊溢位了,那麼獲得的資料已經失去了真實性,所以也就沒有意義了。
我們把上面的問題推廣一下,如何用自己定義的堆棧類比上面的遞迴調用呢?這樣既能滿足遞迴的屬性,又能確保函數深度可控。
大家可以先寫一下自己的方案,下面只是我個人的一個思路。
int iterate(int value){int count = 0;int number =0;push(value);while(-1 != (number = pop())){if(1 != number)push(number -1);count += number;}return count;}
【預告: 下面一篇部落格介紹演算法和記憶體】