深度剖析 函数指针

声明:以下代码全部在windows7  vs2010环境下编译通过,并执行无误。

全局函数指针

是指向函数的指针变量,在C编译时,每一个函数都有一个入口地址,那么这个指向这个函数的函数指针便指向这个地址。

函数指针的用途是很大的,主要有两个作用:用作调用函数和做函数的参数。

函数指针的声明方法:

数据类型标志符指针变量名(形参列表);

一般函数的声明为:

intfunc ( int x);

而一个函数指针的声明方法为:

int (*func) (intx);

前面的那个(*func)中括号是必要的,这会告诉编译器我们声明的是函数指针而不是声明一个具有返回型为指针的函数,后面的形参要视这个函数指针所指向的函数形参而定。

然而这样声明我们有时觉得非常繁琐,于是typedef可以派上用场了,我们也可以这样声明:

typedefint (*PF)(int x);

PF pf;

这样pf便是一个函数指针,方便了许多。当要使用函数指针来调用函数时,func(x)或者  (*fucn)(x) 就可以了,当然,函数指针也可以指向被重载的函数,编译器会为我们区分这些重载的函数从而使函数指针指向正确的函数。

函数指针的调用方法:

         (*pf)(x);

         pf(x);


int fun (inti)
{
	cout<<"call fun\n";
	returni;
}
int _tmain(intargc, _TCHAR* argv[])
{
	typedefint (*pFun)(int);//函数指针类型定义;
	pFun pf1 = fun;//指针赋值 & 符号在这里可有可无;
	pFun pf2 = &fun;
	pf1(5);//函数调用 * 符号可有可无
	(*pf2)(6);
	pFunpfarr[] = {fun,&fun};//函数指针数组;
	pfarr[0](7);//函数指针数组调用;
	(*pfarr[1])(8);
	int (*pArr[5])(int);//函数指针数组的定义
	pArr[0] = fun;
	fun(9);
return 0;
}
全局函数指针用法简单注释已经做了说明就不多说了。


指向成员函数的指针

是指向成员函数的指针变量,在C编译时,每一个成员函数都有一个入口地址,那么这个指向这个函数的函数指针便指向这个地址。相比较于指向函数的指针,在声明、定义时多了函数的作用域,在调用时需要有个一类对象的实体或者指针进行调用。

好了废话不多说了,切入主题。

函数指针的声明方法:

数据类型标志符函数作用域::指针变量名(形参列表);

而一个函数指针的声明方法为:

int (CLASSNAME::*func) (int x);

跟全局函数指针类似。

然而这样声明我们有时觉得非常繁琐,于是typedef可以派上用场了,我们也可以这样声明:

typedefint (CLASSNAME::*PF)(int x);

PF pf;

也跟全局指针类似。

也可以声明类成员变量的指针:

         int(CLASSNAME::*pi) = CLASSNAME::m_i;这个不是重点就不做详细介绍了。

函数指针的调用方法:

         (object.*pf)(x);

         (objectpoint->*pf)(x);

 

举例:

CASE 1

如何声明、调用指向成员函数的指针


classfuncptr
{
public:
	void show(inti)
	{
		cout<<"call funcptr::show"<<i<<endl;
	}
};
int _tmain(intargc, _TCHAR* argv[])
{
	typedefvoid (funcptr::*pFun)(int);
	funcptrfptr;
	funcptr *pfptr = newfuncptr();
	pFun pf1 = &funcptr::show;//必须用 & 取函数地址;
	//pFun pf2 = funcptr::show;//编译错误;请使用“&funcptr::show”创建指向成员的指针;
	//(*pf1)(5); //编译错误
	(fptr.*pf1)(6);
	(pfptr->*pf1)(7);
deletepfptr;
}

CASE 2

如果函数指针定义在类内部,作为一个成员变量又该如何调用呢?请看我下面的例子。

classfuncptr
{
public:
	typedefvoid (funcptr::*pFun)(int);
	pFunpf;
public:
	funcptr()
	{
		pf = &funcptr::show;
	}
	void show(inti)
	{
		cout<<"call funcptr::show"<<i<<endl;
	}
	voidcallfuncptr (inti)
	{
		return (this->*pf)(i);
	}
};
int _tmain(intargc, _TCHAR* argv[])
{
	funcptrfuncp;
	//*(funcp.pf)(8);//编译错误,相当于(*pf1)(5);;
	(funcp.*(funcp.pf))(9);//借用类实体才能实现;
	funcp.callfuncptr(10);
}

