初步探索C++深浅拷贝

前言

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;
}

赋值重载

传统写法

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

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

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

赋值重载需要考虑空间不够或者空间浪费的问题,因此最好先把目的空间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("123456");
	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仓库链接

  • 24
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值