理解函數(shù)和指針的結(jié)合使用,需要理解程序棧。大部分現(xiàn)代的塊結(jié)構(gòu)語言,比如C,都用到了程序棧來支持函數(shù)的運(yùn)行。調(diào)用函數(shù)時,會創(chuàng)建函數(shù)的棧幀并將其推到程序棧上。函數(shù)返回時,其棧幀從程序棧上彈出。
在使用函數(shù)時,有兩種情況指針很有用。一種是將指針作為參數(shù)傳遞給函數(shù),函數(shù)可以修改指針?biāo)玫臄?shù)據(jù),可以高效的傳遞大塊數(shù)據(jù)。另一種是聲明函數(shù)指針。
程序的棧和堆
程序的棧和堆是C程序的重要運(yùn)行時元素。程序棧是支持函數(shù)執(zhí)行的內(nèi)存區(qū)域,通常和堆共享一塊內(nèi)存區(qū)域。通常程序棧在區(qū)域的下部,堆在上部。程序棧存放棧幀(stack frame),棧幀有時候也稱為活躍記錄或活躍幀。棧幀存放函數(shù)參數(shù)和局部變量。堆則管理動態(tài)內(nèi)存。
調(diào)用函數(shù)時,函數(shù)的棧幀被推到棧上,棧向上“長出”一個棧幀。當(dāng)函數(shù)終止時,其棧幀從程序上彈出。棧幀所使用的內(nèi)存不會被清理,但有可能被另一個棧幀覆蓋。
棧幀由以下幾種元素組成:
* 返回地址。函數(shù)完成后要返回的程序內(nèi)部地址。
* 局部數(shù)據(jù)存儲。為局部變量分配的內(nèi)存。
* 參數(shù)存儲。為函數(shù)參數(shù)分配的內(nèi)存。
* 棧指針和基指針。運(yùn)行時系統(tǒng)用來管理棧的指針。
棧指針通常指向棧頂部。基指針(幀指針)通常存在并指向棧幀內(nèi)部的地址,比如返回地址,用來協(xié)助訪問棧幀內(nèi)部的元素。這兩個指針都是運(yùn)行時系統(tǒng)用來管理程序棧的地址。系統(tǒng)在創(chuàng)建棧幀時,將參數(shù)以跟聲明時相反的順序推到幀上,最后推入局部變量。C把塊語句當(dāng)做“微型”函數(shù),會在適當(dāng)?shù)臅r候?qū)⑵渫迫霔:蛷臈I蠌棾觥?
參數(shù)和局部變量的精確地址可能會變化,不過順序一般不變。這一點可以解釋參數(shù)和變量分配內(nèi)存的相對順序。將棧幀推到程序棧上時,系統(tǒng)可能會好近內(nèi)存,這種情況稱為棧溢出。要記住每個線程通常都有自己的程序棧,一個或多個線程訪問內(nèi)存中的同一個對象可能會導(dǎo)致沖突。
通過指針傳遞和返回數(shù)據(jù)
傳遞參數(shù)(包括指針)的時候,傳遞的是它們的值的副本。當(dāng)涉及大型數(shù)據(jù)結(jié)構(gòu)時,傳遞參數(shù)的指針會更高效。傳遞對象的指針意味著不需要復(fù)制對象,但可以通過指針訪問對象。
用指針來傳遞數(shù)據(jù)的一個主要原因是函數(shù)可以修改數(shù)據(jù)。如果不希望函數(shù)修改數(shù)據(jù),可以傳遞指向常量的指針。
從函數(shù)返回指針有兩種情況:一種是在函數(shù)內(nèi)部為指針分配內(nèi)存并返回指向內(nèi)存的指針,另一種是函數(shù)的調(diào)用者負(fù)責(zé)指針內(nèi)存的分配和釋放。從函數(shù)返回指針可能存在幾個潛在的問題:
* 返回未初始化的指針。
* 返回指向無效地址的指針。
* 返回局部變量的指針。
* 返回指針但是沒有釋放內(nèi)存。
設(shè)想一下在函數(shù)中聲明一個指針并為該指針分配內(nèi)存,但是在調(diào)用該函數(shù)的時候沒有使用一個指針變量來接受函數(shù)的返回值,因此我們丟失了該分配內(nèi)存的地址,導(dǎo)致內(nèi)存泄露。
局部數(shù)據(jù)指針是指當(dāng)函數(shù)返回的時候,(非動態(tài)分配的)局部數(shù)據(jù)的地址也就無效了。動態(tài)分配的內(nèi)存與此不同,存在于堆上,即使函數(shù)返回也不受影響。當(dāng)變量為static,就會在棧幀外部為其分配內(nèi)存,每次調(diào)用函數(shù)都會訪問同一塊內(nèi)存。
在將指針作為參數(shù)傳遞時,在使用之前判斷指針是否為NULL是個好習(xí)慣。 重復(fù)free一個指針會引發(fā)錯誤,在free之前也應(yīng)判斷指針是否為NULL。而且在釋放之后將指針置為NULL。
傳遞指針將允許我們修改指針指向的數(shù)據(jù),如果我們想修改的是指針怎么辦?當(dāng)然是傳遞指向指針的指針了。
函數(shù)指針
函數(shù)指針是持有函數(shù)地址的指針。通過將函數(shù)指針作為參數(shù)傳遞,使我們避免將事件處理程序硬編碼進(jìn)函數(shù)里,更加靈活。
void (*foo)();
? void是返回類型,foo是指針變量名,后邊的括號里是參數(shù)。
int(*fptr1)(int); int square(int num) { return num*num; } fptr1 = square; int n = 5; printf(" result is %d\n", fptr1(n));
? 使用函數(shù)的名字直接給函數(shù)指針賦值,它會把函數(shù)的地址賦給指針。也可以對函數(shù)名字使用取地址操作符,但是意思是一樣的,在此處上下文中會忽略取地址操作符。可以為函數(shù)指針聲明一個類型定義,通過類型定義來聲明函數(shù)指針。
typedef int (*funcptr)(int); funcptr ptr2; ptr2 = square;
? 傳遞函數(shù)指針只需把函數(shù)指針聲明為參數(shù)即可。
funcptr ptr2; ptr2 = square; int compute(funcptr squ, int num) { return squ(num); } compute(ptr2,n);
? 返回函數(shù)指針需要把函數(shù)的返回類型聲明為函數(shù)指針。函數(shù)指針數(shù)組可以基于某些條件選擇要執(zhí)行的函數(shù),聲明這種數(shù)組只要把函數(shù)指針聲明為數(shù)組的類型即可。
typedef int (*operation)(int,int); operation operations[128] = {NULL};//使用NULL來初始化所有數(shù)組元素
我們可以用相等和不等操作符來比較函數(shù)指針。不同的函數(shù)指針的長度不一定相等。雖然可以將一種函數(shù)指針轉(zhuǎn)換為另一種函數(shù)指針,但是應(yīng)該謹(jǐn)慎使用這種方法,因為運(yùn)行時系統(tǒng)不會驗證函數(shù)指針?biāo)玫膮?shù)是否正確。不應(yīng)該把函數(shù)指針轉(zhuǎn)換成void*指針。基本指針用作占位符,用來交換函數(shù)指針的值。一定要確保給函數(shù)指針傳遞的參數(shù)是正確的,否則會造成不確定行為。
總體來說,函數(shù)指針允許應(yīng)用程序根據(jù)不同的條件執(zhí)行不同的函數(shù),對 于 控制應(yīng)用 程 序內(nèi)的執(zhí) 行序列很有用。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯(lián)系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
