• C++ 类的复制控制


    写了又删,删了又写,才发现这一章节不好描述。

    那就假定个前提吧,假定已经知道:

    ① C++的类有构造函数。
    
    ② 如果不提供任何构造函数,那编译器会生成默认的无参构造函数--默认构造函数只会进行成员变量的初始化。
    
    ③ 如果提供了任何一个构造函数,那编译器就不会再生成默认的无参构造函数。
    
    ④ 函数的形参都是实参的副本(引用类型除外)。
    
    ⑤ 构造函数也是函数。

    ⑥ 直接初始化是在括号()中,复制初始化则使用=赋值操作符---注意,是定义不是赋值

    针对⑥,Person p2=p1;这是复制初始化! p2=p3;这才是赋值!!!

    在此基础上,稍作推理

    一、如果构造函数是单形参、且形参类型为该类的类型的构造函数:

      以 class Person 为例,

    string name="anonymous";
    int age=18;
    Person p1(name, age);
    Person p2(p1); //note this

      根据上面前提的④,p2 需要 p1 的一个副本(复制一个),这时就出现问题了:该怎么复制

      这就是复制构造函数的作用了--用于指明如何复制。

      再推理一下,如果不能使用副本,那还能使用什么?必须是引用啊(不要说指针,囧)!所以复制构造函数就是单形参、且形参类型为该类类型的引用 (常const修饰)的构造函数。

    class Person{
        public:
            Person(const Person&); //copy-constructor
            ....
    };

      需要说明的是,编译器会自动生成一个默认的复制构造函数,原则就是一一复制成员变量。这里面又存在一个问题,如果存在指针类型的成员变量,那么与其他成员变量不同的是,复制后的指针仍然指向同一个地址!这可能会导致很严重的bug。如下:

    #include <iostream>
    #include <string>
    
    using namespace std;
    
    //注意,有指针成员,但没有自定义复制构造函数。
    //这时,其他成员复制时都是副本,而指针成员却指向同一个地址。
    class HasPtr{
    private:
        int *ptr;
        int val;
    
    public:
        HasPtr(int *p, int i):ptr(p), val(i){}
    
        int *get_ptr() const { return ptr; }
        void set_ptr(int *p){ ptr=p; }
    
        int get_int() const { return val; }
        void set_int(int i){ val=i; }
        
        int get_ptr_val() const { return *ptr; }
        void set_ptr_val(int i) const { *ptr = i; } //why not take a ref?
    };
    
    int main(){
        int obj=10;
    
        HasPtr ptr1(&obj, 42);
        HasPtr ptr2(ptr1); // copy-constructor
    
        cout<<ptr1.get_ptr()<<"--"<<ptr1.get_ptr_val()<<endl;
        cout<<ptr2.get_ptr()<<"--"<<ptr2.get_ptr_val()<<endl;
    
        ptr1.set_int(3);
        ptr2.set_int(5);
    
        cout<<ptr1.get_int()<<"--"<<ptr2.get_int()<<endl;
    
        ptr1.set_ptr_val(7);
        cout<<ptr1.get_ptr()<<"--"<<ptr1.get_ptr_val()<<endl;
        cout<<ptr2.get_ptr()<<"--"<<ptr2.get_ptr_val()<<endl;
    
        cout<<"-----------"<<endl;
    
        int *ip=new int(42);
        HasPtr ptr(ip, 2);
        delete ip;
        ptr.set_ptr_val(3);//OOPS! error
        
    
        return 0;
    }
    View Code

      上面代码充分说明了有指针类型的成员变量时,类的对象的行为变得诡异。

      此外,还有一个小知识点,虽然数组不能支持复制,但是复制构造函数中却可以一一复制数组的元素,从而做到复制数组。

      

      推理一下,如果将复制构造函数设为private或者explicit会怎样?

    二、复制初始化和赋值操作不是同一个概念

      上面这个前提必须搞清楚,因为二者是不同的行为!

    Person p1("Henry", 20);
    Person p2 = p1; //copy-constructor 复制初始化
    Person p3("Jean", 22); 
    p3 = p2; // OK, it's assignment!!! 

      上面的代码,其执行过程分别是:调用构造函数创建p1;调用复制构造函数创建p2;调用构造函数创建p3;调用赋值操作符函数,给p3重新赋值!!!

      这里还需要另外一个前提:“赋值”和“=”不是一回事

      对于C++来说,所有的操作符都是定义出来的,就是说,具体的行为是被定义好的!

      同理,对类来说,这些操作符(+-*/等)也是被定义出来的。--定义操作符的函数叫做操作符函数,这个过程叫做重载操作符。

      例如:

    int val = 3;  
    int val2; // 默认初始化或者声明,看所在作用域

    val2 = val; // 赋值操作

      事实上,你完全可以定义自己的操作符函数,例如混乱一点的,将+作为赋值操作~ 只是没有必要罢了。

      这里不再介绍重载操作符了,有兴趣的新人可以查一下相关的资料。

      

      下面重点说一下赋值操作符。

      编译器也会生成默认的赋值操作符,默认执行一一赋值,并返回对左操作数的引用。

      一般来说,可以使用默认复制构造函数的类,也可以使用默认赋值操作符

      对于类类型对象来说,直接初始化直接调用相应的构造函数;而复制初始化则先调用相应构造函数构造一个临时对象,再调用复制构造函数将临时对象复制到正在创建的对象。

      如下:

    string s1="abc"; //先是string tmp(const char*); 再s1(&tmp);
    string s2=string(); //string()已经是string对象,所以直接调用s2(&string())

      注意上面的代码,tmp 是临时对象,s1(&tmp) 则是调用复制构造函数!

      

    三、所有对象都是有生命周期的,一般生命周期仅限于其作用域范围内---手动释放的除外。

      C++能够指定当一个对象走向死亡的时候所进行的操作,这就是析构函数。--这个名字如果看英文就知道,和构造函数是相反的,constructor - destructor,一个创建,一个拆毁。

      所以,顾名思义,析构函数应该是定义了如何销毁成员变量,虽然还可以定义其他的操作(如释放资源),但那不是我们关注的重点。

      

      编译器总是会生成一个默认的析构函数,以成员变量声明顺序的逆序进行销毁,销毁每一个非static成员。

      且,总是最后调用默认的析构函数。哪怕已经调用过自定义的析构函数!

      需要注意的是,对于类的对象来说,①当超出作用域时,系统会自动调用析构函数。②当主动释放内存时,也会自动调用析构函数。

      这点对于Javaer来说就是一个坑,极容易被带入坑中。例如 :

    Person *p=new Person; // no ()
    {
        Person p2(*p);
        delete p;
    }

      上面,超出花括号时,系统就会自动调用p2的析构函数,销毁p2! -- 注意,引用和指针不会。

      另外,撤销一个容器(包括数组)时,也会一一调用其元素的析构函数,从最后一个开始撤销。

      默认的析构函数并不会删除指针类型成员所指向的对象!

    一个规则:如果类需要析构函数,则它也需要赋值操作符和复制构造函数!

  • 相关阅读:
    java学习中,instanceof 关键字 和 final 关键字、值的传递(java 学习中的小记录)
    java学习中,DVD管理系统纯代码(java 学习中的小记录)
    java学习中,static 修饰符使用、static方法、静态代码块、主函数为何如此写、单例设计模式(java 学习中的小记录)
    java学习中,匿名函数、构造方法、构造代码块、构造方法中调用构造方法(java 学习中的小记录)
    java学习中,成员变量 和 局部变量(java 学习中的小记录)
    Java 面向对象的解释 和 类与对象方法的创建使用 (Java 学习中的小记录)
    java学习中,二分法查找数组中的元素(java 学习中的小记录)
    java学习中,冒泡排序(java 学习中的小记录)
    Kotlin语法 入门篇章(1)
    gitlab
  • 原文地址:https://www.cnblogs.com/larryzeal/p/5624695.html
Copyright © 2020-2023  润新知