C++重载操作符的本质

重载操作符的本质

重载操作符,在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++代码要高效。

时间: 2024-10-21 01:22:28

C++重载操作符的本质的相关文章

C++primer读书笔记8重载操作符

函数的重载操作符中也有不少细节 1 重载操作符特点 重载操作符本质上就是重载函数,它的特点: <1>除了函数调用操作符'()'外,所有的重载操作符的形参数目必须与操作符的操作数目相等. <2>重载操作符的操作数必须至少有一个类类型,否则就不是重载而是原型了. <3>优先级和结合性不会发生改变,例如对于算数操作符的重载,*的优先级肯定还是大于+ <4>不具有短路求值特性(就是根据已有的值就可以判断出最终的逻辑结果的话就会停止计算) <5>操作符可以

C++ operator(重载操作符) 【转】

转自:http://www.cnblogs.com/xiangxiaodong/archive/2012/02/12/2348144.html operator是C++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应将operator=整体上视为一个函数名. 这是C++扩展运算符功能的方法,虽然样子古怪,但也可以理解:一方面要使运算符的使用方法与其原来一致,另一方面扩展其功能只能通过函数的方式(c++中,“功能”都是由函数实现的).  一.为什么使用操作符重载?对于系统的所有操作符,一

C++中operator关键字(重载操作符)

operator是C++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应将operator=整体上视为一个函数名. 这是C++扩展运算符功能的方法,虽然样子古怪,但也可以理解:一方面要使运算符的使用方法与其原来一致,另一方面扩展其功能只能通过函数的方式(c++中,“功能”都是由函数实现的).  一.为什么使用操作符重载? 对于系统的所有操作符,一般情况下,只支持基本数据类型和标准库中提供的class,对于用户自己定义的class,如果想支持基本操作,比如比较大小,判断是否相等,等等,则

[c++primer][14]重载操作符与转换

14.1 重载操作符的定义 不能重载的操作符:.  ?:  sizeof  ::  .* 不能为任何内置类型定义额外的新的操作符:优先级和结合性是固定的:不再具备短路求值特性(不建议重载&&.||.逗号): 一般将算术和关系操作符定义为非成员函数,而将赋值操作符定义为成员: 使用重载操作符的方式: 1)  与内置类型使用操作符方式一样: 2)  也可像调用普通函数一样调用重载操作符函数,指定函数并传递适当类型适当数目的形参: item1 += item2; // expression ba

C++ Primer 学习笔记_64_重载操作符与转换 --转换与类类型【下】

重载操作符与转换 --转换与类类型[下] 四.重载确定和类的实参 在需要转换函数的实参时,编译器自动应用类的转换操作符或构造函数.因此,应该在函数确定期间考虑类转换操作符.函数重载确定由三步组成: 1)确定候选函数集合:这些是与被调用函数同名的函数. 2)选择可行的函数:这些是形参数目和类型与函数调用中的实参相匹配的候选函数.选择可行函数时,如果有转换操作,编译器还要确定需要哪个转换操作来匹配每个形参. 3)选择最佳匹配的函数.为了确定最佳匹配,对将实参转换为对应形参所需的类型转换进行分类.对于

lua中定义操作符行为的元方法(重载操作符)

在c++中可以重载操作符,在lua中也有,不过lua是通过元方法实现的,异曲同工,都是一样的 比如c++,重载了+之后,具体做什么都是自己实现,lua也一样 定义操作符行为的元方法有: __add 对应的运算符 '+'.  可以定义已定义操作符的表和其他表相加的行为,以及操作的值(返回值)(下同) __sub 对应的运算符 '-'. __mul 对应的运算符 '*'. __div 对应的运算符 '/'. __mod 对应的运算符 '%'. __unm 对应的运算符 '-'. 可以定义已定义操作符

C++primer第十四章 重载操作符与转换

这些标准库的类型具有相同的操作符,使用它们就像使用内置数组和指针一样.允许程序使用表达式而不是命名函数,可以使编写和阅读程序容易得多.将 cout << "The sum of " << v1 << " and " << v2 << " is " << v1 + v2 << endl; 和以下更为冗长的代码相比较就能够看到.如果 IO 使用命名函数,类似下面的代码

C++之重载String ----- 构造函数、复制控制、重载操作符

本博文 我们通过 重新实现String类 来说明构造函数,复制控制,重载操作符. 一.构造函数(包括析构函数): 1:默认构造函数: 2:用户自己定义的构造函数 注意:当用户自己定义时,也要明确显示默认构造函数,这是因为,当我们没有定义自己的构造函数时,编译器会为我们自动合成一个,而我们定义了构造函数时,编译器默认构造函数改为我们自己定义的.这时就有可能出现错误: 3:析构函数: 具体声明与实现,代码如下: 1 声明部分: 2 String(); 3 String(const char*s);

转载:C++ operator关键字(重载操作符)

C++ operator关键字(重载操作符) 转自:http://blog.sina.com.cn/s/blog_4b3c1f950100kker.html operator是C++的关键字,它和运算符一起使用,表示一个运算符函数,理解时应将operator=整体上视为一个函数名. 这是C++扩展运算符功能的方法,虽然样子古怪,但也可以理解:一方面要使运算符的使用方法与其原来一致,另一方面扩展其功能只能通过函数的方式(c++中,“功能”都是由函数实现的).  一.为什么使用操作符重载?对于系统的