🌈个人主页:秋风起,再归来~
🔥系列专栏:C++从入门到起飞
🔖克心守己,律己则安
目录
1、浅拷贝
2、深拷贝
3、现代版写法的拷贝构造和赋值重载
4、再探swap!
5、写实拷贝(了解)
6、完结散花
1、浅拷贝
>浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。
>就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一 不想分享就你争我夺,玩具损坏。
当对象当中有对资源的管理和申请时,浅拷贝有俩个问题:
>1、当一个对象对资源改变时,另一个对象中的资源随之而变。
>2、当对象进行销毁时会发生资源的多次释放。
>可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父 母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。
2、深拷贝
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给 出。一般情况都是按照深拷贝方式提供。
3、现代版写法的拷贝构造和赋值重载
>拷贝构造(深拷贝)传统写法
//拷贝构造(深拷贝)传统写法
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
>拷贝构造(深拷贝)现代写法
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
//拷贝构造(深拷贝)现代写法
string(const string& s)
{
string tmp(s._str);
swap(tmp);
//==this.swap(tmp);
}
注意:我们在string类内部自己实现一个swap交换函数,在实现自己的交换函数时,一定要在类内部指定命名空间std不然会调用this.swap,从而发生报错!
深拷贝现代写法和传统写法的不同之处就在于,我们不自己手动去开空间给我们需要构造的对象,而是临时构造一个对象tmp,把开空间的任务交给了带参构造函数。然后,拷贝构造就窃取tmp的成果,用swap将this和tmp交换!从而达到拷贝构造的目的!
这里还需要注意的是我们一定要给this的_str初始化为nullptr,不然_str里面为空,还是随机值这是标准未规定的。如果,this的_str为随机值,那么在交换后,tmp在析构时会释放随机的空间。所以,我们可以在交换前置空,也可以直接在申明处给缺省值!
char* _str=nullptr;//指向字符串的指针
size_t _size=0;//有效字符个数(不包含'\0')
size_t _capacity=0;//空间大小(不包含'\0')
>赋值重载传统写法
//显示赋值运算符重载
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
>赋值重载现代写法1
string& operator=(const string& s)
{
if (this != &s)
{
string tmp(s.c_str());
swap(tmp);
}
return *this;
}
与拷贝构造的写法类似,不过赋值重载中tmp做的事更多。它不仅帮this拷贝构造数据,在交换数据后还要帮忙把this原来指向的资源释放(就好比我做饭给你吃,我还要顺便在你吃完后把碗筷洗了)。
>赋值重载现代写法2
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
这种写法就更加简单了,我们直接在传值传参的时候构造tmp,再进行交换!但是,这种写法在自己给自己赋值时避免不了无用的拷贝构造与交换。不过,这种情况很少并且上面的写法并没有问题,无伤大雅!
4、再探swap!
>std::swap
我们可以看到,std中的swap其实就是一个函数模版,如果我们在string类里面用std中的swap函数的话,一个交换函数就会有三次拷贝构造,效率太低了。所以我们在string中有属于自己的swap函数,这也告诉我们,对内置类型我们可以用std::swap,但是对于类类型,我们不建议使用!
>string的成员函数swap
string成员函数的swap就是直接改变指针的指向,避免了拷贝构造。但是,既然成员函数中已经有了swap函数,那么为什么还要在string中定义非成员函数swap呢?
>string的非成员函数swap
注意看这段英文,这里说这是泛型算法交换的重载,它通过相互转移对其内部数据的所有权到另一个对象(即,字符串交换对其数据的引用,而不实际复制字符)来提高其性能:它的行为就像调用了 x.swap(y)。
这一点我们从反汇编也可以看出来,实际上,它最终还是调用了string的成员函数swap!
这就是在string中还要再实现一个非成员函数swap的意义,有些人并不知道调用string类成员函数swap和在std::定义的swap函数模版有什么不同,所以就可能会直接用全局的函数模版swap。但是,为了让效率提高,在std中就直接再次实现了一个函数swap!既然已经有现成的并且匹配的swap函数,我们在直接调用swap时并不会通过模版再次生成swap函数,而是直接调用现成的(编译器也会偷懒哦~)!
所以,我们以后在交换string类型的对象时,使用哪一个swap都可以!
5、写实拷贝(了解)
写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该 资源,就给计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源, 如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有 其他对象在使用该资源。
>因为随着C++11的更新,移动构造的出现,写实拷贝已经慢慢失去其优势不太常用了,所以这里就不进行详细的讲解。(如果友友们实在感兴趣,可以点击下面的链接看看哦~)
写实拷贝扩展https://coolshell.cn/articles/12199.html写时拷贝在读取时的缺陷https://coolshell.cn/articles/1443.html
6、完结散花
好了,这期的分享到这里就结束了~
如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~
如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~
我们下期不见不散~~