运算符重载的四项基本原则:
a.不可臆造运算符。
b.运算符原有操作数的个数、优先级和结合性不能改变。
c.操作数中至少一个是自定义类型。
d.保持重载运算符的自然含义。
大家都知道一般常用两种重载运算符的方式分别是: 1.作为成员函数重载运算符. 2.作为友元函数重载运算符.
对于单目运算符:
很显然为了尽可能到保证类封装,我们也会尽可能的保证不去添加新的友元函数(毕竟这些函数不是自己人但可以访问私有成员).所以对于单目运算符我们都是重载为成员函数的.
对于双目运算符:
a.当重载为成员函数时,会隐含一个this指针;当重载为友元函数时,将不存在隐含的this指针,需要在参数列表中显示地添加操作数。
b.当重载为成员函数时,只允许右参数的隐式转换;当重载为友元函数时,能够接受左参数和右参数的隐式转换。
这里我给个例子.
这里是DoubleA
class DoubleA
{
public:
DoubleA(){};
DoubleA(DoubleB m); //DoubleB -> DoubleA
~DoubleA(){};
double x;
};
DoubleA::DoubleA(DoubleB m){ x = m.x; }
这里是DoubleB
class DoubleB
{
public:
DoubleB(){};
DoubleB(double m){ x = m; }; // double -> DoubleB
~DoubleB(){};
double x;
};
我们可以看得到 DoubleB 可以由 double 初始化, DoubleA可以由 DoubleB 初始化
那么我们同样可以由隐式转换用double 初始化 DoubleA.
现在我们给 DoubleA 重载加法运算符使得 DoubleA 可以和 DoubleB 相加返回一个 DoubleA 的值.
方案1:
class DoubleA
{
public:
DoubleA(){};
DoubleA(DoubleB m); // double -> DoubleA
~DoubleA(){};
DoubleA operator+(DoubleB m); //DoubleA + DoubleB ->DA _> DoubleB+DoubleB->DA
double x;
};
DoubleA::DoubleA(DoubleB m){ x = m.x; }
DoubleA DoubleA::operator+(DoubleB m){ return m.x + x; } // DoubleA + DoubleB
可以看到这里我们使用的是成员函数
这个时候我们在 Main 函数里面 这么写
int main()
{
DoubleA da;
DoubleB db;
DoubleA tda;
tda = da + db; // 编译通过
tda = db + da; // 编译不通过
tda = db + db; // 编译不通过
return 0;
}
我们看到第一句是完全匹配的,所以编译通过.
第二句因为我们重载运算符为成员变量使得左操作值为本身.使得无法匹配 DoubleB + DoubleA.
第三句我们妄想可以用DoubleA的构造函数将 db 隐式转换为 DoubleA 然后去匹配 + . 然而重载运算符为成员函数的时候只支持右操作值的隐式转换.
方案二:
class DoubleA
{
public:
DoubleA(){};
DoubleA(DoubleB m); // double -> DoubleA
~DoubleA(){};
friend DoubleA operator+(DoubleA n,DoubleB m); //DoubleA + DoubleB ->DA _> DoubleB+DoubleB->DA
double x;
};
DoubleA::DoubleA(DoubleB m){ x = m.x; }
DoubleA operator+(DoubleA n, DoubleB m){ return m.x + n.x; } // DoubleA + DoubleB
可以看到我们这里使用的是友元的运算符重载函数.
对于以上的Main函数的语句我们发现
tda = db + db
可以编译通过,因为 友元的运算符重载函数在函数申明的时候给出了两个参数的申明,这使得两个参数都能够由隐式转换匹配.
所以以上语句左边的 db 可以由 DoubleA 的构造函数隐式转化为 DoubleA 然后相加.
通过以上例子可以看出来对于双目运算符,重载为友元函数可以让程序更容易适应自动类型转换(<<C++ Primer Plus>>P420)
但是有些双目运算符是不能重载为友元函数的,比如赋值运算符=、函数调用运算符()、下表运算符[]、指针->等,因为这些运算符在语义上与this都有太多的关联。
还有一个需要特别说明的就是输出运算符<<。因为<<的第一个操作数一定是ostream类型,所以<<只能重载为友元函数.
好的讨论到这里基本上关于标题的内容已经探讨完了,但是我想继续关于友元重载函数往下探讨一些东西:
假如我们在上面想重载 DoubleA 与 DoubleA 之间的加法 意思是: DoubleA + DoubleA.
因为DoubleA 可以由 DoubleB、double 隐士转化而来所以上面的 DoubleA + DoubleA 也就变成了:
DoubleA / DoubleB / double + DoubleA / DoubleB / double (两边不同为double)
第一种方法:
DoubleA operator+(DoubleA n, DoubleA m){ return m.x + n.x; } // DoubleA + DoubleA
这种方法看来很简便,因为以上的很多种情况都可以通过隐式转换之后来匹配这个重载函数,
优点是:使得程序更加简便,因为定义的函数比较少.也以为程序员完成的工作少,不容易出错.
缺点是:每次需要转换的时候都要调用转换构造函数,这增加了时间和内存的开销.
第二种方法:
DoubleA operator+(DoubleA n){ return x + n.x; } // member function
friend DoubleA operator+(DoubleB m, DoubleA n){ return m.x + n.x; }
这种方法增加了一个显式的匹配类型的函数与上正好相反,使得程序较长,但是运行时间相对较快.
所以我们要根据函数具体使用频率和实际情况来从这两种方法中取舍