模拟实现String类中(什么是深浅拷贝?如何解决浅拷贝问题?)

对于string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。

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

对于下面的一段代码来解释浅拷贝:

class String
{
public:
//构造函数
public:
    String(const char* str = "")
    {
        if (nullptr == str)
            str = "";
        _str = new char[strlen(str) + 1]; //开辟空间   加1为了放入‘/0’
        strcpy(_str, str);
    }
///
    //类似于:编译器按照浅拷贝的方式生成的拷贝构造
    String(const String& s)
        :_str(s._str)
    {}

  //类似于:编译器按照浅拷贝方式生成的默认赋值运算符重载
    String& operator=(const String& s)
    {
        _str = s._str;
        return *this;
    }
/
//析构函数
~String()
{
    if (_str)
{
   delete[] _str;
   _str = nullptr;
}
}

private:
char* _str;
};

void TestString()
{
String s1("hello bit!!!");
String s2(s1);

    String s3("hello");
    String s4("bit");
    //存在2个问题:
    //1.s3给s4赋值之后,s4之前的空间就丢失了
    //2.s3和s4最终共享的是同一份内存空间 浅拷贝
    s4 = s3;
}
//类似于:编译器按照浅拷贝的方式生成的拷贝构造
    String(const String& s)
        :_str(s._str)
    {}

  //类似于:编译器按照浅拷贝方式生成的默认赋值运算符重载
    String& operator=(const String& s)
    {
        _str = s._str;
        return *this;
    }

上述String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构

造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

同时当我们把s3的值赋给s4,那么s4的地址就会变成s3,当程序结束运行进行销毁时,将s4的空间释放后,由于这时s3和s4共用一份内存空间,就会导致出现浅拷贝的情况,导致程序崩溃。

如何解决浅拷贝的问题:

深拷贝

什么是深拷贝:拷贝对象管理的资源中的内容,最终每个对象都有自己独立的资源

对于深拷贝的代码有两种写法:其中最容易理解的就是(1)传统的方法

class String
{
public:
    String(const char* str = "")
    {
        if (nullptr == str)
            str = "";

        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }

    //深拷贝 
   //用s1给s2 拷贝
//然后再把s2拷贝给s3
    String(const String& s)
        :_str(new char[strlen(s._str)+1]) //给要拷贝的对象申请空间
    {
        strcpy(_str, s._str);
    }


    //用s4给s5赋值
    String& operator=(const String& s)
    {
        if (this != &s)
        {
             char* temp = new char[strlen(s._str) + 1];
            strcpy(temp, s._str);

            delete[] _str; //把s5原本空间释放
            _str = temp;
        }
        return *this;
    }


    ~String()
    {
        if (_str)
        {
            delete[] _str;
            _str = nullptr;
        }
    }

private:
    char* _str;
};

void TestString()
{
    String s1("hello");
    String s2(s1);
    String s3(s2);

//赋值运算符调用
    String s4("hello");
    String s5("bit");
    s5 = s4;
}
int main()
{
    TestString();
    return 0;
}

1.对于上述代码中深拷贝解读:

我们为了把s1中的代码拷贝一份,并且要避免出现浅拷贝的情况,所以我们需要s2有自己的独立空间.

//深拷贝  
 String(const String& s)
        :_str(new char[strlen(s._str)+1]) //给要拷贝的对象申请空间  这样s2就有了自己独立的空间
    {
        strcpy(_str, s._str); //s._str里面是s1存放的对象   把s1中hello拷贝到s2中
    }
  1. 对于赋值运算符解读:

我们要用s4给s5赋值,如果直接把s4的空间拷贝给s5,那么s5原来的空间就找不到了。

所以我们先把s5原来的空间先释放了。

对于上述代码解读:

   //用s4给s5赋值
    String& operator=(const String& s)
    {
        if (this != &s)//先来检测是不是自己给自己赋值  this指向的是s5
        {
             char* temp = new char[strlen(s._str) + 1]; //先统计s4中的字符有多长,然后新创建一个空间
            strcpy(temp, s._str); //将s4当前的对象放到temp中

            delete[] _str; //把s5原本空间释放
            _str = temp; //把temp中的资源放进去
        }
        return *this;
    }

(2)现代版写法:

class String
{
public:
    String(const char* str = "")
    {
        if (nullptr == str)
            str = "";

        _str = new char[strlen(str) + 1];
        strcpy(_str, str);
    }

    //现代版
    String(const String& s)
        :_str(nullptr)
        
    {
        String temp(s._str);
        swap(_str, temp._str);
    }

    String& operator=(String& s)
    {
        swap(_str, s._str);
        return *this;
    }

   ~String()
    {
        if (_str)
        {
            delete[] _str;
            _str = nullptr;
        }
    }

private:
    char* _str;
};

void TestString()
{
    String s1("hello");
    String s2(s1);
    String s3(s2);

    String s4("hello");
    String s5("bit");
    s5 = s4;
}

拷贝构造:

  String(const String& s)
        :_str(nullptr)
        
    {
        String temp(s._str); 创建一个临时对象temp将s1中的资源放到temp中
        swap(_str, temp._str);
    }

目的就是将this所指的对象拷贝成功,而目前this所指的对象_str里面是空。

swap(_str, temp._str); 就是将temp中的_str和this所指的_str中的地址进行交换。

交换完了后,this指向的对象就是“hello”。这时候temp指向就是空了。

出了函数的作用域后,temp被销毁,然后调用析构函数,析构函数看到

赋值运算符重载:

我们用s4给s5赋值时,该代码其实就是用拷贝构造的方法把s4拷贝一份为s

即就是用s给s5赋值。

 String& operator=(String& s)
    {
        swap(_str, s._str);
        return *this;
    }

代码是以值的方式传参,在传参时候必须将实参拷贝一份,调用拷贝构造拷贝出了s。

s空间放的就是“hello/0”,当s拷贝构造成功之后,就可以用s给this去赋值。

怎么去赋值:将采用交换 swap(_str, s._str);

结果:s5的指针就指向原来s中资源,s中的指针就指向了原来s5中的资源;

赋值结束之后s这个对象就会被销毁,然后将s5原来的空间就销毁了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值