在C++中当
- 1.用已经存在的对象去构造出另一个新的对象。
- 2.当函数的形参是类的对象时。
- 3.当函数的返回值时类的对象时会用到拷贝构造函数。
会用到拷贝函数。
浅拷贝
class Test
{
public:
//构造函数
Test(int data)
:_data(data)
{}
//拷贝构造函数
Test(const Test& c)
{
_a = c._a;
}
private:
int _data;
};
int main()
{
Test b(10);
Test c(b);
return 0;
}
浅拷贝指的就是当在进行对象的复制的时候,知识对类对象的数据成员的拷贝,其默认的拷贝构造函数也是浅拷贝。
浅拷贝的缺陷:
大多数情况下,浅拷贝时没有问题的。但对于指针成员不可行。多个对象共用同一块空间,同一内存地址,但是在调用析构函数释放空间的时候,多次调用析构函数,这块空间被释放了多次,此时程序就会崩溃。
引用计数的拷贝
1.概念:为了解决多个指针同时指向同一块空间可能导致的内存空间的多次释放这个问题,我们引入了带引用计数的拷贝。(是用来解决浅拷贝的问题的,这也是一种浅拷贝)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
class String
{
public:
//构造函数
String(const char* ptr = "")
{
if (ptr == NULL)
{
_ptr = new char[1];
_pcount = 1;
*_ptr = '\0';
}
else
{
_pcount = 1;
_ptr = new char[strlen(ptr) + 1];
strcpy(_ptr, ptr);
}
}
//拷贝构造函数
String(String& s)
:_ptr(s._ptr)
, _pcount(s._pcount)
{
_pcount++;
}
//赋值运算符重载
String& operator=(const String& s)
{
if (this != &s)
{
if (--_pcount == 0)
{
delete[] _ptr;
//delete _pcount;
}
else
{
_ptr = s._ptr;
_pcount = s._pcount;
(_pcount)++;
}
}
return *this;
}
//析构函数
~String()
{
if ((0 == --_pcount) && _ptr != NULL)
{
delete[]_ptr;
//delete _pcount;
_ptr = NULL;
}
}
//重载[]
char& operator[](size_t size)
{
if (--_pcount >1)
{
char* ptemp = new char[strlen(_ptr) + 1];
int pcount = 1;
strcpy(ptemp, _ptr);
_pcount--;
_ptr = ptemp;
_pcount = pcount;
}
return _ptr[size];
}
private:
char* _ptr;
int _pcount;
};
void FunTest()
{
String s1("hello");
String s2(s1);
String s3(s2);
s3 = s2;
}
int main()
{
FunTest();
system("pause");
return 0;
}
如何来实现这个引用计数呢?
我们为每个内存的字符数组添加一个引用计数pcount,,表示有多少个对象使用这块内存,每多个对象使用,就让pcount值加1,当对象被析构的时候,让pcount值减1,当pcount值为0的时候,将这块内存释放掉。当然pcount也要实现内存共享,所以它也是一个堆中的数据,每个对象都有一个指向它的指针。
使用整形来作为引用计数,结果如图所示,在三个对象指向这个空间的时候,引用计数分别为1,2,3,说明引用计数不同步,所以并不能用整形来作为引用计数。
class String
{
public:
//构造函数
String(const char* ptr = "")
{
if(ptr == NULL)
{
_ptr = new char[1];
_pcount = 1;
*_ptr = '\0';
}
else
{
_pcount = 1;
_ptr = new char[strlen(ptr)+1];
strcpy(_ptr,ptr);
}
}
//拷贝构造函数
String(String& s)
:_ptr(s._ptr)
{
_pcount++; //因为是静态的,所以直接进行计数的增值就可以了
}
//赋值运算符重载
String& operator=(const String& s)
{
if(this != &s)
{
if(--_pcount == 0)
{
delete[] _ptr;
//delete _pcount;
}
else
{
_ptr = s._ptr;
_pcount = s._pcount;
(_pcount)++;
}
}
return *this;
}
//析构函数
~String()
{
if((0 == --_pcount) && _ptr!= NULL)
{
delete[]_ptr;
//delete _pcount;
_ptr = NULL;
}
}
//重载[]
char& operator[](size_t size)
{
if(--_pcount >1)
{
char* ptemp = new char[strlen(_ptr)+1];
int pcount = 1;
strcpy(ptemp,_ptr);
_pcount--;
_ptr = ptemp;
_pcount = pcount;
}
return _ptr[size];
}
private:
char*_ptr;
static int _pcount;
};
int String::_pcount = 0;
void FunTest()
{
String s1("hello");
String s2(s1);
String s3(s2);
s3 = s2;
String s4("world");
String s5(s4);
}
int main()
{
FunTest();
return 0;
}
如果一个对象第一次开辟空间存放字符串再开辟一块新的空间存放新的额引用计数,当它拷贝构造其它对象时让其它对象的引用计数都指向存放引用计数的同一块空间,pcount设置成int*就可以啦,但是这种方式有缺陷。
缺陷一:每次new两块空间,创建多个对象的时候效率比较低
缺陷二:它多次分配小块空间,容易造成内存碎片化,导致分配不出来大块内存
深拷贝
深拷贝也就是地址拷贝,在堆中申请新的空间来存取数据,这样数据之间相互独立。
String(const String& s)
{
_ptr = new char[strlen(s._ptr)+1];
strcpy(_ptr,s._ptr);
}
写时拷贝
在多个指针同时指向一个空间的时候,我们会遇到一个问题,在改变一个指针所指向内容的时候,其他指针指向的内容也是随之改变,所以我们引入了写时拷贝。
给要改变值的那个对象重新new出一块内存,然后先把之前的引用的字符数据复制到新的字符数组中,这就是写时拷贝。注意,同时还要把之前指向的内存的引用计数减1(因为它指向了新的堆中的字符数组),并在堆中重新new一个块内存,用于保存新的引用计数,同时把新的字符数组的引用计数置为1。因为此时只有一个对象(就是改变值的对象)在使用这个内存。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string.h>
using namespace std;
class String
{
friend ostream& operator<<(ostream &os, String &str);
public:
String(const char *str = NULL); //通用构造函数
String(const String &str); //拷贝构造函数
~String(); //析构函数
String& operator=(const String &str); //重载=
char& operator[](int n) const; //重载[]
friend ostream& operator<<(ostream &os, String &str);//输出
private:
char *data; //字符串
size_t length; //长度
};
String::String(const char *str)
{
if (!str)
{
length = 0;
data = new char[1];
data = '\0';
}
else
{
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
}
String::String(const String &str)
{
length = str.length;
data = new char[length + 1];
strcpy(data, str.data);
}
String::~String()
{
delete[] data;
length = 0;
}
String& String::operator=(const String &str)
{
if (this == &str)
return *this;
delete[]data;
length = str.length;
data = new char[length + 1];
strcpy(data, str.data);
return *this;
}
inline char& String::operator[](int n) const//重载[]
{
if (n >= length)
return data[length - 1]; //限定范围
else
return data[n];
}
ostream& operator<<(ostream &os, String &str)
{
os << str.data;
return os;
}
void test()
{
String s1("hello");
String s2(s1);
String s3;
s3 = s2;
s1[0] = 'a';
cout << s1 << endl;
cout << s2 << endl;
cout << s3 << endl;
}
int main()
{
test();
system("pause");
return 0;
}