C/C++拷贝构造函数举例
目录
1.拷贝构造函数定义
2.默认拷贝构造函数
3.显式拷贝构造函数
4.参数传递时,调用拷贝构造函数
5.对象作为返回值,调用拷贝构造函数
6.拷贝构造函数,危害(浅拷贝)
7.拷贝构造函数,常用(深拷贝)
8.拷贝构造函数,私有(深拷贝)
正文
1. 拷贝构造函数定义
拷贝构造函数是一种对构造函数的重载。与构造函数有着相同的作用,在创建新对象时,将数值赋值给它们的成员函数。
2.默认拷贝构造函数
若没有将拷贝构造函数显式地定义出来,类具有默认的拷贝构造函数。下记程序里只有带参数arg的构造函数,并未定义拷贝构造函数。
//例1
#include <iostream>
#include <stdio.h>
using namespace std;
class CExample {
private:
int a;
public:
CExample(int arg) {
printf("func: %s\n", __FUNCTION__);
a = arg;
}
void show() {
printf("func:%s, a:%d", __FUNCTION__, a);
}
};
int main()
{
CExample A(100);
CExample B = A; // 或者写为 CExample B(A); 结果一样,都是调用默认拷贝构造函数。
B.show();
return 0;
}
输出:
func: CExample
func:show, a:100
3.显式拷贝构造函数
如果类中显式定义拷贝构造函数,则系统会执行定义的显式构造函数。
//例2
#include <iostream>
#include <stdio.h>
using namespace std;
class CExample {
private:
int a;
public:
CExample(int arg) {
printf("func: %s(int arg)\n", __FUNCTION__);
a = arg;
}
CExample(const CExample &C) {
printf("func: %s(const)\n", __FUNCTION__);
a = C.a;
}
void show() {
printf("func:%s, a:%d", __FUNCTION__, a);
}
};
int main()
{
CExample A(100);
CExample B = A; // 或者写为, CExample B(A);
B.show();
return 0;
}
输出:
func: CExample(int arg)
func: CExample(const)
func:show, a:100
4.参数传递时,调用拷贝构造函数
对象作为函数参数传递给形参时,调用形参的拷贝构造函数。
将对象test作为实参,传递给g_func()函数时,形参CExample C被赋值为test,相当于执行了CExample C=test;
如果类CExample无显式拷贝构造函数,则执行默认构造函数;
下记类CExample有显示拷贝构造函数CExample(const CExample &C){…},执行该函数。
//例3
#include <iostream>
#include <stdio.h>
using namespace std;
class CExample {
private:
int a;
public:
CExample(int arg) {
printf("func: %s(int arg)\n", __FUNCTION__);
a = arg;
}
CExample(const CExample &C) {
printf("func: %s(const)\n", __FUNCTION__);
a = C.a;
}
void show() {
printf("func:%s, a:%d\n", __FUNCTION__, a);
}
~CExample() {
printf("func:%s\n", __FUNCTION__);
}
};
void g_func(CExample C)
{
printf("func:%s\n", __FUNCTION__);
C.show();
}
int main()
{
CExample test(1);
printf("func:%s, call g_func start\n", __FUNCTION__);
g_func(test);
printf("func:%s, call g_func end\n", __FUNCTION__);
return 0;
}
输出:
func: CExample(int arg)
func:main, call g_func start
func: CExample(const)
func:g_func
func:show, a:1
func:~CExample
func:main, call g_func end
func:~CExample
5.对象作为返回值,调用拷贝构造函数
存疑:1.为什么拷贝构造函数没有被调用?2.为什么析构函数只打印了1次?
//例4
#include <iostream>
#include <stdio.h>
using namespace std;
class CExample {
private:
int a;
public:
CExample(int arg) {
printf("[%s(arg)]\n", __FUNCTION__);
a = arg;
}
CExample(const CExample &C) {
printf("[%s(const)]\n", __FUNCTION__);
a = C.a;
}
void show() {
printf("[%s]a:%d\n", __FUNCTION__, a);
}
~CExample() {
printf("[%s]\n", __FUNCTION__);
}
};
CExample g_func()
{
printf("[%s]\n", __FUNCTION__);
CExample temp(1);
return temp;
}
int main()
{
printf("[%s]call g_func start\n", __FUNCTION__);
CExample A = g_func();
A.show();
printf("[%s]call g_func end\n", __FUNCTION__);
return 0;
}
输出:
[main]call g_func start
[g_func]
[CExample(arg)]
[show]a:1
[main]call g_func end
[~CExample]
6.拷贝构造函数,危害(浅拷贝)
默认拷贝构造函数属于浅拷贝,是指仅简单地拷贝值,一旦成员变量中含有动态成员,则析构或者使用时容易产生野指针,或地址访问错误等问题。这种指针错乱无法通过等于null或者其他的判断检查,非常危险。
//例5
#include <iostream>
#include <stdio.h>
using namespace std;
class Rect {
public:
Rect() {
p = new int(100);
printf("[%s()]p->%d\n", __FUNCTION__, &(*p));
}
~Rect() {
if (p != NULL) {
delete p;
printf("[%s]p->%d\n", __FUNCTION__, &(*p));
}
else {
printf("[%s]p->null\n", __FUNCTION__);
}
}
void show() {
printf("[%s]p->%d\n", __FUNCTION__, &(*p));
}
private:
int width;
int height;
int *p;
};
int main()
{
Rect rect1;
rect1.show();
Rect rect2(rect1);
rect2.show();
return 0;
}
输出:
[Rect()]p->536937200
[show]p->536937200
[show]p->536937200
[~Rect]p->:536937200
Aborted (core dumped)
疑问:p指向了同地址,执行1次析构了以后,就会报错,并且没有输出p->null的log?
解答:两次执行析构函数,会先析构rect2,而后析构rect1。
执行rect2的析构函数delete p之后,rect2.p不会自动将指向null。
即使在delete p;之后再加上p=null,也只是将rect2.p=null。
在之后执行rect1的析构函数时,rect1.p不等于null,rect1.p仍然等于536937200
即,如下记代码所示.
//例6
#include <iostream>
#include <stdio.h>
using namespace std;
class Rect {
public:
Rect() {
p = new int(100);
printf("[%s()]*p:%d,p:%d\n", __FUNCTION__, *p, p);
}
~Rect() {
if (p != NULL) {
printf("[%s]*p:%d, p:%d\n", __FUNCTION__, *p, p);
delete p;
p = NULL;
}
else {
printf("[%s]p==null\n", __FUNCTION__);
}
}
void show() {
printf("[%s]p:%d\n", __FUNCTION__, p);
}
private:
int width;
int height;
int *p;
};
int main()
{
Rect rect1;
Rect rect2(rect1);
rect2.~Rect();
return 0;
}
输出:
[Rect()]*p:100,p:536937200
[~Rect]*p:100, p:536937200
[~Rect]p==null
[~Rect]*p:1630022424, p:536937200
Aborted (core dumped)
7.拷贝构造函数,常用(深拷贝)
当类含有动态成员变量时,要使用显式拷贝构造函数对动态成员变量赋值。在拷贝构造函数中对动态成员变量重新创建新空间。
//例7
#include <iostream>
#include <stdio.h>
using namespace std;
class Rect {
public:
Rect() {
p = new int(100);
printf("[%s()]p:%d,*p:%d\n", __FUNCTION__, p, *p);
}
Rect(const Rect &r) {
p = new int;
*p = *(r.p);
printf("[%s(const)]p:%d, *p:%d\n", __FUNCTION__, p, *p);
}
~Rect() {
if (p != NULL) {
delete p;
printf("[%s]p:%d, *p\n", __FUNCTION__, p, *p);
}
else {
printf("[%s]p==null\n", __FUNCTION__);
}
}
void show() {
printf("[%s]p:%d\n", __FUNCTION__, p);
}
private:
int width;
int height;
int *p;
};
int main()
{
Rect rect1;
Rect rect2(rect1);
return 0;
}
输出:
[Rect()]p:536937192,*p:100
[Rect(const)]p:537166672, *p:100
[~Rect]p:537166672, *p
[~Rect]p:536937192, *p
8.拷贝构造函数,私有(深拷贝)
当类含有动态成员变量时,想要禁用拷贝构造函数,可将其设置为私有成员函数。
//例8
#include <iostream>
#include <stdio.h>
using namespace std;
class Rect {
public:
Rect() {
p = new int(100);
printf("[%s()]*p:%d,p:%d\n", __FUNCTION__, *p, p);
}
~Rect() {
if (p != NULL) {
printf("[%s]*p:%d, p:%d\n", __FUNCTION__, *p, p);
delete p;
p = NULL;
}
}
void show() {
printf("[%s]p:%d\n", __FUNCTION__, p);
}
private:
Rect(const Rect &r);
int width;
int height;
int *p;
};
int main()
{
Rect rect1;
Rect rect2(rect1);
return 0;
}
输出:
test.cpp: 在函数‘int main()’中:
test.cpp:23:9: 错误:‘Rect::Rect(const Rect&)’是私有的
Rect(const Rect &r);
^
test.cpp:32:21: 错误:在此上下文中
Rect rect2(rect1);
参考文章:
[1]https://blog.csdn.net/lwbeyond/article/details/6202256
[2]C++Primer Plus(第六版)中文版