当前位置: 首页 > article >正文

【C++类和对象】类和对象(中):拷贝构造函数 {拷贝构造函数的概念及特征,拷贝构造函数不能使用传值传参,编译器自动生成的拷贝构造函数}

四、拷贝构造函数

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

http://www.kler.cn/a/14229.html

相关文章:

  • 百度文心一言与Notion的比较(机器人通信的例子)
  • linux和windows上RabbitMQ安装教程汇总
  • Vue 事件处理器
  • C# 类库打包推送到nuget
  • 4.7 贝塞尔曲线
  • LaoCat带你认识容器与镜像之Docker网络
  • python基础实战4-python基础语法
  • RabbitMQ实现消息的延迟推送或延迟发送
  • 视频音频提取器推荐:快速提取视频中的音频!
  • 互联网摸鱼日报(2023-04-21)
  • English Learning - L2-15 英音地道语音语调 语音语调四步法 2023.04.17 周一
  • 【C/C++】结构体对齐详解
  • 一、基础算法8:离散化 模板题+算法模板(区间和)
  • Go | 一分钟掌握Go | 1 - 安装Go
  • kafka使用详解、最佳实践和问题排查
  • Java应用的优雅停机
  • python:PyQt5 简单示例
  • 应届生的天坑,悔不该进那外包啊.....
  • linux 命令之 tar -czvf和 tar -xzvf
  • nodejs中使用json