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; }
调试可发现 s1 s2 地址相同,也就是指向同一片空间。
浅拷贝,把s1的值拷贝给s2
s2 后创建,会先析构,析构时虽然先
delete[] _str;
再_str= nullptr;
但操作的只是 s2 的_str
,并没有影响到 s1 的_str
而且调用s1的析构函数时,又会
delete[] _str;
,一块空间被 delete 了2次而且 s1 和 s2 指向的是同一片空间,其中一个对象插入删除数据,会导致另一个对象也会被影响。
程序会崩溃。
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); }
地址不一样了,已经不是一块空间了。
对于拷贝构造函数来说,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; }
针对已经定义出来的对象的赋值拷贝。
赋值运算符的重载也是一个默认成员函数,我们不写,编译器也会自动生成。
默认生成的赋值运算符,特性和拷贝构造一致。
- 针对内置类型,会完成浅拷贝。
- 针对自定义类型,会调用它的赋值运算符重载完成拷贝。
赋值重载需要考虑空间不够或者空间浪费的问题,因此最好先把目的空间delete一下。
又考虑到开辟空间失败的问题,如果开辟失败了,目的的空间却也已经被释放了,不合适,因此先开辟空间再释放目的地空间。
将s3赋值给s1。
难道直接拷过去就行了吗?
万一s1空间不够了,那不就还得先扩容再拷贝吗?
扩容有性能消耗,且有可能产生空间浪费。那我们还是重新开辟个一样大小的空间好了。
#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; }
其实就是调用其他函数来实现自己的功能。
用 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仓库链接
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章