C++函数指针详解
來源:http://www.cnblogs.com/ggjucheng/archive/2011/12/13/2286391.html
指針的概念
指針是一個(gè)特殊的變量,它里面存儲的數(shù)值被解釋成為內(nèi)存里的一個(gè)地址。要搞清一個(gè)指針需要搞清指針的四方面的內(nèi)容:指針的類型,指針?biāo)赶虻念愋?#xff0c;指針的值或者叫指針?biāo)赶虻膬?nèi)存區(qū),還有指針本身所占據(jù)的內(nèi)存區(qū)。讓我們分別說明。?
int *ptr; char *ptr; int **ptr; int (*ptr)[3]; int *(*ptr)[4];
指針的類型
從語法的角度看,只要把指針聲明語句里的指針名字去掉,剩下的部分就是這個(gè)指針的類型。這是指針本身所具有的類型。
int *ptr; //指針的類型是int * char *ptr; //指針的類型是char * int **ptr; //指針的類型是 int ** int (*ptr)[3]; //指針的類型是 int(*)[3] int *(*ptr)[4]; //指針的類型是 int *(*)[4] 怎么樣?找出指針的類型的方法是不是很簡單? ?指針?biāo)赶虻念愋?/span>
當(dāng)你通過指針來訪問指針?biāo)赶虻膬?nèi)存區(qū)時(shí),指針?biāo)赶虻念愋蜎Q定了編譯器將把那片內(nèi)存區(qū)里的內(nèi)容當(dāng)做什么來看待。
從語法上看,你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉,剩下的就是指針?biāo)赶虻念愋汀@?#xff1a; ?
指針的類型(即指針本身的類型)和指針?biāo)赶虻念愋?/span>是兩個(gè)概念。當(dāng)你對C越來越熟悉時(shí),你會發(fā)現(xiàn),把與指針攪和在一起的“類型”這個(gè)概念分成“指針的類型”和“指針?biāo)赶虻念愋汀眱蓚€(gè)概念,是精通指針的關(guān)鍵點(diǎn)之一。我看了不少書,發(fā)現(xiàn)有些寫得差的書中,就把指針的這兩個(gè)概念攪在一起了,所以看起書來前后矛盾,越看越糊涂。
指針的值
指針的值是指針本身存儲的數(shù)值,這個(gè)值將被編譯器當(dāng)作一個(gè)地址,而不是一個(gè)一般的數(shù)值。在32位程序里,所有類型的指針的值都是一個(gè)32位整數(shù),因?yàn)?2位程序里內(nèi)存地址全都是32位長。?
指針?biāo)赶虻膬?nèi)存區(qū)就是從指針的值所代表的那個(gè)內(nèi)存地址開始,長度為sizeof(指針?biāo)赶虻念愋?的一片內(nèi)存區(qū)。以后,我們說一個(gè)指針的值是XX,就相當(dāng)于說該指針指向了以XX為首地址的一片內(nèi)存區(qū)域;我們說一個(gè)指針指向了某塊內(nèi)存區(qū)域,就相當(dāng)于說該指針的值是這塊內(nèi)存區(qū)域的首地址。?
指針?biāo)赶虻膬?nèi)存區(qū)和指針?biāo)赶虻念愋褪莾蓚€(gè)完全不同的概念。在例一中,指針?biāo)赶虻念愋鸵呀?jīng)有了,但由于指針還未初始化,所以它所指向的內(nèi)存區(qū)是不存在的,或者說是無意義的。?
以后,每遇到一個(gè)指針,都應(yīng)該問問:這個(gè)指針的類型是什么?指針指向的類型是什么?該指針指向了哪里? ?
指針本身所占據(jù)的內(nèi)存區(qū)
指針本身占了多大的內(nèi)存?你只要用函數(shù)sizeof(指針的類型)測一下就知道了。在32位平臺里,指針本身占據(jù)了4個(gè)字節(jié)的長度。
指針本身占據(jù)的內(nèi)存這個(gè)概念在判斷一個(gè)指針表達(dá)式是否是左值時(shí)很有用。
指針的算術(shù)運(yùn)算
指針可以加上或減去一個(gè)整數(shù)。指針的這種運(yùn)算的意義和通常的數(shù)值的加減運(yùn)算的意義是不一樣的。例如:
char a[20]; int *ptr=a; ... ... ptr++; 在上例中,指針ptr的類型是int*,它指向的類型是int,它被初始化為指向整形變量a。接下來的第3句中,指針ptr被加了1,編譯器是這樣處理的:它把指針ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字節(jié)做單位的,故ptr所指向的地址由原來的變量a的地址向高地址方向增加了4個(gè)字節(jié)。由于char類型的長度是一個(gè)字節(jié),所以,原來ptr是指向數(shù)組a的第0號單元開始的四個(gè)字節(jié),此時(shí)指向了數(shù)組a中從第4號單元開始的四個(gè)字節(jié)。
我們可以用一個(gè)指針和一個(gè)循環(huán)來遍歷一個(gè)數(shù)組,看例子: ?
int array[20]; int *ptr=array; ... //此處略去為整型數(shù)組賦值的代碼。 ... for(i=0;i<20;i++) { (*ptr)++; ptr++; } 這個(gè)例子將整型數(shù)組中各個(gè)單元的值加1。由于每次循環(huán)都將指針ptr加1,所以每次循環(huán)都能訪問數(shù)組的下一個(gè)單元。再看例子: ?
char a[20]; int *ptr = a; ... ... ptr += 5;
在這個(gè)例子中,ptr被加上了5,編譯器是這樣處理的:將指針ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的單位是字節(jié),故現(xiàn)在的ptr所指向的地址比起加5后的ptr所指向的地址來說,向高地址方向移動了20個(gè)字節(jié)。在這個(gè)例子中,沒加5前的ptr指向數(shù)組a的第0號單元開始的四個(gè)字節(jié),加5后,ptr已經(jīng)指向了數(shù)組a的合法范圍之外了。雖然這種情況在應(yīng)用上會出問題,但在語法上卻是可以的。這也體現(xiàn)出了指針的靈活性。?
如果上例中,ptr是被減去5,那么處理過程大同小異,只不過ptr的值是被減去5乘sizeof(int),新的ptr指向的地址將比原來的ptr所指向的地址向低地址方向移動了20個(gè)字節(jié)。?
總結(jié)一下,一個(gè)指針ptrold加上一個(gè)整數(shù)n后,結(jié)果是一個(gè)新的指針ptrnew,ptrnew的類型和ptrold的類型相同,ptrnew所指向的類型和ptrold所指向的類型也相同。ptrnew的值將比ptrold的值增加了n乘sizeof(ptrold所指向的類型)個(gè)字節(jié)。就是說,ptrnew所指向的內(nèi)存區(qū)將比ptrold所指向的內(nèi)存區(qū)向高地址方向移動了n乘sizeof(ptrold所指向的類型)個(gè)字節(jié)。一個(gè)指針ptrold減去一個(gè)整數(shù)n后,結(jié)果是一個(gè)新的指針ptrnew,ptrnew的類型和ptrold的類型相同,ptrnew所指向的類型和ptrold所指向的類型也相同。ptrnew的值將比ptrold的值減少了n乘sizeof(ptrold所指向的類型)個(gè)字節(jié),就是說,ptrnew所指向的內(nèi)存區(qū)將比ptrold所指向的內(nèi)存區(qū)向低地址方向移動了n乘sizeof(ptrold所指向的類型)個(gè)字節(jié)。
運(yùn)算符&和*
這里&是“取地址”運(yùn)算符,*是...書上叫做“間接運(yùn)算符”。&a的運(yùn)算結(jié)果是一個(gè)指針,指針的類型是a的類型加個(gè)*,指針?biāo)赶虻念愋褪莂的類型,指針?biāo)赶虻牡刂仿?#xff0c;那就是a的地址。*p的運(yùn)算結(jié)果就五花八門了??傊?p的結(jié)果是p所指向的東西,這個(gè)東西有這些特點(diǎn):它的類型是p指向的類型,它所占用的地址是p所指向的地址。
int a=12; int b; int *p; int **ptr; p=&a;//&a的結(jié)果是一個(gè)指針,類型是int*,指向的類型是int,指向的地址是a的地址。 *p=24;//*p的結(jié)果,在這里它的類型是int,它所占用的地址是p所指向的地址,顯然,*p就是變量a。 ptr=&p;//&p的結(jié)果是個(gè)指針,該指針的類型是p的類型加個(gè)*,在這里是int**。該指針?biāo)赶虻念愋褪莗的類型,這里是int*。該指針?biāo)赶虻牡刂肪褪侵羔榩自己的地址。 *ptr=&b;//*ptr是個(gè)指針,&b的結(jié)果也是個(gè)指針,且這兩個(gè)指針的類型和所指向的類型是一樣的,所以?amp;b來給*ptr賦值就是毫無問題的了。 **ptr=34;//*ptr的結(jié)果是ptr所指向的東西,在這里是一個(gè)指針,對這個(gè)指針再做一次*運(yùn)算,結(jié)果就是一個(gè)int類型的變量。指針表達(dá)式
一個(gè)表達(dá)式的最后結(jié)果如果是一個(gè)指針,那么這個(gè)表達(dá)式就叫指針表達(dá)式。下面是一些指針表達(dá)式的例子: ?
int a,b; int array[10]; int *pa; pa=&a;//&a是一個(gè)指針表達(dá)式。 int **ptr=&pa;//&pa也是一個(gè)指針表達(dá)式。 *ptr=&b;//*ptr和&b都是指針表達(dá)式。 pa=array; pa++;//這也是指針表達(dá)式。 char *arr[20]; char **parr=arr;//如果把a(bǔ)rr看作指針的話,arr也是指針表達(dá)式 char *str; str=*parr;//*parr是指針表達(dá)式 str=*(parr+1);//*(parr+1)是指針表達(dá)式 str=*(parr+2);//*(parr+2)是指針表達(dá)式 指針表達(dá)式的結(jié)果是一個(gè)指針。
所以,指針表達(dá)式也具有指針?biāo)哂械乃膫€(gè)要素:指針的類型,指針?biāo)赶虻念愋?#xff0c;指針指向的內(nèi)存區(qū),指針自身占據(jù)的內(nèi)存。
好了,當(dāng)一個(gè)指針表達(dá)式的結(jié)果指針已經(jīng)明確地具有了指針自身占據(jù)的內(nèi)存的話,這個(gè)指針表達(dá)式就是一個(gè)左值,否則就不是一個(gè)左值。 在例七中,&a不是一個(gè)左值,因?yàn)樗€沒有占據(jù)明確的內(nèi)存。*ptr是一個(gè)左值,因?yàn)?ptr這個(gè)指針已經(jīng)占據(jù)了內(nèi)存,其實(shí)*ptr就是指針pa,既然pa已經(jīng)在內(nèi)存中有了自己的位置,那么*ptr當(dāng)然也有了自己的位置。
數(shù)組和指針的關(guān)系
如果對聲明數(shù)組的語句不太明白的話,請參閱我前段時(shí)間貼出的文章<<如何理解c和c++的復(fù)雜類型聲明>>。 數(shù)組的數(shù)組名其實(shí)可以看作一個(gè)指針。看下例:?
int array[10]={0,1,2,3,4,5,6,7,8,9},value; ... ... value=array[0];//也可寫成:value=*array; value=array[3];//也可寫成:value=*(array+3); value=array[4];//也可寫成:value=*(array+4); 上例中,一般而言數(shù)組名array代表數(shù)組本身,類型是int [10],但如果把a(bǔ)rray看做指針的話,它指向數(shù)組的第0個(gè)單元,類型是int *,所指向的類型是數(shù)組單元的類型即int。因此*array等于0就一點(diǎn)也不奇怪了。同理,array+3是一個(gè)指向數(shù)組第3個(gè)單元的指針,所以*(array+3)等于3。其它依此類推。?char *str[3]={ "Hello,this is a sample!", "Hi,good morning.", "Hello world" }; char s[80]; strcpy(s,str[0]);//也可寫成strcpy(s,*str); strcpy(s,str[1]);//也可寫成strcpy(s,*(str+1)); strcpy(s,str[2]);//也可寫成strcpy(s,*(str+2)); 上例中,str是一個(gè)三單元的數(shù)組,該數(shù)組的每個(gè)單元都是一個(gè)指針,這些指針各指向一個(gè)字符串。把指針數(shù)組名str當(dāng)作一個(gè)指針的話,它指向數(shù)組的第0號單元,它的類型是char**,它指向的類型是char *。
*str也是一個(gè)指針,它的類型是char*,它所指向的類型是char,它指向的地址是字符串"Hello,this is a sample!"的第一個(gè)字符的地址,即'H'的地址。 str+1也是一個(gè)指針,它指向數(shù)組的第1號單元,它的類型是char**,它指向的類型是char *。?
*(str+1)也是一個(gè)指針,它的類型是char*,它所指向的類型是char,它指向"Hi,good morning."的第一個(gè)字符'H',等等。??
下面總結(jié)一下數(shù)組的數(shù)組名的問題。聲明了一個(gè)數(shù)組TYPE array[n],則數(shù)組名稱array就有了兩重含義:第一,它代表整個(gè)數(shù)組,它的類型是TYPE [n];第二,它是一個(gè)指針,該指針的類型是TYPE*,該指針指向的類型是TYPE,也就是數(shù)組單元的類型,該指針指向的內(nèi)存區(qū)就是數(shù)組第0號單元,該指針自己占有單獨(dú)的內(nèi)存區(qū),注意它和數(shù)組第0號單元占據(jù)的內(nèi)存區(qū)是不同的。該指針的值是不能修改的,即類似array++的表達(dá)式是錯誤的。?
在不同的表達(dá)式中數(shù)組名array可以扮演不同的角色。??
在表達(dá)式sizeof(array)中,數(shù)組名array代表數(shù)組本身,故這時(shí)sizeof函數(shù)測出的是整個(gè)數(shù)組的大小。??
在表達(dá)式*array中,array扮演的是指針,因此這個(gè)表達(dá)式的結(jié)果就是數(shù)組第0號單元的值。sizeof(*array)測出的是數(shù)組單元的大小。??
表達(dá)式array+n(其中n=0,1,2,....。)中,array扮演的是指針,故array+n的結(jié)果是一個(gè)指針,它的類型是TYPE*,它指向的類型是TYPE,它指向數(shù)組第n號單元。故sizeof(array+n)測出的是指針類型的大小。?
本節(jié)中提到了函數(shù)sizeof(),那么我來問一問,sizeof(指針名稱)測出的究竟是指針自身類型的大小呢還是指針?biāo)赶虻念愋偷拇笮?#xff1f;答案是前者。例如:
int (*ptr)[10]; 則在32位程序中,有: ?
sizeof(int(*)[10])==4 sizeof(int [10])==40 sizeof(ptr)==4 實(shí)際上,sizeof(對象)測出的都是對象自身的類型的大小,而不是別的什么類型的大小。
指針和結(jié)構(gòu)類型的關(guān)系
可以聲明一個(gè)指向結(jié)構(gòu)類型對象的指針。 ?
struct MyStruct { int a; int b; int c; } MyStruct ss={20,30,40};//聲明了結(jié)構(gòu)對象ss,并把ss的三個(gè)成員初始化為20,30和40。 MyStruct *ptr=&ss;//聲明了一個(gè)指向結(jié)構(gòu)對象ss的指針。它的類型是 MyStruct*,它指向的類型是MyStruct。 int *pstr=(int*)&ss;//聲明了一個(gè)指向結(jié)構(gòu)對象ss的指針。但是它的類型和它指向的類型和ptr是不同的。 請問怎樣通過指針ptr來訪問ss的三個(gè)成員變量? ?答案: ptr->a; ptr->b; ptr->c; 又請問怎樣通過指針pstr來訪問ss的三個(gè)成員變量? ?
答案: *pstr;//訪問了ss的成員a。 *(pstr+1);//訪問了ss的成員b。 *(pstr+2)//訪問了ss的成員c。 呵呵,雖然我在我的MSVC++6.0上調(diào)式過上述代碼,但是要知道,這樣使用pstr來訪問結(jié)構(gòu)成員是不正規(guī)的,為了說明為什么不正規(guī),讓我們看看怎樣通過指針來訪問數(shù)組的各個(gè)單元:
int array[3]={35,56,37}; int *pa=array; 通過指針pa訪問數(shù)組array的三個(gè)單元的方法是: ?
*pa;//訪問了第0號單元 *(pa+1);//訪問了第1號單元 *(pa+2);//訪問了第2號單元 從格式上看倒是與通過指針訪問結(jié)構(gòu)成員的不正規(guī)方法的格式一樣。
所有的C/C++編譯器在排列數(shù)組的單元時(shí),總是把各個(gè)數(shù)組單元存放在連續(xù)的存儲區(qū)里,單元和單元之間沒有空隙。但在存放結(jié)構(gòu)對象的各個(gè)成員時(shí),不同編譯器可能會需要字對齊或雙字對齊或者是別的什么對齊,需要在相鄰兩個(gè)成員之間加若干個(gè)“填充字節(jié)”,這就導(dǎo)致各個(gè)成員之間可能會有若干個(gè)字節(jié)的空隙。這就是內(nèi)存對齊。
所以,在例十二中,即使*pstr訪問到了結(jié)構(gòu)對象ss的第一個(gè)成員變量a,也不能保證*(pstr+1)就一定能訪問到結(jié)構(gòu)成員b。因?yàn)槌蓡Ta和成員b之間可能會有若干填充字節(jié),說不定*(pstr+1)就正好訪問到了這些填充字節(jié)呢。這也證明了指針的靈活性。要是你的目的就是想看看各個(gè)結(jié)構(gòu)成員之間到底有沒有填充字節(jié),嘿,這倒是個(gè)不錯的方法。?通過指針訪問結(jié)構(gòu)成員的正確方法應(yīng)該是象例十二中使用指針ptr的方法。
指針和函數(shù)的關(guān)系(函數(shù)指針)
可以把一個(gè)指針聲明成為一個(gè)指向函數(shù)的指針。 ?
int fun1(char*,int); int (*pfun1)(char*,int); pfun1=fun1; .... .... int a = (*pfun1)("abcdefg",7);//通過函數(shù)指針調(diào)用函數(shù)?;蛘?int a = pfun1("abcdefg",7)。但是推薦使用int a = (*pfun1)("abcdefg",7) 與數(shù)據(jù)一樣,函數(shù)也有地址,函數(shù)的地址就是內(nèi)存中存放函數(shù)語言代碼的起始地址。函數(shù)指針就是指向這個(gè)地址。函數(shù)指針?biāo)赶虻念愋?#xff0c;就是函數(shù)本身。我們知道,指針?biāo)赶蝾愋痛砹酥羔標(biāo)赶虻膬?nèi)存區(qū)域的大小。所以函數(shù)指針?biāo)赶虻念愋?#xff0c;就是函數(shù)在內(nèi)存中所占據(jù)內(nèi)存的大小。知道了函數(shù)的起始地址和大小,所以函數(shù)指針可以很輕易的代替函數(shù)完成函數(shù)調(diào)用。#include <string.h> #include <stdio.h> typedef int* PINNT; #define PP int* int funcA(int a,int b); int funcB(int* a,int *b); int main(int argc, char *argv[]) { int (*func)(int,int); //func = &funcA; func = funcA; //兩種賦值給函數(shù)指針的方法都可以 printf("%d",func(1,10)); //printf("%d",(*func)(1,10)); //兩種調(diào)用函數(shù)指針的方法都可以 //兩種賦值方法和兩種調(diào)用方法可以任選一種組合 } int funcA(int a,int b) { return a + b; } int funcB(int* a,int *b) { (*a) = (*a) + (*b); return 1; } #include <iostream> using namespace std;int test(int, int); //自定義一個(gè)新類型,類型名是NewType,自身類型是int ()(int,int) typedef int (NewType)(int,int);int main(void) {int (*pFunc)(int,int); //定義一個(gè)函數(shù)指針 pFuncpFunc = test; //或者 pFunc = &test;cout<<(*pFunc)(10,20)<<endl; // 或者 pFunc(10,20);/* 注意 和 函數(shù)指針 區(qū)別 */NewType* newType = test; //或者 NewType* newType = &test;cout<<newType(30,40)<<endl; //或者 (*newType)(30,40); return 0; }int test(int a, int b) {cout<<"a:"<<a<<endl<<"b:"<<b<<endl;return a+b; }
一、普通函數(shù)指針
通常我們所說的函數(shù)指針指的是指向一般普通函數(shù)的指針。和其他指針一樣,函數(shù)指針指向某種特定類型,所有被同一指針運(yùn)用的函數(shù)必須具有相同的形參類型和返回類型。
int (*pf)(int, int); // 聲明函數(shù)指針這里,pf指向的函數(shù)類型是int (int, int),即函數(shù)的參數(shù)是兩個(gè)int型,返回值也是int型。
注意:*pf兩端的括號必不可少,如果不寫這對括號,則pf是一個(gè)返回值為int指針的函數(shù)。
二、成員函數(shù)指針
定義:typedef 返回類型(類名::*新類型)(參數(shù)表)
一句話,使用類成員函數(shù)指針必須有“->*”或“.*”的調(diào)用。
成員函數(shù)指針(member function pointer)是指可以指向類的非靜態(tài)成員函數(shù)的指針。類的靜態(tài)成員不屬于任何對象,因此無須特殊的指向靜態(tài)成員的指針,指向靜態(tài)成員的指針與普通指針沒有什么區(qū)別。與普通函數(shù)指針不同的是,成員函數(shù)指針不僅要指定目標(biāo)函數(shù)的形參列表和返回類型,還必須指出成員函數(shù)所屬的類。因此,我們必須在*之前添加classname::以表示當(dāng)前定義的指針指向classname的成員函數(shù):
int (A::*pf)(int, int); // 聲明一個(gè)成員函數(shù)指針同理,這里A::*pf兩端的括號也是必不可少的,如果沒有這對括號,則pf是一個(gè)返回A類數(shù)據(jù)成員(int型)指針的函數(shù)。注意:和普通函數(shù)指針不同的是,在成員函數(shù)和指向該成員的指針之間不存在自動轉(zhuǎn)換規(guī)則。所以,必須顯式地使用取址運(yùn)算符(&)
pf = &A::add; // 正確:必須顯式地使用取址運(yùn)算符(&) pf = A::add; // 錯誤 當(dāng)我們初始化一個(gè)成員函數(shù)指針時(shí),其指向了類的某個(gè)成員函數(shù),但并沒有指定該成員所屬的對象——直到使用成員函數(shù)指針時(shí),才提供成員所屬的對象。下面是一個(gè)成員函數(shù)指針的使用示例:class A; typedef int (A::*pClassFun)(int, int); // 成員函數(shù)指針類型class A{ public:int add(int m, int n){cout << m << " + " << n << " = " << m+n << endl;return m+n;}int mns(int m, int n){cout << m << " - " << n << " = " << m-n << endl;return m-n;}int mul(int m, int n){cout << m << " * " << n << " = " << m*n << endl;return m*n;}int dev(int m, int n){cout << m << " / " << n << " = " << m/n << endl;return m/n;}int call(pClassFun fun, int m, int n){// 類內(nèi)部接口return (this->*fun)(m, n);} };int call(A obj, pClassFun fun, int m, int n) { // 類外部接口return (obj.*fun)(m, n); }int call(A* pObj,pClassFun fun, int m, int n) { // 類外部接口return (pObj->*fun)(m, n); }int main() {A a;cout << "member function 'call':" << endl;a.call(&A::add, 8, 4);a.call(&A::mns, 8, 4);a.call(&A::mul, 8, 4);a.call(&A::dev, 8, 4);cout << "external function 'call(A obj, pClassFun fun, int m, int n)':" << endl;call(a, &A::add, 9, 3);call(a, &A::mns, 9, 3);call(a, &A::mul, 9, 3);call(a, &A::dev, 9, 3);cout << "external function 'call(A* pObj,pClassFun fun, int m, int n)':" << endl;call(&a, &A::add, 9, 3);call(&a, &A::mns, 9, 3);call(&a, &A::mul, 9, 3);call(&a, &A::dev, 9, 3);return 0; } 如示例所示,我們一樣可以使用typedef定義成員函數(shù)指針的類型別名。另外,我們需要留意函數(shù)指針的使用方法:對于普通函數(shù)指針,是這樣使用(*pf)(arguments),因?yàn)橐{(diào)用函數(shù),必須先解引用函數(shù)指針,而函數(shù)調(diào)用運(yùn)算符()的優(yōu)先級較高,所以(*pf)的括號必不可少;對于成員函數(shù)指針,唯一的不同是需要在某一對象上調(diào)用函數(shù),所以只需要加上成員訪問符即可:
(obj.*pf)(arguments) // obj 是對象 (objptr->*pf)(arguments) // objptr是對象指針
三、函數(shù)表驅(qū)動
對于普通函數(shù)指針和指向成員函數(shù)的指針來說,一種常見的用法就是將其存入一個(gè)函數(shù)表(function table)當(dāng)中。當(dāng)程序需要執(zhí)行某個(gè)特定的函數(shù)時(shí),就從表中查找對應(yīng)的函數(shù)指針,用該指針來調(diào)用相應(yīng)的程序代碼,這個(gè)就是函數(shù)指針在表驅(qū)動法中的應(yīng)用。
表驅(qū)動法(Table-Driven Approach)就是用查表的方法獲取信息。通常,在數(shù)據(jù)不多時(shí)可用邏輯判斷語句(if…else或switch…case)來獲取信息;但隨著數(shù)據(jù)的增多,邏輯語句會越來越長,此時(shí)表驅(qū)動法的優(yōu)勢就體現(xiàn)出來了。
#include<iostream> #include<string> #include<map> using namespace std; class A; typedef int (A::*pClassFun)(int, int); class A{ public: A(){? // 構(gòu)造函數(shù),初始化表 table["+"] = &A::add; table["-"] = &A::mns; table["*"] = &A::mul; table["/"] = &A::dev; } int add(int m, int n){ cout << m << " + " << n << " = " << m+n << endl; return m+n; } int mns(int m, int n){ cout << m << " - " << n << " = " << m-n << endl; return m-n; } int mul(int m, int n){ cout << m << " * " << n << " = " << m*n << endl; return m*n; } int dev(int m, int n){ cout << m << " / " << n << " = " << m/n << endl; return m/n; } // 查找表,調(diào)用相應(yīng)函數(shù) int call(string s, int m, int n){ return (this->*table[s])(m, n); } private: map<string, pClassFun> table; // 函數(shù)表 }; // 測試 int main() { A a; a.call("+", 8, 2); a.call("-", 8, 2); a.call("*", 8, 2); a.call("/", 8, 2); return 0; } 使用類型定義?可以用類型定義來隱藏復(fù)雜的成員指針語法。例如,下面的語句定義了PMA是一個(gè)指向A成員函數(shù)的指針,函數(shù)返回?zé)o類型值,函數(shù)參數(shù)類型為char * 和 const char *:?
typedef void(A::*PMA)(char *, const char *);?
PMA pmf= &A::strcat; // pmf是PMF類型(類A成員指針)的變量?
下文會看到使用類型定義特別有利于聲明成員指針數(shù)組。?
A a1, a2; A *p= &a1; //創(chuàng)建指向A的指針 //創(chuàng)建指向成員的指針并初始化 void (A::*pmf)(char *, const char *) = &A::strcpy; //要將成員函數(shù)綁定到pmf,必須定義呼叫的對象。 //可以用*號引導(dǎo):void dispatcher(A a, void (A::*pmf)(char *, const char *)) {char str[4];(a.*pmf)(str, “abc”); //將成員函數(shù)綁定到pmf }//或用A的指針表達(dá)方式指向成員指針: void dispatcher(A * p, void (A::*pmf)(char *, const char *)) {char str[4]; (p->*pmf)(str, “abc”); }//函數(shù)的調(diào)用方法為: dispatcher(a, pmf); // .* 方式1 dispatcher(&a, pmf); // ->* 方式2 高級使用技巧?
以上是成員函數(shù)的基本知識。現(xiàn)在介紹它的高級使用技巧。?
成員指針數(shù)組 ?
在下例,聲明了一個(gè)含有二個(gè)成員指針的數(shù)組,并分配類的成員函數(shù)地址給成員指針:?
PMA pmf[2]= {&A::strcpy, &A::strcat};?
也就是
????? void (A::*PMA[2])(char *, const char *)= {&A::strcpy, &A::strcat};?
這樣的數(shù)組在菜單驅(qū)動應(yīng)用中很有用。選擇菜單項(xiàng)后,應(yīng)用將調(diào)用相應(yīng)的回叫函數(shù),如下所示:?
enum MENU_OPTIONS { COPY, CONCAT }; int main() {MENU_OPTIONS option; char str[4];//從外部資源讀取選項(xiàng)switch (option){case COPY: (pa->*pmf[COPY])(str, “abc”); break; case CONCAT: (pa->*pmf[CONCAT])(str, “abc”); break; //… } } Const 類型的成員函數(shù)?
成員指針的類型應(yīng)該與成員函數(shù)類型一致。上面例子中的pmf 可以指向A的任意函數(shù),只要該函數(shù)不是const類型。如下所示,如果將touppercase()的地址分配給pmf,將導(dǎo)致編譯出錯,因?yàn)閠ouppercase() 的類型是const。?
class A { public: void strpcy(char *, const char *); void strcat(char *, const char *); void touppercase(char *, const char*) const; };pmf=&A::touppercase; //出錯,類型不匹配//解決的方法是聲明一個(gè)const類型的成員指針: void (A::pcmf)(char *, const char *) const;pcmf=&A::touppercase; // 現(xiàn)在可以了
一、最簡單的函數(shù)指針
變量都包括聲明和賦值,指針不例外,函數(shù)指針也不例外。我們來看一個(gè)簡單的函數(shù):
void add(int a, int b){cout << a + b << endl; }一個(gè)簡單的加法計(jì)算并輸出到命令行的函數(shù)。那么如何通過函數(shù)指針來調(diào)用它呢?
1、聲明:
函數(shù)指針的聲明很簡單,基本就是通過一個(gè)指針把函數(shù)名替換。指針p1的類型為void (*) (int a,int b),表明指針是一個(gè)指向某個(gè)函數(shù)的指針,指針指向的類型為void () (int a,int b)
2、賦值:
3、也可以直接定義:
void (*p1)(int a, int b) = add;注意,函數(shù)void add(int a,int b)的函數(shù)名add就是函數(shù)的地址。將地址add賦值給指針p1,那么就可以通過函數(shù)指針p1直接調(diào)用函數(shù)了。
4、調(diào)用:
(*p1)(1, 2); p1(1, 2); 注意!出于歷史原因以上2種方式都是可以調(diào)用函數(shù)的。二、包含多個(gè)函數(shù)指針的數(shù)組
有時(shí)候有這種情況,有一個(gè)數(shù)組,數(shù)組中的每個(gè)元素都是一個(gè)函數(shù)指針,該怎么定義這個(gè)數(shù)組呢?
1、解釋*p[n]和(*p)[n]
我們知道,[]運(yùn)算符的優(yōu)先級要高于*,所以,p[3]表示含有3個(gè)元素的數(shù)組,而*p[3] 前面的 " * " 指明了數(shù)組中元素的類型,即*p[3]表示一個(gè)指向3個(gè)指針的數(shù)組。?
p[3]表示含有3個(gè)元素的數(shù)組,那么(*p)[3]就是用 *p 替換了 p,很容易想到,(*p)[3]?表示指向一個(gè)包含3個(gè)元素的數(shù)組的指針。
2、聲明:
數(shù)組名為p2,數(shù)組大小為2,數(shù)組中元素類型為void (*)(int a, int b),表明元素是一個(gè)指向某個(gè)函數(shù)的指針,指針指向的類型為void () (int a,int b)。
3、賦值:
p2[1] = add;
理解上跟上面是一樣的。
4、調(diào)用:
p2[1](2,3); (*p2[1])(3,4); 同樣是2種方式都可以。
三、指向“包含多個(gè)函數(shù)指針的數(shù)組“的指針
這個(gè)標(biāo)題好像有點(diǎn)拗口。簡而言之,這個(gè)指針指向上文中的 “包含多個(gè)函數(shù)指針的數(shù)組” 。其實(shí)很簡單,說白了,就是把上文中的p2用一個(gè)指針來代替。
1、聲明:
? 可以看到,無非就是把p2用*p3代替。
2、賦值,注意,既然是指針,使用前必須初始化:
注意!既然實(shí)質(zhì)上就是把p2用*p3代替,c++11可以很簡單的這樣直接定義:auto p3 = &p2; 代替了void (*(*p3)[2])(int a, int b)= &p2;
3、調(diào)用:
(*p3)[1](1, 2); (*(*p3)[1])(1, 2);
1.?????定義
每一個(gè)函數(shù)都占用一段內(nèi)存單元,它們有一個(gè)起始地址,指向函數(shù)入口地址的指針稱為函數(shù)指針。
2.?????語法
指向函數(shù)的指針變量的一般定義形式為:數(shù)據(jù)類型?(*指針變量名)(參數(shù)表);
3.?????說明
? ? 1)?函數(shù)指針的定義形式中的數(shù)據(jù)類型是指函數(shù)的返回值的類型。
? ? 2)?區(qū)分下面兩個(gè)語句:
? ? ? ? int (*p)(int a, int b); //p是一個(gè)指向函數(shù)的指針變量,所指函數(shù)的返回值類型為整型
? ? ? ? int *p(int a, int b); //p是函數(shù)名,此函數(shù)的返回值類型為整型指針
? ? 3)?指向函數(shù)的指針變量不是固定指向哪一個(gè)函數(shù),只是定義一個(gè)這樣類型的變量,專門用來存放函數(shù)入口地址;程序中把哪一個(gè)函數(shù)的地址賦給它,它就指向哪一個(gè)函數(shù)。
? ? 4)?在給函數(shù)指針變量賦值時(shí),只需給出函數(shù)名,而不必給出參數(shù)。
? ? ? ? 如函數(shù)max的原型為:int max(int x, int y);?指針p的定義為:int (*p)(int a, int b);?則p = max;的作用是將函數(shù)max的入口地址賦給指針變量p。
? ? ? ? 這時(shí),p就是指向函數(shù)max的指針變量,也就是p和max都指向函數(shù)的開頭。
? ? 5)?在一個(gè)程序中,指針變量p可以先后指向不同的函數(shù),但一個(gè)函數(shù)不能賦給一個(gè)不一致的函數(shù)指針(即不能讓一個(gè)函數(shù)指針指向與其類型不一致的函數(shù))。
? ? ? ? 如有如下的函數(shù):int fn1(int x, int y);?int fn2(int x);
? ? ? ? 定義如下的函數(shù)指針:int (*p1)(int a, int b);?int (*p2)(int a);
? ? ? ? 則
? ? ? ? p1 = fn1; //正確
? ? ? ? p2 = fn2; //正確
? ? ? ? p1 = fn2; //產(chǎn)生編譯錯誤
? ? 6)?定義了一個(gè)函數(shù)指針并讓它指向了一個(gè)函數(shù)后,對函數(shù)的調(diào)用可以通過函數(shù)名調(diào)用,也可以通過函數(shù)指針調(diào)用(即用指向函數(shù)的指針變量調(diào)用)。
? ? ? ? 如語句:c = (*p)(a, b); //表示調(diào)用由p指向的函數(shù)(max),實(shí)參為a,b,函數(shù)調(diào)用結(jié)束后得到的函數(shù)值賦給c。
? ? 7)?函數(shù)指針只能指向函數(shù)的入口處,而不可能指向函數(shù)中間的某一條指令。不能用*(p+1)來表示函數(shù)的下一條指令。
? ? 8)?函數(shù)指針變量常用的用途之一是把指針作為參數(shù)傳遞到其他函數(shù)。
#include <stdlib.h> #include <stdio.h> #include <conio.h> using namespace std;int max(int, int); //在“聲明”時(shí)形參名可以省略,“定義”時(shí)只要形參類型相同,就認(rèn)為是同一個(gè)函數(shù) int min(int, int); int add(int x, int y); void process(int i, int j, int (*p)(int, int)); //應(yīng)用函數(shù)指針int max(int x, int y){return x > y ? x : y;} //定義時(shí)指定 形參名 int min(int x, int y){return x > y ? y : x;} int add(int x, int y){return x + y;}void process(int i, int j, int (*p)(int a, int b)) {/* 函數(shù)指針帶不帶*都可以操作所指向的函數(shù) 。 但是 最好 加上*號 以用來指示這是一個(gè)指針 */cout<<p(i, j)<<endl; //直接 不使用 * 操作cout<<(*p)(i, j)<<endl; // 使用 * 也可以操作cout<<"************************"<<endl; }int main(void) {int x=100, y=200;//cout<<"input x and y value(x y):";//cin>>x>>y;cout<<"Max is: ";process(x, y, max);cout<<"Min is: ";process(x, y, min);cout<<"Add is: ";process(x, y, add);getch();return 0; }
總結(jié)
- 上一篇: Spring Data JPA 从入门到
- 下一篇: s3c2440移植MQTT