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

相关推荐
好学且牛逼的马10 分钟前
【SSM框架 | day25 spring IOC 与 DI 注解开发】
java·开发语言
_OP_CHEN40 分钟前
C++进阶:(四)set系列容器的全面指南
开发语言·c++·stl·set·multiset·关联式容器·setoj题
不惑_42 分钟前
Java 使用 FileOutputStream 写 Excel 文件不落盘?
开发语言·python
十五年专注C++开发42 分钟前
Qt-VLC: 一个集成VLC的开源跨平台媒体播放库
开发语言·qt·媒体·libvlc·vlc-qt
郝学胜-神的一滴1 小时前
128天写作之旅:记录与成长的点滴
开发语言·程序人生
superman超哥1 小时前
仓颉语言中流式I/O的设计模式深度剖析
开发语言·后端·设计模式·仓颉
豆浆whisky1 小时前
Go内存管理最佳实践:提升性能的Do‘s与Don‘ts|Go语言进阶(17)
开发语言·后端·golang
Kay_Liang1 小时前
Spring中@Controller与@RestController核心解析
java·开发语言·spring boot·后端·spring·mvc·注解
l1t1 小时前
luadbi和luasql两种lua duckdb驱动的性能对比
开发语言·单元测试·lua·c·csv·duckdb
国服第二切图仔1 小时前
Rust开发实战之使用 Reqwest 实现 HTTP 客户端请求
开发语言·http·rust