• 复制构造函数与赋值函数


    构造函数、析构函数、赋值函数是每个类最基本的的函数。每个类只有一个析构函数和一个赋值函数。但是有很多构造函数(一个为复制构造函数,其他为普通构造函数。对于一个类A,如果不编写上述四个函数,c++编译器将自动为A产生四个默认的函数,即:

    • A(void)                                    //默认无参数构造函数
    • A(const A &a)                         //默认复制构造函数
    • ~A(void);                                //默认的析构函数
    • A & operator = (const A &a); //默认的赋值函数

    既然能自动生成函数,为什么还需要自定义?原因之一是“默认的复制构造函数”和"默认的赋值函数“均采用”位拷贝“而非”值拷贝“

    位拷贝  v.s.  值拷贝

    为便于说明,以自定义String类为例,先定义类,而不去实现。

    复制代码
    复制代码
    #include <iostream>
    using namespace std;
    
    class String  
    {
        public:
            String(void);
            String(const String &other);
            ~String(void);
            String & operator =(const String &other);
        private:
     
    char *m_data;
    int val; };
    复制代码
    复制代码

    位拷贝拷贝的是地址,而值拷贝拷贝的是内容。

    如果定义两个String对象a, b。当利用位拷贝时,a=b,其中的a.val=b.val;但是a.m_data=b.m_data就错了:a.m_data和b.m_data指向同一个区域。这样出现问题:

    • a.m_data原来的内存区域未释放,造成内存泄露
    • a.m_data和b.m_data指向同一块区域,任何一方改变,会影响到另一方
    • 当对象释放时,b.m_data会释放掉两次

    因此

    当类中还有指针变量时,复制构造函数和赋值函数就隐含了错误。此时需要自己定义。

    结论

    • 有一种特别常见的情况需要自己定义复制控制函数:类具有指针哈函数。
    • 赋值操作符和复制构造函数可以看成一个单元,当需要其中一个时,我们几乎也肯定需要另一个
    • 三法则:如果类需要析构函数,则它也需要赋值操作符和复制构造函数

    注意

    • 如果没定义复制构造函数(别的不管),编译器会自动生成默认复制构造函数
    • 如果定义了其他构造函数(包括复制构造函数),编译器绝不会生成默认构造函数
    • 即使自己写了析构函数,编译器也会自动生成默认析构函数

    因此此时如果写String s是错误的,因为定义了其他构造函数,就不会自动生成无参默认构造函数。

    复制构造函数  v.s.  赋值函数

    复制代码
    复制代码
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class String  
    {
        public:
            String(const char *str);
            String(const String &other);
            String & operator=(const String &other);
            ~String(void); 
        private:
            char *m_data;
    };
    
    String::String(const char *str)
    {
        cout << "自定义构造函数" << endl;
        if (str == NULL)
        {
            m_data = new char[1];
            *m_data = '';
        }
        else
        {
            int length = strlen(str);
            m_data = new char[length + 1];
            strcpy(m_data, str);
        }
    }
    
    String::String(const String &other)
    {
        cout << "自定义拷贝构造函数" << endl;
        int length = strlen(other.m_data);
        m_data = new char[length + 1];
        strcpy(m_data, other.m_data);
    }
    
    String & String::operator=(const String &other)
    {
        cout << "自定义赋值函数" << endl; 
    
        if (this == &other)
        {
            return *this;
        }
        else
        {
            delete [] m_data;
            int length = strlen(other.m_data);
            m_data = new char[length + 1];
            strcpy(m_data, other.m_data);
            return *this;
        }
    }
    
    String::~String(void)
    {
        cout << "自定义析构函数" << endl; 
        delete [] m_data;
    }
    int main()
    {
        cout << "a("abc")" << endl;
        String a("abc");
    
        cout << "b("cde")" << endl;
        String b("cde");
        
        cout << " d = a" << endl;
        String d = a;
    
        cout << "c(b)" << endl;
        String c(b);
    
        cout << "c = a" << endl;
        c = a;
    
        cout << endl;
    }
    复制代码
    复制代码

    执行结果

    说明几点

    1. 赋值函数中,上来比较 this == &other 是很必要的,因为防止自复制,这是很危险的,因为下面有delete []m_data,如果提前把m_data给释放了,指针已成野指针,再赋值就错了

    2. 赋值函数中,接着要释放掉m_data,否则就没机会了(下边又有新指向了)

    3. 拷贝构造函数是对象被创建时调用,赋值函数只能被已经存在了的对象调用

        注意:String a("hello"); String b("world");  调用自定义构造函数

                 String c=a;调用拷贝构造函数,因为c一开始不存在,最好写成String c(a);

    C++赋值运算符函数

    为类添加赋值运算符函数:

    类型定义

    复制代码
    class CMyString
    {
    public:
        CMyString(char *pData = NULL);
        CMyString(const CMyString &str);
        ~CMyString(void);
    
        CMyString &operator=(const CMyString &);
    
    private:
        char *m_pData;
    };
    复制代码

    要点:

    1、返回值类型为该类型的引用,并在函数结束前返回实例自身的引用(即 *this);

    2、是否把传入的参数声明为常量引用【const CmyString &str】;

    3、是否释放自身已有内存,否则会造成“内存泄漏”;

    4、是否判断参数与当前示例是指向的同一个对象;

    解法:

    复制代码
    /*
        适用于初级C++程序员的解法
    */
    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);
    
        //谨记:返回*this
        return *this;
    }
    
    
    
    /*
        适用于高级C++程序员的解法
    */
    CMyString &CMyString::operator=(const CMyString &str)
    {
        if (this != &str)                   //确保不指向同一个实例
        {
            CMyString strTemp(str);
    
            char *pTemp = strTemp.m_pData;  //指针指向需要更换的对象
            //strTemp.m_pData指向原来的对象,确保内存不足时可以找到原来对象的值
            strTemp.m_pData = m_pData;      
            m_pData = pTemp;                //更换原对象的值
        }   //自动调用strTemp的析构函数,销毁strTemp对象并回收pTemp的内存
    
        return *this;
    }
  • 相关阅读:
    Android中的5种数据存储方式
    Android Service
    Android BroadcastReceiver
    Android Intent
    Android Fragment
    Android 教学实验计划1
    【Android教学用例程序】计算器
    Android UI 基础知识
    Android 控件
    Android 计算器布局测试2
  • 原文地址:https://www.cnblogs.com/alantu2018/p/8459169.html
Copyright © 2020-2023  润新知