*"和".*"用来调用指针指向的函数。比如:class" />
当前位置:文档之家› C++成员函数_this指针_调用约定

C++成员函数_this指针_调用约定

C++成员函数_this指针_调用约定
C++成员函数_this指针_调用约定

摘要:介绍了如何取成员函数的地址以及调用该地址.

关键字:C++成员函数this指针调用约定

一、成员函数指针的用法

在C++中,成员函数的指针是个比较特殊的东西。对普通的函数指针来说,可以视为一个地址,在需要的时候可以任意转换并直接调用。但对成员函数来说,常规类型转换是通不过编译的,调用的时候也必须采用特殊的语法。C++专门为成员指针准备了三个运算符: "::*"用于指针的声明,而"->*"和".*"用来调用指针指向的函数。比如:

class tt

{

public: void foo(int x){ printf("\n %d \n",x); } };

typedef void ( tt::* FUNCTYPE)(int );

FUNCTYPE ptr = tt::foo; //给一个成员函数指针赋值.

tt a;

(a.*ptr)(5); //调用成员函数指针.

tt *b = new tt;

(b->*ptr)(6); //调用成员函数指针.

注意调用函数指针时括号的用法,因为.* 和->* 的优先级比较低,必须把它们和两边要结合的元素放到一个括号里面,否则通不过编译。不仅如此,更重要的是,无法为成员函数指针进行任何的类型转换,比如你想将一个成员函

数的地址保存到一个整数中(就是取类成员函数的地址),按照一般的类型转

换方法是办不到的.下面的代码:

DWORD dwFooAddrPtr= 0;

dwFooAddrPtr = (DWORD) &tt::foo; /* Error C2440 */

dwFooAddrPtr = reinterpret_cast (&tt::foo); /* Error C2440 */

你得到只是两个c2440错误而已。当然你也无法将成员函数类型转换为其

它任何稍有不同的类型,简单的说,每个成员函数指针都是一个独有的类型,

无法转换到任何其它类型。即使两个类的定义完全相同也不能在其对应成员函

数指针之间做转换。这有点类似于结构体的类型,每个结构体都是唯一的类型,但不同的是,结构体指针的类型是可以强制转换的。有了这些特殊的用法和严

格的限制之后,类成员函数的指针实际上是变得没什么用了。这就是我们平常

基本看不到代码里有"::*", ".*" 和"->*"的原因。

二、取成员函数的地址

当然,引用某位大师的话:"在windows中,我们总是有办法的"。同样,在C++中,我们也总是有办法的。这个问题,解决办法已经存在了多年,并且广为使用(在MFC中就使用了)。一般有两个方法,一是使用内嵌的汇编语言直接取函数地址,二是使用union类型来逃避C++的类型转换检测。两种方法都是利用了某种机制逃避C++的类型转换检测,为什么C++编译器干脆不直接放开这个限制,一切让程序员自己作主呢?当然是有原因的,因为类成员函数和普通函数还是有区别的,允许转换后,很容易出错,这个在后面会有详细的说明。现在先看看取类成员函数地址的两种方法:

第一种方法:

Template

void GetMemberFuncAddr_VC6(ToType& addr,FromType f)

{

union

{

FromType _f;

ToType _t;

}ut;

ut._f = f;

addr = ut._t;

}

这样使用:

DW ORD dwAddrPtr;

GetMemberFuncAddr_VC6(dwAddrPtr,&tt::foo);

为什么使用模版? 呵呵,如果不使用模版,第二个参数该怎么些,写成函数指针且不说太繁琐,关键是没有通用性,每个成员函数都要单独写一个转换函数。

第二种方法:

#define GetMemberFuncAddr_VC8(FuncAddr,FuncType)\

{ \ __asm \

{ \

mov eax,offset FuncType \ }; \

__asm \

{ \

mov FuncAddr, eax \ }; \

}

这样使用:

DW ORD dwAddrPtr;

GetMemberFuncAddr_VC8(dwAddrPtr,&tt::foo);

本来是想写成一个模版函数的,可惜虽然通过了编译,却不能正确运行。估计在汇编代码中使用模版参数不太管用,用offset取偏移量直接就得0。

上面的宏是可以正确运行的,并且还有一个额外的好处,就是可以直接取私有成员函数的地址(大概在asm括号中,编译器不再检查代码的可访问性)。不过缺点是它在vc6下是无法通过编译的,只能在VC8下使用。

三、调用成员函数地址

通过上面两个方法,我们可以取到成员函数的地址。不过,如果不能通过地址来调用成员函数的话,那也还是没有任何用处。当然,这是可行的。不过在这之前,需要了解关于成员函数的一些知识。

我们知道,成员函数和普通函数最大的区别就是成员函数包含一个隐藏的参数this指针,用来表明成员函数当前作用在那一个对象实例上。根据调用约定(Calling Conv ention)的不同,成员函数实现this指针的方式也不同。如果使用__thiscall调用约定,那么this指针保存在寄存器ECX中,VC编译器缺省情况下就是这样的。如果是__stdcall或__cdecl调用约定,this指针将通过栈进行

传递,且this指针是最后一个被压入栈的参数,相当于编译器在函数的参数列

表中最左边增加了一个this参数。

这里还有件事不得不提,虽然v c将__thiscall类型作为成员函数的默认类型,但是vc6却没有定义__thiscall关键字!如果你使用__thiscall来定义一个函数,编译器报错:'__thiscall' key word reserv ed for future use。

知道这些就好办了,我们只要根据不同的调用约定,准备好this指针,然后象

普通函数指针一样的使用成员函数地址就可以了。

对__thiscall类型的成员函数(注意,这个是VC的默认类型),我们在调用之前加一句: mov ecx, this; 然后就可以调用成员函数指针。例如:

class tt

