【Object-Oriented C++】拷贝构造函数 {拷贝构造函数的概念及特征,拷贝构造函数不能使用传值传参,编译器自动生成的拷贝构造函数}

拷贝构造函数用于创建与已有对象相同的新对象,它是构造函数的一种特殊形式,接收同类对象的引用。当类中涉及资源管理时,如动态内存,必须自定义拷贝构造函数以实现深拷贝,防止浅拷贝导致的问题,例如内存泄漏或数据不一致。默认的拷贝构造函数可能只进行值拷贝,对于自定义类型则调用它们的拷贝构造函数。未涉及资源管理的类可以使用编译器生成的拷贝构造函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

四、拷贝构造函数

4.1 概念

在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用


4.2 特征

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 函数名和类名相同,没有返回值。
  3. 可以使用函数法或赋值法调用拷贝构造函数。
  4. 拷贝构造函数的参数只有一个且必须是同类类型对象的引用,而且一般用const修饰以限制引用权限,防止误操作修改拷贝源的属性。
  5. 如果使用传值传参的方式编译器直接报错,因为会引发无穷递归调用。

实现日期类的拷贝构造函数

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// Date(Date d) // 错误写法:编译报错,会引发无穷递归
	Date(const Date& d) // 正确写法
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	//写法一:函数法
	Date d2(d1);//使用d1拷贝构造d2
	//写法二:赋值法
	Date d2 = d1;
	return 0;
}

4.3 拷贝构造函数不能使用传值传参

传值传参的底层是在栈中开辟空间拷贝参数的值。如果拷贝构造函数的参数是同类类型对象的值,那么实例化形参就又要调用拷贝构造。这样就形成了死递归。

注意:类对象传值传参传值返回都会调用拷贝构造函数构造临时对象(出作用域还要析构)。由此可以看出,传引用比传值更高效尤其对于自定义类型,传值不仅要开空间(尤其对于深拷贝)还要调用拷贝构造函数和析构函数(空间时间消耗)代价更大。

在这里插入图片描述

提示:还可以传同类型对象的指针实现拷贝的功能,但要注意的是使用指针实现的函数不是拷贝构造函数(不符合语法);同时指针实现的拷贝函数在使用起来效果也不如引用。如:Date d2(&d1); Date d2 = &d1;


4.4 编译器自动生成的拷贝构造函数

若未显式定义,编译器会生成默认的拷贝构造函数。 默认生成的拷贝构造函数对于内置类型是逐字节拷贝的(aka 浅拷贝 or 值拷贝),而自定义类型是调用其拷贝构造函数完成拷贝的。

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time(const Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		_second = t._second;
		cout << "Time::Time(const Time&)" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	// 对于内置类型(_hour,_minute等)是按照字节方式直接拷贝的,而自定义类型(Time _t)是调用其拷贝构造函数完成拷贝的。
	Date d2(d1);
	return 0;
}

编译器自动生成的拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然,因为默认生成的拷贝构造函数不能解决深拷贝的问题

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType *_array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);//调用的是默认生成的拷贝构造函数,进行值拷贝
	return 0;
}

编译运行上面的代码发现程序崩掉了,程序为什么会崩溃掉呢?
在这里插入图片描述

  • 注意:类中如果没有涉及资源申请时,拷贝构造函数写不写都可以,像Date类;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,像Stack类。

  • 此处默认的浅拷贝出现的问题:

    1. 由于两个对象中的指针指向同一块空间,一个对象修改会影响另外一个对象。
    2. 函数返回,调用析构函数时,对同一块内存空间free两次造成程序崩溃。

解决方法:自己显示实现深拷贝

再看下面这个例子:

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	Stack(const Stack& st){
	//此处实现栈结构的深拷贝
	//........
	}
	//其他方法的实现....

	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType *_array;
	size_t _size;
	size_t _capacity;
};

class MyQueue{
//对于内置类型进行值拷贝
int sz = 0;
//对于自定义类型调用它们的拷贝构造
Stack output;
Stack input;
};

int main()
{
	MyQueue mq1;
	MyQueue mq2 = mq1;
	// 像MyQueue类型这种未直接涉及资源申请的类可以不写拷贝构造,
	// 但前提是其自定义类型成员中涉及资源申请的类实现了深拷贝。
	return 0;
}

像MyQueue类型这种未直接涉及资源申请的类可以不写拷贝构造,但前提是其自定义类型成员中涉及资源申请的类实现了深拷贝

总结:

  • 涉及资源申请的类需要显示的写拷贝构造,以实现类的深拷贝。比如:Stack,Queue
  • 未涉及资源申请的类不需要写拷贝构造,默认生成的就会完成类的值拷贝/浅拷贝。比如:Date
  • 未直接涉及资源申请的类也不需要写拷贝构造,默认生成的就会调用其自定义类型成员的拷贝构造函数。比如:Myqueue
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

52Hertz_Echo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值