(C++)拷贝构造

概念

拷贝,我们都知道

那么,对象拷贝,该如何拷贝呢?

像这样吗?

代码:

class Date
{
public:
//构造函数
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//打印
	void print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _day;
	int _month;
	int _year;
};

int main()
{
	Date d1(2023, 7, 31);
	Date d2;
	d2 = d1;//将d1赋给d2
	d2.print();
	return 0;
}

 

我们看到,d1确实拷贝给了d2,但是对象之间的拷贝真的是这样吗?

拷贝构造函数又是怎么写的呢?

拷贝构造函数:

 这里 d 相当于要被拷贝的对象的别名,也就是下面d1的引用,然后我们将d 中的成员依次赋值给创建的对象。

 那为什么要用引用呢?

这样不可以吗?

 答:这样会发生无穷递归的问题

所以一定要用引用!!! 

我们说,拷贝构造函数时默认成员函数,那么若未显式定义,编译器会生成默认的拷贝构造函数。

这就是我们一开始为什么没写拷贝构造,d2仍然拷贝成功了的原因:

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

那,既然默认生成,我们还需要写吗?不麻烦吗?

答:当然要写,对于日期类来说,可以不写,默认生成的就可以了。但是对于一些需要开空间的,就需要自己写。

比如,像栈类这种的:

代码:

typedef int DataType;
class Stack
{
public:
//构造
	Stack(size_t capacity = 4)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
			_size = 0;
		_capacity = capacity;
	}
//压栈
	void Push(const DataType& data)
	{
		_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;
}

像这种,没有写拷贝构造,用默认的就会崩溃!

崩溃在这:

 在调用析构函数时崩溃,为什么呢?

我们来看:

 两者指向同一块空间,那s1,s2在销毁时,都要调用析构函数,这块空间就相当于释放了两次,所以程序崩溃了!一块内存空间多次释放,导致程序崩溃。

这就需要写一个拷贝构造来解决这个问题

代码:

Stack(const Stack& st)
	{
		//新开一块一样大小的空间
		_array = (DataType*)malloc(st._capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
        //拷贝里面的数据
		memcpy(_array, st._array, st._capacity * sizeof(DataType));

		_size = st._size;
		_capacity = st._capacity;

	}

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

总结

特征:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。
  3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
  4. 一旦涉及到资源申请 时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

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

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

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

### C++ 拷贝构造函数的使用方法和注意事项 在 C++ 中,拷贝构造函数是一种特殊的构造函数,用于创建一个对象作为另一个对象的副本。它通常在以下场景中被调用:将一个对象按值传递给函数、从函数返回一个对象以及初始化一个对象时[^1]。 #### 1. 拷贝构造函数的定义与使用 拷贝构造函数的形式通常为 `ClassName(const ClassName& other)`,其中参数是一个对现有对象的常量引用。通过这种方式,可以避免在复制过程中修改原始对象的内容。例如: ```cpp class MyClass { public: int* data; // 默认构造函数 MyClass() : data(new int(0)) {} // 拷贝构造函数 MyClass(const MyClass& other) { data = new int(*other.data); // 深拷贝 } // 析构函数 ~MyClass() { delete data; } }; ``` 在上述代码中,`MyClass` 的拷贝构造函数实现了深拷贝,确保了两个对象的 `data` 成员指向不同的内存区域[^3]。 #### 2. 注意事项 - **深拷贝与浅拷贝**:如果类中包含指针成员或其他需要动态分配的资源,必须实现深拷贝以避免多个对象共享同一块内存。否则,可能会导致析构函数释放内存时出现未定义行为。 - **默认拷贝构造函数**:如果类中没有动态分配的内存或其他需要深拷贝的资源,编译器会自动生成一个拷贝构造函数。这个默认版本执行的是浅拷贝[^1]。 - **对象生命周期管理**:当对象通过拷贝构造函数创建后,必须确保其生命周期内的资源管理正确,尤其是在涉及动态内存分配时[^2]。 #### 示例代码 以下是一个完整的示例,展示了如何正确实现拷贝构造函数以避免浅拷贝问题: ```cpp #include <iostream> using namespace std; class String { private: char* str; public: // 构造函数 String(const char* s = "") { str = new char[strlen(s) + 1]; strcpy(str, s); } // 拷贝构造函数 String(const String& other) { str = new char[strlen(other.str) + 1]; strcpy(str, other.str); // 深拷贝 } // 析构函数 ~String() { delete[] str; } // 打印字符串 void print() const { cout << str << endl; } }; int main() { String s1("Hello"); String s2(s1); // 调用拷贝构造函数 s1.print(); // 输出 Hello s2.print(); // 输出 Hello return 0; } ``` 在上述代码中,`String` 类的拷贝构造函数确保了 `s1` 和 `s2` 的 `str` 成员指向不同的内存区域,从而避免了浅拷贝带来的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值