1.关于赋值运算符与拷贝构造函数
- 编译器为每个类默认重载了赋值操作符
- 默认的赋值操作符仅完成浅拷贝
- 当需要进行深拷贝时必须重载赋值操作符
- 赋值操作符与拷贝构造函数有相同的存在意义
//对于class Test{};等价于下列写法
class Test
{
public:
Test();//const Test t1; 构造函数
Test(const Test &e); // Test t2(t1); 拷贝构造函数
Test& operator=(const Test &e); // t2 = t1; 赋值运算符
Test* operator&(); // Test *pt2 = &t2; 取址运算符(非const)
const Test* operator&() const; //const Test *pt1 = &t1; 取址运算符(const)
~Test();//析构函数
};
2.编程实验
- 默认赋值操作符重载
#include <iostream>
#include <string>
using namespace std;
class Test
{
int *m_pointer;
public:
Test()
{
m_pointer = NULL;
}
Test(int i)
{
m_pointer = new int(i);
}
Test(const Test &obj)
{
m_pointer = new int(*obj.m_pointer);
}
Test& operator=(const Test &obj)
{
if (this != &obj)
{
delete m_pointer;
m_pointer = new int(*obj.m_pointer);
}
return *this;
}
void print()
{
cout << "m_pointer = " << hex << m_pointer << endl;
}
~Test()
{
delete m_pointer;
}
};
int main()
{
Test t1 = 1; // 调用Test(int i)
Test t2;
Test t3 = t1; // 调用拷贝构造函数
t2 = t1; // 调用了该类的重载的赋值操作符
t1.print();
t2.print();
t3.print();
system("pause");
return 0;
}
- 运行结果
- 两点需要注意的是:
- 拷贝构造函数必须以引用的方式传递参数,基本上都是传常量引用的方式传递函数参数。这是因为,在值传递的方式传递给一个函数的时候,会调用拷贝构造函数生成函数的实参。如果拷贝构造函数的参数仍然是以值的方式,就会无限循环的调用下去,直到函数的栈溢出。
- 赋值运算符函数的返回值类型要声明为该类型的引用,并在函数结束前返回实例自身的的引用(*this),只有返回一个引用,才能进行连续赋值。否则,如果函数的返回值是void,则应用该赋值运算符将不能进行连续赋值。假设有3个Person对象:p1、p2、p3,在程序中语句p1=p2=p3将不能通过编译。
- —般性原则:重载赋值操作符,必然需要实现深拷贝!
3.相关问题
- 浅拷贝和深拷贝
- 这是拷贝构造函数和赋值运算符重载函数都会涉及到的这个问题。
- 所谓浅拷贝,就是说编译器提供的默认的拷贝构造函数和赋值运算符重载函数,仅仅是将对象中各个数据成员的值拷贝给另一个同一个类对象对应的数据成员。
- 在深拷贝情况下,对于对象中动态成员,就不能仅仅简单地赋值,而应该重新动态分配空间。
- 赋值运算符重载函数只能是类的非静态的成员函数
- 因为静态成员函数只能操作类的静态成员,无法操作类的非静态成员。
- 避免二义性,当程序没有显示提供一个以本类或者本类的引用为参数的赋值运算符重载函数时,编译器会自动提供一个。现在假设C++允许友元函数定义的赋值运算符重载函数,而且以引用为参数,与此同时,编译器也提供一个默认的赋值运算符重载函数(由于友元函数不属于这个类,所以此时编译器会自动提供一个)。但是当再执行类似str2 = str1;这样的代码时,编译器就困惑了。
- 为了避免这样的二义性,C++强制规定,赋值运算符重载函数只能定义为类的成员函数,这样编译器就能判断是否需要提供默认版本了。
!