zoukankan      html  css  js  c++  java
  • 深拷贝与浅拷贝深究

    深拷贝与浅拷贝。我们可以通过一个故事来了解一下:甲有一本漫画很好看,这时乙也想看。第一种情况,甲将书借给了乙(浅拷贝:资源重复利用了);第二种情况,甲因自己还没看完,没有借给乙,乙就自己重新买了一本(深拷贝:重新弄一个相同的)。

    在这里,我会从构造函数,拷贝构造函数、=(赋值)重载、析构函数进行深究。

    首先我们通过一个例子来了解一下:

    #include<iostream>
    using namespace std;
    class String
    {
      public:
        String(const char *str=""); //构造函数
        String(const String &s);  //拷贝构造函数
        String& operator=(const String &s);  //重载=运算符
        ~String();  //析构函数
    }
    void main()
    {
      String s="Hello";
      String s1=s;
      String s2;   s2
    =s1; }

    以上的代码是一个简单String类,一般像较为简单的函数,我们可以将函数实现写在类中,这样形成内联函数,执行效率会大大提高。

    1.为什么在函数中要对参数进行const限制?

    答:在函数中,我们使用const对参数进行限制,为了保证数据的安全性。

    2.给形参设置一个默认值(const char  *str="")有什么好处?

    答:在构造函数中,我们给形参设定一个空的的默认值。避免在函数体内进行空指针判断,提高了代码的简约性。

    3.在拷贝构造函数中,为什么对参数要加引用?

    答:为了避免产生拷贝构造后,无休止的拷贝构造。

    初级程序员标准如下代码:

    class String{
    public:
        String(const char *str="")    //构造函数
        {
            m_data=new char[strlen(str)+1];
            strcpy(m_data,str);
        }
        String(const String &s)   //拷贝构造函数
        {
            m_data=new char[strlen(s.m_data)+1];
            strcpy(m_data,s.m_data);
        }
        String& operator=(const String &s)    //重载=
        {
            if(this != &s)
            {
                delete []m_data;
                m_data=new char[strlen(s.m_data)+1];   //问题4:异常安全性原则
                strcpy(m_data,s.m_data);
            } 
            return *this;
        }
        ~String()//析构函数
        {
            delete[] m_data;
            m_data=NULL;
        }
    private:
        char *m_data;
    };

    4.当在重载=运算符时,s1=s2,s1存储数据所需要的空间申请不足时,会导致之前s1对象delete的数据丢失,如何避免这一情况? 这称为“异常安全性原则”。

    答:①我们先申请相应的空间,在将复制实例释放空间。 ②我们申请一个局部实例,然后交换临时实例和原来实例。

    高级程序员标准如下代码:

      String& operator=(const String &s)    //重载=
        {
            if(this != &s)
            {
                String temp_str(s);
           char *temp_data=temp_str.m_data;
           temp_str.m_data=m_data;
           m_data=temp_data;
    } return *this; }

    5.当我们用到浅拷贝时,那我们在执行析构函数时,就要注意释放空间的时机。在这里我们通过两种方法过度:①我们引用静态常量(static int count)来记住指向当前数据的实例个数,当count==0时,执行释放空间。

    class String
    {
    public:
        String(const char *str = "")
        {
            m_data = new char[strlen(str)+1];
            strcpy(m_data, str);
            use_count++;
        }
    
        String(const String &s)
        {
            m_data = s.m_data;
            use_count++;
        }
        //String& operator=(const String &s);
    
        ~String()
        {
            if(--use_count == 0)
            {
                delete []m_data; 
                m_data = NULL;
            }
        }
    private:
        char *m_data;
        static int  use_count;
    };
    
    int String::use_count = 0;
    
    void main()
    {
        String s("Hello");
        String s1 = s;
    
        String s2("Linux");
    }

    注意:当我们调试发现,当重新定义一个不一样的实例时,也会改变use_count的值,这是我们不想看到的。由此当我们执行程序完成,继第二次之后执行构造函数所分配的空间不会被释放,造成内存泄漏。

    ②我们引用技术类将实例,将String对象的初始化和use_count的增减操作分离。

    class String;
    ostream& operator<<(ostream &out, const String &s);
    
    class String_rep
    {
        friend class String;
        friend ostream& operator<<(ostream &out, const String &s);
    public:
        String_rep(const char *str="") : use_count(0)
        {
            m_data = new char[strlen(str)+1];
            strcpy(m_data, str);
        }
    
        String_rep(const String_rep &rep)
        {
            m_data=new char[strlen(rep.m_data)+1];
            strcpy(m_data,rep.m_data);
        }
        String_rep& operator=(const String_rep &rep)
        {
            if(this!=&rep)
            {
                /*  同问题4:会发生异常
                delete m_data;
                m_data=new char[strlen(rep.m_data)+1];
                strcpy(m_data,rep.m_data)*/
                String_rep temp_rep(rep);
                char *temp_data=temp_rep.m_data;
                temp_rep.m_data=m_data;
                m_data=temp_data;
            }
            return *this;
        }
    ~String_rep() { delete []m_data; m_data = NULL; } public: void increment() {++use_count;} void decrement() {
      
      if(--use_count==0)
          delete this;
      }
    private: char *m_data; int use_count; }; //////////////////////////////////////////////////////// class String { friend ostream& operator<<(ostream &out, const String &s); public: String(const char *str = "") : rep(new String_rep(str)) { rep->increment(); } String(const String &s) { rep = s.rep; rep->increment(); } String& operator=(const String &s); ~String() { rep->decrement(); } public:
      //当完成写实拷贝,如若用户想要对数据进行修改,则需要发生深拷贝,将原类的实例引用常量-1,将新生实例的引用常量+1.
    void to_upper()
      {
            rep->decrement();
            rep=new String_rep(rep->m_data);
            rep->increment();

            char *ch = rep->m_data;
            while(*ch++ != '')
                *ch = toupper(*ch);
        }
    private: String_rep *rep; }; ostream& operator<<(ostream &out, const String &s) { out<<(s.rep)->m_data; return out; } void main() { String s("Hello"); String s1 = s; cout<<"s = "<<s<<endl; cout<<"s1 = "<<s1<<endl; s.to_upper(); //写实拷贝 cout<<"s = "<<s<<endl; cout<<"s1 = "<<s1<<endl; }

    对s进行写实拷贝,执行结果如下:

     

  • 相关阅读:
    Chapter 4 持久存储数据对象
    pyhton Chapter3 读文件
    python笔记1
    C#读写txt文件
    机器学习第一讲
    Json对象
    表单加载
    多列树
    Java 基础【11】@注解
    Java 基础【06】复合赋值运算
  • 原文地址:https://www.cnblogs.com/single-dont/p/10415838.html
Copyright © 2011-2022 走看看