1. 程式人生 > >c++ typedef 函式指標詳細說明(包含類函式指標)

c++ typedef 函式指標詳細說明(包含類函式指標)

http://blog.csdn.net/future200x/article/details/5350134

    一個函式在編譯時被分配一個入口地址,將這個入口地址稱為函式的指標,可以用一個指標變數指向該函式指標,然後通過該變數來呼叫函式。

 
有關說明:
1、  函式指標的宣告格式:
函式返回值型別(*指標變數名)(引數型別列表)
或者是:
typedef 函式返回值型別 (*指標變數名)(引數型別列表)
2、一個函式指標只能指向一種型別的函式,即具有相同的返回值和相同的引數的函式
3、關於函式指標的加減運算沒有意義
 
函式定義: 
[cpp] view plaincopy
void fun1(void *p);  
void fun2(void *p);  
void fun3(void *p);  

函式指標陣列定義:
[cpp] view plaincopy
void(*fun[3])(void*); //typedef void(*pfun)(void*);pfun fun[3];  

指標賦值:
[cpp] view plaincopy
fun[0] = fun1;  
fun[1] = fun2;  
fun[2] = fun3;  

函式呼叫:
[cpp] view plaincopy
fun[0](&a); //int a;  
fun[1](&b); //int b;  
fun[3](&c); //int c;  

宣告一個指向成員函式的指標
一個成員函式指標包括成員函式的返回型別,後隨::操作符類名,指標名和函式的引數。初看上去,語法有點複雜。其實可以把它理解為一個指向原函式的指標,格式是:函式返回型別,類名,::操作符,指標星號,指標名,函式引數。
 
指向外部函式的指標可如下宣告:
[cpp] view plaincopy
void (*pf)(char *, const char *);  
void strcpy(char * dest, const char * source);  
pf=strcpy;  

    相應指向類A的成員函式的指標如下表示:
[cpp] view plaincopy
void (A::*pmf)(char *, const char *);  

以上pmf是指向類A的一個成員函式的指標,傳遞兩個變數char *和 const char *,沒有返回值。注意星號前面的A::符號,這和前面的宣告是一致的。
 
賦值
為了給一個指向成員函式的指標賦值,可以採用成員函式名並再其前面加一個&的方式。
[cpp] view plaincopy
class A   
{   
public:   
 void strcpy(char *, const char *);   
 void strcat(char *, const char *);   
};   
pmf = &A::strcpy;   

有些老的編譯器可以通過沒有&號的賦值方式,但標準C++強制要求加上&號。
 
使用typedef
你可以使用typedef來隱藏一些指向成員函式的複雜指標。例如,下面的程式碼定義了一個類A中的成員函式的指標PMA,並傳遞char *和const char *引數。
[cpp] view plaincopy
typedef void(A::*PMA)(char *, const char *);  
PMA pmf= &A::strcat; // use a typedef to define a pointer to member  

使用typedef特別有用,尤其是對於指向成員函式的陣列指標。下文會看到使用型別定義特別有利於宣告成員指標陣列。


通過成員指標呼叫成員函式
可以在不必知道函式名的情況下,通過成員指標呼叫物件的成員函式。例如,函式dispatcher有一個變數pmf,通過它呼叫類成員函式,不管它呼叫的是strcpy()函式還是strcat()函式。指向外部原函式的指標和指向類成員函式的指標是有很大區別的。後者必須指向被調函式的宿主物件。因此,除了要有成員指標外,還要有合法物件或物件指標。
現舉例做進一步說明。假設A有二個例項,成員函式指標支援多型性。這樣在成員指標呼叫虛成員函式時是動態處理的(即所謂後聯編 - 譯註)。注意,不可呼叫構造和解構函式。示例如下:
 

 
 
[cpp] view plaincopy
A a1, a2;   
A *p= &a1; //建立指向A的指標   
//建立指向成員的指標並初始化   
void (A::*pmf)(char *, const char *) = &A::strcpy;   
//要將成員函式繫結到pmf,必須定義呼叫的物件。   
//可以用*號引導:   
void dispatcher(A a, void (A::*pmf)(char *, const char *))   
{   
 char str[4];   
 (a.*pmf)(str, “abc”); //將成員函式繫結到pmf   
}   
//或用A的指標表達方式指向成員指標:   
void dispatcher(A * p, void (A::*pmf)(char *, const char *))   
{   
 char str[4]; (p->*pmf)(str, “abc”);   
}   
//函式的呼叫方法為:   
dispatcher(a, pmf); // .* 方式   
dispatcher(&a, pmf); // ->* 方式   

高階使用技巧
以上是成員函式的基本知識。現在介紹它的高階使用技巧。 
成員指標陣列
在下例,聲明瞭一個含有二個成員指標的陣列,並分配類的成員函式地址給成員指標 
[cpp] view plaincopy
PMA pmf[2]= {&A::strcpy, &A::strcat};   

也就是
[cpp] view plaincopy
void (A::*PMA[2])(char *, const char *)= {&A::strcpy, &A::strcat};   

這樣的陣列在選單驅動應用中很有用。選擇選單項後,應用將呼叫相應的回叫函式,如下所示:
[cpp] view plaincopy
enum MENU_OPTIONS { COPY, CONCAT };   
int main()   
{   
 MENU_OPTIONS option; char str[4];   
 //從外部資源讀取選項   
 switch (option)   
 {   
     case COPY:   
    (pa->*pmf[COPY])(str, “abc”);   
    break;   
     case CONCAT:   
    (pa->*pmf[CONCAT])(str, “abc”);   
    break;   
    //…   
 }   
}   
 
