重载操作符的本质
重载操作符,在C++中,占有很重要的位置,其本质,也是一般的函数,我们今天就来探讨重载操作符,看看他的真实面目。
重载操作符的目的,是为数据结构提供自己的操作方法,例如两个同类型的类相加,其规则到底是什么样的,就在重载操作符中定义。
我们先看一段简单的代码:
#include<iostream> using namespace std; class A { public: int x; int y; A(int a=0,int b=0):x(a),y(b){}; A __stdcall operator +(A a) { A tmp; tmp.x=x+a.x; tmp.y=y+a.y; returntmp; } }; int main() { A a(1,1),b(2,2),c; c=a+b; cout<<c.x<<endl; cin.get(); return 0; }
这段代码,没有任何特殊的地方,关键是重载函数A __stdcall operator+(A a)在C++中,他的原型到底是什么样的呢?
下面的代码,是c=a+b;反汇编的结果:
//c=a+b; 01201402 mov eax,dword ptr [ebp-18h] 01201405 push eax 01201406 mov ecx,dword ptr [b] 01201409 push ecx 0120140A lea edx,[ebp-0FCh] 01201410 push edx 01201411 lea eax,[a] 01201414 push eax 01201415 call A::operator+ (120108Ch) 0120141A mov ecx,dword ptr [eax] 0120141C mov edx,dword ptr [eax+4] 0120141F mov dword ptr [c],ecx 01201422 mov dword ptr [ebp-28h],edx
我们可以看到,在调用重载操作符前(call A::operator+ (120108Ch),到底需要做哪些准备:
1)将b参数的值,压住栈:
01201402 mov eax,dword ptr [ebp-18h] 01201405 push eax 01201406 mov ecx,dword ptr [b] 01201409 push ecx
2)将一个不知道的变量,压住栈(其实这个变量是一个指针,其类型和函数的返回类型一致)
0120140A lea edx,[ebp-0FCh] 01201410 push edx
3)将参数a的地址压入栈:
01201411 lea eax,[a] 01201414 push eax
4)调用函数
01201415 call A::operator+ (120108Ch)
由此我们基本可以推断,函数的原型大概如下:
A __stdcall Add(A* This,A* b,A c)
此时我们可以使用如下方法,来将重载操作函数,作为普通函数进行调用,见代码:
#include<iostream> using namespace std; class A { public: int x; int y; A(int a=0,int b=0):x(a),y(b){}; A __stdcall operator +(A a) { A tmp; tmp.x=x+a.x; tmp.y=y+a.y; returntmp; } A __stdcallshow(){}; }; union U { A (__stdcallA::*opt)(A); A (__stdcall*fun)(A*,A*,A); }; int main() { A a(1,1),b(2,2),c; c=a+b; U u; u.opt=&A::operator+; u.fun(&a,NULL,b); cout<<c.x<<endl; cin.get(); return 0; }
此时,如果运行程序,将会出现错误,我们将这两个调用:c=a+b和u.fun(&a,NULL,b);的反汇编代码列出来:
//c=a+b; 00D21402 mov eax,dword ptr[ebp-18h] 00D21405 push eax 00D21406 mov ecx,dword ptr [b] 00D21409 push ecx 00D2140A lea edx,[ebp-118h] 00D21410 push edx 00D21411 lea eax,[a] 00D21414 push eax 00D21415 call A::operator+(0D2108Ch) 00D2141A mov ecx,dword ptr[eax] 00D2141C mov edx,dword ptr[eax+4] 00D2141F mov dword ptr [c],ecx 00D21422 mov dword ptr[ebp-28h],edx //u.fun(&a,NULL,b); 00D2142C mov esi,esp 00D2142E mov eax,dword ptr[ebp-18h] 00D21431 push eax 00D21432 mov ecx,dword ptr [b] 00D21435 push ecx 00D21436 push 0 00D21438 lea edx,[a] 00D2143B push edx 00D2143C lea eax,[ebp-108h] 00D21442 push eax 00D21443 call dword ptr [u] 00D21446 cmp esi,esp 00D21448 call @ILT+320(__RTC_CheckEsp) (0D21145h)
我们可以看到,u.fun(&a,NULL,b);函数的调用前,多了下列的汇编语句:
00D2143C lea eax,[ebp-108h] 00D21442 push eax
这说明,函数的调用方式不对,我们将fun函数的原型,修改如下:
void(__stdcall *fun)(A*,A*,A);
整个代码如下:
#include<iostream> using namespace std; class A { public: int x; int y; A(int a=0,int b=0):x(a),y(b){}; A __stdcall operator +(A a) { A tmp; tmp.x=x+a.x; tmp.y=y+a.y; returntmp; } A __stdcallshow(){}; }; union U { A (__stdcallA::*opt)(A); void (__stdcall *fun)(A*,A*,A); }; int main() { A a(1,1),b(2,2),c; c=a+b; U u; u.opt=&A::operator+; u.fun(&a,&c,b); cout<<c.x<<endl; cin.get(); return 0; }
这个运行结果没有问题!!!!说明我们猜对了!
大家观察下,此时,有两句代码做了修改:
void (__stdcall*fun)(A*,A*,A);
u.fun(&a,&c,b);
这是修改后代,这两个函数调用的汇编代码,调用前的汇编准备指令,完全一致!!!
//c=a+b; 01221402 mov eax,dword ptr[ebp-18h] 01221405 push eax 01221406 mov ecx,dword ptr [b] 01221409 push ecx 0122140A lea edx,[ebp-108h] 01221410 push edx 01221411 lea eax,[a] 01221414 push eax 01221415 call A::operator+(122108Ch) 0122141A mov ecx,dword ptr[eax] 0122141C mov edx,dword ptr[eax+4] 0122141F mov dword ptr [c],ecx 01221422 mov dword ptr[ebp-28h],edx //u.fun(&a,&c,b); 0122142E mov eax,dword ptr[ebp-18h] 01221431 push eax 01221432 mov ecx,dword ptr [b] 01221435 push ecx 01221436 lea edx,[c] 01221439 push edx 0122143A lea eax,[a] 0122143D push eax 0122143E call dword ptr [u]
我们还可以看到,c=a+b的反汇编语句,在函数被调用完后,多了个变量c的赋值过程。
01221415 call A::operator+(122108Ch) 0122141A mov ecx,dword ptr[eax] 0122141C mov edx,dword ptr[eax+4] 0122141F mov dword ptr [c],ecx 01221422 mov dword ptr[ebp-28h],edx
而使用u.fun(&a,&c,b),则直接将函数结果,保存在了变量c中!执行效率更高!
这样,我们就可以确定,重载操作符A::operator+的原型是:
void (__stdcall*fun)(A*,A*,A);
且中间的一个变量可以用来接收函数的返回值,而函数本身编程了void类型。
总结:
C++中的重载操作符,本身也是一个普通的函数。注意,本例是在VC中完成的,对于其他编译器,可能会有不同的结果。从这个分析,我们也可以看出,C代码确实比C++代码要高效。