前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++干货基地】面向对象核心概念与实践原理:拷贝构造函数的全面解读

【C++干货基地】面向对象核心概念与实践原理:拷贝构造函数的全面解读

作者头像
鸽芷咕
发布2024-05-26 16:41:42
210
发布2024-05-26 16:41:42
举报
文章被收录于专栏:C++干货基地C++干货基地

一、拷贝构造函数的引入

我们都知道面向对象的对象是一个宏观的概念, 万事万物都可以当成一个对象。而现实中我们的对象是可以复制的,那么我们在编程中创建的对象如何进行复制呢?

1.1 拷贝构造的概念

在C++中祖师爷规定了:当我们想把一个对象赋值给另一个对象的时候

  • 或者创建一个与已存在对象一某一样的新对象
  • 时需要调用它的拷贝函数来进行复制

如图所见拷贝构造函数是我们的六大成员默认函数之一,构造函数的作用是初始化,析构函数是复制清理工作,而我们的构造拷贝函数是用来同类对象进行赋值给另一个对象时的工作:

二、拷贝构造函数的特征

2.1 拷贝构造的书写形式

讲了怎么长时间拷贝构造是干什么的,下面就来到拷贝构造的创建把: 其实构造的前几个特征是需要先了解才能去书写的所以博主这里把他都给整合到前面了,后面的其他特征单独介绍:

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

这里第二个特征就特别强调了,我们在书写拷贝构造函数的时候一定要使用传引用

  • 是因为如果我们传值当形参的话那么形参是实参的一份临时拷贝又会拷贝构造函数
  • 这样就会无限递归下去

🍸 代码演示:

代码语言:javascript
复制
class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 // Date(const 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);
 return 0;
}
  • 所以我们在书写构造函数的时候一定要使用传引用当做形参

2.2 不显示定义自动创建

构造拷贝函数既然是六个默认成员函数之一的话,那么肯定也是符合默认成员函数的特点如果我们没有显示定义的话自动生成:

  • 那么自动生成的拷贝构造函数帮我们完成了什么事情呢?

🍸 代码演示:

这里我们就可以看到就算我们不写默认成员函数那么编译器也会自动生成 默认拷贝构造函数 去拷贝和赋值,这是可能就有人要问了既然默认生成的 拷贝构造函数 可以完成复制那么为什么要我们手动创建呢?

🔥 这是因为默认生成的拷贝构造函数完成的只是浅拷贝,只是把值复制过去了

2.3 浅拷贝与深拷贝

说到浅拷贝和深拷贝很多铁汁们不太明白,什么是浅拷贝?深拷贝拷贝了那些内容?

  • 下面我们就来看一下这段代码

🍸 代码演示:

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdlib.h>
using namespace std;

class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (int*)malloc(capacity * sizeof(int));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}

		_size = 0;
		_capacity = capacity;
	}
	void Push(const int& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	int* _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;
}

这里就是我们说的浅拷贝了,为什么程序回出现崩溃呢?这里刚开始创建了一个S1 对象,又创建了一个S2 对象去进行调用拷贝构造函数进行拷贝:

  • 而这里只进行了浅拷贝,在 S2 进行拷贝构造的时候只是简单的把值复制过去了
  • 所以 S2 和 S1 是指向同一块空间并没有给 S2 去单独开辟一个一模一样的空间

这里就是我们说的浅拷贝,S2 和 S1 指向用一块空间而当程序结束的时候 S2 调用它的析构函数去吧 S1 所指向的空间给释放了, 那么当 S1 在释放的时候就重复释放了原来释放的空间导致程序崩溃。

🔥 所以在这些去动态申请资源的函数的类去,一定要显示定义拷贝构造函数进行深拷贝

  • 把空间大小和值(内容)完全拷贝进去
代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdlib.h>
using namespace std;

class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (int*)malloc(capacity * sizeof(int));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}

		_size = 0;
		_capacity = capacity;
	}
	Stack(const Stack& p1)
	{
		int* tmp = (int*)malloc(sizeof(int) * p1._capacity);
		if (tmp == nullptr)
		{
			perror("file malloc");
			exit(-1);			

		}

		memcpy(tmp, p1._array, sizeof(int) * p1._size);

		_array = tmp;
		_capacity = p1._capacity;
		_size = p1._size;
		
	}
	void Push(const int& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	int* _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;
}

2.4 不申请资源不需要显示定义

如果我们一个类里面并不会去申请资源那么它的默认生成的拷贝构造函数 ,也就够用了。默认生成的拷贝构造函数只会进行值拷贝而我们在不申请资源的话,值拷贝就是我们需要的功能。

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

三、拷贝构造函数调用场景

到了这里我相信大家一定对靠北构造函数有一定认知了那么大家知道拷贝函数在哪些场景会自动调用呢?

3.1 使用已存在对象创建新对象

这个就是最常见的场景了,使用已存在的对象去创建新对象。

🍸 代码演示:

代码语言:javascript
复制
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;
};

int main()
{
 Date d1(2022,1,13);
 Test(d1);
 return 0;
}

3.2 函数参数类型为类类型对象

在以前学习函数的时候我们知道,形参是实参的一份临时拷贝所以当函数参数类型为 类 的类型对象的话也会自动调用 拷贝构造函数

🍸 代码演示:

代码语言:javascript
复制
Date Test(Date d)
{
 Date temp(d);
 return temp;
}

3.3 函数返回值类型为类类型对象

函数的返回值返回给我们调用的对象时候,返回的是 temp 的一份临时拷贝并不是对象本身

🍸 代码演示:

代码语言:javascript
复制
Date Test(Date d)
{
 Date temp(d);
 return temp;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、拷贝构造函数的引入
    • 1.1 拷贝构造的概念
    • 二、拷贝构造函数的特征
      • 2.1 拷贝构造的书写形式
        • 2.2 不显示定义自动创建
          • 2.3 浅拷贝与深拷贝
            • 2.4 不申请资源不需要显示定义
            • 三、拷贝构造函数调用场景
              • 3.1 使用已存在对象创建新对象
                • 3.2 函数参数类型为类类型对象
                  • 3.3 函数返回值类型为类类型对象
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档