重载赋值操作符
赋值操作符“=”可以用来将一个对象拷贝给另一个已经存在的对象。如果我们重新定义了一种新的数据类型,比如说复数类,那么我们就需要重载一下赋值操作符,使之能够满足我们的赋值需求。
当然,拷贝构造函数也有同样的功能。拷贝构造函数可以将一个对象拷贝给另一个新建的对象。如果我们没有在类中显式定义拷贝构造函数,也没有重载赋值操作符,那么系统会为我们的类提供一个默认的拷贝构造函数和一个赋值操作符。前面在介绍类的相关知识时已经提到,系统为我们提供的默认拷贝构造函数只是将源对象中的数据一一拷贝给目标对象,而系统为类提供的赋值操作符也是这样的一种功能。
complex c1(4.3, -5.8);
complex c2;
c2 = c1;
cout<<c1<<endl;
cout<<c2<<endl;
执行结果为:
4.3 + -5.8 i
4.3 + -5.8 i
利用前面我们定义的 complex 类,我们先定义了两个 complex 类的对象 c1 和 c2,c1 对象通过带参构造函数初始化,之后用 c1 来初始化 c2,最后输出这两个复数类对象。
注意在 complex 类中,我们并未定义拷贝构造函数,也没有重载赋值操作符,但是例 1 中c2 = c1;并未有语法错误,根据程序运行结果可以得知,该赋值操作成功地完成了。这是因为,系统默认为类提供了一个拷贝构造函数和一个赋值操作符,而数据一对一的拷贝也满足我们复数类的需求了。
在前面介绍拷贝构造函数时我们提到过,系统提供的默认拷贝构造函数有一定的缺陷,例如当类中的成员变量包含指针的时候,会导致一些意想不到的程序漏洞,此时就需要重新定义一个拷贝构造函数。在相同的情况下,系统提供的赋值操作符也无法满足我们的需求,必须要进行重载。
在前面介绍拷贝构造函数一节中,我们已经详细分析了系统提供的默认拷贝构造函数遇到指针成员变量时带来的风险,直接使用系统默认提供的赋值操作符同样会有此种风险,在此我们将不再重新分析这一问题,而只是将前面的示例再次拿过来,并且在程序中补上赋值操作符重载函数。
#include<iostream>
using namespace std;
class Array
{
public:
Array(){length = 0; num = NULL;};
Array(int * A, int n);
Array(Array & a);
Array & operator= (const Array & a);
void setnum(int value, int index);
int * getaddress();
void display();
int getlength(){return length;}
private:
int length;
int * num;
};
Array::Array(Array & a)
{
if(a.num != NULL)
{
length = a.length;
num = new int[length];
for(int i=0; i<length; i++)
num[i] = a.num[i];
}
else
{
length = 0;
num = 0;
}
}
//重载赋值操作符
Array & Array::operator= (const Array & a)
{
if( this != &a )
{
delete[] num;
if(a.num != NULL)
{
length = a.length;
num = new int[length];
for(int i=0; i<length; i++)
num[i] = a.num[i];
}
else
{
length = 0;
num = 0;
}
}
return *this;
}
Array::Array(int *A, int n)
{
num = new int[n];
length = n;
for(int i=0; i<n; i++)
num[i] = A[i];
}
void Array::setnum(int value, int index)
{
if(index < length)
num[index] = value;
else
cout<<"index out of range!"<<endl;
}
void Array::display()
{
for(int i=0; i<length; i++)
cout<<num[i]<<" ";
cout<<endl;
}
int * Array::getaddress()
{
return num;
}
int main()
{
int A[5] = {1,2,3,4,5};
Array arr1(A, 5);
arr1.display();
cout << "*********copy constructor**********" << endl;
Array arr2(arr1);
arr2.display();
arr2.setnum(8,2);
arr1.display();
arr2.display();
cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
cout << "*********assignment constructor**********" << endl;
arr1 = arr2;
arr1.display();
arr2.display();
arr2.setnum(9,3);
arr1.display();
arr2.display();
cout<<arr1.getaddress()<<" "<<arr2.getaddress()<<endl;
return 0;
}
深拷贝:如果在复制这个对象的时候为新对象制作了外部对象的独立复制,就是深拷贝。
通常大家会对拷贝构造函数和赋值函数混淆,这儿仔细比较两者的区别:
1)拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作
2)一般来说在数据成员包含指针对象的时候,需要考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象
3)实现不一样。拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。赋值函数则是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。(这些要点会在下面的String实现代码中体现)
@& 如果不想写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,最简单的办法是将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。如:
class A
{
private:
A(const A& a); //私有拷贝构造函数
A& operate=(const A& a); //私有赋值函数
}
A a;
A b(a); //调用了私有拷贝构造函数,编译出错
A b;
b=a;
4.复制构造函数是构造函数,而赋值操作符属于操作符重载范畴,它通常是类的成员函数
5.复制构造函数原型ClassType(const ClassType &);无返回值;赋值操作符原型ClassType& operator=(const ClassType &);返回值为ClassType的引用,便于连续赋值操作
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
class Person{
public:
Person(char *name);
Person(const Person& a);
~Person();
public:
Person& operator =(const Person& a);
void dis();
private:
int m_len;
char *m_name;
};
Person::Person(char *name)
{
printf("%s %d %s\n",__FILE__, __LINE__, __FUNCTION__);
m_len = strlen(name)+1;
m_name = new char[m_len+1];
memcpy(m_name, name, m_len);
}
Person::Person(const Person& a)
{
printf("%s %d %s\n",__FILE__, __LINE__, __FUNCTION__);
m_len = a.m_len;
m_name = new char[m_len+1];
memcpy(m_name, a.m_name, m_len);
}
Person::~Person()
{
printf("%s %d %s\n",__FILE__, __LINE__, __FUNCTION__);
delete[] m_name;
}
Person& Person::operator =(const Person& a)
{
printf("%s %d %s\n",__FILE__, __LINE__, __FUNCTION__);
if(&a == this)
{
return *this;
}
else
{
delete []this->m_name;
int lens = strlen(a.m_name);
this->m_name = new char[lens +1];
strcpy(this->m_name, a.m_name);
return *this;
}
}
void Person::dis()
{
cout<<"len: " << m_len<<endl;
cout<<"name: " << m_name<<endl;
}
int main()
{
cout << "Hello World!" << endl;
Person a("wangwu");
a.dis();
Person b(a);//调用复制构造函数
b.dis();
Person c = b;//调用复制构造函数
c.dis();
Person d("zhangshan");
d = c; //调用赋值构造函数
d.dis();
return 0;
}
自己小结(心法)
拷贝构造函数是无中生有(克隆羊),本质是构造函数。
赋值函数是重新做人(肉身不变,大换血,精神面貌焕然一新),本质是类的成员函数。
reference
1.微学苑 C++重载赋值操作符
2.C++中构造函数,拷贝构造函数和赋值函数的区别和实现
3.拷贝构造函数和赋值函数的区别