菜鸟笔记
提升您的技术认知

拷贝构造函数总结-ag真人游戏

拷贝构造函数用于将一个对象复制到一个新创建的对象中。也就是说,它用在初始化过程中,而不是常规的赋值操作中。类的拷贝构造函数的原型如下:

class_name(const class_name&);

这个构造函数接受一个指向类对象的常量引用作为参数。如果没有定义,c 会提供一个默认的拷贝构造函数,不过这个默认的拷贝构造函数有一些坑需要注意(见后面的例子)。编译器新建一个对象,并将其初始化为同类的现有对象时,都会调用拷贝构造函数。最常见的情况就是将新对象显式地初始化为现有的对象。例如,假如有一个定义好的类man和它的一个实例a,那么下面四种声明都将调用拷贝构造函数。

man b(a);
man b = a;
man b = man(a);
man* b = new man(a);

另外,每当程序生成对象副本时,编译器也会使用拷贝构造函数。具体来说,就是当函数按值传递对象或者函数返回对象时,拷贝构造函数会被调用。下面看几个例子:

例1

#include
#include
using namespace std;
class man {
    char* name;
    int age;
    public:
        man(char* name, int age);
        man();
        void show();
};
man::man(char* name, int age)
{
    cout<<"call self-def constructor"<name = new char[strlen(name)   1];
    strcpy(this->name, name);
    this->age = age;
}
man::man()
{
    cout<<"call default constructor"<name = new char[8];
    strcpy(this->name, "unknown");
    age = -1;
}
void man::show()
{
    cout<<"name:"<

程序很简单,不解释,直接看运行结果:

程序定义了4个man对象。a使用默认构造函数,b拷贝了a的内容,c使用带参数的构造函数,d拷贝了c的内容。可以看到,a和b的成员变量值是一模一样的,c和d的成员变量值也是一模一样的。a的name指针和b的name指向同一片内存地址。c,d同理。
这就是默认拷贝构造函数的作用,默认拷贝构造函数逐个复制非静态成员(成员复制也称为浅拷贝),复制的是成员的值

对于含有指针成员的类来说,c 提供的默认拷贝构造函数是存在问题的。如上例所示,a和b的name指针指向同一块内存。修改任意一个对象的指针指向的内存区,另外一个对象的内容也会跟着变化。如果a的name指针指向的内存片被释放掉,此时b也不能访问这片内存,b的name成为一个野指针。b被销毁时,会试图释放b的name指向的内存片,造成同一个内存片被释放两次,有可能导致程序异常终止。

例2

我们把main函数修改一下,看一下运行结果

int main()
{
    man* a = new man((char*)"zhengkang", 26);
    man* b = new man(*a);
    a->show();
    delete a;
    b->show();
    delete b;
}

程序运行结果如下:

咦?程序正常运行,并没有崩溃,也没有打印出乱码,貌似一切正常。为什么会这样呢?
原因也简单,因为我们没有提供析构函数。当调用delete a;时,a的成员变量age和name被销毁,注意,name被销毁,但是name指向的内存片并没有被释放。因此调用b->show()照样能打印出正确的结果。c 提供的默认析构函数并不能释放通过由new申请的指针指向的内存。我们来提供一个析构函数。

例3

定义一个析构函数

man::~man()
{
    cout<<"call destructor"<

程序运行结果如下:

调用delete a;之后会调用a的析构函数,释放掉a的name指向的内存片。打印b的name显示乱码。顺便看一下delete a;执行前后,a和*a的变化

通过上面的几个例子,可以看出。c 提供的隐式拷贝构造函数只能完成浅拷贝,浅拷贝带来的问题上面已经讲过。解决这些问题的方法是进行深度拷贝。也就是说,拷贝构造函数应当复制name指针指向的字符串并将副本的地址复制给新对象的name成员,而不仅仅是复制字符串地址。这样每个对象都有自己的字符串,互不干扰,而不是引用另一个对象的字符串。调用析构函数时,各自释放自己的字符串,而不是试图释放别的对象可能已经被释放的字符串。定义一个这样的显式拷贝构造函数,在这个函数中,重新申请内存,复制内存块的地址到这块内存中,并把内存地址复制给新对象的指针成员。

man::man(const man & m)
{
    cout<<"call copy constructor"<age = m.age;
    this->name = new char[strlen(m.name)   1];
    strcpy(this->name, m.name);
}

再次执行,结果如下:

从运行结果可以看出,a的name和b的name指向不同的内存地址,delete a不会影响到b.

网站地图