当前位置 : 主页 > 编程语言 > c语言 >

“复制”不只是Ctrl+C和Ctrl+V!深浅拷贝你都要知道

来源:互联网 收集:自由互联 发布时间:2023-09-06
类中有6大默认成员函数 本文将详细介绍C++默认成员函数的拷贝赋值,拷贝构造分为浅拷贝和深拷贝。还有拷贝构造的重载。 一、拷贝构造函数的定义 拷贝构造函数:只有单个形参,该

类中有6大默认成员函数

“复制”不只是Ctrl+C和Ctrl+V!深浅拷贝你都要知道_C++

本文将详细介绍C++默认成员函数的拷贝赋值,拷贝构造分为浅拷贝和深拷贝。还有拷贝构造的重载。


一、拷贝构造函数的定义

拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存

在的类类型对象创建新对象时由编译器自动调用。

“复制”不只是Ctrl+C和Ctrl+V!深浅拷贝你都要知道_C++_02

二、拷贝构造的特征

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

1. 拷贝构造函数是构造函数的一个重载形式,无返回值。

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

2.1浅拷贝

2.1.1显式浅拷贝

#include<iostream>
using namespace std;

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) //拷贝构造函数 一定是对类类型的引用 而且参数只能有一个 因为自带了内置this参数
	{
		_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;
}

Date d2(d1); 是用一个已经初始化的d1来初始化拷贝给d2

这里进行了显式的拷贝构造用了引用,它没有对类中指针等资源进行复制,所以还是浅拷贝。

	Date(const Date& d) //拷贝构造函数 一定是对类类型的引用 而且参数只能有一个 因为自带了内置this参数
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

注意:拷贝构造函数一定是对类类型的引用 而且参数只能有一个因为自带了内置this参数。

如果这里不用这个引用,会消耗大量内存来进行传值拷贝占用空间,如图:

“复制”不只是Ctrl+C和Ctrl+V!深浅拷贝你都要知道_深浅拷贝_03


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

2.1.2隐式浅拷贝

#include<iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1) //构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
	// Date(const Date& d)   // 正确写法
	// 错误写法:编译报错,会引发无穷递归
	//Date(const Date& d) //拷贝构造函数 一定是对类类型的引用 而且参数只能有一个 因为自带了内置this参数
	//{
	//	_year = d._year;
	//	_month = d._month;
	//	_day = d._day;
	//}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2(d1); 

	d1.print();
	d2.print();


	return 0;
}

程序运行如图:

“复制”不只是Ctrl+C和Ctrl+V!深浅拷贝你都要知道_深浅拷贝_04

我将拷贝构造函数进行注释掉,之后仍然进行用d1来拷贝初始化d2,那么编译器自动调用的默认的拷贝构造函数,实现的是浅拷贝,也是值拷贝。

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

  1. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?

对于日期类来说,成员变量都是内置变量,使用默认拷贝构造函数来进行拷贝构造也是可以的,但是当在其他的类里面,

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;
}

上述程序在进行拷贝构造就出现了bug,这里使用编译器默认生成的拷贝构造是行不通的。

“复制”不只是Ctrl+C和Ctrl+V!深浅拷贝你都要知道_拷贝构造函数_05

对Stack这个类的剖析:

“复制”不只是Ctrl+C和Ctrl+V!深浅拷贝你都要知道_深浅拷贝_06

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


2.2深拷贝

2.2.1深拷贝定义

深拷贝是指在程序中对于一个数据结构进行复制时,对该数据结构内所有层次的引用和子引用,以及包含在其中的数据对象进行递归式的拷贝,从而产生一个新的数据结构,与原始数据结构互不干扰。

2.2.2深拷贝的实现

在实现深拷贝时,需要对类中的指针等资源进行复制,以避免资源重复释放或内存泄漏等问题。下面以一个日期类的深拷贝实现为例进行说明

class Date {
public:
    Date(int year = 1900, int month = 1, int day = 1);
    Date(const Date& other); //深拷贝构造函数
    ~Date();
private:
    int* _year;
    int* _month;
    int* _day;
};

Date::Date(int year, int month, int day)
{
    _year = new int(year);
    _month = new int(month);
    _day = new int(day);
}

Date::Date(const Date& other) //深拷贝构造函数
{
    _year = new int(*other._year);
    _month = new int(*other._month);
    _day = new int(*other._day);
}

Date::~Date() //析构函数
{
    delete _year;
    delete _month;
    delete _day;
}

在这个例子中,日期类包含了三个指针类型成员变量,为了防止拷贝后出现指向同一块内存的情况,需要在构造函数中对其进行深拷贝。具体来说,在拷贝构造函数中,对每个成员变量使用new运算符分配内存,并将原有内存中的值复制到新的内存中。在析构函数中,需要释放掉这些内存。


2.2.3深拷贝的注意事项

  1. 拷贝构造函数和拷贝赋值运算符必须深拷贝指针类型成员变量。
  2. 如果类中包含自定义对象的成员变量,那么也需要对它们进行深拷贝。
  3. 一旦深拷贝过程中出现内存分配错误,就需要手动释放已经分配的内存。


2.2.4 深拷贝的使用场景

  1. 对象之间需要独立拥有一份数据而不会互相影响。
  2. 传递对象时需要避免出现浅拷贝产生的问题。
  3. 对象中包含有指针类型成员变量,需要在多个调用栈中共享同一个对象时,需要避免它们指向同一块内存。


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

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
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(2022,1,13);
 Test(d1);
 return 0;
}

“复制”不只是Ctrl+C和Ctrl+V!深浅拷贝你都要知道_C++_07

为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

三、拷贝构造函数总结

拷贝构造函数是 C++ 中的一种特殊的构造函数,与普通构造函数不同,它的参数是该类对象的引用。当一个类的对象被用于另一个类对象的初始化、传递参数或返回值时,拷贝构造函数会被调用,以实现对象之间的克隆。

下面是对拷贝构造函数的总结:

  1. 拷贝构造函数的定义方式为:类名(const 类名& other),其中 other 表示另一个同类对象。
  2. 拷贝构造函数是一种特殊的构造函数,用于在对象初始化时从现有对象中创建新对象。
  3. 如果程序没有提供自定义的拷贝构造函数,编译器会生成一个合成的默认拷贝构造函数,该函数将执行浅拷贝操作,即只复制对象中的指针而不复制指向的内存。
  4. 如果对象中存在成员变量为指针类型,则需要进行深拷贝,以避免多个对象共享同一个资源,造成意外错误。
  5. 拷贝构造函数的调用时机包括了对象初始化、传参和返回值三种情况,每次调用都会创建一个新的对象,并将原对象的数据拷贝到其中。
  6. 拷贝构造函数也可以通过其它成员函数、友元函数调用,但一般情况下不需要手动调用,否则可能会导致意外错误。

总之,拷贝构造函数是用于对象克隆的一种特殊函数,它和普通的构造函数类似,但需要注意在拷贝过程中对指针类型成员变量进行深拷贝。正确地理解和使用拷贝构造函数有助于程序的正确性和性能优化。


上一篇:C++ ----&gt;大类 ~~ 大对象(中)__01
下一篇:没有了
网友评论