(funcp.*(funcp.pf))(9);//借用类实体才能实现;

想要直接用对象的成员变量调用函数指针就是如此的麻烦,所以推荐大家在类中定义一个专门用来调用函数指针的函数来调用函数指针。


voidcallfuncptr (inti)
	{
		(this->*pf)(i);
	}

CASE 3

如果在B 类中定义了 A 类的成员函数指针,又该如何做呢?

Class funcptr
{
public:
	typedefvoid (funcptr::*pFun)(int);
	pFun pf;
public:
	funcptr()
	{
		pf = &funcptr::show;
	}
	void show(inti)
	{
		cout<<"call funcptr::show"<<i<<endl;
	}
	void show2(inti)
	{
		cout<<"call funcptr::show2"<<i<<endl;
	}
	voidcallfuncptr (inti)
	{
		return (this->*pf)(i);
	}
};

class other
{
private:
	funcptr m_funcp;
public:
	typedefvoid (funcptr::*pFun)(int);
	pFunpf;
public:
	other(){}
	other(inti)
	{
		if (1 == i)
		{
			pf = &funcptr::show;
		} 
		else
		{
			pf = &funcptr::show2;
		}
		
	}
	void callfunptr(inti,funcptr&funcp) //在这里需要外部传入一个对象实习抑或是一个指针都可以,这一点很重要。
	{
		return (funcp.*(this->pf))(i);
	}
};
int _tmain(intargc, _TCHAR* argv[])
{
	funcptr funcp;
	//*(funcp.pf)(8);//编译错误,相当于(*pf1)(5);;
	(funcp.*(funcp.pf))(9);//借用类实体才能实现;
	funcp.callfuncptr(10);
	other oth1(1);//定义两个不同的对象实体让同一个函数指针指向不用的函数;
	other oth2(2);
	oth1.callfunptr(11,der);//传入需要的funcptr实体类;
	oth2.callfunptr(12,der);
}

CASE 4

如果类出现了继承和派生又要如何处理呢?c++中的继承本身就是一个十分复杂的东西,各个不同的编译器为我们做了不同的内部处理总是让程序员摸不着头脑。明知山有虎偏向虎山行,那我们就来试试看出现了简单的单继承继承又该怎么办呢?

CASE 4.1

         简单继承不做扩展和改写


classderive:publicfuncptr
{
	//子类中什么都不写,全部直接用父类;
};

int _tmain(intargc, _TCHAR* argv[])
{
	funcptrfuncp;
	derive der; //在这里仅仅是用派生类对象替换了父类对象,显然是满足 “is-a”这种关系;
	//*(funcp.pf)(8);//编译错误,相当于(*pf1)(5);;
	(der.*(der.pf))(9);//借用类实体才能实现;
	der.callfuncptr(10);
	other oth1(1);//定义两个不同的对象实体让同一个函数指针指向不同的函数;
	other oth2(2);
	oth1.callfunptr(11,der);//传入需要的funcptr实体类;
	oth2.callfunptr(12,der);
}

derive der; //在这里仅仅是用派生类对象替换了父类对象,显然是满足 “is-a”这种关系;

这里用派生类和基类没有任何区别;

CASE 4.2

派生类覆盖、改写基类中的函数

classfuncptr
{
public:
	typedefvoid (funcptr::*pFun)(int);
	pFun pf;
public:
	funcptr()
	{
		pf = &funcptr::show;
	}
	void show(inti)
	{
		cout<<"call funcptr::show"<<i<<endl;
	}
	Virtual void show2(inti)//这里写成虚函数;
	{
		cout<<"call funcptr::show2"<<i<<endl;
	}
	voidcallfuncptr (inti)
	{
		return (this->*pf)(i);
	}
};
Class derive:publicfuncptr
{
public:
	derive()
	{
		
	}
	void show (inti)
	{
		cout<<"call derive::show"<<i<<endl;
	}
	virtualvoid show2(inti)
	{
		cout<<"call derive::show2"<<i<<endl;
	}
};
int _tmain(intargc, _TCHAR* argv[])
{
	derive der; //在这里仅仅是用派生类对象替换了父类对象,显然是满足 “is-a”这种关系;
	der.callfuncptr(10);
}

