类的默认成员函数——拷贝构造函数

1.概念引入

在现实生活中,如果有两个兄弟长得一模一样,我们就称其为双胞胎

 当我们创建了一个新的对象,需要用同类型的对象拷贝并初始化,就要用到拷贝构造函数

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。MyClass(const MyClass& other)

2.特征

1.拷贝构造函数是构造函数的一个重载形式

2.拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。 

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	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);//或者写成 Date d2 = d1;
	return 0;
}

3.若未显示定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储,按字节序完成拷贝,这种拷贝叫浅拷贝,或者值拷贝。与之相应的叫做深拷贝

在下面这个案例中,用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数,但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数 

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;
	Date d2(d1);
	return 0;
}

下面对浅拷贝和深拷贝分别做一个简单的介绍

浅拷贝

  • 浅拷贝仅仅复制对象的第一层属性。
  • 对于基本数据类型(如数字、字符串、布尔值等),浅拷贝会直接复制其值
  • 对于引用类型(如数组、对象等),浅拷贝不会复制引用类型数据本身,而是复制引用的地址。这意味着原始对象和拷贝对象在引用类型数据上共享同一块内存地址。
  • 因此,如果修改原始对象的引用类型属性,拷贝对象的相应属性也会受到影响。

深拷贝: 

  • 深拷贝会复制对象的所有属性,包括嵌套的引用类型数据。
  • 对于基本数据类型,深拷贝同样复制其值
  • 对于引用类型,深拷贝会创建一个新的对象,并递归地复制原始对象中引用类型数据的所有属性,从而在内存中创建一个全新的副本
  • 这意味着原始对象和拷贝对象在内存中是完全独立的,修改拷贝对象不会影响原始对象。

对比:

  • 内存独立性:深拷贝产生的对象在内存中是完全独立的,而浅拷贝产生的对象在引用类型数据上与原始对象共享内存。
  • 修改影响:修改深拷贝对象的属性不会影响原始对象,而修改浅拷贝对象的引用类型属性会影响原始对象。
  • 性能开销:深拷贝通常比浅拷贝更耗时,因为它需要递归复制所有嵌套的对象。
  • 适用场景:如果需要完全独立的数据副本,应该使用深拷贝;如果只需要复制顶层属性,或者原始对象不会改变,可以使用浅拷贝。 

案例:栈的深拷贝 

Stack(const Stack& st)
{
	_array = (DataType*)malloc(sizeof(DataType*) * st._capacity);
	if (NULL == _array)
	{
		perror("malloc申请空间失败");
		return;
	}
	memcpy(_array, st._array, sizeof(DataType) * st._size);
	_size = st._size;
	_capacity = st._capacity;
}

4.类中如果没有涉及资源申请,拷贝构造函数是否写都可以;但是一旦涉及到资源申请,拷贝构造函数是一定要写的,否则就是浅拷贝

3.拷贝构造函数典型调用场景

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

 综合案例分析

#include<iostream>
using namespace std;
class Date
{
public:
	//构造函数
	Date(int year, int minute, int day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}

	//拷贝构造函数
	Date(const Date& d)
	{
		cout << "Date(const Date& d):" << this << endl;
	}
	//析构函数
	~Date()
	{
		cout << "~Date():" << this << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};

Date Test(Date d)
{
	Date temp(d);
	return temp;
}

int main()
{
	Date d1(2024, 6, 25);
	Test(d1);
	return 0;
}

由以上案例可以看出,传值传参会调用拷贝构造,所以为了提高程序的效率,返回时根据实际场景,能用引用尽量使用引用 

4.总结

  • 如果没有管理资源,一般情况下就不需要写拷贝构造,默认生成的拷贝构造就可以,比如日期类
  • 如果都是自定义类型成员,内置类型成员没有指向资源,也是使用默认生成的拷贝构造即可,如MyQueue
  • 一般情况下,不需要显式写析构,就不用写拷贝构造
  • 如果内部有指针或一些值指向资源,需要显式写析构,通常就需要显式写构造完成深拷贝,如:Stack,Queue,List 

拷贝构造函数也比较抽象,希望大家看完本文有所收获

点赞收藏关注是博主不断更新优质好文的动力哦~  

 

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值