类的成员函数与外部函数(静态函数)的区别
在C语言中,所有的函数都是外部函数,而在C++中,从面向对象的角度来说,由于封装的作用,函数式属于某个类的。类的静态成员函数在调用特性上与外部函数相同,因外它不依赖于类的实例。
类的成员函数与外部函数(静态函数)的区别有以下几点:
1.外部函数(静态函数)可以赋值给 void* 型指针,但类的非静态成员函数不行。
如果类的非静态成员函数可以转换为void *型指针,那么就可以将void *型指针转换成函数指针,从而抛开对象的地址直接调用类的非静态成员函数。这样就破坏了面向对象的封装性。。。。。。。。
例子:
#include <iostream>
using namespace std;
void print()
{
cout<<"Extenal Function\n";
}
class A
{
public:
//static void print()
void print()
{
cout<<"A's Member Function\n";
}
};
typedef void (*ptrFun)(); //定义函数指针
int main()
{
ptrFun p;
void * v;
v = (void*)&print; //将函数地址转换为 void * 类型
p = (ptrFun)v; //将void * 类型转换为函数指针
p(); //这样就可以调用外部函数 print()
v = (void*)&A::print; //试图将 A::print()函数转换为void *类型的指针,,可惜这样不行
//如果为 A::print()为static 则不会报错
p = (ptrFun)v;
p();
return 0;
}
2.可以通过内联汇编方式获取类的非静态成员函数的入口地址;
内联函数的两个作用:
1).程序的某些关键代码直接用汇编语言编写可以提高代码的执行效率;
2).有些操作无法通过高级语言来实现,或者实现起来很困难,必须借助汇编语言达到目的。例如用内联函数获取类的非静态成员函数的入口地址。
测试程序:
#include <iostream> using namespace std; void print() { cout<<"Extenal Function\n"; } class A { public: //static void print() void print() { cout<<"A's Member Function\n"; } }; typedef void (*ptrFun)(); //定义函数指针 int main() { ptrFun p; void * v; v = (void*)&print; //将函数地址转换为 void * 类型 p = (ptrFun)v; //将void * 类型转换为函数指针 p(); //这样就可以调用外部函数 print() //_asm //{ // lea eax, A::print // mov v, eax //} //p = (ptrFun)v; //p(); _asm //和上面的代码是一样的作用 { mov eax, A::print mov v, eax } p = (ptrFun)v; p(); return 0; }
但是上面的代码有一个很严重的缺陷,如果A::print()需要访问A类对象的数据成员,就会引发运行错误。
解决办法:在调用函数之前还应将类对象 的首地址送入ecx寄存器,只有这样才能保证正确的调用。
修改之后的代码:
#include <iostream>
using namespace std;
void print()
{
cout<<"Extenal Function\n";
}
class A
{
public:
int i;
public:
A(int num)
{
i = num;
}
//static void print()
void print()
{
cout<<"A's Member Function\n";
cout<<this->i<<endl;
}
};
typedef void (*ptrFun)(); //定义函数指针
int main()
{
A a(111);
ptrFun p;
void * v;
v = (void*)&print; //将函数地址转换为 void * 类型
p = (ptrFun)v; //将void * 类型转换为函数指针
p(); //这样就可以调用外部函数 print()
_asm
{
lea eax, A::print
mov v, eax
lea ecx, a
//mov ecx, a //如果将lea换成mov 则会抛出异常
}
p = (ptrFun)v;
p();
return 0;
}
总结一下上面的代码: 可以看出即使是用了内联汇编,仍然需要一个A类对象的实例,这也就证实了一个类的非静态成员函数必须由这个类的一个实例来调用。
为什么将lea ecx, a改为 mov ecx, a 会抛出异常呢? 由于传递的不是对象的引用,所以在汇编代码中必须使用lea ecx, a, 将放在堆栈上的实参的副本地址送入寄存器ecx。