C++浅拷贝和深拷贝


1、浅拷贝

浅拷贝:又称值拷贝,将源对象的值拷贝到目标对象中去,本质上来说源对象和目标对象共用一份实体,只是所引用的变量名不同,地址其实还是相同的。 举个简单的例子,你的小名叫西西,大名叫冬冬,当别人叫你西西或者冬冬的时候你都会答应,这两个名字虽然不相同,但是都指的是你。

假设有一个String类,String s1;String s2(s1);在进行拷贝构造的时候将对象s1里的值全部拷贝到对象s2里。

我们现在来简单的实现一下这个类:

#include <iostream>
#include<cstring>
using namespace std;

class STRING 
{
public:
    STRING( const char* s = "" ) :_str( new char[strlen(s)+1] )
    {
        strcpy_s( _str, strlen(s)+1, s );
    }

    STRING( const STRING& s )
    {
        _str = s._str;
    }
    
    STRING& operator=(const STRING& s)
    {
        if (this != &s)
        {
            this->_str = s._str;
        }
        return *this;
    }
    
    ~STRING()
    {
        cout << "~STRING" << endl;
        if (_str)
        {
            delete[] _str;
            _str = NULL;
        }
    }

    void show()
    {
        cout << _str << endl;
    }

private:
    char* _str;
};

int main()
{
    STRING s1("hello linux");
    STRING s2(s1);

    s2.show();
    return 0;
} 

其实,这个程序是存在问题的,什么问题呢?
我们想以下,创建s2的时候程序必然会去调用拷贝构造函数,这时候拷贝构造函数仅仅只是完成了值拷贝,导致两个指针指向了同一块区域。随着程序程序的运行结束,又去调用析构函数,先是s2去调用析构函数,释放了它指向的内存区域,接着s1又去调用析构函数,这时候析构函数企图释放一块已经被释放的内存区域,程序将会崩溃。

s1和s2的关系就是这样的:
在这里插入图片描述
进行调试时发现s1和s2确实指向了同一块内存:
在这里插入图片描述

浅拷贝问题:

(1)如果类中叧包含简单数据成员,没有指向堆的指针, 可以使用编译器提供的默认复制构造函数。
(2)如果类中包含指向堆中数据的指针,浅复制将出现 严重问题

  • 浅复制直接复制两个对象间的指针成员,导致两个指针 指向堆中同一坑内存区域。
  • 一个对象的修改将导致另一个对象的修改。
  • 一个对象超出作用域,将导致内存释放,使得另一个对 象的指针无效,对其访问将导致程序异常。

那么这个问题应该怎么去解决呢?这就引出了深拷贝。


2、深拷贝

2.1 深拷贝定义

深拷贝,拷贝的时候先开辟出和源对象大小一样的空间,然后将源对象里的内容拷贝到目标对象中去,这样两个指针就指向了不同的内存位置。 并且里面的内容是一样的,这样不但达到了我们想要的目的,还不会出现问题,两个指针先后去调用析构函数,分别释放自己所指向的位置。即为每次增加一个指针,便申请一块新的内存,并让这个指针指向新的内存,深拷贝情况下,不会出现重复释放同一块内存的错误。

深拷贝实际上是这样的:
在这里插入图片描述

2.2 深拷贝的拷贝构造函数和赋值运算符的重载传统实现

STRING( const STRING& s )
{
    //_str = s._str;
    _str = new char[strlen(s._str) + 1];
    strcpy_s( _str, strlen(s._str) + 1, s._str );
}

STRING& operator=(const STRING& s)
{
    if (this != &s)
    {
        //this->_str = s._str;
        delete[] _str;
        this->_str = new char[strlen(s._str) + 1];
        strcpy_s(this->_str, strlen(s._str) + 1, s._str);
    }
    return *this;
}

这里的拷贝构造函数我们很容易理解,先开辟出和源对象一样大的内存区域,然后将需要拷贝的数据复制到目标拷贝对象,那么这里的赋值运算符的重载是怎么样做的呢?
在这里插入图片描述
这种方法解决了我们的指针悬挂问题,通过不断的开空间让不同的指针指向不同的内存,以防止同一块内存被释放两次的问题.


2.3 深拷贝的现代写法

STRING( const STRING& s ):_str(NULL)
{
    STRING tmp(s._str);// 调用了构造函数,完成了空间的开辟以及值的拷贝
    swap(this->_str, tmp._str); //交换tmp和目标拷贝对象所指向的内容
}

STRING& operator=(const STRING& s)
{
    if ( this != &s )//不让自己给自己赋值
    {
        STRING tmp(s._str);//调用构造函数完成空间的开辟以及赋值工作
        swap(this->_str, tmp._str);//交换tmp和目标拷贝对象所指向的内容
    }
    return *this;
}

我们先来分析拷贝构造是怎么实现的:
在这里插入图片描述
拷贝构造调用完成之后,会接着去调用析构函数来销毁局部对象tmp,按照这种思路,不难可以想到s2的值一定和拷贝构造里的tmp的值一样,指向同一块内存区域,通过调试可以看出来:

在拷贝构造函数里的tmp:
在这里插入图片描述
调用完拷贝构造后的s2:(此时tmp被析构)
在这里插入图片描述
可以看到s2的地址值和拷贝构造里的tmp的地址值是一样。

关于赋值运算符的重载还可以这样来写:

STRING& operator=(STRING s)
{
  swap(_str, s._str);
  return *this;
}
#include <iostream>
#include<cstring>
using namespace std;

class STRING 
{
public:
    STRING( const char* s = "" ) :_str( new char[strlen(s)+1] )
    {
        strcpy_s( _str, strlen(s)+1, s );
    }

    //STRING( const STRING& s )
    //{
    //    //_str = s._str; //浅拷贝的写法
    //    cout << "拷贝构造函数" << endl;
    //    _str = new char[strlen(s._str) + 1];
    //    strcpy_s( _str, strlen(s._str) + 1, s._str );
    //}
    
    //STRING& operator=(const STRING& s)
    //{
    //    cout << "运算符重载" << endl;
    //    if (this != &s)
    //    {
    //        //this->_str = s._str; //浅拷贝的写法
    //        delete[] _str;
    //        this->_str = new char[strlen(s._str) + 1];
    //        strcpy_s(this->_str, strlen(s._str) + 1, s._str);
    //    }
    //    return *this;
    //}


    STRING( const STRING& s ):_str(NULL)
    {
        STRING tmp(s._str);// 调用了构造函数,完成了空间的开辟以及值的拷贝
        swap(this->_str, tmp._str); //交换tmp和目标拷贝对象所指向的内容
    }

    STRING& operator=(const STRING& s)
    {
        if ( this != &s )//不让自己给自己赋值
        {
            STRING tmp(s._str);//调用构造函数完成空间的开辟以及赋值工作
            swap(this->_str, tmp._str);//交换tmp和目标拷贝对象所指向的内容
        }
        return *this;
    }

    ~STRING()
    {
        cout << "~STRING" << endl;
        if (_str)
        {
            delete[] _str;
            _str = NULL;
        }
    }

    void show()
    {
        cout << _str << endl;
    }
private:
    char* _str;
};

int main()
{
    //STRING s1("hello linux");
    //STRING s2(s1);
    //STRING s2 = s1;
    //s2.show();
    
    const char* str = "hello linux!";
    STRING  s1(str);
    STRING s2;
    s2 = s1;
    s1.show();
    s2.show();

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值