c++中深拷贝和浅拷贝_python中的深浅拷贝

(1) 2024-07-20 13:12

Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
c++中深拷贝和浅拷贝_python中的深浅拷贝,希望能够帮助你!!!。

文章目录

  • 前言
  • 浅拷贝
  • 拷贝构造
    • 传统写法
    • 现代写法
  • 赋值重载
    • 传统写法
    • 现代写法
  • 尾声

前言

hello,大家好啊,今天做题时无意间碰到深浅拷贝问题,遂去学习了一番,并整理一下笔记。

浅拷贝

若未显示定义拷贝构造函数,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <assert.h> using namespace std; namespace yzq { 
    class String { 
    public: String(const char* str = "") :_str(new char[strlen(str) + 1]) // +1是为\0开的空间 { 
    strcpy(_str, str); // \0也拷过去的了 } // 浅拷贝 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 = nullptr; } const char* c_str() const { 
    return _str; } char& operator[](size_t pos) { 
    assert(pos < strlen(_str)); return _str[pos]; } size_t size() { 
    return strlen(_str); } private: char* _str; }; } void test_string1() { 
    yzq::String s1("hello world"); cout << s1.c_str() << endl; s1[0] = 'a'; // 本质就是s1.operator[](0) = 'x' cout << s1.c_str() << endl; } void test_string2() { 
    yzq::String s1("hello world"); yzq::String s2(s1); cout << s1.c_str() << endl; cout << s2.c_str() << endl; } 

c++中深拷贝和浅拷贝_python中的深浅拷贝_https://bianchenghao6.com/blog__第1张

c++中深拷贝和浅拷贝_python中的深浅拷贝_https://bianchenghao6.com/blog__第2张

调试可发现 s1 s2 地址相同,也就是指向同一片空间。
浅拷贝,把s1的值拷贝给s2

c++中深拷贝和浅拷贝_python中的深浅拷贝_https://bianchenghao6.com/blog__第3张

s2 后创建,会先析构,析构时虽然先 delete[] _str;_str= nullptr;但操作的只是 s2 的 _str,并没有影响到 s1 的_str

而且调用s1的析构函数时,又会delete[] _str;,一块空间被 delete 了2次

而且 s1 和 s2 指向的是同一片空间,其中一个对象插入删除数据,会导致另一个对象也会被影响。

程序会崩溃。

c++中深拷贝和浅拷贝_python中的深浅拷贝_https://bianchenghao6.com/blog__第4张

拷贝构造

传统写法

class string { 
    public: string(const char* str = "") : _str(new char[strlen(str) + 1]) { 
    strcpy(_str, str); //拷贝时 \0也会一起拷贝 } // s2(s1) 去开辟和s1一样大的空间,再把内容拷贝过去 string(const string& s) : _str(new char[strlen(s._str)] + 1) { 
    strcpy(_str, s._str); } ~string() { 
    cout << "~string()" << endl; delete[] _str; _str = nullptr; } private: char* _str; }; void Test() { 
    yzq::String s1("hello"); yzq::String s2(s1); } 

c++中深拷贝和浅拷贝_python中的深浅拷贝_https://bianchenghao6.com/blog__第5张

地址不一样了,已经不是一块空间了。

对于拷贝构造函数来说,s2先开一块和s1一样大小的空间即可。

而对于赋值运算符重载函数来说s2已经存在,则必须先释放s2的空间然后才让s2开与s1一样大的空间

然后再让s2指向这片空间,再拷贝s1的数据到s2里面。

现代写法

void swap(string& s) { 
    std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } // 传统写法的拷贝构造深拷贝,老老实实去干活,该开空间就开空间,该拷贝数据就拷贝数据 // 现代写法:一种剥削行为,安排别人去干活 string(const string& s) :_str(nullptr) ,_size(0) ,_capacity(0) { 
    // 就是去利用传过来的s的_str去构造新的tmp // tmp是个临时对象,出作用域时会销毁 // 而swap过去的指针是随机值,delete时可能会崩溃,因此自身的成员变量最好初始化一下 string tmp(s._str); /* swap(_str, tmp._str); swap(_size, tmp._size); swap(_capacity, tmp._capacity); */ // 利用自己写的swap函数 swap(tmp); } void test_string13() { 
    yzq::string s1("hello world"); yzq::string s2(s1); cout << s2 << endl; yzq::string s3; s3 = s1; cout << s3 << endl; } 