当派生类调用其继承的函数指针时,并没有理会派生类覆盖掉子类的 show; 函数指针任然指向基类的函数地址,调用了基类的 show()函数;

但是如果将

pf = &funcptr::show;改写为

pf = &funcptr::show2;//注意show2为一个虚函数。





那么函数指针指向的将是派生类的函数地址,调用的是派生类的改写的函数。基类经过派生后,若派生类改写了基类的虚函数,那么基类的虚函数列表中相应的虚函数地址也修改为派生的虚函数地址,这样才能实现C++的多态性。《深度探索C++对象模型》给出了很详细的解释,这里就不再做说明。


CASE 4.3

函数指针定义在其他的非派生类中

class other
{
private:
	funcptrm_funcp;
public:
	typedefvoid (funcptr::*pFun)(int); //在这里已经将pFun的作用域设定为 funcptr::;
	pFunpf;
public:
	other(){}
	other(inti)
	{
		if (1 == i)
		{
			pf = &funcptr::show;//在给函数指针赋值时已经确定函数的作用域;
		} 
		else
		{
			pf = &funcptr::show2;
		}
	}
	voidcallfunptr(inti,funcptr&funcp) //在这里需要外部传入一个对象实习抑或是一个指针都可以,这一点很重要。
	{
		return (funcp.*(this->pf))(i);//这里与借用的对象实体没有关系;
	}
};
int _tmain(intargc, _TCHAR* argv[])
{
	funcptrfuncp;
	derive der; //在这里仅仅是用派生类对象替换了父类对象,显然是满足 “is-a”这种关系;
	//*(funcp.pf)(8);//编译错误,相当于(*pf1)(5);;
	//(der.*(der.pf))(9);//借用类实体才能实现;
	//der.callfuncptr(10);
	other oth1(1);//定义两个不同的对象实体让同一个函数指针指向不同的函数;
	other oth2(2);
	oth1.callfunptr(11,funcp);//传入需要的funcptr实体类;
	oth1.callfunptr(12,der);
	oth1.callfunptr(13,funcp);
	oth1.callfunptr(14,der);
}

若在外部定义函数指针,在定义之初就已经确定了指向函数的作用域,与借用的对象实体没有关系; 

class other
{
private:
	funcptrm_funcp;
public:
	typedefvoid (derive::*pFun)(int); //在这里已经将pFun的作用域设定为 funcptr::;
	pFunpf;
public:
	other(){}
	other(inti)
	{
		if (1 == i)
		{
			pf = &derive::show;//在给函数指针赋值时已经确定函数的作用域;
		} 
		else
		{
			pf = &derive::show2;
		}
	}
	voidcallfunptr(inti,derive* funcp) //在这里需要外部传入一个对象实习抑或是一个指针都可以,这一点很重要。
	{
		return (funcp->*(this->pf))(i);//这里与借用的对象实体没有关系;
	}
};
int _tmain(intargc, _TCHAR* argv[])
{
	funcptrfuncp;
	derive der; //在这里仅仅是用派生类对象替换了父类对象,显然是满足 “is-a”这种关系;
	//*(funcp.pf)(8);//编译错误,相当于(*pf1)(5);;
	//(der.*(der.pf))(9);//借用类实体才能实现;
	//der.callfuncptr(10);
	other oth1(1);//定义两个不同的对象实体让同一个函数指针指向不同的函数;
	other oth2(2);
	oth1.callfunptr(11,dynamic_cast<derive*>(&funcp));//传入需要的funcptr实体类;
	oth1.callfunptr(12,&der);
	oth1.callfunptr(13,dynamic_cast<derive*>(&funcp));
	oth1.callfunptr(14,&der);
}


总结:

以上的分析已经很深入了,如果读者能阅读到这里,那么相信也已经对函数指针有了充分的了解。

综上所诉:

   总的来说就一句话,函数指针就是函数入口地址, 下面再根据不同种类指针分别总结.

  

         全局的函数指针保存了函数的入口地址,这个是毋庸置疑的。

         成员函数的函数指针

1.        非虚函数即该函数的入口地址;

2.        虚函数即该对象的虚函数列表中相应虚函数的地址值,有可能派生类已经改写过了这个虚函数。亦可理解为记录了虚函数在虚函数列表中的便宜量,而不是记录虚函数的地址值。

 

 

 

如有说明不正确的地方肯请指正。






  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值