《C和指標》讀書筆記(6)
宣告:該讀書筆記摘抄自《C和指標》——Kenneth A.Reek (著) 徐波(譯)。為了克服自己走馬觀花,提高閱讀和學習效率,決定將自己在讀書過程中遇到的一些知識點加以摘抄和總結備忘,在此感謝原書作者和翻譯。
一、遞迴的兩個特性
1、存在限制條件
2、每次遞迴之後越來越接近限制條件
二、將二進位制數字轉換為可列印字元的遞迴實現
void binary_to_ascii(unsigned int value)
{
unsigned int quotient ;
quotient = value / 10 ;
if (0 !=
quotient
binary_to_ascii(quotient) ;
}
putchar(value % 10 + '0') ;
}
追蹤遞迴函式的執行過程的關鍵是理解函式中所宣告的變數是如何儲存的,當函式被呼叫時,它的變數空間時是宣告在執行時堆疊上的。以前呼叫的函式的變數仍保留在堆疊上,但它們被新函式所宣告的變數所覆蓋,不能訪問。遞迴函式呼叫自身時,每呼叫一次都將在堆疊上建立一批新的變數,它們將覆蓋前一次呼叫在堆疊上建立的變數。最後一次遞迴呼叫的函式的變數位於棧頂。這樣,當遞迴完成時,最後一次的呼叫將開始首先返回,並且該函式的變數位於棧頂,首先出棧;第一次被呼叫的遞迴函式最後返回,它的變數最後出棧。因此,可以總結出,遞迴函式展開時完成入棧操作,迴歸時完成出棧操作。
三、遞迴與迭代
1、計算n的階乘
1)遞迴實現n的階乘
long factorial(int n)
{
if (0 >= n) {
return 1;
} else {
return n*factorial(n-1); //尾部遞迴可以方便得轉換為迭代實現
}
}
2)、迭代實現n的階乘
long factorial(int n)
{
int result = 1;
while (1 < n) {
result *= n;
n -= 1;
}
return result;
}
對比:在計算n的階乘的兩種方法中,遞歸併沒有體現出它的優勢,因為遞迴函式呼叫將涉及到執行時開銷,比如引數入棧,為區域性變數分配記憶體空間,儲存暫存器的值,在函式返回時,引數出棧,銷燬區域性變數,暫存器恢復。這樣的話,用迭代效率更高一些。
2、計算斐波那契數。斐波那契數其實是一個數列,它的每個數的數值是前兩個數之和,第一個和第二個數為1。
1)斐波那契數的遞迴實現,計算第n個斐波那契數
long fibonacci(int n)
{
if (2 >= n) {
return 1;
} else {
return (fibonacci(n-1) + fibonacci(n-2)) //尾部遞迴可以用迭代實現
}
}
2)斐波那契數的迭代實現,計算第n個斐波那契數
long fibonacci(int n)
{
long result;
long prev; //前面的那個數
long next; // 前面的第二個數
result = 1;
prev = 1;
while (2 < n) {
next = prev;
prev = result;
result = prev + next;
n--;
}
return result;
}
對比:在遞迴版本中存在冗餘計算,效率相當低;應採用第二種方法。
四、可變引數
1、標頭檔案 <stdarg.h>
2、 可變引數列表省略號(三個點): 用於提示此處可以傳遞數量和型別未定的引數,宣告函式原型時也必須這樣宣告。
va_list :變數型別,用於訪問引數列表中的未確定的部分
va_start : 是一個巨集函式,用於初始化va_list定義的變數。它接受兩個引數,第一個是var_list定義的變數,第二個引數是引數列表中省略號前面最後一個有名字的引數。該初始化過程將var_list定義的變數指向了可變引數部分的第一個引數。
va_arg: 是一個巨集函式,用於依次訪問可變引數變數列表。它也接受兩個引數,第一個是var_list定義的變數,第二個是引數列表中下一個引數的型別(在某些函式中可能需要根據前面獲得的資料來判斷下一個引數的型別)。該巨集函式返回該這個引數的值,並且使va_list定義的變數指向下一個可變引數。
va_end:是一個巨集函式,當訪問完最後一個可變引數後,需要呼叫該巨集函式。
3、可變引數的限制
1)可變引數列表必須從第一個可變引數開始逐個訪問,可以不全部訪問,不能跳序訪問;
2)所有引數列表的變數沒有原型說明,因此傳遞的值都將進行預設引數型別提升;例如char、short提升為int,float提升為double
3)引數列表中至少要有一個命名引數,否則無法使用va_start查詢引數列表的可變部分;
以上是第七章的摘抄和總結,未完待續。。。。。