• 拷贝控制之拷贝、赋值、销毁


    拷贝控制操作:拷贝构造函数 拷贝赋值运算符 移动构造函数 移动赋值运算符 析构函数

    如果一个类没有定义所有这些拷贝控制成员,编译器会自动为它定义缺失的操作。

    一、拷贝构造函数

    定义:一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值(拷贝构造函数第一个参数必须是引用类型,而且一般是const引用)

    原因:拷贝构造函数被用来初始化非引用类型参数,如果其参数不是引用类型,为了调用拷贝构造函数,我们必须拷贝它的实参,无限循环。

    使用拷贝初始化的场合

    1.将一个对象作为实参传递给一个非引用类型的形参(对象以值传递的方式传入函数参数)

    2.从一个返回类型为非引用类型的函数返回一个对象(对象以指传递的方式从函数返回)

    3.对象需要另外一个对象初始化

    如果两个对象已经存在,就会使用拷贝赋值运算符,如果是使用一个对象去创建(注意是创建)另一个不存在的对象,则会使用拷贝构造函数。

    拷贝运算(对象在声明的同时马上进行初始化操作):class1 A("af"); class1 B=A;        赋值运算:class1 A("af"); class1 B;B=A;

    合成的拷贝构造函数会将其参数成员逐个拷贝到正在创建的对象中,编译器从给定对象中依次将每个非static成员拷贝到正在创建的对象中(浅拷贝)。(因为static成员属于类,不属于对象)

    class A{
    public:
        A(){ sn++; }
        ~A(){ sn--; }
        static int GetValue(){
            return sn;
        }
    private:
        static int sn;
    };
    int A::sn = 0;
    int main(void)
    {
        A a1;//调用构造函数,sn++
        cout << "1:" << A::GetValue() << endl;
        A a2(a1);//调用合成拷贝构造函数,对sn无操作,因为sn不属于对象
        cout << "2:" << A::GetValue() << endl;
        system("pause");
        return 0;
    }

    输出均为1,注意析构的时候sn会减两次,变为-1

    二、拷贝赋值运算符

    如果赋值操作符可以作为全局函数重载的话,可能会出现表达错误的语句

    int operator=(int a, integer b);
    这样重载之后,语句
    2 = a; 表述也是正确的,但是却是明显的语法错误
    为了避免此类错误,需要将赋值操作符重载为成员函数
    --------------------------------------------------
    首先要知道,如果类中没有重载赋值操作符时,类会自动生成一个默认的赋值操作符。例如,有两个同类对象A和B,当你没有将赋值操作符重载,而进行 A=B 的操作时,编译器会自动调用赋值操作将B的数据成员拷贝到A中。
    而如果你重载了一个全局的赋值操作符,那么编译器不知道是否还需要再自己合成一个赋值操作符,从而引发歧义。

    重载运算符的参数表示运算符的运算对象,某些运算符,包括赋值运算符,必须定义为成员函数,如果一个运算符是一个成员函数,其左侧运算对象就绑定到隐式的this参数

    拷贝赋值运算符接受一个与其所在类相同类型的参数(引用):

    class foo
    {
    public:
        foo& operator=(const foo&);
        //...
    };

    为了与内置类型的赋值保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用(return *this),目的是进行链式赋值(a=b=c).

    class A
    {
    public:
    
        A(){}
        A(int id, char *t_name)
        {
            _id = id;
            name = new char[strlen(t_name) + 1];
            strcpy(name, t_name);
        }
        //重载copy构造函数,深拷贝
        A::A(A &a)
        {
            _id = a._id;
            name = new char[strlen(a.name) + 1];
            if (name != NULL)
                strcpy(name, a.name);
        }
        //如果直接返回对象,而不是引用,则会调用拷贝构造函数
        A& operator =(A& a)
        {
            if (this == &a)//  问:什么需要判断这个条件?(不是必须,只是优化而已)。答案:提示:考虑a=a这样的操作。
                return *this;
            //首先释放自己的内存,避免内存泄漏
            if (name != NULL)
                delete name;
            this->_id = a._id;
            int len = strlen(a.name);
            name = new char[len + 1];
            strcpy(name, a.name);
            return *this;
        }
        ~A()
        {
            cout << "~destructor" << endl;
            delete name;
        }
    
        int _id;
        char *name;
    };

    三、析构函数

    由于析构函数不接受参数,因此它不能被重载,对一个给定类,只会有唯一一个析构函数。

    析构函数完成的工作:在一个构造函数中,成员的初始化是在函数体执行之前完成的,且按照它们在类中出现的顺序进行初始化,在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序的逆序销毁

    四、三五法则

    1.需要析构函数的类也需要拷贝和赋值操作

    合成析构函数不会delete一个指针数据成员,故在构造函数中分配动态内存后需要定义一个析构函数来释放内存。合成的拷贝和赋值操作只是简单的拷贝指针成员,这样可能会出现多个对象拥有同一个指针,当它们销毁时同一个指针会被delete多次,出现未定义错误。

    2.需要拷贝操作的类也需要赋值操作,反之亦然。

    五、阻止拷贝

    1.定义删除的函数

    在新标准下,我们可以通过将拷贝构造函数和拷贝赋值运算符定义为delete function来阻止拷贝delete function是指我们虽然声明了它们,但不能以任何方式使用它们。

    =delete必须出现在函数第一次声明的时候,因为编译器需要知道一个函数是删除的,以便禁止试图使用它的操作。

    注意:我们不能删除析构函数,对于一个删除了析构函数的类型,编译器将不允许定义该类型的变量或创建该类型的临时对象,但可以动态分配这种类型的对象,不能释放这些对象

    struct NoDtor
    {
        NoDtor() = default;//使用合成默认构造函数
        ~NoDtor() = delete;
    };
    
    NoDtor nd;//错误
    NoDtor *p = new NoDtor();//动态分配,准确
    delete p;//错误,编译阶段就出错

    2.private拷贝控制

    在新标准发布之前,类是通过将其拷贝构造函数和拷贝赋值运算符声明为private的来阻止拷贝,为了阻止友元和成员函数进行拷贝,我们将这些拷贝控制成员声明为private的,但是不定义它们。(正常情况下,友元可以访问private成员)

    声明但不定义一个成员函数是合法的,试图访问一个未定义的成员将导致一个链接错误。试图拷贝对象的用户代码将在编译阶段被标记为错误(因为不能访问private成员),成员函数或友元函数中的拷贝操作将会导致链接时错误。

  • 相关阅读:
    数据结构与算法-基础(七)完全二叉树
    数据结构与算法-基础(六)二叉树基础
    数据结构与算法-基础(五)队列(Qeque)
    数据结构与算法-基础(四)栈(Stack)
    数据结构与算法-基础(三)- 循环链表(补充)
    数据结构与算法-基础(二)单向链表
    数据结构与算法-基础(一)动态数组
    Swift-Button 的 highlighted(高亮)
    Android现有工程使用Compose
    Android Jetpack Compose 引入示例工程
  • 原文地址:https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/3819769.html
Copyright © 2020-2023  润新知