C++从入门到起飞之——深浅拷贝&string类补充 全方位剖析!

秋风起,再归来~ 2024-08-25 13:05:02 阅读 78

🌈个人主页:秋风起,再归来~

🔥系列专栏:C++从入门到起飞          

🔖克心守己,律己则安

目录

1、浅拷贝

2、深拷贝

3、现代版写法的拷贝构造和赋值重载

4、再探swap!

5、写实拷贝(了解)

6、完结散花


1、浅拷贝

>浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致 多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该 资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

>就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一 不想分享就你争我夺,玩具损坏。

当对象当中有对资源的管理和申请时,浅拷贝有俩个问题:

        >1、当一个对象对资源改变时,另一个对象中的资源随之而变。

        >2、当对象进行销毁时会发生资源的多次释放。

>可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父 母给每个孩子都买一份玩具,各自玩各自的就不会有问题了。

2、深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给 出。一般情况都是按照深拷贝方式提供。

3、现代版写法的拷贝构造和赋值重载

>拷贝构造(深拷贝)传统写法

<code>//拷贝构造(深拷贝)传统写法

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在析构时会释放随机的空间。所以,我们可以在交换前置空,也可以直接在申明处给缺省值!

<code>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的更新,移动构造的出现,写实拷贝已经慢慢失去其优势不太常用了,所以这里就不进行详细的讲解。(如果友友们实在感兴趣,可以点击下面的链接看看哦~)

写实拷贝扩展

icon-default.png?t=N7T8

https://coolshell.cn/articles/12199.html写时拷贝在读取时的缺陷

icon-default.png?t=N7T8

https://coolshell.cn/articles/1443.html

6、完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​​

​​​



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。