簡答的理解C語言中的各種類型函數


1.變參函數

變長參數的函數即參數個數可變、參數類型不定 的函數。最常見的例子是printf函數、scanf函數和高級語言的Format函數。在C/C++中,為了通知編譯器函數的參數個數和類型可變(即是不定的、未知的),就必須以三個點結束該函數的聲明。

1 // printf函數的聲明  
2 int printf(const char * _Format, ...);      //const char * _Format是格式控制,控制有多少個%d...,確定輸出的個數與類型
3 4 //scanf函數聲明  5 int scanf(const char * _Format, ...); 6 7 //自定義變長參數函數func的聲明  8 int func(int a,int b,...);

注意:上面func 函數的聲明指出該函數至少有兩個整型參數和緊隨其后的0個或多個類型未知的參數。在C/C++中,任何使用變長參數聲明的函數都必須至少有一個指定的參數(又稱強制參數),即至少有一個參數的類型是已知的,而不能用三個點省略所有參數的指定,且已知的指定參數必須聲明在函數最左端。在c++類似的應用中稱之為重載函數。

 

1 //下面這種聲明是非法的  
2 int func(...);    //錯誤 3 int func(...,int a);    //錯誤

----------------------------------------------------------------------

變參函數的實現

我們知道函數調用過程中參數傳遞是通過棧來實現的,一般調用都是從右至左的順序壓參數入棧。例如int func(int a,int b,float c),則float類型的c在最下面,然后到int類型的 b ,int類型的a在最上面!

  因此參數與參數之間是相鄰的,知道前一個參數的類型及地址,根據后一個參數的類型就可以獲取后一個參數的內容。對於變長參數函數,結合一定的條件,我們可以根據最后一個指定參數獲取之后的省略參數內容。如,對於函數func,我們知道了參數b的地址及類型,就可知道第一個可變參數的棧地址(如果有的話),如果知道第一個可變參數的類型,就可知道第一個可變參數的內容和第二個可變參數的地址(如果有的話)。以此類推,可以實現對可變參數函數的所有參數的訪問。

       那么,要怎么指定上訴的“一定的條件”呢?最簡單的方法就像printf等函數一樣,使用格式化占位符。分析格式化字符串參數,通過事先定義好的格式化占位符可知可變參數的類型及個數,從而獲取各個參數內容。一般對於可變參數類型相同的函數也可直接在強制參數中指定可變參數的個數和類型,這樣也能獲取各個參數的內容。

       無論哪種,都涉及對棧地址偏移的操作。結合棧存儲模式和系統數據類型的字長,我們可根據可變參數的類型很容易得到棧地址的偏移量。這里簡單介紹使用va_start、va_arg、va_end三個標准宏來實現棧地址的偏移及獲取可變參數內容。這三個宏定義在stdarg.h頭文件中,他們可根據預先定義的系統平台自動獲取相應平台上各個數據類型的偏移量。

//訪問可變參數流程  
  
va_list args; //定義一個可變參數列表  

va_start(args,arg);//初始化args指向強制參數arg的下一個參數;  

va_arg(args,type);//獲取當前參數內容並將args指向下一個參數  
  
...//循環獲取所有可變參數內容  
  
va_end(args);//釋放args  

下面實現一個簡單的例子:

 1 //sum為求和函數,其參數類型都為int,但參數個數不定  
 2 //第一個參數(強制參數)n指定后面有多少可變參數  
 3 int sum(unsigned int n,...)  
 4 {  
 5    int sum=0;  
 6    va_list args;  
 7    va_start(args,n);  
 8    while(n>0)  
 9    {  
10     //通過va_arg(args,int)依次獲取參數的值  
11      sum+=va_arg(args,int);  
12      n--;  
13    }  
14    va_end(args);  
15    return sum;  
16 }  

注意:對於可變參數函數的調用有一點需要注意,實際的可變參數的個數必須不小於前面強制參數中指定的個數要多,即后續參數多一點不要緊,但不能少,如果少了則會訪問到函數參數以外的堆棧區域,這可能會把程序搞崩掉。前面強制參數中指定的類型和后面實際參數的類型不匹配也有可能造成程序崩潰。 

參考的是 http://blog.csdn.net/tht2009/article/details/7019635#

 

2.內部函數和外部函數

當一個源程序由多個源文件組成時,根據函數能否被其它源文件中的函數調用,將函數分為內部函數和外部函數。

