C++中的复制构造函数以及浅复制和深复制的区别

近期在学C++的过程中,看到了复制拷贝函数,第一遍看过去有些许迷糊,然后网上找了很多资料来学习,自己也敲了一些代码来验证,现将学习成果总结一下,和各位一起交流学习。

一、什么是复制构造函数

       一种特殊的重载构造函数,他的形式很固定:[类名]::[构造函数名](const [类名] &[参数]);  (参数是本类型的一个引用变量

      实际代码:MyString::MyString(const MyString &CopySource);

二、什么情况下会用到复制构造函数

     复制构造函数英文copy constructior,顾名思义在类的对象间发生拷贝时会调用,下面我们分情况讨论: 先贴出代码

#include<iostream>
using namespace std;
class Example
{
private:
    int a;
public:
    Example(int b)                                //构造函数
    {
        cout << "constructor is called" << endl;
        a = b;
    }
    Example(const Example & c)       //复制构造函数 (注意规定格式)  
    {
        cout << "copy constructor is called" << endl;
        a = c.a;
        
    }
    ~Example()                                   //析构函数
    {
        cout << "destructor is called" << endl;
    }
    void Show()
    {
        cout << a << endl;
    }
};
void g_fun(Example c)
{
    cout << "g_func" << endl;
}

Example r_fun()
{
    Example temp(0);
    return temp;
}
int main()
{
    Example A(100);
    Example B = A;
    B.Show();
    g_fun(A);
    r_fun();

    system("pause");
    return 0;
}

分析代码我们可以发现,复制构造函数被调用三次,涉及类的对象拷贝出现了三次,分别是;

(1)一个对象通过拷贝另一个以及初始化好了的对象来初始化自己

    Example A(100);
    Example B = A;

(2)将实参A拷贝给形参c,然后调用g_fun()

void g_fun(Example c)
{
    cout << "g_func" << endl;
}

 g_fun(A);

(3)函数的返回值类型是类的对象,实际调用过程是,函数内先创建一个temp对象,再把这个对象拷贝给返回的对象

Example r_fun()
{
    Example temp(0);
    return temp;
}

 r_fun();

到这里,我们基本清楚了,复制构造函数什么时候会被用到,但是我们在实际码代码的过程或者看代码的时候,出现类的地方并不是都有复制构造函数,是因为用户未定义复制构造函数,编译系统则会调用默认的复制构造函数。一般情况问题也不大,但是在下面这种情况下问题就大了,先贴出代码:

#include <iostream>  
using namespace std;
 
class Student
{
private:
    int num;
    char *name;
public:
    Student();
    ~Student();
};
 
Student::Student()
{
    name = new char(20);
    cout << "Student" << endl;
 
}
Student::~Student()
{
    cout << "~Student " << (int)name << endl;
    delete name;
    name = NULL;
}
 
int main()
{
    {// 花括号让s1和s2变成局部对象,方便测试
        Student s1;
        Student s2(s1);// 复制对象
    }
    system("pause");
    return 0;
}
运行结果如下

       由结果,我们可以分析:调用一次构造函数,调用两次析构函数,两个对象的指针成员所指内存相同,这会导致严重的问题,name指针被分配一次内存,但是程序结束时该内存却被释放了两次,会导致程序崩溃!下面进一步分析这个问题。

三、复制构造函数中的浅复制和深复制

      接着讨论上面程序运行结果,这是由于编译系统在我们没有自己定义复制构造函数时,会在复制对象时调用默认复制构造函数,进行的是浅复制!即对指针name复制后后会出现两个指针指向同一个内存空间。

官方概念->浅复制:复制一个类的对象时,将复制他的指针成员,但不复制指针指向的缓冲区,结果是,两个对象指向同一块动态分配的内存。这就叫浅复制,会威胁程序的稳定性。

 

       所以,在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,使拷贝后的对象指针成员有自己的内存空间,即进行深拷贝,这样就避免了内存泄漏发生。故添加构造函数如下:

#include <iostream>  
using namespace std;
 
class Student
{
private:
    int num;
    char *name;
public:
    Student();
    ~Student();
    Student(const Student &s);//拷贝构造函数,const防止对象被改变
};
 
Student::Student()
{
    name = new char(20);
    cout << "Student" << endl;
 
}
Student::~Student()
{
    cout << "~Student " << (int)name << endl;
    delete name;
    name = NULL;
}
Student::Student(const Student &s)
{
    name = new char(20);
    memcpy(name, s.name, strlen(s.name));
    cout << "copy Student" << endl;
}
 
int main()
{
    {// 花括号让s1和s2变成局部对象,方便测试
        Student s1;
        Student s2(s1);// 复制对象
    }
    system("pause");
    return 0;
}
 

运行结果如下:

        可分析到:调用一次构造函数,一次自定义拷贝构造函数,两次析构函数。两个对象的指针成员所指内存不同。这里我们叫做深复制,而并非浅复制(复制指针的值),即将指向的内容复制给当前对象新分配的缓冲区中。

四、在实际码代码中我们要注意什么?

       1、类包含原始指针成员(如char*)时,一定要编写复制构造函数和复制赋值运算符;

       2、写复制构造函数时,要将接受源对象的参数声明为const引用;

       3、将成员变量声明为std::string和智能指针。

五、面试可能碰到的问题:(参考:https://www.cnblogs.com/wuchanming/p/4050969.html

 

       Q1:构造函数能否重载,析构函数能否重载,为什么?
       A1:构造函数可以,析构函数不可以。

      Q2:析构函数为什么一般情况下要声明为虚函数?

       A2:虚函数是实现多态的基础,当我们通过基类的指针是析构子类对象时候,如果不定义成虚函数,那只调用基类的析构函                 数,子类的析构函数将不会被调用。如       果定义为虚函数,则子类父类的析构函数都会被调用。

       Q3:什么情况下必须定义拷贝构造函数?

       A3:当类的对象用于函数值传递时(值参数,返回类对象),拷贝构造函数会被调用。如果对象复制并非简单的值拷贝,那                 就必须定义拷贝构造函数。例如大的堆       栈数据拷贝。如果定义了拷贝构造函数,那也必须重载赋值操作符。

 

参考博客:

https://blog.csdn.net/caoshangpa/article/details/79226270

https://www.cnblogs.com/wuchanming/p/4050969.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值