C基礎篇之函數指針的介紹與運用(內含有簡易四則運算計算器)

最近在探究Objective-C中block的實現原理,然后就不自覺的復習了一下C語言的函數指針。正所謂萬變不離其宗,雖說OC中的block跟簡單的函數指針相比已經大有不同,不過二者的表現形式還是有很多相似的地方。

首先做一個聲明:本文中的一些基礎理論知識,來自其他的技術博客或者論壇,為了尊重原創,在這里將盡可能完整無損的呈現給想要夯實一下基礎知識的小伙伴。

在開始之前,可以先下載作者為這篇文章所寫的demo:簡易四則運算器,總共代碼在100行左右,通過這個小demo來一窺函數指針的大概。

四則運算計算器.gif

(附贈一款錄制gif的工具LICEcap,使用上跟蘋果的QuickTime相像,需要劃定一個錄制范圍,當最后結束錄制的時候會自動為你生成gif圖。)

函數指針是什么?

先來看函數調用是怎么回事。一個函數占用一段連續內存。當調用一個函數時,實際上是跳轉到函數入口地址,執行函數體的代碼,完成后返回。如何找到對應的入口地址?這是由函數名來標記的,實際上,函數名就是函數的入口地址。

函數指針是一種特殊類型的指針,它指向一個函數的入口地址。

注意:除了void類型指針是無類型的指針外,其他所有指針都是有對應類型的,例如int *pintstruct studentdata *psdata等,只有指明了指針所指的數據類型,編譯器才能為指針分配或預計分配相應大小的存儲空間,指針的算術運算如pint++等才是有意義的。因此,定義了某種類型的指針之后,除非使用強制類型轉換,那么它只能指向相應數據類型的變量或常量,不同類型的指針或數據之間不可混用。所以指針的類型實際上是一種身份標志的作用

函數指針如何表明自己的身份呢?為了避免混亂,必須也要作出相應規定,不同函數的函數指針不能混用。例如,int func1(int arg11, char arg12)int func2(char arg)的函數指針就不能混用,要定義可以指向func1的函數指針應該這樣:

 int (*pfunc1)(int, char) = func1;

定義可以指向func2的函數指針則該如下:

int (*pfunc2)(char) = func2;

從函數指針的定義可以看出,函數指針的類型實際上是由函數簽名決定的。函數簽名就象是函數的身份證,一個函數的函數簽名是獨一無二的,具有相同函數簽名的函數實際上就是同一函數。函數簽名包括函數名、函數形參類型的有序列表和函數返回值類型。

一個函數指針的定義規定了它只能指向特定類型的函數。如果兩個函數的形參列表和返回值類型相同,只有函數名和函數體不同,則可以使用相同類型的函數指針。

例如,如果還有一個函數int func3(char arg),則上面定義的可以指向函數func2的函數指針也可以用于指向func3,即:
  pfunc2 = func3;
再使用pfunc2(char ARG)就可以調用函數func3,這時指令計數器(PC)指向函數入口,從此開始執行函數體代碼。

如何使用函數指針?

  • 定義合適類型的函數指針變量:int (*pfunc)(int, int);
  • 給函數指針變量賦值,使它指向某個函數入口:int example(int, int); pfunc = example;/將函數入口地址賦給函數指針變量/
  • 使用函數指針來調用相應的函數;
    retval = pfunc(10, 16); 或者:retval = (*pfunc)(10, 16);

上面兩句都與retval = example(10, 16);等價。

理解:一個指針變量p實際上也和普通的變量一樣,要占存儲空間(通常與平臺的虛擬地址一樣寬),也有其自身的存儲地址&p;不同的是,在指針變量p的值有特殊的意義,它是另外一個變量或常量的地址值,也就是說,在地址為&p的存儲單元上存放著另外一個數據的地址。因此,p實際上是將p看作它指向的數據的地址來使用,操作符是引用相應地址中的數據,也就是對地址為p的存儲單元中存放的數據進行操作。

為什么要使用函數指針?

前面介紹了函數指針的基本知識和使用規范。下面介紹函數指針的實際用途。不過首先要對前面的知識再做一個補充,因為下面的應用很可能用到這一特性。前面指出,除函數名之外的函數簽名內容(函數返回值類型和形參列表)決定了函數指針的類型。實際上還有一種特殊的或說通用的函數指針,在定義這類函數指針時,只需要指定函數返回值類型,而留空形參列表,這樣就可以指向返回值類型相同的所有函數。例如:
int (*pfunc)();
這樣定義的pfunc就可以指向前面提到的func1func2,因為他們都返回整型值。
注意:int (*pfunc)()int (*pfunc)(void)不是一回事,后者不允許接受任何參數。

函數指針最常見的三個用途是:

  • 作為參數傳遞給其他函數。這樣可以把多個函數用一個函數體封裝起來,得到一個具有多個函數功能的新函數,根據傳遞的函數指針變量值的不同,執行不同的函數功能。這是函數嵌套調用難以實現的。參數的傳遞可以由程序員設定,也可以由用戶輸入讀取,因此具有較大的靈活性和交互性。另外還可以用于回調函數。使用void配合,還可以將對不同數據類型的數據進行相同處理的多個函數封裝為一個函數,增強函數的生命力。

  • 用于散轉程序。這種程序首先建立一個函數表(實際上是一個函數指針數組),表中存放了各個函數的入口地址(或函數名),根據條件的設定來查表選擇執行相應的函數。這樣也可以將多個函數封裝為一個函數或者程序,散轉分支條件可以由程序員設定,也可以由用戶輸入讀取,甚至是外設的某種特定狀態(這種狀態可以是不受人為控制的)。

  • 實現C的面向對象的類的封裝。C語言中的struct與C++中的class有很大不同,除了缺省的成員屬性外(struct的成員缺省為public的,可隨意使用,而class成員缺省為private的),struct還很難實現類成員函數的封裝。struct的成員一般都是數據成員,而非函數成員。因此,為了在C語言中,為某個struct定義一套自己的函數對結構數據成員進行操作,可以在struct結構體中增加函數指針變量成員,在初始化時使它指向特定函數即可。

基礎的理論知識就介紹這些,下面來舉例分析四則運算計算器demo中對函數指針的運用。

首先是定義加減乘除的基本運算如下。這是最基礎的運算,不需要考慮調用的順序。

long long add(int a,int b){
    
    return a + b;
}

long long int sub(int a,int b){
    
    return a - b;
}

long long int mul(int a ,int b){
    
    return a*b;
    
}
long long int divi(int a,int b){
    
    return a/b;
}

然后觀察上面的函數,發現除了函數名不一樣外,返回值與參數類型都是一樣的,所以可以用一個函數指針來指向它們。

函數指針的聲明如下所示:

typedef long long int (*FUNC)();

FUNC pfunc;

首先定義了一個函數指針的類型:FUNC,這樣我們就可以更加方便的使用這個類型來聲明函數指針變量了。下面的FUNC pfunc;就是聲明了一個名為pfunc的函數指針變量。

最后就是求和運算了

double calculator(long long x,long long y,FUNC func){
    
    double result;

    result = (*func)(x,y);
    
    return result;
    
}

其中的func函數指針會根據我們所點擊的運算符的不同而指向不同的函數,這樣就實現了一個非常簡單的計算器了。

最后附上demo地址

參考資料:
http://www.360doc.com/content/13/1104/12/13670635_326518097.shtml

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容