更多C++知识点:C++目录索引
浅拷贝
概念:
浅拷贝也称位拷贝,是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。
浅拷贝的问题
当多个对象共用同一块内存,某一对象使用完毕后会进行析构,其他对象再次使用这块空间,发生非法访问
多个对象,必定析构多次,但只有一块内存,导致非法析构
注意
当在一个自定义的类中,没有给出构造函数,重载赋值运算符的时候,一般编译器就会以浅拷贝的形式自动合成。
浅拷贝代码
class String
{
public:
String(const char* str="")
:_str(new char[strlen(str)+1])
{
strcpy(_str,str);
}
String(const String& s)
:_str(s._str)
{
}
String& operator=(const String& s)
{
if(this!= &s)
{
_str = s._str;
}
return *this;
}
~String()
{
if(_str)
{
delete[] _str;
_str = NULL;
}
}
private:
char* _str;
};
void Test()
{
String s1("hello world");
String s2=s1;
}
画图解释一下这个代码:
深拷贝
深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响
代码一:传统写法
class String
{
public:
//构造
String::String(const char* str )
{
if (str == NULL)
{
_str = new char[1];
_str = '\0';
}
else
{
_str = new char[strlen(str) + 1];
strcpy(_str, str);
}
}
//拷贝构造
//String s2(s1)
String::String(const String& s)
:_str(new char[strlen(s._str)+1])
{
if (this != &s)
{
strcpy(_str, s._str);
}
}
// s1 = s2
String& String::operator=(String s)
{
//1.将S2的字符串拷贝下来
//2.释放原字符串s1空间
//3.将临时字符串给s1
if (this != &s)
{
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = NULL;
_str = tmp;
}
return *this;
}
String::~String()
{
if (_str != NULL)
{
delete[] _str;
_str = NULL;
}
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
代码二:现代写法
画图解释一下现代写法中的交换指针
String::String(const char* str="")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
void String::Swap(String& s)
{
swap(_str, s._str);
}
//拷贝构造
String::String(const String& s)
{
if (this != &s)
{
String tmp(s._str);//构造临时对象,出了作用域自动析构
Swap(tmp);//将指针进行交换
}
}
//赋值运算符重载
String& String::operator=(String s)
{
if (this != &s)
{
String tmp(s._str);
Swap(tmp);
}
return *this;
}
写时拷贝+引用计数
引用计数:
在引用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。
两种引用计数方式:
方式一:重新开辟四个字节专门做引用计数
方式二:引用计数和字符串使用一个内存块,方便管理
代码一:方式一的引用计数
//引用计数为另一块空间
引用计数为另一块空间
class String
{
public:
void show()
{
cout << _str << endl;
}
//构造
String(const char* str="")
:_str(new char[strlen(str)+1])
, _pCount(new size_t(1))
{
strcpy(_str, str);
}
//析构
~String()
{
if (--(*_pCount) == 0)
{
delete[] _str;
delete _pCount;
_str = NULL;
_pCount = NULL;
}
}
//拷贝构造
String(const String& s)
:_str(s._str)
, _pCount(s._pCount)
{
++(*_pCount);
}
//赋值运算符重载
String& operator=(const String& s)
{
if (this != &s)
{
if (--(*_pCount) == 0)
{
delete[] _str;
delete _pCount;
_str = NULL;
_pCount = NULL;
}
_str = s._str;
_pCount = s._pCount;
++(*_pCount);
}
return *this;
}
//写时拷贝
void CopyOnWrite()
{
if (*_pCount > 1)
{
char* newstr = new char[strlen(_str) + 1];
strcpy(newstr, _str);
_str = newstr;
--(*_pCount);
_pCount = new size_t(1);
}
}
//可读可写
char& operator[](size_t pos)
{
CopyOnWrite();
return _str[pos];
}
//可读
const char& operator[](size_t pos) const
{
return _str[pos];
}
private:
char* _str;
size_t* _pCount;
};
代码二:方式二的写时拷贝
//引用计数在头上
class String
{
public:
//构造
String(char* str = "")
:_str(new char[strlen(str)+5])
{
*((int*)(_str - 4)) = 1;
strcpy(_str + 4, str);
}
//拷贝构造
// s2(s1)
String(const String& s)
:_str(s._str)
{
++(*((int*)(_str - 4)));
}
//赋值运算符重载
//s2 = s1
String& operator=(const String& s)
{
if (this != &s)
{
if (--(*((int*)(_str - 4))) == 0)
{
delete[](_str - 4);
}
_str = s._str;
++(*((int*)(_str - 4)));
}
return *this;
}
//析构
~String()
{
if (--(*((int*)(_str - 4))) == 0)
{
delete[](_str - 4);
}
}
const char* c_str()
{
return _str;
}
void CopyOnWrite()
{
if ((*((int*)(_str - 4))) > 1)
{
char* newstr = new char[strlen(_str) + 5];
newstr += 4;
strcpy(newstr, _str);
--(*((int*)(_str - 4)));
_str = newstr;
(*((int*)(_str - 4))) = 1;
}
}
char& operator[](size_t pos)
{
CopyOnWrite();
return _str[pos];
}
const char& operator[](size_t pos) const
{
return _str[pos];
}
private:
char* _str; // 引用计数在头上
};