{

public:

void foo(int x,char c,char *s)//没有指定类型,默认是

__thiscall.

{

printf("\n m_a=%d, %d,%c,%s\n",m_a,x,c,s);

}

int m_a;

};

typedef void (__stdcall *FUNCTYPE)(int x,char c,char *s);//

定义对应的非成员函数指针类型,注意指定__stdcall.

tt abc;

abc.m_a = 123;

DWORD ptr;

DWORD This = (DWORD)&abc;

GetMemberFuncAddr_VC6(ptr,tt::foo); //取成员函数地址.

FUNCTYPE fnFooPtr = (FUNCTYPE) ptr;//将函数地址转化为普通函数的指针.

__asm //准备this指针.

{

mov ecx, This;

}

fnFooPtr(5,'a',"7xyz"); //象普通函数一样调用成员函数的地址.

对其它类型的成员函数,我们只要申明一个与原成员函数定义完全类似的普通函数指针,但在参数中最左边加一个v oid * 参数。代码如下:

class tt

{

public:

void __stdcall foo(int x,char c,char *s)//成员函数指定了__stdcall调用约定.

{

printf("\n m_a=%d, %d,%c,%s\n",m_a,x,c,s);

}

int m_a;

};

typedef void (__stdcall *FUNCTYPE)(void *This,int x,char

c,char *s);//注意多了一个void *参数.

tt abc;

abc.m_a = 123;

DWORD ptr;

GetMemberFuncAddr_VC6(ptr,tt::foo); //取成员函数地址.

FUNCTYPE fnFooPtr = (FUNCTYPE) ptr;//将函数地址转化为普通函数的指针.

fnFooPtr(&abc,5,'a',"7xyz"); //象普通函数一样调用成员函数的地址,注意第一个参数是this指针.

每次都定义一个函数类型并且进行一次强制转化,这个事是比较烦的,能不能将这些操作写成一个函数,然后每次调用是指定函数地址和参数就可以了呢?当然是可以的,并且我已经写了一个这样的函数。

//调用类成员函数

//callflag:成员函数调用约定类型,0--thiscall,非0--其它类型.

//funcaddr:成员函数地址.

//This:类对象的地址.

//count:成员函数参数个数.

//...:成员函数的参数列表.

DWORD CallMemberFunc(int callflag,DWORD funcaddr,void *This,int count,...)

{

DWORD re;

if(count>0)//有参数,将参数压入栈.

{

__asm

{

mov ecx,count;//参数个数,ecx,循环计数

器.

mov edx,ecx;

shl edx,2;

add edx,0x14; edx = count*4+0x14;

next: push dword ptr[ebp+edx];

sub edx,0x4;

dec ecx

jnz next;

}

}

//处理this指针.

if(callflag==0) //__thiscall,vc默认的成员函数调用类型.

{

__asm mov ecx,This;

}

else//__stdcall

{

__asm push This;

}

__asm//调用函数

{

call funcaddr;

mov re,eax;

}

return re;

}

使用这个函数,则上面的两个调用可以这样写:

CallMember Func(0,ptr1,&abc,3,5,'a',"7xy z");//第一个参数0,表示采用

__thiscall调用.

CallMember Func(1,ptr2,&abc,3,5,'a',"7xy z");//第一个参数1,表示采用非

__thiscall调用.

需要说明的是,CallMember Func是有很多限制的,它并不能对所有的情况都产生正确的调用序列。原因之一是它假定每个参数都使用了4个字节的栈空间。这在大多数情况下是正确的,比如参数为指针,char,short,int,long以及对应的无符号类型,这些参数确实都是每一个参数使用了4字节的栈空间。但是还有很多情况下,参数不使用4字栈空间,比如double,自定义的结构或类.float虽

然是占了4字节,但编译器还产生了一些浮点指令,而这些无法在

CallMember Func被模拟出来,因此对float参数也是不行的。

总结一下,如果成员函数的参数都是整型兼容类型,则可以使用CallMember Func调用函数地址。如果不是,那就只有按前面的方法,先定义对应的普通函数类型,强制转化,准备this指针,然后调用普通函数指针。

四、进一步的讨论

到目前为止,已经讨论了如何取成员函数的地址,然后如何使用这个地址。但是还有些重要的情况没有讨论,我们知道成员函数可分为三种:普通成员函数,静态,虚拟。另外更重要的是,在继承甚至多继承下情况如何。

首先看看最简单的单继承,非虚拟函数的情况。

class tt1

{

public:

void foo1(){ printf("\n hi, i am in tt1::foo1\n"); } };

class tt2 : public tt1

{

public:

void foo2(){ printf("\n hi, i am in tt2::foo2\n"); } };

注意,tt2中没有定义函数foo1,它的foo1函数是从tt1中继承过来的。这种情况下,我们直接取tt2::foo1的地址行会发生什么?

DWORD tt2_foo1;

tt1 x;

GetMemberFuncAddr_VC6(tt2_foo1,&tt2::foo1);

CallMemberFunc(0,tt2_foo1,&x,0); // tt2::foo1 = tt1::foo1

运行结果表明,一切正常!当我们写下tt2::foo1的时候,编译器知道那实际上是tt1::foo1,因此它会暗中作替换。编译器(VC6)产生的代码如下:

GetMemberFuncAddr_VC6(tt2_foo1,&tt2::foo1); //源代码.

//VC6编译器产生的汇编代码:

push offset @ILT+235(tt1::foo1)(004010f0) //直接用tt1::foo1 替换tt2::foo1.

...

再看看稍微复杂些的情况,继承情况下的虚拟函数。

class tt1

