`

认清C++语言之《函数指针》

 
阅读更多

函数指针:

1) 可以声明一个指向特定类型函数的指针:

void (*fp)(int); //指向函数的指针

注意:其中的括号是必不可少的,它表明fp是一个指向返回值为void的函数的指针,而不是返回值为void*的函数。

指向函数的指针可为空,否则它应该指向一个具有适当类型的函数:

extern int acef(int);

extern void aceg(long);

extern void aceh(int);

.....

fp = acef; //Err&acef的类型是int(*)(int)而不是void(*)(int)

fp = aceg; //Err&aceg的类型是void(*)(long)而不是void(*)(int)

fp = 0; //OK!设置为null

fp = h; //OK!指向h

fp = &h; //OK!明确地赋予函数地址

注意:将一个函数的地址初始化或赋值给一个指向函数的指针时,无需显式地取得函数地址,编译器知道隐式地获得函数的地址,即在此种情况下,&操作符是可选的,通常省略不用。

类似地,为了调用函数指针所指向的函数而对指针进行解引用操作也是不必要的,编译器会进行解引用:

(*fp)(12); //显式地解引用

fp(12); //隐式地解引用,结果相同

注意:和void*指针可以指向任何类型的数据不同,不存在可以指向任何类型函数的通用指针。同时,非静态成员函数的地址不是一个指针,因此不能将一个函数指针指向一个非静态成员函数。

函数指针的一个传统用途就是实现回调(callback)。

指向类成员的指针:

1)“指向类成员的指针”描述中的“指针”其实不合适,因为它们既不包含地址,行为也不像指针。声明一个指向类成员的指针,语法如下:

int *ip; //一个指向int的指针

int C::*pc; //一个指针pc,指向类C的一个int成员

我们所要做的全部事情就是使用classname::*,而不是普通的*,来表明现在指向的是classname的一个成员。

2) 一个常规指针包含一个地址,如果解引用该指针,就会得到位于该地址的对象;

与常规指针不同,一个指向类成员的指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向一个特定对象里的特定成员。最清晰地做法,是将指向类成员的指针看作是一个偏移量。

C++标准对于一个指向类成员的指针究竟如何实现只字未提,标准只是说明了它的语法形式以及必须表现出来的行为;然而,多数编译器都将指向类成员的指针实现为一个整数,其中包含被指向的成员的偏移量,另外加上1(加1是为了让值0可以表示一个空的数据成员指针)。这个偏移量表示的是一个特定成员的位置距离对象的起点有多少个字节。

class ACEClass

{

public:

//...........

int a_;

};

int ACEClass::*pace; //一个指针pace,指向ACEClass的一个int成员

ACEClass ac;

ACEClass *pc = ∾

pace = &ACEClass::a_; //pace设置为a_ACEClass内的偏移量

//这个偏移量适用于ACEClass的任何对象

ac.*pace = 0; //请求将ac的地址加上pace中的偏移量

int b = pc->*pace; //请求将pc内的地址加上pace内的偏移量

3)“逆变性”

C++中,存在从指向派生类的指针到指向其任何共有基类的预定义转换;但不存在从指向基类的指针到指向派生类的指针的隐式转换。

在指向类成员的指针的情况恰恰相反:存在从指向基类成员的指针到指向共有派生类成员的指针的隐式转换;但不存在从指向派生类成员的指针到指向其任何一个基类成员的指针的转换。

指向成员函数的指针:

1) 获取非静态成员函数的地址时,得到的不是一个地址,而是一个指向成员函数的指针。

class ACEShape

{

public:

//..........

void moveTo(Point newLocation);

bool validate() const;

virtual bool draw() const = 0;

//.........

};

class ACECircle : public ACEShape

{

//..............

bool draw() const;

//.............

};

//..........

void (ACEShape::*pace1)(Point) = &ACEShape::moveTo; //不是地址,是指针

与指向数据成员的指针一样,我们使用classname::*而不是*来指明所指向的函数是classname的一个成员函数。

与指向常规函数的指针不同,指向成员函数的指针可以指向一个常量成员函数:

bool (ACEShape::*pace2)() const = &ACEShape::validate;

和指向数据成员的指针一样,为了对一个指向成员函数的指针进行解引用,需要一个对象或一个指向对象的指针。如前所述,为了访问数据成员,需要将对象的地址和成员的偏移量(包含于指向数据成员的指针中)相加;为了访问该成员函数,需要将对象的地址用作(或用于计算)this指针的值,进行函数调用,以及用作其他用途。

ACECircle circle; //一个对象

ACEShape *pShape = &circle; //一个指向对象的指针

(circle.*pace2)(); //调用ACEShape::validate

(pShape->*pace2)(); //调用ACEShape::validate

不存在什么指向成员函数的“虚拟”指针,虚拟性是成员函数自身的属性,而不是指向它的指针所具有的属性:

pace2 = &ACEShape::draw; //draw是虚函数

(pShape->*pace2)(); //调用ACECircle::draw

因此,一个指向成员函数的指针,通常不能被实现为一个简单的指向函数的指针。一个指向成员函数的指针的实现自身必须存储一些信息,诸如它所指向的成员函数是虚拟的还是非虚拟的,到哪里去找适当的虚函数表指针,从函数的this指针加上或减去的一个偏移量,以及可能还有其他一些信息。

指向成员函数的指针通常实现为一个小型结构,其中包含这些信息。解引用和调用一个指向成员函数的指针通常涉及到检查这些存储的信息,并有条件地执行适当的虚拟或非虚拟的函数调用序列。

和指向数据成员的指针一样,指向成员函数的指针也表现出一种“逆变性”,即存在从指向基类成员函数的指针到指向派生类成员函数的指针的预定义转换,反之则不然。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics