C++拷贝构造函数

学习笔记~

Posted by 婷 on February 19, 2020 本文总阅读量

今天偶然看到拷贝构造函数的概念,在网上搜索以及翻阅了一些书籍后,特地写下自己一个大概的理解。

什么叫拷贝构造函数

C++ Primer 中是这么说明的

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,那么这个构造函数就叫做拷贝构造函数。

按照书中的定义,像长得这样的就叫拷贝构造函数

class demo
{
public:
    demo();//默认构造函数
    demo(const demo &d);//拷贝构造函数
    ~demo();
};

关于为什么这个拷贝构造函数的参数列表为什么是const,书中在介绍拷贝构造函数的开头那里是这么说的:

虽然我们可以定义一个接受非const引用的拷贝构造函数,但是此参数几乎总是一个const的引用。

个人对这个const跟引用的理解是这样的,引用是为了不再多“用”一些空间,而const是来修饰引用变量的,为了防止原来的变量被误操作而导致改变。这部分我也没去深究下去,等以后如果碰到了再去学习。

所以总结一下什么是拷贝构造函数呢?首先它都叫做拷贝构造函数,那肯定也是构造函数的一员啦。再者引用书中的原话:

拷贝构造函数的第一个参数必须是一个引用类型。

调用拷贝构造函数

那什么时候会调用拷贝构造函数呢?一共有三种情况。

1.用一个已经存在的对象去初始化同类的另一个新对象时

#include <iostream>

using namespace std;

class demo
{
private:
    int var;
public:
    demo();
    demo(const demo &d);
    ~demo();
};

demo::demo(const demo &d)//拷贝构造函数
{
    var=d.var;//拷贝值
    var=6;//对拷贝值进行修改
    cout<<"调用拷贝构造函数"<<endl;
}

demo::demo()//普通构造函数
{
    var = 2;
    cout<<"调用普通构造函数"<<endl;
}

demo::~demo()//析构函数
{
    cout<<"析构的对象的私有变量的值"<<this->var<<endl;
}

int main(void)
{
    demo a;
    demo b=a;//或者是  demo b(a);
    return 0;
}

输出结果如下:

1.png

当然有一种情况要与这个区分开来。在上面的例子中做个修改,如果我的对象b已经初始化过了,这时候如果也来一句b=a,并不会调用到任何的构造函数,因为这时候b这个对象已经“造”出来了,不需要有任何的构造函数去给他分配空间了,更别说是拷贝构造函数了。

int main(void)
{
    demo a;
    demo b;
    b=a;
    return 0;
}

2.png

其实这个情况认真看一下还是好区分的。

2.函数形参与实参结合

在类demo中我们添加了一个函数func1

class demo
{
private:
    int var;
public:
    demo();
    demo(const demo &d);
    ~demo();
    void func1(demo aa);
};

void demo::func1(demo aa)
{
    aa.var=888;
    cout<<"现在在函数func1里面"<<endl;
}

然后在main函数里面也做下修改

int main(void)
{
    demo a;//对象 a b 都是调用的默认构造函数
    demo b;
    b.func1(a);//对象b调用了func1函数
    return 0;
}

运行结果:

3.png

按照程序的运行顺序,先调用了两次普通构造函数来初始化a,b,然后因为调用了func1函数传入了一个实参,所以调用拷贝构造函数来初始化这个形参aa

最后按照先构造的后析构的顺序,依次析构aaab

但是有一个也是特别容易搞混的情况。

现在我在类里面再加一个func2函数,形参跟func1函数差不多,但是这次的形参是demo类对象的引用

class demo
{
private:
    int var;
public:
    demo();
    demo(const demo &d);
    ~demo();
    void func1(demo aa);
    void func2(demo &aa);//新添加的函数 ---经常手贱打成两个&&符号
};

void demo::func2(demo &&aa)
{
    aa.var=999;
    cout<<"现在在函数func2里面"<<endl;
}

main函数做下小修改

int main(void)
{
    demo a;
    demo b;
    b.func2(a);//改成调用func2函数
    return 0;
}

程序运行结果如下:

4.png

可以看到值调用了两次普通的构造函数,却没有给func2的形参调用拷贝构造函数,这是因为func2的形参是引用!!!!这是个坑,在一开始自己敲代码的时候受到前面拷贝构造函数的第一个形参是引用类型这句话的影响就傻乎乎的给函数的形参写成了引用类型。

3.函数返回值

这个在c++ primer书上还有很多网上的资料都有提及这一点,但是自己在编写程序去验证的时候,函数的返回值并没有调用到拷贝构造函数,直到看到这篇博客 ,才明白原来是gcc编译器对这个点做了优化。

默认拷贝构造函数

如果程序中我们并没有去自己定义一个拷贝构造函数的时候,编译器会自动调用默认的拷贝构造函数,这就跟我们以前没写构造函数的时候一样。编译器自己调用的默认的拷贝构造函数也是对变量进行拷贝赋值。

#include <iostream>

using namespace std;

class demo
{
private:
    int var;
public:
    demo();
    ~demo();
};

demo::demo()
{
    var = 2;
    cout<<"调用普通构造函数"<<endl;
}

demo::~demo()
{
    cout<<"调用析构函数"<<endl;
}

int main(void)
{
    demo a;
    demo b=a;
    return 0;
}

程序运行结果如下:

5.png

从输出的信息看,在创建对象b的时候编译器自己“偷偷”调用了拷贝构造函数。

所以是不是就是我们遇到需要拷贝构造函数的时候就可以不写呢?其实并不是的。

浅拷贝与深拷贝

浅拷贝

深浅二字从字面上其实还蛮好会意的。浅拷贝是指,在对象复制时,对成员数据进行简单的copy赋值。我们的默认拷贝构造函数就是浅拷贝。

但是浅拷贝能够执行了我们想要copy的功能了为什么还要有个深拷贝,因为总有那么些情况是浅拷贝做不了的。

1.当数据成员中有静态成员时

比如我创建了一个demo类对象a,与此同时在初始化a的是我们会对静态成员变量counter执行增加操作。

#include <iostream>

using namespace std;

class demo
{
private:
    int var;
    static int counter;//定义一个私有的静态变量
public:
    demo();
    ~demo();
    static int getcount(void);//定义一个公有的静态函数
};

int demo::getcount(void)//计数函数
{
    return counter;
}

demo::demo()
{
    counter++;
    var =666;
    cout<<"调用普通构造函数"<<endl;
}

demo::~demo()
{
    cout<<"调用析构函数"<<endl;
}

int demo::counter=0;//对静态变量进行初始化,注意静态数据成员只能在类外初始化
int main(void)
{
    demo a;
    demo b(a);
    cout<<"计数器的数值是"<<demo::getcount()<<endl;
    return 0;
}

输出结果如下:

6.png

很明显默认的拷贝构造函数当然无法随我们心意让b在初始化的时候跟a一起将静态变量执行增加操作。

2.当数据成员中有指针变量时

如果我们希望对象b跟对象a都各自有一个10个存储int类型变量的空间

#include <iostream>

using namespace std;

class demo
{
private:
    int var;
    int *ptr;
public:
    demo();
    ~demo();
   void getaddr(void);//输出私有变量*ptr的地址
};

void demo::getaddr(void)
{
    cout<<ptr<<endl;
}

demo::demo()
{
    var =666;
    ptr=new int [10];//让指针变量指向一个有10个int类型元素的指针
    cout<<"调用普通构造函数"<<endl;
}

demo::~demo()
{
    cout<<"调用析构函数"<<endl;
}

int main(void)
{
    demo a;
    demo b(a);
    a.getaddr();
    b.getaddr();
    return 0;
}

输出结果如下:

7.png

通过最终我们输出的指针指向的地址可以看出ba都指向了相同的空间,也就是说在默认拷贝构造函数里面应该是这样执行的,这就不难说明为什么两者都指向同一块空间了,这就没法符合我们原本的需求了。

demo::demo(const demo &aa)
{
    ptr=aa.ptr;
    var=aa.var;
}

深拷贝

怎么解决上面的情况呢,这就是深拷贝啦,对于这些特殊的情况,自己写个拷贝构造函数解决一下就好啦。

#include <iostream>

using namespace std;

class demo
{
private:
    int var;
    int *ptr;
    static int counter;
public:
    demo();
    demo(const demo &aa);
    ~demo();
    void getaddr(void);
    static int getcount(void);
};

int demo::getcount(void)
{
    return counter;
}

void demo::getaddr(void)
{
    cout<<ptr<<endl;
}

demo::demo(const demo &aa)//拷贝构造函数--深拷贝
{
    counter++;
    ptr=new int [10];
    var=aa.var;
    cout<<"调用拷贝构造函数"<<endl;
}

demo::demo()
{
    counter++;
    var =666;
    cout<<"调用普通构造函数"<<endl;
}

demo::~demo()
{
    cout<<"调用析构函数"<<endl;
}

int demo::counter=0;
int main(void)
{
    demo a;
    demo b(a);
    a.getaddr();
    b.getaddr();
    cout<<"计数器的数值是"<<demo::getcount()<<endl;
    return 0;
}

当当解决~

8.png