Const型別的成員函式
成員指標的型別應該與成員函式型別一致。上面例子中的pmf 可以指向A的任意函式,只要該函式不是const型別。如下所示,如果將touppercase()的地址分配給pmf,將導致編譯出錯,因為touppercase() 的型別是const。
 
[cpp] view plaincopy
Class A   
{   
 public:   
  void strpcy(char *, const char *);   
  void strcat(char *, const char *);   
  void touppercase(char *, const char*) const;   
};   
pmf=&A::touppercase; //出錯,型別不匹配   
//解決的方法是宣告一個const型別的成員指標:   
void (A::pcmf)(char *, const char *) const;   
pcmf=&A::touppercase; // 現在可以了  

有些差勁的編譯器允許一個非const型別的成員指標指向const型別的成員函式。這在標準C++是不允許的。
可能有點費解:成員指標不是真正的指標。傳統意義上的指標是一個整數,儲存指向某個變數或函式的地址。成員指標則是一個複合資料結構,包含有若干個資料成員。成員指標的這個複雜性使得入門比較困難。然而,一旦掌握了它的語法,就能感到它是在事件驅動和多執行緒應用中呼叫回叫函式必不可少的工具。 
 
 
■ void型別的指標
void含義:
void是“無型別”,void*則為無型別指標,void*可以指向任何型別的資料。
void a;//此變數沒有任何實際意義,無法編譯通過“illegal use of type”
 
void 的作用:
1、對程式返回的限定
2、對函式引數的限定
我們知道,如何指標p1和p2的型別相同,那麼我們可以直接在p1和p2間賦值,如果不同,必須使用強制型別轉換。
如:float *p1; int *p2;
若:p1 = p2; 編譯出錯:“can not covert from int* to float*”
必須為:p1 = (float*)p2;
而void*不同,任何型別的指標都可以直接賦為它,不需要強制型別轉換:
如:void *p1; int *p2;
可作:p1 =p2;
無型別可以包容有型別,有型別不能包容無型別:
必須為:p2 = (int*)p1;
 
viod 和 void*使用規則總結:
● 如果函式沒有返回值,那麼應宣告為void型別
在C語言中,凡不加返回值型別限定的函式,就會被編譯器作為返回整型值處理。但是許多程式設計師卻誤以為其為void型別. 林銳博士《高質量C/C++程式設計》中提到:“C++語言有很嚴格的型別安全檢查,不允許上述情況(指函式不加型別宣告)發生”。可是編譯器並不一定這麼認定,譬如在Visual C++6.0中上述add函式的編譯無錯也無警告且執行正確,所以不能寄希望於編譯器會做嚴格的型別檢查。
因此,為了避免混亂,我們在編寫C/C++程式時,對於任何函式都必須一個不漏地指定其型別。如果函式沒有返回值,一定要宣告為void型別。這既是程式良好可讀性的需要,也是程式設計規範性的要求。另外,加上void型別聲明後,也可以發揮程式碼的“自注釋”作用。程式碼的“自注釋”即程式碼能自己註釋自己。
● 如果函式無引數,那麼應宣告其引數為void
● 小心使用void指標型別
按照ANSI的標準,不能對void指標進行演算法操作,即下列操作是不合法的:
[cpp] view plaincopy
void *pvoid;  
pvoid ++; //ansi錯誤  
pvoid += 1;//ansi 錯誤  
 
ansi標準之所以這樣認定,是因為它堅持,進行演算法操作的指標必須是確定知道其指向資料型別的大小的。
但GUN(GUN’s Not Unix)則不這麼認為,它指定void*的演算法操作與char*一致。因此在GUN編譯器中上述語句是正確的。
在實際的程式中,為了迎合ansi標準,並提高程式的可移植性,我們可以這樣實現同樣功能的程式碼:
[cpp] view plaincopy
void *pvoid;  
(char*)pvoid++;  
(char*)pvoid += 1;  

● 如果函式的引數可以是任意型別指標,那麼應宣告其引數為void *
典型的如記憶體操作函式memcpy和memset的函式原型分別為:
[cpp] view plaincopy
void* memcpy(void *dest, const void *src, size_t len);  
void* memset(void *buffer,int c, size_t num);  

這樣,任何型別的指標都可以傳入memcpy和memset中,這也真實地體現了記憶體操作函式的意義,因為它操作的物件僅僅是一片記憶體,而不論記憶體是什型別。
● void不能代表一個真實的變數
[cpp] view plaincopy
void a; //錯誤  
function(void a); //錯誤  
  
■ this指標
《深入淺出MFC》中解釋:
定義類CRect,定義兩個物件rect1、rect2,各有自己的m_color成員變數,但rect1.setcolor和rect2.setcolor卻都是通往唯一的CRect::setcolor成員函式,那麼CRect::setcolor如何處理不同物件的m_color?答案是由一個隱藏引數,名為this指標。當你呼叫:
[cpp] view plaincopy
CRect::setcolor(2,(CRect*)&rect1);  
CRect::setcolor(3,(CRect*)&rect2);  

時,編譯器實際上為你做出來一下的程式碼:
[cpp] view plaincopy
CRect::setcolor(2,(CRect*)&rect1);  
CRect::setcolor(3,(CRect*)&rect2);  

多出來的引數,就是所謂的this指標。
[cpp] view plaincopy
class CRect  
{  
    ……  
public:  
    void setcolor(int color){m_color = color};  
};  

被編譯後,其實為:
[cpp] view plaincopy
class CRect  
{  
    ……  
public:  
    void setcolor(int color,(CRect*)this){this->m_color = color};  
};