一.关于参数类型不是对象的情况:(非成员函数赞。)
类:
主函数:
重点是倒数第二行,
c = 5.6 + b; //如果我只有类里面的+成员函数,这里是没法匹配的。即使我有double转换成Complex的构造函数,依然没法匹配。
要知道,成员函数的+是和左操作数匹配的,5.6是个double型的数据,那么它会想要把右操作数b也转换成double型,再进行计算。这显然是不现实的,首先,我没有写将Complex型转换成double型的函数;再者,复数类转换成double类根本就没有意义,所以这是一种错误的做法。
正确做法:(c = 5.6 + b;)
有三种方法:
1.将 + 运算符的重载从成员函数改成非成员函数,声明为友元。
说明:这样做是调用了非成员的 + 函数,在传参的时候讲double型隐式转换为Complex型(通过double转Complex的构造函数),再进行计算。
2.直接写一个 + 的非成员函数,参数分别为(double,const Complex& )
说明:这样就根本用不到 double 转Complex的构造函数了,c = 5.6 + b;直接调用这个函数就可以了。但是有一点,比较麻烦,如果像下面这样:
c = 5.6 + b;
c = b + 5.6;
c= a + b;
这样岂不是要写三个 + 运算符重载函数?
Complex operator +(double a,const Complex &b){ }
Complex operator +(const Complex &a,double b){ }
Complex operator +(const Complex &a,const Complex &b){ }
当然可以偷个懒,写成:
Complex operator +(double a,const Complex &b){ }
inline Complex operator + (const Complex &a,double b){
return a + b;
}
Complex operator +(const Complex &a,const Complex &b){ }
可是这样依然这样太麻烦。
3.转换运算符
说明:对于复数类,不能将double和Complex随意相互切换,因为Complex型的不是都可以转换成double型的。然而对于有的情况,比如RMB类,就可以RMB和double随意相互切换。这时当运算符的参数是(RMB , double )或(double , RMB ),就可以用转换运算符。
在RMB类里面写:
//double转换成RMB
RMB(double value=0.0){
yuan=value;
jf=(value-yuan)*100+0.5;
}
//RMB转换成double
operator double(){
return yuan+jf/100.0;
}
有了这两个函数,就不需要写运算符重载函数了。因为遇到RMB它就自将其转换成double再调用原本的+运算符函数进行运算。
不过这有一个缺点:可能或出现转换的二义性。
总结:个人建议用非成员函数进行双目运算符的重载。这样只要写了相应转换类型的构造函数,就不会出问题,而且简洁,一个运算符重载函数就够了。
二.关于运算符重载里面的引用
写两个例子:1.双目运算符 +
(1)成员函数:
Increase Increase::operator + (Increase & s){
Increase result(value+s.value);
return result;
}
(2)非成员函数:(友元):
Increase operator + (Increase & s1,Increase &s2){
Increase result(s1.value+s2.value);
return result;
}
参数用了引用:
为什么不直接将s1,s2作为参数呢?注意这里我们是对类的操作。如果参数类型不是引用,那么传参的时候就需要一个对象副本,将实参复制到形参,这种复制是浅复制,并不会为形参分配内存空间,这就导致如果类中有指针类型,形参和实参的指针就指向同一块内存,再调用析构函数时重复delete,出现错误。
当然你可以用指针,但是指针用了之后可读性不佳:
Increase operator + (Increase* s1,Increase* s2)
//调用时
Increase s1(2);
Increase s1(4);
Increase s3 = &s1 + &s2; //读起来像是地址
(1)成员函数:
//前置 ++
Increase& Increase::operator ++(){
value++;
return *this;
}
//后置 ++
Increase Increase::operator ++(int ){
Increase old(*this);
value ++;
return old;
}
(2)非成员函数:(友元):
//前置 ++
Increase& operator ++(Increase& s){
s.value++;
return s;
}
//后置 ++
Increase operator ++(Increase& s,int ){
Increase old(s);
s.value ++;
return old;
}
前置++返回值用了引用:(为了提高函数效率,减小开销)
因为需要我这个值本身改变,那么用引用传进来,并且返回值就是它本身。用引用返回值则没有返回值的临时副本,从始至终都是它。
如果不用引用返回,那么就会返回一个s的临时副本,虽然可以达到相同的效果,但是增加了不必要的开销,函数效率下降。
后置++返回值没有用引用:(防止局部变量被引用到全局)
后置++其实就是 ++了但是返回的是旧值。那这个值改变了,所以用引用传进来。但是返回的是一个中间值,不是其本身。因为这个中间变量是一个临时变量,是在局部作用域里面,如果用引用返回,是很危险的。所以不能加引用。
3.赋值运算符
例:
#include <iostream>
using namespace std;
class StupidClass {
int flag;
public:
StupidClass(int flag): flag(flag) {
cout << "Constructor " << flag << endl;
}
~StupidClass() {
cout << "Destructor " << flag << endl;
}
StupidClass(const StupidClass& rhs) {
flag=rhs.flag;
cout << "Copy Constructor *this=" << flag << " rhs=" << rhs.flag << endl;
}
StupidClass& operator=(const StupidClass& rhs) {
cout << "Operator = *this=" << flag << " rhs=" << rhs.flag << endl;
return *this;
}
StupidClass& operator+=(const StupidClass& rhs) {
cout << "Operator += *this=" << flag << " rhs=" << rhs.flag << endl;
flag += rhs.flag;
return *this;
}
};
int main() {
StupidClass var1(1), var2(2);
StupidClass var3 = var1 += var2; // 1
//StupidClass &var3 = var1 += var2; // 2
var3=var1;
return 0;
}
上述代码
(1)用语句1
输出结果:
Constructor 1
Constructor 2
Operator += *this=1 rhs=2
Copy Constructor *this=3 rhs=3
Operator = *this=3 rhs=3
Destructor 3
Destructor 2
Destructor 3
(2)用语句2
输出结果:
Constructor 1
Constructor 2
Operator += *this=1 rhs=2
Operator = *this=3 rhs=3
Destructor 2
Destructor 3
返回值都用了引用:
(1)减小不必要的开销(如果不加引用,则会返回临时副本,这没必要,因为自身本来就改变了,且我们需要这个改变了的值)。
(2)另外,防止++出现问题。
说到这里了,就总结下没有引用返回的运算符重载结果的++出现的问题:
1.如果赋值运算符函数返回值不加引用:RMB operator = (RMB & s){
yuan=s.yuan;
jf=s.jf;
}
RMB a(5.2), b (2.6);
(b = a) ++; //结果b=5.2,而不是5.3
就是因为b = a返回值是一个b的副本,副本进行++,这条语句结束以后这个副本才消失,然而b实际上并没有++
2.前面已说,后置++运算符重载函数返回是不带引用的。
a=5;
(a++)++; //结果a=6
与上面同样的道理. 所以不能连续++,达不到想要的效果.
简单总结:就是返回值不带引用的话,返回的是临时副本。
这时需要警戒存不存在把局部变量扩展到全局的问题; 还有就是++的问题。
赋值运算符重载还有一点要注意:
void fn(A & a){
A a1=a; //这是拷贝构造函数,没有调用=运算符函数
A1=a; //这是赋值运算符,没有调用拷贝构造函数。
}
在浅拷贝会出现问题的时候,用赋值运算符重载函数,在这个函数里面用delete取消已占用的资源,再开辟新空间放置新内容。