2.1內部函數

  如果在一個源文件中定義的函數,只能被本文件中的函數調用,而不能被同一程序其它文件中的函數調用,這種函數稱為內部函數。定義這種函數,在函數類型之前添加“static”關鍵字即可。

  使用內部函數的好處:不用的人編寫不用的函數時,不用擔心自己定義的函數是否與其他文件中的函數同名,因為同名也沒關系。

2.2外部函數

  在定義函數時沒有加關鍵字,如“static”或者“extern”等等,表明此函數就是外部函數,也就是我們平時用的最多的函數類型。

參考 http://www.cnblogs.com/JessonChan/archive/2010/12/12/1903983.html

 

3.inline(內聯)函數

在函數聲明之前添加關鍵字"inline"的函數,就是內聯函數。

  函數調用需要時間和空間開銷,調用函數實際上將程序執行流程轉移到被調函數中,被調函數的代碼執行完后,再返回到調用的地方。這種調用操作要求調用前保護好現場並記憶執行的地址,返回后恢復現場,並按原來保存的地址繼續執行。對於較長的函數這種開銷可以忽略不計,但對於一些函數體代碼很短,又被頻繁調用的函數,就不能忽視這種開銷。引入內聯函數正是為了解決這個問題,提高程序的運行效率。

  在程序編譯時,編譯器將程序中出現的內聯函數的調用表達式用內聯函數的函數體來進行替換。由於在編譯時將內聯函數體中的代碼替代到程序中,因此會增加目標程序代碼量,進而增加空間開銷,而在時間開銷上不象函數調用時那么大,可見它是以目標代碼的增加為代價來換取時間的節省。

inline函數是提高運行時間效率,但卻增加了空間開銷。

即inline函數目是:為了提高函數的執行效率(速度)。
非內聯函數調用有棧內存創建和釋放的開銷
在C中可以用宏代碼提高執行效率,宏代碼不是函數但使用起來像函數,編譯器用復制宏代碼的方式取代函數調用,省去了參數壓棧、生成匯編語言的CALL調用、返回參數、執行return等過程,從而提高速度。在多語句宏定義時候,運用inline函數代替宏定義。

 

 

  使用內聯函數時應注意以下幾個問題:
(1) 在一個文件中定義的內聯函數不能在另一個文件中使用。它們通常放在頭文件中共享。
(2) 內聯函數應該簡潔,只有幾個語句,如果語句較多,不適合於定義為內聯函數。 
(3) 內聯函數體中,不能有循環語句、if語句或switch語句,否則,函數定義時即使有inline關鍵字,編譯器也會把該函數作為非內聯函數處理。
(4) 內聯函數要在函數被調用之前聲明。

 

4.回調(遞歸)函數

什么是遞歸函數?遞歸函數是在函數體內部直接或間接地自己調用自己的函數。

1 void int f(int n)
2 {
3   if (條件)    //滿足條件則退出循環,返回所需的值
4   {
5      return ;    
6    } 
7    f(n-1);      //否則繼續調用本函數。。。
8 }    

例子1:

 1 //逆轉數組a的元素順序
 2 void revert(int a[], int len)
 3 {
 4    if(len = 1)
 5    {
 6         return;  
 7     }    
 8 
 9     revert(a+1, len-2);
10 
11     int tmp;
12     tmp = a[0];
13     a[0] = a[len-1];
14     a[len-1] = tmp; 
15 }   

例子2:漢諾塔程序

問題描述: A、B、C 三個桌子,其中A桌子上放了幾個大小不同的盤子,盤子的排列順序為: 從上到下,依次從小到大遞增;現要求把這些盤子從 A 桌子上移動到 C 桌子上,盤子移動時有一點要求:每次移動必須保證三張桌子上大盤子在下、小盤子在上;打印移動次序。

 1 void recursion_hano(int n,char A, char B, char B,)
 2 {
 3     //停止條件
 4     if(n == 1) 
 5     {    
 6         move(A,C);
 7         return; 
 8     } 
 9      
10     recursion_hano(n-1,A,C,B);//將 上一個盤子 從A移動到B 
11     move(A,C);    //將最大的盤子移動到 C  
12     recursion_hano(n-1,B,A,C);//將B中盤子移到C 
13 }

還有一種圓形排列法....

參考http://blog.csdn.net/leo115/article/details/7991734

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM