一、指針簡介
什么是指針?相信大家對這個問題其實并不陌生,對指針的概念也不會很模糊,在這里我也大概介紹一下。
1.1、指針定義
指針 其實可以理解為某一對象的內(nèi)存地址,指針變量就是用來存放內(nèi)存地址的變量。由此我們發(fā)現(xiàn),其實指針和地址是一種相互對應(yīng)關(guān)系。
1.2、指針變量
指針變量 上面我們談到指針變量是存放地址的變量,故此我們也可稱指針變量我地址變量。在聲明指針變量時,我們通常用數(shù)據(jù)類型符++標(biāo)識符(即變量名)的格式定義。如int p1,char* p2等形式。 需要說明的是類型說明符表示指針變量所指向變量的數(shù)據(jù)類型;**" * "** 表示這是一個指針變量;變量名表示定義的指針變量名,其值是一個地址。例如:int*p1則表示 p1 是一個int型的指針變量,它的值是某個整型變量的地址。
1.2.1、指針類型大小
在 C/C++語言中,指針一般被認為是指針變量,指針變量的內(nèi)容存儲的是其指向的對象的首地址,指向的對象可以是變量(指針變量也是變量),數(shù)組,函數(shù)等占據(jù)存儲空間的實體。
以上我們知道指針變量是存儲地址的,且其可以指向char型,int型等存儲在占據(jù)一定空間的實體。以32位機為例,在32位機的存儲中,int型,char型變量都分別占據(jù)不同的字節(jié)。
在這里插入圖片描述
那么指針變量的大小如何呢?通過驗證我們發(fā)現(xiàn)其實指針類型的占位都是4/8個字節(jié),如下圖所示。由此我們知道,地址值在內(nèi)存中的存儲空間占4/8個字節(jié),這也是指針類型的大小了。
在這里插入圖片描述
1.2.2、指針注意事項
我們在對指針進行聲明時,切記不可把一個數(shù)賦值給指針變量。如下:
?
????int*?p?=?200;//erro ????int?a?=?20; ????int*?p1; ????*p1?=?a;//erro ????//或者 ????int?*p2; ????p2?=?200;//erro ????printf("%d ",*p); ????printf("%d ",*p1); ????printf("%d ",*p2); 12345678910在這里插入圖片描述
?
在以上例子中分別定義了一個指針變量 p,p1,p2,但是不能直接把 200 賦值給指針變量 p2,也不可把一個賦值為20的整型變量 a,再賦值給指針變量p1,p1中只能用來存放整型變量的地址,而不能直接把整型變量a賦值給這個指針變量 p1。但可以把a的地址賦值給 p1;即可改成 **p1 = &a;**同時直接在聲明指針變量p時也是不可以賦給其200這個整型數(shù)值的。
注意,這里雖然只有兩個報錯,原因是在wind環(huán)境下,語法的不嚴(yán)格導(dǎo)致的,在Linux環(huán)境下其實是報錯的情況。
1.2.3、野指針
在使用指針時,野指針是我們需要避免的一個重要點。
C 語言中指針初始化是指給所定義的指針變量賦初值。指針變量在被創(chuàng)建后,如果不被賦值,他的缺省值是隨機的,它的指向是不明確的,這樣的指針形象地稱為 “ 野指針 ”。野指針是很危險的,容易造成程序出錯,且程序本身無法判斷指針指向是否合法。
指針變量初始化時避免野指針的方法:可以在指針定義后,賦值 NULL 空值,也可寫成如下形式:
p=0 或 p=‘?’
這兩種形式和 p = NULL 是等價的
如int * p;
p = NULL;
上面兩行代碼的含義是,指針變量 p 被賦值為空。雖然定義了一個指針變量,但是它并不指向任何存儲空間。
以上是我們在使用指針時需要注意的內(nèi)容。同時也應(yīng)該避免指針越界等問題,這以后細說。
二、函數(shù)
2.1、函數(shù)簡介
這里的函數(shù)是同我們理解中的數(shù)學(xué)函數(shù)有一定區(qū)別。計算機上的函數(shù)一般是指一段可以直接被另一段程序或代碼引用的程序或代碼。也叫做子程序,或在OOP中稱為方法,如java等面向?qū)ο蟮某绦蛟O(shè)計語言。
在C語言中,函數(shù)的定義同大多數(shù)變量定義意義,都需要聲明,給定類型等且在定義非void型函數(shù)時,需要有返回值。值得注意的是,函數(shù)名后的()內(nèi)可進行參數(shù)的定義,如定義一個int型的main函數(shù),可接收兩個參數(shù)時,參照如下定義:
?
//?定義main函數(shù) int?main(int?x,int?y) { ?printf("%d ",x+y); ?return?0; } 123456
?
2.2、函數(shù)地址
函數(shù)有地址嗎?我們先來看下面這幾行代碼。在main函數(shù)中,通過調(diào)用add函數(shù)得到一個返回值,這個返回值被在main函數(shù)進行打印。我們知道,在聲明一個變量時,內(nèi)存空間都會為該變量分配一個相應(yīng)地址值,同樣地,在聲明函數(shù)使,內(nèi)存也會為函數(shù)分配一個地址值,如下代碼中的add函數(shù),其在64位機下的內(nèi)存地址值為0000000000401550;
?
//定義加法函數(shù)add() int?add(int?x,int?y) { ?int?z?=?0; ?z?=?x?+?y; ?return?z; } //?定義main函數(shù) int?main() { ?int?a?=?10; ?int?b?=?20; ?printf("%d ",add(a,b));//30 ?printf("%d ",&add);//0000000000401550,這是個地址值,這里用&add和直接用add是一樣的,沒區(qū)別。不同于數(shù)組! ?return?0; } 12345678910111213141516
?
綜上我們可以很清楚的知道,在函數(shù)調(diào)用的時候其實就像在主函數(shù)中找到了被調(diào)函數(shù)的地址作為入口進行傳參,進而能很好幫我們解決很多問題。而函數(shù)指針就是基于這樣一個思想來實現(xiàn)指針變量存放函數(shù)地址的。
2.3、回調(diào)函數(shù)
最為編程語言中的一種機制,回調(diào)函數(shù)就是一個被作為參數(shù)傳遞的函數(shù)。在C語言中,回調(diào)函數(shù)只能使用函數(shù)指針實現(xiàn)。
對于回調(diào)函數(shù),經(jīng)典的如qsort()函數(shù),其格式如下:
?
void?qsort(void*?base, ????????????????size_t?num, ????????????????size_t?width, ????????????????int(*cmp)(const?void?*e1,const?void*?e2) ????????????????);? 12345
?
關(guān)于上述代碼,這里不作細講。
回調(diào)函數(shù)在實際中有許多作用。假設(shè)有這樣一種情況:我們要編寫一個庫,它提供了某些排序算法的實現(xiàn)(如冒泡排序、快速排序、shell排序、shake排序等等),為了能讓庫更加通用,不想在函數(shù)中嵌入排序邏輯,而讓使用者來實現(xiàn)相應(yīng)的邏輯;或者,能讓庫可用于多種數(shù)據(jù)類型(int、float、string),此時,可以使用函數(shù)指針,并進行回調(diào)。
可見回調(diào)函數(shù)于函數(shù)指針的關(guān)系極為密切。在此介紹回調(diào)函數(shù),當(dāng)然也是方便下文很好的理解函數(shù)指針的一些應(yīng)用,對于回調(diào)函數(shù)感興趣的讀者朋友可以查閱相關(guān)資料進行了解。
注意:以上說明僅為本次文章做個鋪墊,下面進入正題。
三、函數(shù)指針
顧名思義, 函數(shù)指針就是函數(shù)與指針的結(jié)合,相信大多數(shù)初學(xué)者可能也同我一樣,開始接觸函數(shù)指針時會疑惑,這是函數(shù)還是指針呢?下面我們進行淺析。
3.1、函數(shù)指針定義
函數(shù)指針是指向函數(shù)的指針變量。本質(zhì)上“函數(shù)指針”就是指針變量,這點是需要強調(diào)的,只不過該指針變量指向函數(shù)。其類型就由函數(shù)類型所決定。函數(shù)在C編譯過程中,編譯器會給每一個函數(shù)分配一個入口地址,該入口地址就是函數(shù)指針?biāo)赶虻牡刂?。有了指向函?shù)的指針變量后,可用該指針變量調(diào)用函數(shù),就如同用指針變量可引用其他類型變量一樣,在這些概念上是大體一致的。函數(shù)指針有兩個用途:調(diào)用函數(shù)和做函數(shù)的參數(shù)。
3.2、函數(shù)指針聲明
函數(shù)指針的聲明問題也是一直困擾大家的一個點,就如同對數(shù)組指針的聲明一樣。需要明確的是,函數(shù)指針其本質(zhì)是指針,所以在聲明時應(yīng)該注意,必須體現(xiàn)指針這一概念。下面我們來看這兩個聲明的異同。
?
//int?fun(int??,int??);//此為一個函數(shù),這點是毫無疑問的,關(guān)鍵在于以下兩條語句 int??*?fun(int??,??int???); int??(*fun)(int??,?int??); 123
?
以上“函數(shù)”的聲明唯一的不同點在于,*fun有無括號的問題,正因為這個小括號,讓我們認識了函數(shù)和指針組合的強大。
首先,對于以上兩種寫法的問題,根本在于括號的優(yōu)先級。第一條語句在聲明中,*fun沒有括號,其優(yōu)先與fun后面的括號結(jié)合,在不考慮 * 的情況下,這是一個指向int型的函數(shù),但有了 * 后,* 與int結(jié)合,形成了 int* 類型,此時這個fun函數(shù)指向的是 int* 類型。這時我們稱第一條語句為指針函數(shù),其是指向指針的函數(shù),這后續(xù)再討論。
有如上基礎(chǔ),對于第二條語句我們就很容易理解了。通過將 fun 與括號中的 “ * ” 強制組合在一起,表示定義的 fun 是一個指針,然后與下面的 “ ( ) ” 再次組合,表示的是該指針指向一個函數(shù),括號里表示為 int 類型的參數(shù),最后與前面的 int 組合,此處 int 表示該函數(shù)的返回值。因此,fun 是指向函數(shù)的指針,該函數(shù)的返回值為 int。函數(shù)指針與指針函數(shù)的含義大不相同。函數(shù)指針本身是一個指向函數(shù)的指針。指針函數(shù)本身是一個返回值為指針的函數(shù)。
綜上,函數(shù)指針的聲明就顯而易見了,其格式為:
返回值的數(shù)據(jù)類型 ( * 函數(shù)名)(參數(shù)1,參數(shù)2,…);
如果上述感覺有點啰嗦,我們可以這樣來理解:
通過上述代碼可知函數(shù)有一個地址值,那么如果我們想存放這個地址值,該如何存放呢??赡苡腥藭氲降氖侵苯佑煤瘮?shù)來接收,如int pf(int x,inty) = add;用函數(shù)來接收函數(shù),確實是個不錯的想法,但我們說地址是一個指針變量,應(yīng)該用指針來接收地址值,所以很容易想到直接在int 后加上 * 這個符號表示指針,這時有int *pf(int x,inty) = add;但真的是這樣嗎?
有人可能會發(fā)現(xiàn)pf(int x,inty) = add;其實是一個函數(shù),返回類型是int*,這認同我們前面認識的其他指針類型一樣。沒錯,這就是關(guān)鍵。由此我們探究出了函數(shù)指針的寫法,即先讓pf變量是個指針,用來接收add函數(shù)的地址,再讓其指向int型。所以得到了int (*pf)(int x,inty) = add;這樣一個完美的寫法。
3.3、函數(shù)指針的應(yīng)用
上面我們提到,函數(shù)指針有兩個用途,即調(diào)用函數(shù)和做函數(shù)的參數(shù),下面我們以此進行一些淺析。
3.3.1函數(shù)指針的調(diào)用
當(dāng)我們需要把一個函數(shù)作為參數(shù)傳給其他參數(shù)的時候就必須使用函數(shù)指針才能很好的完成。下面的程序說明了函數(shù)指針調(diào)用函數(shù)的方法:這段代碼中主要執(zhí)行過程主要是把add函數(shù)的地址賦給pf指針,而后通過(*pf)進行解引用找到原來的add函數(shù),再對其進行傳參3和5,最后將結(jié)果賦給c,再進行打印。得到下圖結(jié)果。
?
//定義加法函數(shù)add() int?add(int?x,int?y) { ?int?z?=?0; ?z?=?x?+?y; ?return?z; } //?定義main函數(shù) int?main() { ?int?(*pf)(int?x,int?y)?=?add; ?int?c?=?(*pf)(5,8); ?printf("%d ",c);//13 ?return?0; } 123456789101112131415
?
pf 是指向函數(shù)的指針變量,【(*pf)(int x,int y)這個里邊的參數(shù) x和y 可以省略】所以可把函數(shù) add() 賦給pf作為 pf 的值,即把add()的入口地址賦給pf,之后就可以用pf對該函數(shù)進行調(diào)用,實際上pf和add都指向同一個入口地址,不同就是pf是一個指針變量,它可以指向任何函數(shù)。在程序中把哪個函數(shù)的地址賦給它,它就指向哪個函數(shù)。而后用指針變量調(diào)用它,因此可以先后指向不同的函數(shù)。不過注意,指向函數(shù)的指針變量沒有++和–運算,用時要小心。
3.3.2函數(shù)的傳參
上面提到函數(shù)指針不僅可以調(diào)用,還能進行傳參。函數(shù)傳參的本質(zhì)就是把需要傳的函數(shù)當(dāng)成一個參數(shù),然后傳給被調(diào)函數(shù)使用。下面依然通過代碼來演示,并得到下圖結(jié)果。
?
//定義加法函數(shù)add() void?chuancan(int?(*pf)(int?x,int?y),int?x,int?y) { ?printf("這是函數(shù)指針傳的使用演示 "); ?int?c?=?pf(x,y); ?printf("%d ",c);//5 } int?add(int?x,int?y) { ?int?z?=?0; ?z?=?x?+?y; ?return?z; } //?定義main函數(shù) int?main() { ?int?(*pf)(int?x,int?y)?=?add; ?chuancan(pf,2,3); ?return?0; } 1234567891011121314151617181920
?
我們把函數(shù)pf當(dāng)做一個參數(shù)傳到了函數(shù)chuancan里面去,也可以實現(xiàn)對函數(shù)pf的調(diào)用,顯然,這樣的做法讓我們見識到了函數(shù)指針的極大魅力。這就意味著,當(dāng)我們有很多類似的數(shù)量極大的函數(shù)時,我們怎么管理和使用,這是個棘手的問題,這時候我們可以用函數(shù)指針去管理和調(diào)用他們,把他們都裝進一個函數(shù)指針數(shù)組,這無疑讓我們的算法實現(xiàn)了優(yōu)化。
在這里插入圖片描述
四、指針函數(shù)
當(dāng)讀者朋友讀到這一節(jié)時,相信你對指針函數(shù)已經(jīng)不再陌生,因為在前文我們也已然提過這一概念。接下來我們將對指針函數(shù)作進一步淺析。
4.1指針函數(shù)的定義
如你所想,指針函數(shù),其實就是帶指針的函數(shù),其本質(zhì)就是函數(shù),這點并不稀奇,稀奇的是它的返回類型是某一類型的指針。
4.2 指針函數(shù)的聲明
指針函數(shù)的聲明如下:類型標(biāo)識符?*函數(shù)名(參數(shù)表)?;
?
int??*?fun(int??,??int?,...?);//這是一個指針函數(shù) 1
?
下面是關(guān)于指針函數(shù)的相關(guān)聲明格式解釋。
在 類型名 *函數(shù)名(函數(shù)參數(shù)列表); 格式中,后綴運算符括號“()”表示這是一個函數(shù),其前綴運算符星號“ * ”表示此函數(shù)為指針型函數(shù),其函數(shù)值為指針,即它帶回來的值的類型為指針,當(dāng)調(diào)用這個函數(shù)后,將得到一個“指向返回值為…的指針(地址),“類型名” 表示函數(shù)返回的指針指向的類型”。
“(函數(shù)參數(shù)列表)”中的括號為函數(shù)調(diào)用運算符,在調(diào)用語句中,即使函數(shù)不帶參數(shù),其參數(shù)表的一對括號也不能省略。其示例如下:
int *pfun(int, int);
由于“*”的優(yōu)先級低于“()”的優(yōu)先級,因而pfun首先和后面的“()”結(jié)合,也就意味著,pfun是一個函數(shù)。即:
int *(pfun(int, int));
接著再和前面的“ * ”結(jié)合,說明這個函數(shù)的返回值是一個指針。由于前面還有一個int,也就是說,pfun是一個返回值為整型指針的函數(shù)
相信到此讀者朋友應(yīng)該對指針函數(shù)的定義,聲明及其注意點有了深度的認識,需要注意的是:函數(shù)指針聲明為指針,它與變量指針不同之處是,它不是指向變量,而是指向函數(shù)。
下面將對指針函數(shù)應(yīng)用方面作進一步的淺析。
4.3指針函數(shù)的應(yīng)用
指針函數(shù)既然本質(zhì)上是函數(shù),那么其用法與函數(shù)的一般用法基本無異。都需要對其進行聲明,調(diào)用等。請看一下例子,結(jié)果如下圖:
?
char?*?my_strcat(char*?dest,const?char?*?src) { ????char*?ret?=?dest;? ????assert(dest?!=?NULL); ????assert(src); ????//1.找到目的字符串的? ????while(*dest?!=?'?') ????{ ????????dest++; ????} ????//2.追加 ????while?(*dest++?=?*src++) ????{ ????????;/*?code?*/ ????} ????return?ret; ???? } int?main() { ????char?arr1[30]?=?"abcdefghi";//這里目的地要足夠大,否則追加時容易出錯 ????char?arr2[]?=?"abtc"; ????char?arr11[]?=?"abcdefghi"; ????char?arr22[]?=?"abtc"; ????char*?arr3?=?my_strcat(arr1,arr2); ????printf("這是自記實現(xiàn)的追加字符串結(jié)果:%s ,這是系統(tǒng)調(diào)用strcat函數(shù)追加的結(jié)果:%s ",arr3,strcat(arr11,arr22)); ????return?0; } 12345678910111213141516171819202122232425262728
?
在本例中,聲明char * my_strcat(char* dest,const char * src)這樣一個返回值類型為char*的指針函數(shù),參數(shù)為char* dest,const char * src,該函數(shù)用來實現(xiàn)對兩個字符串的后綴字符追加問題。當(dāng)我們在主函數(shù)調(diào)用my_strcat函數(shù)時,其實和一般函數(shù)調(diào)用并無區(qū)別,值得一提的是,當(dāng)我們想要把被調(diào)函數(shù)返回給某個變量時,記住返回類型即可,如char* arr3 = my_strcat(arr1,arr2);
追加字符串
指針函數(shù)的返回類型可以是任何基本類型和復(fù)合類型。返回指針的函數(shù)的用途十分廣泛。事實上,每一個函數(shù),即使它不帶有返回某種類型的指針,它本身都有一個入口地址,該地址相當(dāng)于一個指針。比如函數(shù)返回一個整型值,實際上也相當(dāng)于返回一個指針變量的值,不過這時的變量是函數(shù)本身而已,而整個函數(shù)相當(dāng)于一個“變量”。
當(dāng)然了,在函數(shù)調(diào)用過程中,我們會發(fā)現(xiàn),每當(dāng)封裝一個函數(shù),相當(dāng)于把這個函數(shù)給定死了,今后再使用時發(fā)現(xiàn)只能使用固定返回類型的函數(shù),這是令人頭大的問題,為此c語言還是很友好的,在這里我們簡單介紹遇到這種情況該如何解決。
一般而言,為了讓被調(diào)函數(shù)具有一般通用性,我們可以將其返回值類型聲明為 void * ,這樣就極大方便我們在今后的使用了。
五、函數(shù)指針與指針函數(shù)的區(qū)別
經(jīng)過上述探索,我們悄然發(fā)現(xiàn),函數(shù)指針和指針函數(shù)的區(qū)別依然明了。在這里需要總結(jié)一下即可:
1、指針函數(shù): 即帶指針的函數(shù),其本質(zhì)是一個函數(shù),函數(shù)返回是某一類型的指針。聲明如ret * fun(ret ,ret ,...);
2、函數(shù)指針: 即指向函數(shù)的指針變量,其本質(zhì)是一個指針變量。指向函數(shù)的入口地址,可以通過它來調(diào)用函數(shù)。聲明如ret (*fun)(ret ,ret ,...);
綜上,二者在定義,寫法及用法方面都存在不同,至于對二者更進一步的學(xué)習(xí),還請各位讀者朋友查詢相關(guān)資料進行深度學(xué)習(xí)了。鑒于個人技術(shù)學(xué)淺,存在不足之處,歡迎指教。
評論
查看更多