最近在探究Objective-C中block的實(shí)現(xiàn)原理,然后就不自覺的復(fù)習(xí)了一下C語言的函數(shù)指針算撮。正所謂萬變不離其宗,雖說OC中的block跟簡單的函數(shù)指針相比已經(jīng)大有不同,不過二者的表現(xiàn)形式還是有很多相似的地方杭攻。
首先做一個聲明:本文中的一些基礎(chǔ)理論知識,來自其他的技術(shù)博客或者論壇疤坝,為了尊重原創(chuàng)兆解,在這里將盡可能完整無損的呈現(xiàn)給想要夯實(shí)一下基礎(chǔ)知識的小伙伴。
在開始之前跑揉,可以先下載作者為這篇文章所寫的demo:簡易四則運(yùn)算器,總共代碼在100行左右痪宰,通過這個小demo來一窺函數(shù)指針的大概。
(附贈一款錄制gif的工具LICEcap畔裕,使用上跟蘋果的QuickTime相像衣撬,需要劃定一個錄制范圍,當(dāng)最后結(jié)束錄制的時候會自動為你生成gif圖扮饶。)
函數(shù)指針是什么具练?
先來看函數(shù)調(diào)用是怎么回事。一個函數(shù)占用一段連續(xù)內(nèi)存甜无。當(dāng)調(diào)用一個函數(shù)時扛点,實(shí)際上是跳轉(zhuǎn)到函數(shù)入口地址,執(zhí)行函數(shù)體的代碼岂丘,完成后返回陵究。如何找到對應(yīng)的入口地址?這是由函數(shù)名來標(biāo)記的奥帘,實(shí)際上铜邮,函數(shù)名就是函數(shù)的入口地址。
函數(shù)指針是一種特殊類型的指針寨蹋,它指向一個函數(shù)的入口地址松蒜。
注意:除了void
類型指針是無類型的指針外,其他所有指針都是有對應(yīng)類型的已旧,例如int *pint
秸苗、struct studentdata *psdata
等,只有指明了指針?biāo)傅臄?shù)據(jù)類型运褪,編譯器才能為指針分配或預(yù)計(jì)分配相應(yīng)大小的存儲空間惊楼,指針的算術(shù)運(yùn)算如pint++等才是有意義的玖瘸。因此,定義了某種類型的指針之后檀咙,除非使用強(qiáng)制類型轉(zhuǎn)換店读,那么它只能指向相應(yīng)數(shù)據(jù)類型的變量或常量,不同類型的指針或數(shù)據(jù)之間不可混用攀芯。所以指針的類型實(shí)際上是一種身份標(biāo)志的作用屯断。
函數(shù)指針如何表明自己的身份呢?為了避免混亂侣诺,必須也要作出相應(yīng)規(guī)定殖演,不同函數(shù)的函數(shù)指針不能混用。例如年鸳,int func1(int arg11, char arg12)
與int func2(char arg)
的函數(shù)指針就不能混用趴久,要定義可以指向func1的函數(shù)指針應(yīng)該這樣:
int (*pfunc1)(int, char) = func1;
定義可以指向func2的函數(shù)指針則該如下:
int (*pfunc2)(char) = func2;
從函數(shù)指針的定義可以看出搔确,函數(shù)指針的類型實(shí)際上是由函數(shù)簽名決定的彼棍。函數(shù)簽名就象是函數(shù)的身份證,一個函數(shù)的函數(shù)簽名是獨(dú)一無二的膳算,具有相同函數(shù)簽名的函數(shù)實(shí)際上就是同一函數(shù)座硕。函數(shù)簽名包括函數(shù)名、函數(shù)形參類型的有序列表和函數(shù)返回值類型涕蜂。
一個函數(shù)指針的定義規(guī)定了它只能指向特定類型的函數(shù)华匾。如果兩個函數(shù)的形參列表和返回值類型相同,只有函數(shù)名和函數(shù)體不同机隙,則可以使用相同類型的函數(shù)指針蜘拉。
例如,如果還有一個函數(shù)int func3(char arg)
有鹿,則上面定義的可以指向函數(shù)func2
的函數(shù)指針也可以用于指向func3
旭旭,即:
pfunc2 = func3;
再使用pfunc2(char ARG)
就可以調(diào)用函數(shù)func3
,這時指令計(jì)數(shù)器(PC)指向函數(shù)入口葱跋,從此開始執(zhí)行函數(shù)體代碼持寄。
如何使用函數(shù)指針?
- 定義合適類型的函數(shù)指針變量:
int (*pfunc)(int, int)
; - 給函數(shù)指針變量賦值年局,使它指向某個函數(shù)入口:
int example(int, int)
;pfunc = example;
/將函數(shù)入口地址賦給函數(shù)指針變量/ - 使用函數(shù)指針來調(diào)用相應(yīng)的函數(shù)际看;
retval = pfunc(10, 16);
或者:retval = (*pfunc)(10, 16);
上面兩句都與retval = example(10, 16);等價。
理解:一個指針變量p實(shí)際上也和普通的變量一樣矢否,要占存儲空間(通常與平臺的虛擬地址一樣寬),也有其自身的存儲地址&p脑溢;不同的是僵朗,在指針變量p的值有特殊的意義赖欣,它是另外一個變量或常量的地址值,也就是說验庙,在地址為&p的存儲單元上存放著另外一個數(shù)據(jù)的地址顶吮。因此,p實(shí)際上是將p看作它指向的數(shù)據(jù)的地址來使用粪薛,操作符是引用相應(yīng)地址中的數(shù)據(jù)悴了,也就是對地址為p的存儲單元中存放的數(shù)據(jù)進(jìn)行操作。
為什么要使用函數(shù)指針违寿?
前面介紹了函數(shù)指針的基本知識和使用規(guī)范湃交。下面介紹函數(shù)指針的實(shí)際用途。不過首先要對前面的知識再做一個補(bǔ)充藤巢,因?yàn)橄旅娴膽?yīng)用很可能用到這一特性搞莺。前面指出,除函數(shù)名之外的函數(shù)簽名內(nèi)容(函數(shù)返回值類型和形參列表)決定了函數(shù)指針的類型掂咒。實(shí)際上還有一種特殊的或說通用的函數(shù)指針才沧,在定義這類函數(shù)指針時,只需要指定函數(shù)返回值類型绍刮,而留空形參列表温圆,這樣就可以指向返回值類型相同的所有函數(shù)。例如:
int (*pfunc)();
這樣定義的pfunc
就可以指向前面提到的func1
和func2
孩革,因?yàn)樗麄兌挤祷卣椭怠?br>
注意:int (*pfunc)()
與int (*pfunc)(void)
不是一回事捌木,后者不允許接受任何參數(shù)。
函數(shù)指針最常見的三個用途是:
作為參數(shù)傳遞給其他函數(shù)嫉戚。這樣可以把多個函數(shù)用一個函數(shù)體封裝起來刨裆,得到一個具有多個函數(shù)功能的新函數(shù),根據(jù)傳遞的函數(shù)指針變量值的不同彬檀,執(zhí)行不同的函數(shù)功能帆啃。這是函數(shù)嵌套調(diào)用難以實(shí)現(xiàn)的。參數(shù)的傳遞可以由程序員設(shè)定窍帝,也可以由用戶輸入讀取努潘,因此具有較大的靈活性和交互性。另外還可以用于回調(diào)函數(shù)坤学。使用void配合疯坤,還可以將對不同數(shù)據(jù)類型的數(shù)據(jù)進(jìn)行相同處理的多個函數(shù)封裝為一個函數(shù),增強(qiáng)函數(shù)的生命力深浮。
用于散轉(zhuǎn)程序压怠。這種程序首先建立一個函數(shù)表(實(shí)際上是一個函數(shù)指針數(shù)組),表中存放了各個函數(shù)的入口地址(或函數(shù)名)飞苇,根據(jù)條件的設(shè)定來查表選擇執(zhí)行相應(yīng)的函數(shù)菌瘫。這樣也可以將多個函數(shù)封裝為一個函數(shù)或者程序蜗顽,散轉(zhuǎn)分支條件可以由程序員設(shè)定,也可以由用戶輸入讀取雨让,甚至是外設(shè)的某種特定狀態(tài)(這種狀態(tài)可以是不受人為控制的)雇盖。
實(shí)現(xiàn)C的面向?qū)ο蟮念惖姆庋b。C語言中的struct與C++中的class有很大不同栖忠,除了缺省的成員屬性外(struct的成員缺省為public的崔挖,可隨意使用,而class成員缺省為private的)庵寞,struct還很難實(shí)現(xiàn)類成員函數(shù)的封裝狸相。struct的成員一般都是數(shù)據(jù)成員,而非函數(shù)成員皇帮。因此卷哩,為了在C語言中,為某個struct定義一套自己的函數(shù)對結(jié)構(gòu)數(shù)據(jù)成員進(jìn)行操作属拾,可以在struct結(jié)構(gòu)體中增加函數(shù)指針變量成員将谊,在初始化時使它指向特定函數(shù)即可。
基礎(chǔ)的理論知識就介紹這些渐白,下面來舉例分析四則運(yùn)算計(jì)算器demo中對函數(shù)指針的運(yùn)用尊浓。
首先是定義加減乘除的基本運(yùn)算如下。這是最基礎(chǔ)的運(yùn)算纯衍,不需要考慮調(diào)用的順序栋齿。
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;
}
然后觀察上面的函數(shù),發(fā)現(xiàn)除了函數(shù)名不一樣外襟诸,返回值與參數(shù)類型都是一樣的瓦堵,所以可以用一個函數(shù)指針來指向它們。
函數(shù)指針的聲明如下所示:
typedef long long int (*FUNC)();
FUNC pfunc;
首先定義了一個函數(shù)指針的類型:FUNC
,這樣我們就可以更加方便的使用這個類型來聲明函數(shù)指針變量了歌亲。下面的FUNC pfunc;
就是聲明了一個名為pfunc的函數(shù)指針變量菇用。
最后就是求和運(yùn)算了
double calculator(long long x,long long y,FUNC func){
double result;
result = (*func)(x,y);
return result;
}
其中的func
函數(shù)指針會根據(jù)我們所點(diǎn)擊的運(yùn)算符的不同而指向不同的函數(shù),這樣就實(shí)現(xiàn)了一個非常簡單的計(jì)算器了陷揪。
最后附上demo地址
參考資料:
http://www.360doc.com/content/13/1104/12/13670635_326518097.shtml