C++之类和对象三

目录

拷贝构造函数

定义铺垫

浅拷贝

深拷贝

总结


拷贝构造函数

那在创建对象时,可否创建一个与一个对象一某一样的新对象呢?

定义铺垫

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

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

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
  3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷 贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
  4. 那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像 日期类这样的类是没必要的
cpp 复制代码
class Date
{
public:
	Date()
	{
		_year = -1;
		_month = -1;
		_day = -1;
	}
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << &_year << "-" << &_month << "-" << &_day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 4, 18);
	d1.Print();
	Date d2(d1);
	d2.Print();
	return 0;
}

在这里我们给一段代码。

其中要注意这里的拷贝构造是引用传参

我们发现他们的打印结果相同 地址也相同

这就是拷贝函数

浅拷贝

cpp 复制代码
class Date
{
public:
	Date()
	{
		_year = -1;
		_month = -1;
		_day = -1;
	}
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << &_year << "-" << &_month << "-" << &_day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void func(Date d)
{
	d.Print();
}
int main()
{
	Date d1(2024, 4, 18);
	func(d1);
	return 0;
}

然后我们在进行调试

我们会发现当我们的d1初始化完后,我们下一步按F11时,会先进入Date的拷贝构造中,然后再调用func函数

所以

调用func得先传参,自动以类型对象传值传参要调用拷贝构造

当然了我们也可以不去使用拷贝构造

比如使用指针或者使用引用

cpp 复制代码
void func(Date& d)
{
	d.Print();
}
void func(Date* d)
{
	d.Print();
}

一个是d1的地址一个是d1的别名

在这里我们还可以继续更改一下代码

cpp 复制代码
Date(const Date& d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}

加上一个const去缩小权限

因为我们只是拷贝构造不需要改变值

这里也是存在this指针的

cpp 复制代码
Date(const Date& d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
	}

拷贝构造的形式有很多

cpp 复制代码
int main()
{
	Date d1(2024, 4, 18);
	Date d2 = d1;
    Date d3(d2);
	return 0;
}

这种类似于赋值的形式也是拷贝构造

cpp 复制代码
class Date
{
public:
	Date()
	{
		_year = -1;
		_month = -1;
		_day = -1;
	}
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	/*Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}*/

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << &_year << "-" << &_month << "-" << &_day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void func(Date d)
{
	d.Print();
}
int main()
{
	Date d1(2024, 4, 18);
	Date d2 = d1;
	Date d3(d2);
	d2.Print();
	d3.Print();
	return 0;
}

我们把我们的拷贝构造给注释掉

然后在进行打印会发现依旧会进行拷贝构造

原因是拷贝构造是默认的成员函数,不写他会进行值的拷贝,简称值拷贝

深拷贝

但如果是栈呢?

cpp 复制代码
struct stack
{
public:
	int* a;
	int size;
	int capacity;
	void Init(int n = 4)
	{
		a = (int*)malloc(sizeof(int) * n);
		if (a == nullptr)
		{
			perror("malloc fail");
			return;
		}
		//...
		size = 0;
		capacity = n;
	}
	void Push(int x)
	{
		a[size++] = x;
	}
};
int main()
{
	/*Date d1(2024, 4, 18);
	Date d2 = d1;
	Date d3(d2);
	d2.Print();
	d3.Print();*/
	stack st;
	st.Push(1);
	st.Push(1);
	st.Push(1);
	stack st1 = st;
	return 0;
}

我们会发现代码崩溃了

但拷贝是否完成了呢?

拷贝完成了

我们这里也是完成了值拷贝

这里会存在一个大问题

如果只进行了值拷贝,也就是说这两个栈都是使用的同一块内存,如果进行析构的话会进行两次,出现错误,所以只要是存在浅拷贝/值拷贝,就会可能出现一定的错误

所以,浅拷贝的类我们可以进行值拷贝,但如果是深拷贝就需要慎重考虑了

所以这里怎么办呢?

我们可以在构造函数时进行一定的改进

cpp 复制代码
struct stack
{
public:
	/*void Init(int n = 4)
	{
		a = (int*)malloc(sizeof(int) * n);
		if (a == nullptr)
		{
			perror("malloc fail");
			return;
		}
		size = 0;
		capacity = n;
	}*/
	void Push(int x)
	{
		a[size++] = x;
	}
	stack(const stack& st)
	{
		a = (int*)malloc(sizeof(int) * st.capacity);
		if (a == nullptr)
		{
			perror("malloc fail");
			return;
		}
		memcpy(a, st.a, sizeof(int) * st.size);
		size = st.size;
		capacity = st.capacity;
	}
	stack()
	{
		a = (int*)malloc(sizeof(int) * capacity);
		if (a == nullptr)
		{
			perror("malloc fail");
			return;
		};
		size = capacity = 4;
	}
	~stack()
	{
		a = nullptr;
		size = capacity = 0;
	}
private:
	int* a = nullptr;
	int size = 4;
	int capacity = 4;
};
int main()
{
	/*Date d1(2024, 4, 18);
	Date d2 = d1;
	Date d3(d2);
	d2.Print();
	d3.Print();*/
	stack st1;
	st1.Push(1);
	st1.Push(1);
	st1.Push(1);

	stack st2 = st1;
	return 0;
}

这样我们的代码就可以正常运行拷贝构造了

总结

总结一下:

  1. 如果没有管理资源,一般情况下不需要写拷贝构造,默认生成的拷贝构造就可以,如:Date

  2. 如果自定义类型的话,内置类型没有指向资源,也类似默认生成的拷贝构造就可以

  3. 一般情况下,不需要显示写析构函数,就不需要写拷贝构造

  4. 如果内部有指针或者有一些只想资源,需要显示写析构函数,通常就需现实些构造完成深拷贝。如:stack,queue

相关推荐
wuyk55516 分钟前
【Socket 进阶之路】第 7 章 IO 多路复用 select & poll 完整实战|多 fd 监听、超时等待、优缺点横向对比
服务器·开发语言·网络·数据库·物联网
stolentime23 分钟前
CSP-J2026第一轮试题(附答案解析、markdown源码)
c++·csp
船厂电气自动化ai大模型24 分钟前
AI大模型与数学|第81天 课程:正交向量、正交基、格拉姆‑施密特(Gram‑Schmidt)正交化
开发语言·数据结构·人工智能·线性代数·机器学习
Rabitebla25 分钟前
【Linux系统编程】 指令(二):一条路径是怎么定位到文件的
linux·c++·笔记·学习·算法
lv__pf1 小时前
seata【实战 msb】
java·开发语言·后端
点纭1 小时前
C 语言 第七章 常用函数(3)
c语言·开发语言·算法·oracle·c#
程序员老陆1 小时前
std::experimental::when_any 深度解析:等待“第一个完成”的异步组合原语
c++·多线程
Rabitebla2 小时前
【Linux系统编程】指令(三):创建、删除、复制、移动、看内容
linux·数据结构·c++·算法
程序员老陆2 小时前
CMake 常用关键字与命令全景:从 set到 target_link_options的现代工程视角
c++·cmake·程序开发
事已至此先睡覺吧2 小时前
第三篇:Java 流程控制详解:条件判断、循环与跳转语句
java·开发语言