如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”
的方式自动生成缺省的函数(浅拷贝)。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。
以类 String 的两个对象 a,b 为例,假设 a.m_data 的内容为“hello”, b.m_data 的内容为“world”。现将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data= a.m_data。 这将造成三个错误:
1. b.m_data 原有的内存没被释放,造成内存泄露;
2. b.m_data 和 a.m_data 指向同一块内存,a 或 b 任何一方变动都会影响另一方;
3. 在对象被析构时,m_data 被释放了两次。
故需要深拷贝
下面是最容易理解的一种写法:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string.h>
using namespace std;
class String
{
friend ostream& operator<<(ostream &out,String &rhs);
friend istream& operator>>(istream &in,String &rhs);
public:
String(char *str = "")
:_pstr(new char[strlen(str)+1])
{
strcpy(_pstr,str);
}
String(const String& s)
:_pstr(new char [strlen(s._pstr)+1])
{
strcpy(_pstr,s._pstr);
}
String& operator=(const String& other)
//1.检查自赋值
//2.用 delete 释放原有的内存资源
//3.分配新的内存资源,并复制字符串
//4.返回本对象的引用(连等)
{
if(this != &other)
{
delete []_pstr;
_pstr = new char [strlen(other._pstr)+1];
strcpy(_pstr,other._pstr);
}
return *this;
}
~String()
{
if(_pstr != NULL)
delete []_pstr;
}
void Debug()
{
cout<<"string:> "<<_pstr<<endl;
cout<<"count:> "<<*_ref<<endl;
}
private:
char *_pstr;
};
ostream& operator<<(ostream &out,String &rhs)
{
out<<rhs._pstr;
return out;
}
istream& operator>>(istream &in,String &rhs)
{
in>>rhs._pstr;
return in;
}
void Fun1()
{
String A("aaaaa");
String B(A);
String C = B;
C = A;
cin>>C;
cout<<C<<endl;
}
下面还有一种简洁的拷贝构造和赋值运算符重载函数:
理解图:
String(const String& s)
:_pstr(NULL)//没有指向的指针不能交换里面的内容
{
String temp(s._pstr);
std::swap(temp._pstr,_pstr);
}
String& operator=(String rhs)//运算符重载隐含了左值,故不需要赋空
//此时没有将参数赋为const String& other的原因:
//将下面实现换成注释也可完成同样功能,但重复了上面的拷贝构造内容,如换成没有注释的形式,则增强代码重用率,在下面进行交换的时候可以重复调用拷贝构造函数,也可达到赋值的目的。
{
//String temp(s._pstr);
//std::swap(temp._pstr,_pstr);
std::swap(_pstr,rhs._pstr);
return *this;
}//返回值联等 参数会产生临时变量
然而深拷贝并不见的完全好,如果多个对象都存储了相同的内容,那岂不是很浪费空间,于是我们又发明了一个叫做引用计数的东西,就是把相同内容的对象只存储一遍,用一个计数器记下他的数目,每构造一次将其置一,每拷贝构造一次将其加一,每赋值一次将其加一(原来的引用计数减一),但如果将引用计数设为 int _ref 我们每次进行操作都会对其原来的引用计数加一(其实他原来的_ref并不需要改变,甚至在调用赋值重载时还会减一),而将其换为 static int _ref 时,如果我们申请了新的对象
比如:
String A;
String B(A);
String C;
C的出现会将前面A B的引用计数一同改变,所以这也不是什么好办发,然而最好的办法是将引用计数设为指针
int * _ref 这样实现起来解容易多了,也可像库函数STL实现一样将该引用计数放于私有成员字符串的前4个字节,节省了一个私有成员的开销,这种方法与库函数中的new类似,new = 开辟空间(malloc)+调用构造函数(定位new) new operator = 开辟空间。
下面是写时拷贝的实现:
class String
{
friend ostream& operator<<(ostream &out,String &rhs);
friend istream& operator>>(istream &in,String &rhs);
public:
String(char *str = "")
:_pstr(new char[strlen(str)+1])
,_ref(new int[1])//申请一个4字节的空间 new int(1)
{
strcpy(_pstr,str);
_ref[0]=1;//里面存放计数
}
String(const String &s)
:_pstr(NULL)
,_ref(NULL)
{
_pstr = s._pstr;
_ref = s._ref;
(*_ref)++;
}
String& operator=(const String &rhs)
{
if(rhs._pstr != this->_pstr)
{
if( --(*_ref) == 0)
{
delete []_pstr;
if(_ref != NULL)//_ref可能为空
delete []_ref;
}
_pstr = rhs._pstr;
_ref = rhs._ref;
(*_ref)++;//_red[0]++
}
return *this;
}
~String()
{
if(--(*_ref) == 0)
{
delete []_pstr;
if(_ref != NULL)
delete []_ref;
}
}
void Debug()
{
cout<<"string:> "<<_pstr<<endl;
cout<<"count:> "<<*_ref<<endl;
}
private:
char *_pstr;
int *_ref;
};
ostream& operator<<(ostream &out,String &rhs)
{
out<<rhs._pstr;
return out;
}
istream& operator>>(istream &in,String &rhs)
{
in>>rhs._pstr;
return in;
}
void Fun2()
{
String a("qqq");
a.Debug();
String b("dfhdfhd");
//a.Debug();
String c(b);
b.Debug();
c = a;
a.Debug();
b.Debug();
c.Debug();
}