赋值重载

传统写法

针对已经定义出来的对象的赋值拷贝。

赋值运算符的重载也是一个默认成员函数,我们不写,编译器也会自动生成。
默认生成的赋值运算符,特性和拷贝构造一致。

  1. 针对内置类型,会完成浅拷贝。
  2. 针对自定义类型,会调用它的赋值运算符重载完成拷贝。

赋值重载需要考虑空间不够或者空间浪费的问题,因此最好先把目的空间delete一下。
又考虑到开辟空间失败的问题,如果开辟失败了,目的的空间却也已经被释放了,不合适,因此先开辟空间再释放目的地空间。

将s3赋值给s1。

难道直接拷过去就行了吗?
万一s1空间不够了,那不就还得先扩容再拷贝吗?
扩容有性能消耗,且有可能产生空间浪费。那我们还是重新开辟个一样大小的空间好了。

c++中深拷贝和浅拷贝_python中的深浅拷贝_https://bianchenghao6.com/blog__第6张

c++中深拷贝和浅拷贝_python中的深浅拷贝_https://bianchenghao6.com/blog__第7张

#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <assert.h> using namespace std; namespace yzq { 
    class String { 
    public: String(const char* str = "") :_str(new char[strlen(str) + 1]) // +1是为\0开的空间 { 
    strcpy(_str, str); } //s2(s1) s1传给s,s2传给this指针 String(const String& s) :_str(new char[strlen(s._str) + 1]) { 
    strcpy(_str, s._str); } // s1 = s3 也就是s1.operator=(&s1, s3) String& operator=(const String& s) { 
    if (this != &s) // 避免自己赋值给自己 { 
    /* 万一开空间失败了,s1却已经被释放了 delete[] _str;//释放s1的空间 _str = nullptr; _str = new char[strlen(s._str) + 1];// 开辟和s3一样大的空间 strcpy(_str, s._str); */ // 先开空间比较合适 char* tmp = new char[strlen(s._str) + 1]; strcpy(tmp, s._str); delete[] _str; _str = tmp; } return *this; // 为了支持连续赋值,返回左操作数 } // 如果用传值返回,又是一个深拷贝,代价比较大。 ~String() { 
    if (_str) { 
    delete[] _str; } _str = nullptr; } const char* c_str() const { 
    return _str; } char& operator[](size_t pos) { 
    assert(pos < strlen(_str)); return _str[pos]; } size_t size() { 
    return strlen(_str); } private: char* _str; }; } void test_string2() { 
    yzq::String s1("hello world"); yzq::String s2(s1); cout << s1.c_str() << endl; cout << s2.c_str() << endl; yzq::String s3(""); s1 = s3; cout << s3.c_str() << endl; } int main() { 
    try { 
    test_string2(); } catch (const std::exception& e) { 
    cout << e.what() << endl; } return 0; } 

c++中深拷贝和浅拷贝_python中的深浅拷贝_https://bianchenghao6.com/blog__第8张

现代写法

其实就是调用其他函数来实现自己的功能。

用 s1 拷贝构造一个 s2 对象s2(s1)可以通过构造函数将 s1 里的指针_str构造一个临时对象 tmp(构造函数不仅会开空间还会将数据拷贝至tmp),此时 tmp 就是我们想要的那个对象,然后将tmp 的指针_str与自己的指针进行交换。

void swap(string& s) { 
    std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } // 赋值重载现代写法1 s3 = s1 string& operator=(const string& s) { 
    if (this != &s) { 
    string tmp(s._str); swap(tmp); } return *this; } // 原来的s3交换给tmp了,tmp是临时对象,出作用域时顺带xiao'h 
// 现代写法2 更加剥削 s3 = s1 // s1传过来直接就是拷贝构造 拷贝构造完成深拷贝后 再直接交换 string& operator=(string s) { 
    swap(s); return *this; } 

尾声

🌹🌹🌹

写文不易,如果有帮助烦请点个赞~ 👍👍👍

Thanks♪(・ω・)ノ🌹🌹🌹

😘😘😘

👀👀由于笔者水平有限,在今后的博文中难免会出现错误之处,本人非常希望您如果发现错误,恳请留言批评斧正,希望和大家一起学习,一起进步ヽ( ̄ω ̄( ̄ω ̄〃)ゝ,期待您的留言评论。
附GitHub仓库链接

今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。

上一篇

已是最后文章

下一篇

已是最新文章

发表回复