美文网首页
(二十)C++篇-自定义类(五)-复制构造函数

(二十)C++篇-自定义类(五)-复制构造函数

作者: GoodTekken | 来源:发表于2022-07-05 14:32 被阅读0次

    复制构造函数是一种特殊构造函数,具有单个形参,该形参(常用 const 修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。当将该类型的对象传递给函数或函数返回该类型的对象时,将隐式使用复制构造函数。

    析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。析构函数可用于释放对象时构造或在对象的生命期中所获取的资源。不管类是否定义了自己的析构函数,编译器都自动执行类中非static 数据成员的析构函数。

    复制构造函数、赋值操作符和析构函数总称为复制控制。编译器自动实现这些操作,但类也可以定义自己的版本。

    通常,编译器合成的复制控制函数是非常精练的——它们只做必需的工作。但对某些类而言, 依赖于默认定义会导致灾难。 实现复制控制操作最困难的部分,往往在于识别何时需要覆盖默认版本。 有一种特别常见的情况需要类定义自己的复制控制成员的:类具有指针成员。

    复制构造函数
    只有单个形参,而且该形参是对本类类型对象的引用(常用 const 修饰),这样的构造函数称为复制构造函数。与默认构造函数一样,复制构造函数可由编译器隐式调用。复制构造函数可用于:
    • 根据另一个同类型的对象显式或隐式初始化一个对象。
    • 复制一个对象,将它作为实参传给一个函数。
    • 从函数返回时复制一个对象。
    • 初始化顺序容器中的元素。
    • 根据元素初始化式列表初始化数组元素。

    禁止复制
    为了防止复制,类必须显式声明其复制构造函数为 private

    如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。
    然而,类的友元和成员仍可以进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个(private)复制构造函数但不对其定义。

    赋值操作符
    重载操作符是一些函数,其名字为 operator 后跟着所定义的操作符的符号。因此,通过定义名为 operator= 的函数,我们可以对赋值进行定义。像任何其他函数一样,操作符函数有一个返回值和一个形参表。形参表必须具有与该操作符数目相同的形参(如果操作符是一个类成员,则包括隐式 this 形参)。赋值是二元运算,所以该操作符函数有两个形参:第一个形参对应着左操作数,第二个形参对应右操作数。
    大多数操作符可以定义为成员函数或非成员函数。当操作符为成员函数时,它的第一个操作数隐式绑定到 this 指针。有些操作符(包括赋值操作符)必须是定义自己的类的成员。因为赋值必须是类的成员,所以 this 绑定到指向左操作数的指针。因此,赋值操作符接受单个形参,且该形参是同一类类型的对象。右操作数一般作为 const 引用传递。

    一般而言,如果类需要复制构造函数,它也会需要赋值操作符。
    实际上,就将这两个操作符看作一个单元。如果需要其中一个,我们几乎也肯定需要另一个。

    析构函数
    构造函数的一个用途是自动获取资源。例如,构造函数可以分配一个缓冲区或打开一个文件,在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源。析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充。

    动态分配的对象只有在指向该对象的指针被删除时才撤销。如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放。

    当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。

    何时编写显式析构函数
    许多类不需要显式析构函数,尤其是具有构造函数的类不一定需要定义自己的析构函数。仅在有些工作需要析构函数完成时,才需要析构函数。析构函数通常用于释放在构造函数或在对象生命期内获取的资源

    如果类需要析构函数,则它也需要赋值操作符和复制构造函数,这是一个有用的经验法则。这个规则常称为三法则,指的是如果需要析构函数,则需要所有这三个复制控制成员。

    测试代码如下:

    #include <vector>
    #include <iostream>
    
    struct Exmp1
    {
        //默认构造函数
        Exmp1(){std::cout<<"Exmp1()"<<std::endl;}
        
        //复制构造函数
        Exmp1(const Exmp1&)
        {std::cout<<"Exmp1(const Exmp1&)"<<std::endl;}
        
        //赋值操作符
        Exmp1& operator = (const Exmp1&abc)
        {
            std::cout<<"Operator:(const Exmp1&)"<<std::endl;
            return *this;
        }
        
        //析构函数
        ~Exmp1(){std::cout<<"~Exmp1()"<<std::endl;}
    };
    
    void func1(Exmp1 obj)  //形参为Exmp1对象
    {
    }
    
    void func2(Exmp1& obj)  //形参为Exmp1对象的引用
    {
    }
    
    Exmp1 func3()
    {
        Exmp1 obj;
        return obj;  //返回Exmp1对象
    }
    
    int main()
    {
        Exmp1 eobj;  //调用默认构造函数创建Exmp1对象eobj
        func1(eobj); //调用复制构造函数
                //将形参Exmp1对象创建为实参Exmp1对象的副本
                //函数执行完毕后调用析构函数撤销形参Exmp1对象
        func2(eobj);  //形参为Exmp1对象的引用,无需调用复制构造函数传递实参
        
        eobj = func3();//调用默认构造函数创建局部Exmp1对象
                //函数返回时调用复制构造函数创建作为返回值副本的Exmp1对象
                //然后调用析构函数撤销局部Exmp1对象
                //然后调用赋值操作符
                //执行完赋值操作符
                //调用析构函数撤销作为返回值副本的Exmp1对象
                
        Exmp1 *p = new Exmp1;//调用默认构造函数动态创建Exmp1对象
        std::vector<Exmp1> evec(3);//调用3次默认构造函数
                //然后调用3次析构函数撤销局部Exmp1对象
        delete p;//调用析构函数撤销动态创建的Exmp1对象
        return 0;
    }
    

    测试结果:

    tekken@tekken:~/C++WS$ ./a.out 
    Exmp1()
    Exmp1(const Exmp1&)
    ~Exmp1()
    Exmp1()
    Operator:(const Exmp1&)
    ~Exmp1()
    Exmp1()
    Exmp1()
    Exmp1()
    Exmp1()
    ~Exmp1()
    ~Exmp1()
    ~Exmp1()
    ~Exmp1()
    ~Exmp1()
    

    相关文章

      网友评论

          本文标题:(二十)C++篇-自定义类(五)-复制构造函数

          本文链接:https://www.haomeiwen.com/subject/qrssbrtx.html