{

public:

void foo1(){ printf("\n hi, i am in tt1::foo1\n"); }

virtual void foo3(){ printf("\n hi, i am in

tt1::foo3\n"); }

};

class tt2 : public tt1

{

public:

void foo2(){ printf("\n hi, i am in tt2::foo2\n"); }

virtual void foo3(){ printf("\n hi, i am in

tt2::foo3\n"); }

};

现在tt1和tt2都定义了虚函数foo3,按C++语法,如果通过指针调用foo3,应该发生多态行为。下面的代码:

DWORD tt1_foo3,tt2_foo3;

GetMemberFuncAddr_VC6(tt1_foo3,&tt1::foo3); GetMemberFuncAddr_VC6(tt2_foo3,&tt2::foo3);

tt1 x;

tt2 y;

CallMemberFunc(0,tt1_foo3,&x,0); // tt1::foo3

CallMemberFunc(0,tt2_foo3,&x,0); // tt2::foo3

CallMemberFunc(0,tt1_foo3,&y,0); // tt1::foo3

CallMemberFunc(0,tt2_foo3,&y,0); // tt2::foo3

输出如下:

hi, i am in tt1::foo3

hi, i am in tt1::foo3

hi, i am in tt2::foo3

hi, i am in tt2::foo3

请注意第二行输出,tt2_foo3取的是&tt2::foo3,但由于传递的this指针产生是&x,所以实际上调用了tt1::foo3。同样,第三行输出,取的是基类的函数

地址,但由于实际对象是派生类,最后调用了派生类的函数。这说明取得的成员函数地址在虚拟函数的情况下仍然保持了正确的行为。

你若真的理解了上面所说的,一定会觉得奇怪。取函数地址的时候就得到了一个整数(成员函数地址),为何调用的时候却进了不同的函数? 只要看看汇编代码就都清楚了,"源码之前,了无秘密"。源代码:

GetMemberFuncAddr_VC6(tt1_foo3,&tt1::foo3); 产生的汇编代码如下:

push offset @ILT+90(`vcall') (0040105f)

...

原来取tt1::foo3地址的时候,并不是真的就将tt1::foo3的地址传给了函数,而是传了一个vcall函数的地址。顾名思义,v call当然是虚拟调用的意思。我们找到地址0040105f,看看这个函数到底干了些什么。

@ILT+90(??_9@$BA@AE):

0040105F jmp `vcall' (00401380)

该地址只是ILT的一个项,直接跳转到真正的v call函数,00401380。找到00401380,就可以看到v call的代码。

`vcall':

00401380 mov eax,dword ptr [ecx] ;//将this指针视为dword类型,并将指向的内容(对象的首个dword)放入eax.

00401382 jmp dword ptr [eax] ;//跳转到eax所指向的地址.

代码执行的时候,ecx就是this指针,具体说就是上面对象x或y的地址。而eax就是对象x或y的第一个dword的值。我们知道,对于有虚拟函数的类对象,其对象的首地址处总是一个指针,该指针指向一个虚函数的地址表。上面的对象由于只有一个虚函数,所以虚函数表也只有一项。因此,直接跳转到eax指向的地址就好。如果有多个虚函数,则eax还要加上一个偏移量,以定位到不同的虚函数。比如,如果有两个虚函数,则会有两个v call代码,分别对应不同的虚函数,其代码大概是下面的样子:

`vcall':

00401BE0 mov eax,dword ptr [ecx]

00401BE2 jmp dword ptr [eax]

`vcall':

00401190 mov eax,dword ptr [ecx]

00401192 jmp dword ptr [eax+4]

编译器根据取的是哪个虚函数的地址,则相应的用对应的v call地址代替。

总结一下:用前面方法取得的成员函数地址在虚拟函数的情况下仍然保持正确的行为,是因为编译器实际上传递了对应的vcall地址。而v call代码会根据上下文this指针定位到对应的虚函数表,进而调用正确的虚函数。

最后,我们看一下多继承情况。很明显,现在情况要复杂得多。如果实际试一下,会碰到很多困难。首先,指定成员函数的时候可能会碰到冲突。其次,给定this指针的时候需要经过调整。另外,对虚拟继承可能还要特别处理。解决所有这些问题已经超出了这篇文章的范围,并且我想要的成员函数指针是一个真正的指针,而在多继承的情况下,很多时候成员函数指针已经变成了一个结构体(见参考文献),这时要正确调用该指针就变得格外困难。因此结论是,上面讨论的方法并不适用于多继承的情况,要想在多继承的情况下直接调用成员函数地址,必须手工处理各种调整,没有简单的统一方法。

参考文献成员函数指针与高性能的C++委托

_stdcall介绍

stdcall调用约定: stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。 stdcall调用约定声明的语法为(以前文的那个函数为例): int __stdcall function(int a,int b) stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。 以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处 翻译成汇编语言将变成: push 2 第二个参数入栈 push 1 第一个参数入栈 call function 调用参数,注意此时自动把cs:eip入栈 而对于函数自身,则可以翻译为: push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b mov esp,ebp 恢复esp pop ebp ret 8 而在编译时,这个函数的名字被翻译成_function@8 注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。 从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。 cdecl调用约定:

函数指针

方法 指针函数和函数指针的区别 关于函数指针数组的定义 为函数指针数组赋值 函数指针的声明方法为: 数据类型标志符 (指针变量名) (形参列表); 注1:“函数类型”说明函数的返回类型,由于“()”的优先级高于“*”,所以指针变量名外的括号必不可少,后面的“形参列表”表示指针变量指向的函数所带的参数列表。例如: int func(int x); /* 声明一个函数 */ int (*f) (int x); /* 声明一个函数指针 */ f=func; /* 将func函数的首地址赋给指针f */ 赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。 注2:函数括号中的形参可有可无,视情况而定。 下面的程序说明了函数指针调用函数的方法: 例一、 #include int max(int x,int y){ return(x>y?x:y); } void main() { int (*ptr)(int, int); int a,b,c; ptr=max; scanf("%d%d",&a,&b); c=(*ptr)(a,b); printf("a=%d,b=%d,max=%d",a,b,c); } ptr是指向函数的指针变量,所以可把函数max()赋给ptr作为ptr的值,即把max()的入口地址赋给ptr,以后就可以用ptr来调用该函数,实际上ptr 和max都指向同一个入口地址,不同就是ptr是一个指针变量,不像函数名称那样是死的,它可以指向任何函数,就看你想怎么做了。在程序中把哪个

回调函数与回调机制

回调函数与回调机制 1. 什么是回调函数 回调函数(callback Function),顾名思义,用于回调的函数。回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机。回调函数包含下面几个特性: ?属于工作流的一个部分; ?必须按照工作流指定的调用约定来申明(定义); ?他的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能; 2. 回调机制 回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。 如上图所示,工作流提供了两个对外接口(获取参数、显示结果),以回调函数的形式实现。 ?“获取参数”回调函数,需要工作流使用者设定工作流计算需要的参数。 ?“显示结果”回调函数,提供计算结果给工作流使用者。

再以Windows的枚举顶级窗体为例。函数EnumWindows用于枚举当前系统中的所有顶级窗口,其函数原型为: BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ); 其中lpEnumFunc是一个回调函数,他用于返回枚举过程中的获得的窗口的句柄。其定义约定为: BOOL CALLBACK EnumWindowsProc( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value ); 在这个例子中,EnumWindows 是一个工作流,这个工作流用于遍历windows的所有窗口并获得其句柄。用户使用EnumWindows工作流的目的是想通过工作流来来获取窗口的句柄以便针对特定的一个或多个窗口进行相关处理。于是EnumWindows就扩展出接口lpEnumFunc,用于返回遍历的窗口句柄。 EnumWindows工作流的结束有两个方式:1,用户在回调函数中返回FALSE;2,再也找不到顶级窗口。我们可以推测EnumWindows的实现机制如下: 注:下列代码中的FindFirstTopWindows(), FindNextTopWindow()为假设的,Windows API 没有此函数,只是为了表明Enumwindows的内部流程。 BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ) { BOOL bRet = TRUE; HWND hWnd = ::FindFirstTopWindows(); // 此函数是假设的,查找第一个顶级窗口 // 当hWnd为0时表示再也找不到顶级窗口 while( hWnd ) { bRet = (*lpEnumFunc)( hWnd, value ); if( !bRet) break; // 终止EnumWindows工作流; hWnd = ::FindNextWindow(); // 此函数是假设的,查找下一个顶级窗口 } } 在EnumWindows(...)函数中,实现了窗口枚举的工作流,他通过回调机制把用户关心(顶级窗口句柄)的和枚举工作流分开,用户不需要知道EnumWindows的具体实现,用户只要知道,设定了lpEnumFunc函数,然后把函数指针传给EnumWindwos就可以获得想要的窗口句柄。

指向函数的指针详解

指向函数的指针 函数指针是指指向函数而非指向对象的指针。像其他指针一样,函数指针也指向某个特定的类型。函数类型由其返回类型以及形参表确定,而与函数名无关: bool (*pf)(const string &,const string &); 这个语句将pf声明为指向函数的指针,它所指向的函数带有两个const string &类型的形参和bool 类型的返回值。 注意:*pf两侧的括号是必需的。 1.typedef简化函数指针的定义: 函数指针类型相当地冗长。使用typedef为指针类型定义同义词,可将函数指针的使用大大简化: Typedef bool (*cmpfn)(const string &,const string &); 该定义表示cmpfn是一种指向函数的指针类型的名字。该指针类型为“指向返回bool类型并带有两个const string 引用形参的函数的指针”。在要使用这种函数指针类型时,只需直接使用cmpfcn即可,不必每次都把整个类型声明全部写出来。 2.指向函数的指针的初始化和赋值 在引用函数名但又没有调用该函数时,函数名将被自动解释为指向函数的指针。假设有函数: Bool lengthcompare(const string &,const string &); 除了用作函数调用的左操作数以外,对lengthcompare的任何使用都被解释为如下类型的指针:

bool (*)(const string &,const string &); 可使用函数名对函数指针初始化或赋值: cmpfn pf1=0; cmpfn pf2=lengthcompare; pf1=legnthcompare; pf2=pf1; 此时,直接引用函数名等效于在函数名上应用取地址操作符: cmpfcn pf1=lengthcompare; cmpfcn pf2=lengthcompare; 注意:函数指针只能通过同类型的函数或函数指针或0值常量表达式进行初始化或赋值。 将函数指针初始化为0,表示该指针不指向任何函数。 指向不两只函数类型的指针之间不存在转换: string::size_type sumLength(const string &,const string &); bool cstringCompare(char *,char *); //pointer to function returning bool taking two const string& cmpFcn pf;//error:return type differs pf=cstringCompare;//error:parameter types differ pf=lengthCompare;//ok:function and pointer types match exactly 3.通过指针调用函数 指向函数的指针可用于调用它所指向的函数。可以不需要使用解引用

指针函数与函数指针的区别

指针函数与函数指针的区别 一、 在学习arm过程中发现这“指针函数”与“函数指针”容易搞错,所以今天,我自己想一次把它搞清楚,找了一些资料,首先它们之间的定义: 1、指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针 类型标识符 *函数名(参数表) int *f(x,y); 首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变量来接受,也就是说,指针函数一定有函数返回值,而且,在主调函数中,函数返回值必须赋给同类型的指针变量。 表示: float *fun(); float *p; p = fun(a); 注意指针函数与函数指针表示方法的不同,千万不要混淆。最简单的辨别方式就是看函数名前面的指针*号有没有被括号()包含,如果被包含就是函数指针,反之则是指针函数。来讲详细一些吧!请看下面 指针函数: 当一个函数声明其返回值为一个指针时,实际上就是返回一个地址给调用函数,以用于需要指针或地址的表达式中。 格式: 类型说明符* 函数名(参数) 当然了,由于返回的是一个地址,所以类型说明符一般都是int。 例如:int *GetDate(); int * aaa(int,int); 函数返回的是一个地址值,经常使用在返回数组的某一元素地址上。 int * GetDate(int wk,int dy); main() { int wk,dy; do { printf(Enter week(1-5)day(1-7)\n); scanf(%d%d,&wk,&dy); } while(wk<1||wk>5||dy<1||dy>7); printf(%d\n,*GetDate(wk,dy));

指向函数的指针

指向函数的指针 c/c++ 2010-11-20 13:17:02 阅读41 评论0 字号:大中小订阅首先看这个程序: #include using namespace std; void max(int a, int b) { cout<<"now call max("<b?a:b; cout<

我曾经写过一个命令行程序,有很多命令,于是构着了一个结构的数组,大概是这样 struct{ char *cmd_name; bool (*cmd_fun)(); }cmd_info_list[MAX_CMD_NUM]; 程序中得到一个用户输入的命令字符串后,就匹配这个数组,找到对应的处理函数。 以后每次添加一个命令,只需要加个函数,然后在这个数组中加一个记录就可以了,不需要修改太多的代码。 这可以算是一种用法吧。呵呵。 Windows 中,窗口的回调函数就用到了函数指针。 用VC向导 New Projects ----> Win32 Application ----> A typical "Hello World!" application 其中的WndProc 是WNDPROC 类型的函数typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM); WndProc 作为窗口的回调函数,用来填充WNDCLASSEX 结构。 WNDCLASSEX wcex; wcex.lpfnWndProc = (WNDPROC)WndProc; void ListTraverse(LinkList L,void (*visit)(int)) { Link p; p=L->next; while(p) { visit(p->data); p=p->next; } return OK; } void print(int c) { printf("%d",c); } ListTraverse(L,print); 这算是个例子吧??? #include #include #include double Add (double x, double y) { return x+y; } double Sub (double x, double y) { return x-y; } double Mul (double x, double y)

第三章 使用MASM

标题:【原创】windows下32位汇编语言学习笔记第三章使用MASM 作者:jasonnbfan 时间: 2009-05-03,02:48:43 链接: https://www.doczj.com/doc/6a12526362.html,/showthread.php?t=87752 windows下32位汇编语言学习笔记第三章使用MASM 本章讲述的是masm 汇编的程序结构,基本语法,定义等,本章这些内容只是汇编指令里比较常用的,在下面的章节将要用到的指令。实际上汇编指令远不止这些。感兴趣可以参照其他的汇编书籍了解一下。不过对于本书下面的章节来说,这些指令基本上够用了。 Win32汇编程序的基本结构 从例子可以看出来,Win32汇编的结构很简单,下面简单分析下。 模式定义 .386 .model falt,stdcall option casemap:none 这个地方书上已经将的很清楚了。关于.386 .486 .586 .686 之类的指令集,我没找到资料,试验了一下写成.686也没什么问题。 include includelib语句 include windows.inc includelib kernel32.lib 这里的include 和C语言里的include 头文件一个道理,都是导入预先声明好的函数,包括定义好的各种结构。 includelib 就是指定连接的时候告诉连接器从那个lib里找你通过include引入并使用的函数,win32API都是以动态链接库的形式提供的,所以这里就需要对你使用的winAPI包含在那个dll里做到心中有数,不知道的就查msdn,每个API说明后面都有这个API包含在那个头文件中,比如: Header: Declared in Winuser.h; include Windows.h. winAPI是C语言写的,所以头文件都是.h的,汇编的头文件声明是.inc的,打开kernel32.inc 找找Exitprocess 的申明 ExitProcess PROTO :DWORD 你也可以不用预定义的.inc头文件,自己定义。 如果你使用了函数确没有包含对应的.lib,比如使用了ExitProcess函数,没有includelib kernel32.lib,连接时就会报错: error LNK2001: 无法解析的外部符号 __imp__ExitProcess@4 这个外部符号名就是你要调用的函数,名字很诡异吧,这里先有个了解,讲到调用约定的时候再详细说明。

C#调用API函数详细说明

C#:[DllImport("kernel32.dll")]是什么意思?? 这叫引入kernel32.dll这个动态连接库。 这个动态连接库里面包含了很多WindowsAPI函数,如果你想使用这面的函数,就需要这么引入。举个例子: [DllImport("kernel32.dll")] private static extern void 函数名(参数,[参数]); 函数名就是一个属于kernel32.dll里的一个函数。完了你就可以用那个函数了。 kernel32.dll调用kernel32.dll这个DLL里面的API接口! 系统API 例如 [DllImport("user32.dll")]//--引入API public static extern ReturnT ype FunctionName(type arg1,type arg2,...);//--声明方法 调用该方法是和调用普通方法没区别 DLL Import 属性 现在是更深入地进行探讨的时候了。在对托管代码进行P/Invoke 调用时,DllImportAttribute 类型扮演着重要的角色。DllImportAttribute 的主要作用是给CLR 指示哪个DLL 导出您想要调用的函数。相关DLL 的名称被作为一个构造函数参数传递给DllImportAttribute。 如果您无法肯定哪个DLL 定义了您要使用的Windows API 函数,Platform SDK 文档将为您提供最好的帮助资源。在Windows API 函数主题文字临近结尾的位置,SDK 文档指定了 C 应用程序要使用该函数必须链接的.lib 文件。在几乎所有的情况下,该.lib 文件具有与定义该函数的系统DLL 文件相同的名称。例如,如果该函数需要 C 应用程序链接到Kernel32.lib,则该函数就定义在Kernel32.dll 中。您可以在MessageBeep 中找到有关MessageBeep 的Platform SDK 文档主题。在该主题结尾处,您会注意到它指出库文件是User32.lib;这表明MessageBeep 是从User32.dll 中导出的。 可选的DllImportAttribute 属性 除了指出宿主DLL 外,DllImportAttribute 还包含了一些可选属性,其中四个特别有趣:EntryPoint、CharSet、SetLastError 和CallingConvention。 EntryPoint 在不希望外部托管方法具有与DLL 导出相同的名称的情况下,可以设置该属性来指示导出的DLL 函数的入口点名称。当您定义两个调用相同非托管函数的外部方法时,这特别有用。另外,在Windows 中还可以通过它们的序号值绑定到导出的DLL 函数。如果您需要这样做,则诸如“#1”或“#129”的EntryPoint 值指示DLL 中非托管函数的序号值而不是函数名。 CharSet 对于字符集,并非所有版本的Windows 都是同样创建的。Windows 9x 系列产品缺少重要的Unicode 支持,而Windows NT 和Windows CE 系列则一开始就使用Unicode。

调用类的方法

语法如下: 语法 [访问修饰符] 返回值的类型方法名([参数列表]){ //方法体 }

(1)访问修饰符 已经讲述过类的访问修饰符,其实同理,这里的方法的访问修饰符功能也是一样,public 表示公共的,private 表示私有的。 在程序中,如果将变量或者方法声明为public,就表示其他类可以访问,如果声明为private,

(2)方法的返回类型。 方法是供别人调用的,调用后可以返回一个值,这个返回值的数据类型就是方法的返回类型,可以是int、float、double、bool、string 等。如果方法不返回任何值,就使用void。

语法 return 表达式; 如果方法没有返回值,则返回类型应该使用void(空虚;空的),用于说明无返回值。 如:public void Singing() //无返回值 { Console.Write(“在唱歌。。。”); } return 语句做两件事情:表示已经完成,现在要离开这个方法;如果方法产生一个值,这个值放置在return 后面,即<表达式>部分。意思就是“离开该方法,并且将<表达式>的值返回给调用其的程序”。

注意:在编写程序的时候,一定要注意方法声明中返回值的类型和方法体中真正的返 回的值的类型是否匹配,如果不匹配,后果很严重。比如在下面这个ToString()方法中,返 回类型是String 类型,因此在方法体中必须用return 返回一个字符串,否则编译器将报错。

(3)方法名 定义一个方法都要有一个名称 注意:方法名主要用于调用这个方法时用,命名方法就像命名变量、类一样,要遵守一定的规则,如必须以字母、下划线“_”或“$”开头,绝对不能以数字开头。

C++中函数调用时的三种参数传递方式

在C++中,参数传递的方式是“实虚结合”。 ?按值传递(pass by value) ?地址传递(pass by pointer) ?引用传递(pass by reference) 按值传递的过程为:首先计算出实参表达式的值,接着给对应的形参变量分配一个存储空间,该空间的大小等于该形参类型的,然后把以求出的实参表达式的值一一存入到形参变量分配的存储空间中,成为形参变量的初值,供被调用函数执行时使用。这种传递是把实参表达式的值传送给对应的形参变量,故称这种传递方式为“按值传递”。 使用这种方式,调用函数本省不对实参进行操作,也就是说,即使形参的值在函数中发生了变化,实参的值也完全不会受到影响,仍为调用前的值。 [cpp]view plaincopy 1./* 2. pass By value 3.*/ 4.#include https://www.doczj.com/doc/6a12526362.html,ing namespace std; 6.void swap(int,int); 7.int main() 8.{ 9.int a = 3, b = 4; 10. cout << "a = " << a << ", b = " 11. << b << endl; 12. swap(a,b); 13. cout << "a = " << a << ", b = " 14. << b << endl; 15.return 0; 16.} 17.void swap(int x, int y) 18.{ 19.int t = x; 20. x = y; 21. y = t; 22.}

如果在函数定义时将形参说明成指针,对这样的函数进行调用时就需要指定地址值形式的实参。这时的参数传递方式就是地址传递方式。 地址传递与按值传递的不同在于,它把实参的存储地址传送给对应的形参,从而使得形参指针和实参指针指向同一个地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。 [cpp]view plaincopy 1.#include https://www.doczj.com/doc/6a12526362.html,ing namespace std; 3.void swap(int*,int*); 4.int main() 5.{ 6.int a = 3, b = 4; 7. cout << "a = " << a << ", b = " 8. << b << endl; 9. swap(&a,&b); 10. cout << "a = " << a << ", b = " 11. << b << endl; 12. system("pause"); 13.return 0; 14.} 15.void swap(int *x,int *y) 16.{ 17.int t = *x; 18. *x = *y; 19. *y = t; 20.} 按值传递方式容易理解,但形参值的改变不能对实参产生影响。 地址传递方式虽然可以使得形参的改变对相应的实参有效,但如果在函数中反复利用指针进行间接访问,会使程序容易产生错误且难以阅读。

C语言函数调用规定

在C语言中,假设我们有这样的一个函数: int function(int a,int b) 调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。 栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。 函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。 在参数传递中,有两个很重要的问题必须得到明确说明: 当参数个数多于一个时,按照什么顺序把参数压入堆栈 函数调用后,由谁来把堆栈恢复原装 在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:stdcall cdecl fastcall thiscall naked call stdcall调用约定 stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall.在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK. stdcall调用约定声明的语法为(以前文的那个函数为例): int __stdcall function(int a,int b) stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸 以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成: push 2 第二个参数入栈 push 1 第一个参数入栈 call function 调用参数,注意此时自动把cs:eip入栈 而对于函数自身,则可以翻译为: push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复 mov ebp,esp 保存堆栈指针

函数指针的使用方法

对指针的应用是C语言编程的精髓所在,而回调函数就是C语言里面对函数指针的高级应用。简而言之,回调函数是一个通过函数指针调用的函数。如果你把函数指针(函数的入口地址)传递给另一个函数,当这个函数指针被用来调用它所指向的函数时,我们就说这个函数是回调函数。 为什么要使用回调函数呢?我们先看一个小例子: Node * Search_List (Node * node, const int value) { while (node != NULL) { if (node -> value == value) { break; } node = node -> next; } return node; } 这个函数用于在一个单向链表中查找一个指定的值,返回保存这个值的节点。它的参数是指向这个链表第一个节点的指针以及要查找的值。这个函数看上去很简单,但是我们考虑一个问题:它只能适用于值为整数的链表,如果查找一个字符串链表,我们不得不再写一个函数,其实大部分代码和现在这个函数相同,只是第二个参数的类型和比较的方法不同。 其实我们更希望令查找函数与类型无关,这样它就能用于查找存放任何类型值的链表了,因此必须改变比较的方式,而借助回调函数就可以达到这个目的。我们编写一个函数(回调函数),用于比较两个同类型的值,然后把一个指向这个函数的指针作为参数传递给查找函数,查找函数调用这个比较函数来执行比较,采用这个方法,任何类型的值得都可以进行比较。 我们还必须给查找函数传递一个指向待比较的值的指针而不是值本身,也就是一个void *类型的形参,这个指针会传递给回调函数,进行最终的比较。这样的修改可以让我们传递指向任何类型的指针到查找函数,从而完成对任何类型的比较,这就是指针的好处,我们无法将字符串、数组或者结构体作为参数传递给函数,但是指向它们的指针却可以。 现在,我们的查找函数就可以这样实现: NODE *Search_List(NODE *node, int (*compare)(void const *, void const *) , void const *desired_value); { while (node != NULL) { if (compare((node->value_address), desired_value) == 0) { break; } node = node->next; } return node; }

函数的定义和调用

函数的定义和调用 7.2函数定义 函数定义的一般形式: 类型标识符函数名(形式参数表列) 函数定义函数首部不要以分号结尾 { 说明部分 执行部分 } 例: int max(int a,int b)/*函数首部*/ ○1类型标识符○2函数名○3形式参数表列 { /*函数体开始*/○4 int z;/*说明部分*/ if(a>b)z=a; /*执行部分*/ else z=b; return(z); } 说明:函数定义包括函数首部和函数体两部分。 ○1类型标识将是指函数返回值的类型,简称函数值类型。函数的返回值由函数中的return 语句获得,即return后的表达式的值,可以是简单类型、void类型或构造类型等,注意一般函数返回什么类型的数据,函数的类型就定义成相应的类型。void类型为空类型,表示函数没有返回值。如希望不返回值,可以定义函数类型为void类型,当函数值类型为int时,可省略函数类型的说明。关于return:函数的值只能通过return语句返回主调函数,返回函数值的类型和函数定义中函数的类型应保持一致,如果函数值为int型可以省略函数类型说明,不返回函数值的函数,明确定义成空类型。 ○2函数名是函数的标识符。函数名取名遵循c语言标识符的命名规则,区分大小写。函数名后的形式参数表列给出函数的形式参数及其类型说明。 ○3形式参数简称形参,形式参数及其类型说明放在函数名后的一对圆括号中.无论函数是否有形式参数,函数名后的圆括号不可省;圆括号内没有形式参数的函数我们称之为无参函数,有形式参数的函数我们称为有参函数。强调:没有形式参数圆括号也不能省。形式参数可以是各种类型的变量,形式为:形参1类型形参1,形参2类型形参2 各参数之间用逗号间隔。在进行函数调用时,主调函数将赋予这些形式参数实际的值。 ○4函数体:函数说明之后的花括号“{}”括起来的部分,包括声明部分和执行部分: 1)声明部分:用来对函数中使用的变量和函数作说明。 2)执行部分由基本语句组成.函数的功能由函数体内的各个语句的执行来实现。 解释函数 函数的调用 一个函数被定义后,程序中的其他函数就可以使用这个函数,这个过程称为函数调用。 1。函数调用的一般形式 函数名(实参表列);实际参数表中的参数可以是常数、变量或构造类型数据,各实参之间也是用逗号分隔。对无参函数调用时无实际参数表。 函数有以下三种调用方式: (1) 函数表达式:函数调用出现在一个表达式中、这种表达式称为函数表达式。例如w =max(x,y);此时要求函数返回一个确定的值.参加表达式的计算。这里把max的返回值

VC++深入详解 - 窗口的创建

1.4.2 窗口的创建 创建一个完整的窗口,需要经过下面几个操作步骤: *设计一个窗口类; *注册窗口类; *创建窗口; *显示及更新窗口。 下面的四个小分节将分别介绍创建窗口的过程。完整的例程请参见光盘中的例子代码Chapter1目录 下WinMain。 1.设计一个窗口类 一个完整的窗口具有许多特征,包括光标(鼠标进入该窗口时的形状)、图标、背景色等。窗口的创建过程类似于汽车的制造过程。我们在生产一个型号的汽车之前,首先要对该型号的汽车进行设计,在图纸上画出汽车的结构图,设计各个零部件,同时还要给该型号的汽车取一个响亮的名字,例如“奥 迪A6”。在完成设计后,就可以按照“奥迪A6”这个型号生产汽车了。 类似地,在创建一个窗口前,也必须对该类型的窗口进行设计,指定窗口的特征。当然,在我们设计一个窗口时,不像汽车的设计这么复杂,因为Windows已经为我们定义好了一个窗口所应具有的基本属性,我们只需要像考试时做填空题一样,将需要我们填充的部分填写完整,一种窗口就设计好了。 在Windows中,要达到作填空题的效果,只能通过结构体来完成,窗口的特征就是由WNDCLASS结构体来定义的。WNDCLASS结构体的定义如下(请读者自行参看MSDN): typedef struct _WNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground;

LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS; 下面对该结构体的成员变量做一个说明。 第一个成员变量style指定这一类型窗口的样式,常用的样式如下: n CS_HREDRAW 当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。当窗口发生重绘时,窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。 n CS_VREDRAW 当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。 n CS_NOCLOSE 禁用系统菜单的Close命令,这将导致窗口没有关闭按钮。 n CS_DBLCLKS 当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。 style成员的其他取值请参阅MSDN。 知识点在Windows.h中,以CS_开头的类样式(Class Style)标识符被定义为16位的常量,这些常量都只有某1位为1。在VC++开发环境中,利用goto definition功能,可以看 到CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS =0x0008,CS_NOCLOSE=0x0200,读者可以将这些16进制数转换为2进制数,就可以发现它们都只有1位为1,并且为1的位各不相同。用这种方式定义的标识符称为“位标志”,我们可以使用位运算操作符来组合使用这些样式。例如,要让窗口在水平和垂直尺寸发生变化时发生重绘,我们可以使用位或(|)操作符将CS_HREDRAW和CS_VREDRAW组合起来,如style=CS_HREDRAW | CS_VREDRAW。假如有一个变量具有多个样式,而我们并不清楚该变量都有哪些样式,现在我们想要去掉该变量具有的某个样式,那么可以先对该样式标识符进行取反(~)操作,然后再和这个变量进行与(&)操作即可实现。例如,要去掉先前的style变量所具有的CS_VREDRAW样式,可以编写代 码:style=style & ~ CS_VREDRAW。 在Windows程序中,经常会用到这种位标志标识符,后面我们在创建窗口时用到的窗口样式,也是属于位标志标识符。

C# 如何跨平台调用C++的函数指针

C# 如何跨平台调用C++的函数指针! 函数指针搞C++的人应该都知道,效率高,易用性强,隐蔽代码等。在C++里面调用C++写的dll的函数指针那是在容易不过了。使用C#就稍微麻烦点了!那怎么掉呢?通过上面的第一篇文章我们知道应该使用委托 delegate。如果再高级点,定义一个函数指针结构(有点像linux的内核),也同样可以用C#调用。 提示:委托就和C++中的函数指针一样 借用一下别人的列子:在C++的一个标准Win32 api 库ccLic.dll中有一个函数void* WINAPI GetFunctionAddress(unsigned int sn);此函数通过传sn序号得到函数指针即一个函数的地址.之后再通过返回回来的地址进行其它函数的调用那么我们必须知道.一个sn号对应的函数结构如 sn=1 -> bool WINAPI CCAskServerLicenseInfo(const char* server_address,unsigned short port,PCcLic_Info plicenseinfo) 在其中 typedef struct _CcLic_Info { char ower[64]; unsigned short manage_ip; unsigned short ramained_ip; unsigned short useable_time; unsigned char type; } CcLic_Info,*PCcLic_Info; 此列的目的就是通过C#调用 CCAskServerLicenseInfo 函数. [DllImport(@"ccLic.dll")] public static extern System.IntPtr Matrix(System.UInt32 sn);//声名入口函数 //定义函数指针模型 public delegate System.Int32 CCAskServerLicenseInfoHandle(Syste m.String servername, System.UInt16 port, System.IntPtr ptr); public static LicenseInfo GetLicentInfo(String server, System.UInt16

C语言学习笔记之函数指针

C/C++学习笔记之函数指针 函数指针的概念,在潭浩强先生的《C语言程序设计》这本经典的教程中提及过,在大多数情况下我们使用不到,也忽略了它的存在。函数名实际上也是一种指针,指向函数的入口地址,但它又不同于普通的如int*、double*指针,看下面的例子来理解函数指针的概念:view plain int function(int x,int y); int main(void) { int(*fun)(int x,int y); int a=10,b=20; function(a,b); fun=function; (*fun)(a,b);…… } 第一行代码首先定义了一个函数function,其输入为两个整型数,返回也为一个整型数(输入参数和返回值可为其它任何数据类型);后面又定义了一个函数指针fun,与int*或double*定义指针不同的是,函数指针的定义必须同时指出输入参数,表明这是一个函数指针,并且*fun也必须用一对括号括起来;并将函数指针赋值为函数function,前提条件是*fun 和function的输入参数和返回值必须保持一致,否则无法通过编译。可以直接调用函数function(),也可以直接调用函数指针,二者是等效的。 回调函数(callback)是一个程序员不能显式调用的函数;通过将回调函数的地址传给调用者从而实现调用。简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。要实现回调,必须首先定义函数指针。尽管定义的语法有点不可思议,但如果你熟悉函数声明的一般方法,便会发现函数指针的声明与函数声明非常类似。请看下面的例子: void f();//函数原型 上面的语句声明了一个函数,没有输入参数并返回void.那么函数指针的声明方法如下:void(*)(); 函数存放在内存的代码区域内,它们同样有地址,我们如何能获得函数的地址呢? 如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。 定义一个指向函数的指针用如下的形式,以上面的test()为例: int(*fp)(int a);//这里就定义了一个指向函数的指针 函数指针绝对不能指向不同类型,或者是带不同形参的函数,在定义函数指针的时候我们很容易犯如下的错误。 int*fp(int a);//这里是错误的,因为按照结合性和优先级来看就是先和()结合,然后变成了一个返回整型指针的函数了,而不是函数指针,这一点尤其需要注意! 例如函数原型为: int fun(int*,int); 则函数指针可以声明为:int(*pf)(int*,int);当然从上述例子看不出函数指针的优点,目的主要是想引出函数指针数组的概念。我们从上面例子可以得知,既然函数名可以通过函数指针加以保存,那们也一定能定义一个数组保存若干个函数名,这就是函数指针数组。正确使用函数指针数组的前提条件是,这若干个需要通过函数指针数组保存的函

相关主题
文本预览
相关文档 最新文档