`
yidongkaifa
  • 浏览: 4038401 次
文章分类
社区版块
存档分类
最新评论

C++中的函数指针详解

 
阅读更多

1.1. 函数指针

函数指针仍然与其它类型的指针本质是一样的,其值仍然是一个地址,一般指向代码段,用于执行某个函数。可以作为参数传递,也可以作为返回值返回。(为了节约行数,文中不再给出主函数,而只给一个没有任何返回值和参数的函数,测试时直接在主函数中调用该函数即可)

1.1.1. 定义函数指针及使用
1.1.1.1. 全局函数指针

在C和C++语言中,对于定义指向全局函数的定义函数指针与声明函数原型类似,只是在函数名前多了一个*号,表示是指针,这与定义其它类型变量的格式是一致的,就是在变量名前加一个*号表示指针,例:

void (*fp)(void); // 函数指针定义
就定义了一个没有任何参数和返回值的指针,注意*fp两边的括号,如果去掉就成了函数声明:
void *fp(void); // 函数声明
声明了一个没有参数,返回值为void*类型的函数fp。

为了提高代码的可读性,可以使用typedef先定义一种函数指针类型,然后再定义一个变量,如:
typedef void (*FuncPtr)(void);
FuncPtr fp;
就定先定义了一种数据类型FuncPtr,该类型是一种函数指针,指向没有返回值和参数的函数。

对函数指针进行赋值时可以直接使用函数名,前面可以有一个可选的取址运算符,例如:
void SayHello( viod ) { cout<<”Hello everyone!”<fp = SayHello; // 合法
fp = &SayHello; // 合法, &符可选。

通过函数指针可以很方便地用于动态构建程序的行为,例如做一个数据过滤器,可以根据配置在初始化时添加不同的过滤器:

typedef bool (*Filter)(int);

struct FilterConfig{

bool negative_filter;

bool large_filter;

bool odd_filter;

};

// false if filtered out

bool LargeFilter( int v) { return v < 1024; }

bool NegativeFilter( int v) { return v > 0; }

bool OddFilter (int v) { return 0 == v % 2; }

void FuncPointerDemos( void )

{

// load config here, from file or other media

FilterConfig conf = {true, true, false};

cout<<"Odd filter:"<

<<"; Large Filter:"<

<<"; Negative Filter:"<

Filter filters[3] = {conf.large_filter ? &LargeFilter : NULL

, conf.negative_filter ? &NegativeFilter : NULL

,conf.odd_filter ? &OddFilter : NULL};

int data[] = {1080, 30000, 50, 80, 135, 278, 995, 672, -20, 87, 724, -6, 922, 723};

for(int i=0; i

bool p = true;

for(int f=0; f

if( NULL == filters[f] )

continue;

p = p && filters[f](data[i]);

}

if( p )

cout<<" data["<

}

}

1.1.1.2. 类成员指针
指向静态成员函数的指针

其实类的静态成员函数与全局函数的本质是一样的,除类的静态成员函数可以进行权限控制外,可以将类看作是命名空间的一种等价物,访问静态成员函数就好比是访问以类名为命令空间的一个全局函数,例:

class MathClass{

public:

static int Add( int a, int b) { return a + b ; }

};

void StaticMemberDemo( void )

{

int (*add)(int, int) = &MathClass::Add;

int a = 5, b = 7;

cout<<"a = "<

};

void MembFuncDemo( void )

{

HelloSayer tom("Tom");

HelloSayer* pmary = new HelloSayer("Mary");

void (HelloSayer::*member)(void) = &HelloSayer::SayHello; // 定义

(tom.*member)(); // 通过对象绑定并调用

(pmary->*member)(); // 通过指针绑定并调用

}

定义了一个HelloSayer的成员函数指针member,没有任何参数和返回值,然后分别使用对象和指针进行了调用,调用指向成员函数的函数指针时必须通过对象使用.*运算符调用,而通过对象指针时需要使用->*运算符,输出结果:

Hello from Tom!

Hello from Mary!

非静态成员函数必须在调用时与函数绑定,而不是赋值时,不绑定到对象也是非法的,例如:
void (HelloSayer::*member)(void);
member = HelloSayer::SayHello; // 合法
member = &HelloSayer::SayHello; // 合法,&运算符可选
member = &tom.SayHello; // 非法
member(); // 非法,没有绑定到对象

其实在本质上,非静态成员函数就是比普通全局函数多了一个参数,就是第一个参数,编译器为成员函数自动增加了一个指向类对象的指针,我们可以做个实验,代码如下:

class SumClass{

private:

int _a; int _b; int _sum ;

public:

SumClass(int a, int b) : _a(a), _b(b), _sum(a + b) { }

int Sum( void ) { return _sum ; }

};

typedef int (SumClass::*MemberFunc)( void );

typedef int (*GeneralFunc)(SumClass*);

union MemFuncConvertor{

GeneralFunc general;

MemberFunc member;

};

void NonStaticMember( void )

{

SumClass* pa = new SumClass(5, 7);

MemFuncConvertor conv;

conv.member = &SumClass::Sum;

cout<<"conv.general(pa ) = "<

cout<<"(pa->*conv.member)() = "<<(pa->*conv.member)()<

delete pa;

}

在Microsoft Visual C++ 2010、IBM xlC、GCC 4.4.3都可以编译通过,并可得到正确的结果。对于带两个参数的成员函数也是可以的,例如:

class SumClass2{

private:

int _a, _b, _sum ;

public:

SumClass2(int a, int b) : _a(a), _b(b), _sum(a + b) { }

int Sum( int c ) { return _sum + c; }

};

typedef int (SumClass2::*MemberFunc2)( int );

typedef int (*GeneralFunc2)(SumClass2*, int);

union MemFuncConvertor2{

GeneralFunc2 general;

MemberFunc2 member;

};

void NonStaticMember2( void )

{

SumClass2* pa = new SumClass2(5, 7);

SumClass2 ob(3,7);

MemFuncConvertor2 conv;

conv.member = &SumClass2::Sum;

cout<<"conv.general(pa, 10) = "<

cout<<"(pa->*conv.member)(10) = "<<(pa->*conv.member)(10)<

cout<<"conv.general(&ob,10 ) = "<

cout<<"(pa->*conv.member)(10) = "<<(ob.*conv.member)(10)<

delete pa;

}

这在IBM xlC和GCC 4.4.3上都可以正常运行并得到正确结果,而在VC上不能正常运行,而且两段代码在HP-UX的aCC上都无法正常运行,可能是因为其对参数传递的处理方式不同造成的,但这已经足够看到非静态成员函数指针的本质。

1.1.1.3. 函数指针作为参数与返回值

函数指针既然与普通的指针是一样的,就可以作为参数与返回值来传递,用于动态构建软件流程,提高的软件的灵活性,同时又与直接调用函数一样不失性能。

当函数指针作为参数或返回值时,建议先使用typedef定义一种类型,然后再使用定义的函数指针类型去定义参数或返回值,可以提高可读性。比如我们改写一下前例中的NonStaticMember( )函数,我们单独写一个函数完成从成员函数指针到普通函数指针的转换,函数定义可以这样:

inline int (*cast_to_general_fptr( int (SumClass::* const mem)(void) ))(SumClass*)

{

union {

int (SumClass::*member)( void );

int (*general)(SumClass*);

}u;

u.member = mem;

return u.general;

}

但显然,其可读性很差,因为它的确让很多人怀疑是不是合法的函数定义,但它的确是,而且这样的函数定义的确出现在生产代码中。无论出于什么目的,这种生产代码都是不应该被接受的,因为我们稍加改造就会使代码变得可读性很好:

typedef int (SumClass::*MemberFunc)( void );

typedef int (*GeneralFunc)(SumClass*);

union MemFuncConvertor{

GeneralFunc general;

MemberFunc member;

};

GeneralFunc cast_to_general_fptr(MemberFunc mem)

{

MemFuncConvertor conv ;

conv.member = mem;

return conv.general;

}

所以,强烈建议采用良好的编码风格,养成良好的编码习惯,不要写晦涩难懂的代码,因为除了节约几个字节的硬盘空间之外,不会有任何好处,只会让一些后来都瞠目结舌(或许这也是相要的结果之一)。

1.1.2. 几种特殊情况下的函数指针
1.1.2.1. 对于重载(overload)函数的选择

当一个函数名有多个重载的函数时,编译器会根据所定义的指针类型选择合适的参数,例如:

void f(int);

void f(double);

void (*p1)(int) = &f; // 选择 void f(int)

void (*p2)(double) = &f; // 选择 void f(double)

1.1.2.2. 指向虚函数的指针

当使用指向成员函数的函数指针所指向的函数为虚函数时,其表现出为的多态行为与直接调用虚函数具有一致的行为,例:

class Base{

public:

virtual void vf( void ) { cout<<__FUNCTION__<

void nvf( void ) { cout<<__FUNCTION__<

};

class Child : public Base{

public:

virtual void vf ( void ) { cout<<__FUNCTION__<

void nvf( void ) { cout<<__FUNCTION__<

};

class Grandson : public Child{

public:

virtual void vf ( void ) { cout<<__FUNCTION__<

};

void VirtualFuncPtrDemo( void )

{

typedef void (Base::*basefp_t)( void );

typedef void (Child::*childfp_t)( void );

Child* pc =new Child;

Base* pb = pc;

Grandson* pg = new Grandson();

basefp_t fp1 = &Base::vf;

basefp_t fp2 = &Base::nvf;

childfp_t fp3 = &Child::vf;

childfp_t fp4 = &Child::nvf;

cout<

(pb->*fp1)();

(pb->*fp2)();

(pc->*fp1)();

(pc->*fp2)();

(pc->*fp3)();

(pc->*fp4)();

pb = pg;

pc = pg;

(pb->*fp1)();

(pc->*fp1)();

}

输出结果:

Child::vf

Base::nvf

Child::vf

Base::nvf

Child::vf

Child::nvf

Grandson::vf

Grandson::vf

1.1.2.3. 指向模板函数的指针

当将模板函数赋值给函数指针时,编译器会根据所定义的函数指针类型选择一个合适的特化版本,例:

template

void PrintSize( T ) { cout<<"sizeof(T)="<

template<>

void PrintSize(char* str){ cout<<"sizeof(char*) = "<

void PonterToTemplateDemo( void )

{

void (*p1)(double) = PrintSize;

void (*p2)(int) = &PrintSize;

void (*p3)(char) = &PrintSize;

void (*p4)(char*) = &PrintSize;

p1(1); p2(1); p3(1), p4("this is a string");

}

输出结果:

sizeof(T)=8

sizeof(T)=4

sizeof(T)=1

sizeof(char*) = 24

1.1.3. 函数指针与其它类型的转换

有些场合需要将一种函数指针转换成另外一种指针,或者从另外一种指针转换成一种函数指针,适合的方法也不同。

只有原型相同的函数指针变量才能互相直接赋值。

函数指针转换成void*类型时,不需要强制转换,可以直接赋值。

当函数指针原型一致时,父类的函数可以直接赋值给子类成员函数指针,例:

class B { public: void f( void ) { } };

class D : public B { public void f(void) { } };

void (D::* pf)(void) = &B::f;

而对于两种原型不一致的函数指针互相赋值时可以使用union,比如上节中的示例,这种方法是最稳妥的方法,因为所有编译器都不会提示警告,有利于编写干净的代码;而且这种方法是万能的,不仅可以用于函数指针之前的强制转换,而且可以用于任何两种数据类型之前的强制转换(按字节)。但是,这种野蛮的转换一般是没有意义的,不建议这么做,除非在操作知道你知道你是在做什么。最常用的场景是将void*类型转成某种类型的函数指针。

1.1.4. 函数指针实例
1.1.4.1. 指向数据段的函数指针

一开始说到一般情况下指针指向代码段地址,用于调用某个函数,但并不是绝对的。既然函数指针与普通的其它变量指针一样表示一个地址,所以它可以指向任何地址,例如:

#include

using namespace std;

static void PointerToDataSeg( void );

int main( void )

{

PointerToDataSeg();

return 0;

}

namespace { int global_value = 5; }

static void IncreaseValue( void ){ global_value += 10; }

typedef void (*FuncPtr)(void);

void PointerToDataSeg( void )

{

FuncPtr fp = &IncreaseValue;

cout<<"global_value = "<

fp(); // execute in code segment

cout<<"global_value = "<

void* pmem = new char[256];

memcpy(pmem, fp, 256);

fp = (FuncPtr)pmem;

fp(); // execute code on heap

cout<<"global_value = "<

}

运行结果:

global_value = 5

global_value = 15

global_value = 25

程序中定义了一个函数IncreaseValue,然后定义了一个函数指针fp,通过函数指针将函数后面的一些代码复制到了堆上,然后将fp指向了堆上的代码数据,通过函数指针执行这些代码。

这也是黑客攻击的一种重要手段,所以在一些操作系统中对这种操作进行了限制,例如在windows 7中,对数据段调用fp时将会抛出非法访问的异常。

使用函数指针调用函数其实在CPU内部就是直接将程序指针指到某个位置,以前在没的操作系统的嵌入式系统通过函数指针实现重启:
void (*begin)( void ) = 0;
begin();
但在有操作系统托管的系统中这样做是不行的,操作系统会禁止此类操作。

1.1.4.2. 一个应用实例

通过函数指针进行操作的一个典型应用就是进行单元测试,例如:

class PendulumTest{

public:

void TestHardware( void ) { cout<<"OK!"<

void TestDiscreteModel( void ) { cout<<"OK!"<

void TestInterface( void) { cout<<"OK!"<

void Run(void);

};

struct TestItem{

char* name;

char* message;

void (PendulumTest::*func)(void) ;

};

void PendulumTest::Run( void )

{

TestItem tests[]={

{ "Discrete Model Test","Test discrete model"
, &PendulumTest::TestDiscreteModel},

{ "Interface test", "Test interface without hardware",
&PendulumTest::TestInterface},

{ "Hardware Test", "Test hardware", &PendulumTest::TestHardware},

};

for(int i=0; i

cout<<"Running test["<

(this->*tests[i].func)();

}

}

void TestDemo( void )

{

PendulumTest test;

test.Run();

}

通过函数指针可以简化主函数PendulumTest::Run,使程序可读性、易维护性都有显示提高,增加新的测试,只需要增加一个测试函数,并将其添加到测试列表中即可,并可以通过注释掉某行或做一些开关来控制测试的内容。

分享到:
评论

相关推荐

    C++中函数指针详解及代码分享

    主要介绍了C++中函数指针详解及代码示例,具有一定参考价值,需要的朋友可以了解下。

    C++函数指针详解

    学习c++的过程中,指针是难点,熟悉了指针之后,还有一个让人很蛋疼的难点,那是函数指针了。本博文详细介绍一下常见的各种坑爹的函数指针。  至于指针的详细学习,推荐这篇博文C++指针详解  与数据一样,函数也...

    C++获取类的成员函数的函数指针详解及实例代码

    主要介绍了C++获取类的成员函数的函数指针详解及实例代码的相关资料,需要的朋友可以参考下

    详解C++中的指针、数组指针与函数指针

    C++中一个重要的特性就是指针,指针不仅具有获得地址的能力,还具有操作地址的能力。指针可以用于数组、或作为函数的参数,用来访问...常见的指针定义有3种:变量指针、数组指针和函数指针的定义。 (1)、变量指针的定

    C++ 虚函数的详解及简单实例

    C++ 虚函数的详解 虚函数的使用和纯虚函数的使用。 虚函数是在基类定义,然后子类重写这个函数后,基类的指针指向子类的对象,可以调用这个函数,这个函数同时保留这子类重写的功能。 纯虚函数是可以不用在基类定义...

    C++中回调函数及函数指针的实例详解

    主要介绍了C++中回调函数及函数指针的实例详解的相关资料,希望通过本文能帮助到大家,让大家理解掌握这部分内容,需要的朋友可以参考下

    C++智能指针详解.pdf

    C++智能指针详解 智能指针详解 智能指针内容很多,重点是基本⽤法。 #include &lt;boost/shared_ptr.hpp&gt; class CBase: public boost::enable_shared_from_this&lt;CBase&gt; { public: virtual void f(){}//必须有个虚函数...

    C/C++指针详解

    在 C 语言的学习中,指针的运用被认为是最大的难关。 关于指针的学习,我们经常听到下面这样的建议: “如果理解了计算机的内存和地址等概念,指针什么的就简单 了。” “因为 C 是低级语言,所以先学习汇编语言比较...

    C++中this指针用法详解及实例

    C++中this指针用法详解及实例 概要: 本文简单介绍this指针的基本概念,并通过一个实际例子介绍this指针用于防止变量命名冲突和用于类中层叠式调用的两个用法。 this指针概览 C++中,每个类 对应了一个对象,每个...

    C++智能指针-unique-ptr智能指针详解.pdf

    C++智能指针 智能指针_unique_ptr智能指针详解 智能指针详解 作为智能指针的⼀种,unique_ptr 指针⾃然也具备"在适当时机⾃动释放堆内存空间"的能⼒。和 shared_ptr 指针最⼤的不同之处在 于,unique_ptr 指针指向的...

    C++指向函数的指针用法详解

    主要介绍了C++指向函数的指针用法,对函数指针的声明、优先级、指针类型等概念做了较为详尽的分析,需要的朋友可以参考下

    C++智能指针详解(1).pdf

    C++智能指针详解 1、概述 我们知道除了静态内存和栈内存外,每个程序还有⼀个内存池,这部分内存被称为⾃由空间或者堆。程序⽤堆来存储动态分配的对象即那些 在程序运⾏时分配的对象,当动态对象不再使⽤时,我们的...

    c/c++ 指针详解教程

    让你不再害怕指针,深入理解C语言指针的奥秘。 C++程序设计中使用指针可以: 使程序简洁、紧凑、高效 有效地表示复杂的数据结构 动态分配内存 得到多于一个的函数返回值

    C++ 中的this指针详解及实例

    C++ this 指针详解 学习 C++ 的指针既简单又有趣。通过指针,可以简化一些 C++ 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C++ 程序员,学习指针是很有必要的...

    C++指针详解内容全面透彻

    全面透彻地讲解了C++指针的相关知识 C++程序设计中使用指针可以: 使程序简洁、紧凑、高效 有效地表示复杂的数据结构 动态分配内存 得到多于一个的函数返回值

Global site tag (gtag.js) - Google Analytics