赋值运算符重载
在c++中,有许多的符号可以来让我们自定义它们的用法,所以可以讨论一下比较特殊的符号“=”号的“自定义”,也就是对它进行重载.
- 声明:
Baseclass &operator=(const Baseclass &p);
通常运算符重载都是在一个类中定义,这样类与类之间就可以很方便的进行赋值,虽然不使用赋值运算符重载也能实现对类与类之间的赋值,但是当你在delete(free)时就会出现问题。
副本构造器,赋值构造函数与double free问题
我们一般说赋值语句就是拷贝数据而已,将拷贝赋值变量的数据到被赋值变量中,但是如果我们上升到地址的拷贝,比如来进行地址的赋值拷贝,虽然也没问题,因为栈内存无需释放,但是如果我们malloc或new一个堆内存进行地址赋值,这时候就需要注意可能在释放时释放两次相同的地址,所以要使用两个不同的地址就不会发生错误。
所以在处理这类问题,c++中在操作类时我们可以定义副本构造器和赋值构造函数
1.副本构造器:
就是将普通的赋值语句包装一下,还是拷贝数据,在初始化时可以用
class example
{
int Test;
public:
example(int x):Test(x) //带参数构造函数
{
cout << "constructor with argument\n";
}
example(const example & ex) //拷贝构造函数
{
Test = ex.Test; //将传入的数据拷贝到类的属性中
cout << "copy constructor\n";
}
};
2.赋值(重载)构造函数
为了避免赋值拷贝使用相同的地址,我们通过重载在函数内将地址重新修改
Baseclass &Baseclass::operator=(const Baseclass &rth)
{
std::cout << "进入等号重载器" << std::endl;
if(this != &rth) //如果两个类不同的话,那么将类内部参数的地址修改为不同于赋值类内部参数的地址
{
delete pth;
pth = (new int);
*pth = *rth.pth;
}
else
{
std::cout << "对象相同,不作处理" << std::endl;
}
std::cout << "离开等号重载器" << std::endl;
return *this;
}
这样析构时就避免了double free的问题
- 总体示例
#include<cctype>
#include<string>
#include<iostream>
class Baseclass
{
public:
Baseclass();
Baseclass(int *p);
Baseclass(const Baseclass &p);
~Baseclass();
Baseclass &operator=(const Baseclass &p);
void print();
private:
int *pth;
};
Baseclass::Baseclass()
{
std::cout << "进入Baseclass int 构造器" << std::endl;
pth = new int;
std::cout << "离开Baseclass int 构造器" << std::endl;
}
Baseclass::Baseclass(int *p)
{
std::cout << "进入Baseclass int 构造器" << std::endl;
pth = p;
std::cout << "离开Baseclass int 构造器" << std::endl;
}
Baseclass::Baseclass(const Baseclass &p)
{
std::cout << "进入副本构造器" << std::endl;
*this = p;
std::cout << "离开副本构造器" << std::endl;
}
Baseclass::~Baseclass()
{
std::cout << "进入析构器" << std::endl;
delete pth;
std::cout << "离开析构器" << std::endl;
}
Baseclass &Baseclass::operator=(const Baseclass &rth)
{
std::cout << "进入等号重载器" << std::endl;
if(this != &rth)
{
delete pth;
pth = (new int);
*pth = *rth.pth;
}
else
{
std::cout << "对象相同,不作处理" << std::endl;
}
std::cout << "离开等号重载器" << std::endl;
return *this;
}
void Baseclass::print()
{
std::cout << pth << std::endl;
}
int main(void)
{
Baseclass base1(new int(1));
Baseclass base2(new int(2));
base1 = base2;
base1.print();
base2.print();
std::cout << "\n" << std::endl;
Baseclass base3(new int(3));
Baseclass base4;
base4 = base3;
base3.print();
base4.print();
std::cout << "\n" << std::endl;
Baseclass base5(new int(5));
base5 = base5;
base5.print();
return 0;
}
类的赋值初始化与初始化后赋值
这是一个比较绕的话题,在知道了副本构造器和赋值构造器后,我们需要对其执行过程进行一个探索
我们继续来看示例:
class example
{
int Test;
public:
example(int x):Test(x) //带参数构造函数
{
cout << "constructor\n";
}
example(const example & ex) //拷贝构造函数
{
Test = ex.Test;
cout << "copy constructor\n";
}
example &operator = (const example &ex)//赋值函数(赋值运算符重载) 返回该类的引用
{
cout << "= operator\n";
Test = ex.Test;
return *this; //解引用,将返回的是exaple类型的类
}
void Func(example ex)
{
}
};
int main()
{
example a(2);
example b(3);
b = a;
example c = a;
b.Func(a);
return 0;
}
在运行后我们发现其结果是如下:
constructor
constructor
= operator
copy constructor
copy constructor
前两个进入example(int x):Test(x)函数,但是第三个和第四个就有区别了,都是看似赋值但是第四个却进入了副本构造器,所以这就是初始化后赋值与赋值初始化的区别。
无限递归问题
我开始好奇为什么example &operator = (const example &ex)
为什么参数需要引用,在查阅资料后发现如果不是引用类型的话会造成无限拷贝赋值递归,如果刚开始来赋值会形成以下死循环
example c = a;
->会调用example(const example ex)
->const example ex = a
->example(const example ex)
…
即便是初始化后再赋值也是一样,只不过多加了赋值构造函数,所以要使用引用类型!