• C++ 拷贝构造函数


    引入

    对于普通类型的对象来说,他们之间的复制很简单:

    int a = 10;
    int b = a;

    但是对于类对象来说,其中会存在许多的成员变量。

    #include 
    using namespace std;
    
    class CExample {
    private:
         int a;
    public:
          //构造函数
         CExample(int b)
         { a = b;}
    
          //一般函数
         void Show ()
         {
            cout<<a<<endl;
          }
    };
    
    int main()
    {
         CExample A(100);
         CExample B = A; //注意这里的对象初始化要调用拷贝构造函数,而非赋值
          B.Show ();
         return 0;
    }
    

    从以上代码可以看出系统为对象 B 分配了内存并完成了与对象 A 的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。

    下面这个则是拷贝构造函数的工作过程

    #include 
    using namespace std;
    
    class CExample {
    private:
        int a;
    public:
        //构造函数
        CExample(int b)
        { a = b;}
        
        //拷贝构造函数
        CExample(const CExample& C)
        {
            a = C.a;
        }
    
        //一般函数
        void Show ()
        {
            cout<<a<<endl;
        }
    };
    
    int main()
    {
        CExample A(100);
        CExample B = A; // CExample B(A); 也是一样的
         B.Show ();
        return 0;
    } 
    

    在这里CExample(const CExample& C) 就是我们自定义的拷贝构造函数。

    一.什么是拷贝构造函数?

    同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或称拷贝是完全可行的。这个拷贝过程只需要拷贝数据成员,而函数成员是共用的(只有一份拷贝)。在建立对象时可用同一类的另一个对象来初始化该对象的存储空间,这时所用的构造函数称为拷贝构造函数。

    拷贝构造函数本质上来说也是构造函数。

    二.什么情况下使用拷贝构造函数?

    一般来说有以下三种情况:

    1. 用旧对象去初始化新对象
    2. 值传递—参数是类类型的值类型,从实参传递给形参的过程,是用实参去构造形参
    3. 函数返回值是值类型–用局部对象去构造临时对象调用拷贝构造
    class A
    {
    public:
    	A(int i = 0):m_i(i)
    	{
    		cout<<"A(int) "<<m_i<<endl;
    	}
    	A(const A &a):m_i(a.m_i)
    	{
    		cout<<"A(A) "<<m_i<<endl;
    	}
    	~A()
    	{
    		cout<<"~A "<<m_i<<endl;
    	}
    private:
    	int m_i;
    };
    void fn(A t) //2.将c传递给t的过程,是值传递,此时临时对象形参t是新对象,用c去构造t,调用拷贝构造
    {
    	cout<<"fn end"<<endl;
    	//在退出fn函数时,将临时对象t释放,调用析构函数
    }
    A test()
    {
    	A d(40); //调用普通构造A(int)构造对象d
    	return d;
    	/*
    	 3.在返回的时候,将局部对象的值给了临时对象(值传递,调用拷贝构造,用旧局部对象去构造新的临时对象)
    	 然后局部对象d就释放了,临时对象将值带回到主调函数中后,临时对象的值才释放
    	*/
    }
    A fnn()
    {
    	A s(50); //A(int) 50
    	return s; //
    	/*1.s->临时对象  拷贝构造
    	2.析构函数局部对象s
    	*/
    }
    void main()
    {
    	A a(20);  //A(int) 20
    	A b = a;  //1.用a初始化b A(A)
    	cout<<"fn"<<endl;
    	A c(30); //A(int) 30
    	fn(c);
    	cout<<"test"<<endl;
    	c = test(); //临时对象将值赋给c(调用赋值函数)之后,调用析构,析构临时对象
    	cout<<"fnn"<<endl;
    	A t = fnn(); //临时对象初始化新对象t,是否会调用拷贝构造?--调用了,不过编译器做过优化
    	cout<<"main end"<<endl;
    }
    

    三.使用拷贝构造函数需要注意什么?

    1. 拷贝构造函数是构造函数的一个重载形式
    2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
    3. 若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,称为:位拷贝

    四.深拷贝浅拷贝

    4.1 浅拷贝

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

    对于下面函数来说有指针作为数据成员,则用s1对象去构造s2对象的时候,调用默认拷贝构造,用s1中的数据成员指针m_str去初始化s2对象中的数据成员m_str,即是s2.m_str = s1.m_str,那么导致两个对象中的指针指向同一块内存单元,指向的都是构造s1对象时开辟的内存单元,所以在主函数退出时候要析构s2和s1时,将同一段空间释放两次出现内存错误。

    class Str
    {
    public:
    	Str(const char *str = "")
    	{
    		m_str = new char[strlen(str)+1];
    		strcpy(m_str,str);
    	}
    	~Str()
    	{
    		delete[]m_str;
    	}
    	void Print()
    	{
    		cout<<m_str<<endl;
    	}
    private:
    	char *m_str;
    };
    void main()
    {
    	Str s1("pangpang");
    	Str s2(s1);
    	cout<<sizeof(Str)<<endl;
    	s1.Print();
    	s2.Print();
    }
    

    上述程序的内存布局:
    在这里插入图片描述
    对于指针作为数据成员的类,用s1对象去构造s2对象的时候,调用默认拷贝构造函数时,二者指向同一内存单元,即二者的初始地址相同这里均为0X0002000,当我们构造完以后将要进行析构时,这里将会出现错误:因为析构函数要释放空间,而这里我们的空间对应的是一块空间,当我们析构完s2后:这一块空间的内容已经被delete,而我们还需要析构s1,即:一个内存空间析构了两次,出现内存错误。

    为了解决上述问题 我们就需要给s2中的m_str也开辟和s1中的m_str一样大小的空间,所以我们就需要 深拷贝

    4.2 深拷贝

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

    class Str
    {
    public:
    	Str(const char *str = "")
    	{
    		m_str = new char[strlen(str)+1];
    		strcpy(m_str,str);
    	}
    	~Str()
    	{
    		cout<<"~Str"<<endl;
    		delete[]m_str;
    	}
    	Str(const Str& s)
    	{
    		cout<<"Str(Str)"<<endl;
    		m_str = new char[strlen(s.m_str)+1]; //开辟同样大小的空间
    		strcpy(m_str,s.m_str); //将内容拷贝成一样的
    	}
    	void Print()
    	{
    		cout<<m_str<<endl;
    	}
    private:
    	char *m_str;
    };
    void main()
    {
    	Str s1("pangpang");
    	Str s2(s1); //拷贝构造
    	s1.Print();
    	s2.Print();
    }
    

    现在的程序内存布局为:
    在这里插入图片描述
    各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

  • 相关阅读:
    个人开发者轻松接入支付回调
    Vue实现日期选择器
    c语言入门——操作符
    基于Python+Django深度学习的身份证识别考勤系统设计与实现
    基于深度学习的车辆检测系统(MATLAB代码,含GUI界面)
    安装vllm的时候卡主:Collecting vllm-nccl-cu12<2.19,>=2.18 (from vllm)
    Excel 从网站获取表格
    【老生谈算法】matlab实现LMS算法的自适应滤波算法源码——自适应滤波
    【云IDE】CSDN云IDE的初探以及实战操作
    Protobuf: 高效数据传输的秘密武器
  • 原文地址:https://blog.csdn.net/weixin_56935264/article/details/126941151