第五十一章 指針(六)
「哦,曉得了?!?p> 老爹說(shuō)這些的確是現(xiàn)實(shí)中存在的問(wèn)題,班上就有很多同學(xué)的家長(zhǎng)不讓他們玩兒手機(jī)和電腦,說(shuō)是會(huì)影響到學(xué)習(xí)。一方為了防止對(duì)方玩兒手機(jī),另一方想突破對(duì)方的封鎖,于是雙方展開了一場(chǎng)場(chǎng)斗智斗勇,各有輸贏。
這大概就和老爹說(shuō)的一樣吧,不能單純地從某個(gè)方面來(lái)看待這件事情。學(xué)習(xí)成績(jī)好的人有玩兒手機(jī)和電腦的,學(xué)習(xí)成績(jī)不好的也有不玩兒手機(jī)和電腦的,關(guān)鍵還是在于使用者如何使用吧。
當(dāng)然了,一般來(lái)說(shuō)小孩兒的自制能力比較差,這個(gè)時(shí)候就需要家長(zhǎng)合理的管控了。毋庸置疑的是,老爹在這一點(diǎn)做得非常好。
「好了好了,閑話就說(shuō)到這里,咱們還是繼續(xù)說(shuō)函數(shù)和指針之間那點(diǎn)事兒。其實(shí)函數(shù)和指針的關(guān)系也挺簡(jiǎn)單的,無(wú)非就三種情況:指針作為函數(shù)參數(shù),函數(shù)返回值為指針。
其中指針作為函數(shù)參數(shù)又有兩種情況,第一是指針變量指向的是數(shù)據(jù),如int、double以及結(jié)構(gòu)體或者枚舉這種,還有一種特例,那就是指針變量指向的是一個(gè)函數(shù),我們把一個(gè)函數(shù)A的指針作為參數(shù)傳給另一個(gè)函數(shù)B,這樣在函數(shù)B中就可以通過(guò)這個(gè)指針調(diào)用函數(shù)A了,這就是所謂的回調(diào)函數(shù)。
概念性的東西咱們就先說(shuō)到這里,還是以實(shí)際的例子來(lái)說(shuō)明吧。
首先說(shuō)普通指針作為函數(shù)的參數(shù),其實(shí)這種情況你們已經(jīng)見識(shí)過(guò)了,我一開始說(shuō)到的swap函數(shù)就是這樣了。我相信通過(guò)之前的講解,你們對(duì)這個(gè)函數(shù)的理解已經(jīng)算是比較透徹了,這里我們就不再贅述。
所以我們接下來(lái)看看當(dāng)指針作為一個(gè)函數(shù)的返回值的這種情況,比如說(shuō)我們來(lái)實(shí)現(xiàn)一個(gè)函數(shù),功能是把給定的字符串轉(zhuǎn)成大寫的,并把轉(zhuǎn)換后的字符串返回。
由于字符串是一個(gè)char*,正好滿足指針作為返回值。
typedef char* String;
String toUpperCase(String str)
{
const char delta ='A'-'a';
String temp = str;
while (*temp !='\0')
{
if ('a'<=*temp &&*temp <='z')
{
*temp =*temp + delta;
}
temp++;
}
return str;
}
因?yàn)槲覀円呀?jīng)給char*定義了一個(gè)String別名,所以我們?cè)诖a中就使用它,比較利于閱讀理解。
在使用這個(gè)函數(shù)的時(shí)候,我們就能夠體會(huì)到char*和char[]的區(qū)別了。
String str =“hello world“;
String result = toUpperCase(str);
將會(huì)報(bào)錯(cuò),而
char[] str =“hello world“;
String result = toUpperCase(str);
卻能夠得到正確的結(jié)果,至于為什么會(huì)這樣,結(jié)合字符串是常量的概念,以你們現(xiàn)在的水品來(lái)說(shuō),應(yīng)該是能夠明白這其中的道理的?!?p> 老爹笑了笑。
「第一種情況str是一個(gè)指針,它直接指向了一個(gè)常量,而toUpperCase()卻試圖修改一個(gè)常量的值,所以在運(yùn)行的時(shí)候報(bào)錯(cuò)了。
但是第二種情況str是一個(gè)數(shù)組,沒有任何限制,在函數(shù)中temp獲取了這個(gè)數(shù)組的指針地址,然后對(duì)其修改,自然不會(huì)出任何問(wèn)題?!?p> 小弦子不愧是小弦子,我都還沒有來(lái)得及嘗試運(yùn)行代碼,他居然憑空推算出來(lái)了結(jié)果,不佩服真的不行。
「嗯,不錯(cuò),的確是這樣。這就是指針作為返回值的用法,應(yīng)該沒有什么難度吧?」
若是按照老爹例程代碼來(lái)看,好像確實(shí)沒有什么難度。
「不過(guò)返回一個(gè)指針,一定要確保這個(gè)指針是可用的,如果你返回的指針地址是函數(shù)中的一個(gè)局部變量,就可能引發(fā)不可預(yù)知的錯(cuò)誤。
因?yàn)榫植孔兞吭诤瘮?shù)執(zhí)行結(jié)束后就被回收了,這時(shí)如果通過(guò)指針來(lái)訪問(wèn)一個(gè)被釋放的了內(nèi)存,會(huì)引發(fā)的后果是真的無(wú)法預(yù)料?!?p> 我和小弦子都深以為然地點(diǎn)了點(diǎn)頭,畢竟類似的問(wèn)題老爹已經(jīng)強(qiáng)調(diào)過(guò)好多遍了。
「好了,那我們就來(lái)講講最后一種情況,一個(gè)指針指向一個(gè)函數(shù)。先說(shuō)如何定義一個(gè)函數(shù)指針:
返回值類型(*fp_name)(參數(shù)類型1,參數(shù)類型2……)
例如:
int (*fp)(int,int)
就聲明了一個(gè)函數(shù)指針變量,它可以指向返回值為int類型,參數(shù)列表為兩個(gè)int類型的所有函數(shù)。
int max(int a, int b)
{
return a > b ? a : b;
}
int min(int a, int b)
{
return a > b ? b : a;
}
比如說(shuō)這里的max和min都符合條件,所以我們就可以這樣:
int (*fp_max)(int,int);
int (*fp_min)(int,int);
fp_max = max;
fp_min = min;
如果要使用函數(shù)指針來(lái)執(zhí)行指向的函數(shù),就需要這樣:
int maxValue =(*fp_max)(3,4);
int minValue =(*fp_min)(1,2);」
「看上去就好復(fù)雜的樣子……」
我皺著眉頭看著老爹幻燈片上敲出來(lái)的這些內(nèi)容,突然覺得腦袋運(yùn)轉(zhuǎn)都幾乎停滯了。
「既然如此,那我們就來(lái)解析一下。我們?cè)诼暶饕粋€(gè)變量時(shí),會(huì)使用int、
double等類型描述符,其本質(zhì)在于告訴計(jì)算機(jī)這個(gè)變量的內(nèi)存占用情況,以及讀寫時(shí)的規(guī)則。是這樣的吧?」
我和小弦子點(diǎn)了點(diǎn)了點(diǎn)頭。
「那么我們聲明這些變量的指針的時(shí)候,是不是也要聲明使用指針讀寫數(shù)據(jù)時(shí)的規(guī)則呢?」
「嗯嗯~」
「同樣的,我們?cè)诙x一個(gè)函數(shù)的時(shí)候,是不是要告訴計(jì)算機(jī)這個(gè)函數(shù)的返回值、參數(shù)列表?那么同樣的,我們聲明一個(gè)函數(shù)指針也要具備這些啊。
在聲明一個(gè)指針變量的時(shí)候我們是不是會(huì)用到『*』?」
「是的~」
「同理,函數(shù)指針?biāo)彩且粋€(gè)指針,那它憑哪樣搞特殊不使用『*』來(lái)自報(bào)家門,告訴計(jì)算機(jī)自己是一個(gè)指針變量呢?
再問(wèn),當(dāng)我們要訪問(wèn)一個(gè)指針的指向的地址中的內(nèi)容是,會(huì)使用什么?」
「*」
「那不就結(jié)了么?那函數(shù)指針雖然指向的是一個(gè)函數(shù),那么我們要訪問(wèn)這個(gè)函數(shù),不得也用『*』么?
按照這個(gè)思路,你們?cè)偃タ纯瓷厦娴拇a,是不是就能夠明白其中的含義了呢?」
「臥槽,有道理啊!」
還別說(shuō),經(jīng)過(guò)老爹這么解釋,似乎還真的有那么一些道理。
「其實(shí)啊,這種格式看上去的確是麻煩,而且fp_max和fp_min除了指針名字不一樣之外,其它都完全一樣,這不是重復(fù)代碼么?所以我們得像個(gè)辦法把這個(gè)問(wèn)題給解決了,不然以后我們要聲明很多類似函數(shù)指針的時(shí)候還不得累死。
要是后面再修改,比如說(shuō)修改返回值類型,或者修改參數(shù)列表類型……」