zoukankan      html  css  js  c++  java
  • 剑指offer:赋值运算符函数和复制构造函数

    赋值运算符函数

    对于定义一个赋值运算符函数时,需要注意一下几点:

    (1)函数的返回类型必须是一个引用,因为只有返回引用,才可以连续赋值

    (2)传入的参数声明为常量引用,可以提高代码效率,同时赋值运算函数内不会改变传入的实例状态

    (3)一定要记得释放实例自身已有的内存,否则程序容易出现内存泄露

    (4)注意传入的参数和当前的实例是不是同一个实例,如果是同一个,则不用进行赋值操作,直接返回即可。

    复制构造函数

    如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是复制构造函数。

    什么时候调用复制构造函数?

     (1)当用类的一个对象初始化该类的另一个对象时;

     (2)将一个对象作为实参传递给一个非引用类型的形参时;

     (3)从一个返回类型为非引用类型的函数返回一个对象时;

    深拷贝和浅拷贝的区别:

    1. 默认拷贝构造函数

        很多时候在我们都不知道拷贝构造函数的情况下,传递对象给函数参数或者函数返回对象都能很好的进行,这是因为编译器会给我们自动产生一个拷贝构造函数,这就是“默认拷贝构造函数”,这个构造函数很简单,仅仅使用“老对象”的数据成员的值对“新对象”的数据成员一一进行赋值,它一般具有以下形式:

    Rect::Rect(const Rect& r)
    {
        width = r.width;
        height = r.height;
    }

    2. 浅拷贝

        所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员,那么浅拷贝就会出问题了。

     

    当然,这不是我们所期望的结果,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。

    3. 深拷贝

        在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,上面的例子就应该按照如下的方式进行处理:

     

     

     

     

     

     

    深拷贝主要解决的问题是指针成员变量浅拷贝的问题。

    1. 防止默认拷贝(也能够禁止复制)

      有一个小技巧可以防止按值传递——声明一个私有拷贝构造函数。甚至不必去定义这个拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类对象,将得到一个编译错误,从而可以避免按值传递或返回对象。如下程序:

     

    #include <iostream>
    using namespace std;
    
    class CExample
    {
    private:
        int value;
    
    public:
        //构造函数
        CExample(int val)
        {
            value = val;
            cout << "creat: " << value << endl;
        }
    
    private:
        //拷贝构造,只是声明
        CExample(const CExample& C);
    
    public:
        ~CExample()
        {
            cout << "delete: " << value << endl;
        }
    
        void Show()
        {
            cout << value << endl;
        }
    };
    
    //全局函数
    void g_Fun(CExample C)
    {
        cout << "test" << endl;
    }
    
    int main()
    {
        CExample test(1);
        // g_Fun(test); // 按值传递将出错
    
        return 0;
    }

    而根据《C++ Primer》第四版13.1.3节,要禁止类的复制, 类必须显示声明其复制构造函数为private。

     小问题:一个类中可以有多个拷贝构造函数吗?

      解答:类中可以存在超过一个拷贝构造函数。 

    1 class X { 
    2 public:       
    3   X(const X&);      // const 的拷贝构造
    4   X(X&);            // 非const的拷贝构造
    5 };

    关于拷贝构造函数与拷贝赋值操作符的区别:

     两都都是用已存在的对象A来初始化另一个对象B。不同之处在于:

     复制构造函数是针对一个未存在的对象进行初始化;赋值是针对已存在的对象进行初始化。

    #include<iostream>
    #include<cstring>
    using namespace std;
    class CMyString
    {
        private:
            //int value;
            char *m_pdata;
        public:
            CMyString(char *pdata=NULL);
            CMyString(const CMyString &str);//复制构造函数 
            CMyString & operator = (const CMyString &str);//赋值运算符函数 
            ~CMyString(void);
            void print();
    };
    CMyString::CMyString(char *pdata)
    {
        if(pdata==NULL)
        {
            m_pdata=new char[1];
            m_pdata[0]='';
        }
        else
        {
            int len=strlen(pdata);
            m_pdata=new char[len+1];
            strcpy(m_pdata,pdata);
        }
    }
    CMyString::CMyString(const CMyString &str)//深拷贝 默认的是浅拷贝 
    {
      // cout<<"hello xiaoming"<<endl;
       int len=strlen(str.m_pdata);
       m_pdata=new char[len+1];
       strcpy(m_pdata,str.m_pdata);    
    } 
    CMyString::~CMyString()
    {
        delete []m_pdata;
    }
    CMyString& CMyString::operator =(const CMyString&str)
    {
        if(this==&str)
           return *this;
        delete []m_pdata;
        m_pdata=NULL;
        
        m_pdata=new char[strlen(str.m_pdata)+1];
        strcpy(m_pdata,str.m_pdata);
        
        return *this;
    }
    void CMyString::print()
    {
        cout<<m_pdata<<endl;
    }
    
    
    
    void test1()
    {
        cout<<"test() begin"<<endl;
        char text[]="hello world";
        CMyString str1(text);
        CMyString str2,str3;
        str3=str2=str1;//调用赋值运算操作符 
        cout<<"The expected result is: "<<text<<endl;  
        cout<<"the str2 actual result is: "<<endl;  
        str2.print();
        cout<<endl;
        cout<<"The expected result is: "<<text<<endl;  
        cout<<"the str3 actual result is: "<<endl; 
        str3.print();
        cout<<endl;
    }
    void test2()
    {
        cout<<"test4() begin"<<endl;
        char text[]="hello world";
        CMyString str1(text); //初始化操作 调用构造函数 
        CMyString str2=str1; //标准写法CMyString str2(str1); 调用复制构造函数而不是赋值 
        str2.print();
        cout<<endl;
    }
    int main()
    {
        test1();
        test2();   
        return 0;
    }

     代码

    class CMyString
    {
    public:
        CMyString(char *ptr = nullptr);
        CMyString(const CMyString &str);
        ~CMyString();
        CMyString& operator=(const CMyString& str);
    
    private:
        char *pData;
    };
    
    CMyString& CMyString::operator=(const CMyString& str)
    {
        pData = str.pData;
        return *this;
    }

    存在问题:

    这个赋值运算符重载函数存在的问题如下:

      1)浅拷贝;

      2)没有(检查)释放实例自身已有的内存。如果我们忘记在分配新内存之前释放自身已有的空间,程序将出现内存泄漏;

      3)没有判断传入的参数和当前的实例(*this)是不是同一个实例。如果是同一个,则不进行复制操作,直接返回。如果事先不判断就进行赋值,那么在释放实例自身的内存的时候就会导致严重问题:当*this和传入的参数是同一个实例时,那么一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。

      修改之后的赋值运算符重载函数如下:

    CMyString& CMyString::operator=(const CMyString& str)
    {
        if (this == &str)
            return *this;
    
        delete []pData;
        pData = nullptr;
    
        pData = new char[strlen(str.pData) + 1];
        strcpy(pData, str.pData);
    
        return *this;
    }

    上述代码现在的问题在于4)异常安全性,即new可能会抛出异常,而我们却没有处理!所以我们可以将程序继续修改:

    CMyString& CMyString::operator=(const CMyString& str)
    {
        if (this == &str)
            return *this;
    
        char *tmp = new(nothrow) char[strlen(str.pData) + 1];
        if (tmp == nullptr)
            return *this;
    
        strcpy(tmp, str.pData);
    
        delete []pData;
        pData = tmp;
        tmp = nullptr;
        
        return *this;
    }

    除了前边提到的4个点,赋值运算符重载还有两点需要注意:

      5)是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(即*this)。只有返回一个引用,才可以允许连续赋值。否则如果函数的返回值是void,应用该赋值将不能做连续赋值。假设有3个CMyString对象:str1、str2和str3,在程序中语句str1=str2=str3将不能通过编译。

      6)是否把传入的参数的类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参会调用一次拷贝构造函数。把参数声明为引用可以避免这样的无谓消耗,从而提高代码效率。同时,我们在赋值运算符函数内不会修改传入的实例的状态,因此应该为传入的引用参数加上const关键字。

  • 相关阅读:
    Django学习2
    Django学习1
    python 基于tcp协议的文件传输3_解决粘包问题
    python socketserver
    python hashlib
    python struct模块
    python json 模块
    python socket模块
    13暑假集训#10 总结
    hdu 4493 卡输入输出
  • 原文地址:https://www.cnblogs.com/wft1990/p/6172222.html
Copyright © 2011-2022 走看看