类中有6大默认成员函数
本文将详细介绍C++默认成员函数的拷贝赋值,拷贝构造分为浅拷贝和深拷贝。还有拷贝构造的重载。
一、拷贝构造函数的定义
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存
在的类类型对象创建新对象时由编译器自动调用。
二、拷贝构造的特征
拷贝构造函数也是特殊的成员函数,其特征如下:
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参数。
如果这里不用这个引用,会消耗大量内存来进行传值拷贝占用空间,如图:
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;
}
程序运行如图:
我将拷贝构造函数进行注释掉,之后仍然进行用d1来拷贝初始化d2,那么编译器自动调用的默认的拷贝构造函数,实现的是浅拷贝,也是值拷贝。
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
- 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
对于日期类来说,成员变量都是内置变量,使用默认拷贝构造函数来进行拷贝构造也是可以的,但是当在其他的类里面,
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,这里使用编译器默认生成的拷贝构造是行不通的。
对Stack这个类的剖析:
注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
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深拷贝的注意事项
- 拷贝构造函数和拷贝赋值运算符必须深拷贝指针类型成员变量。
- 如果类中包含自定义对象的成员变量,那么也需要对它们进行深拷贝。
- 一旦深拷贝过程中出现内存分配错误,就需要手动释放已经分配的内存。
2.2.4 深拷贝的使用场景
- 对象之间需要独立拥有一份数据而不会互相影响。
- 传递对象时需要避免出现浅拷贝产生的问题。
- 对象中包含有指针类型成员变量,需要在多个调用栈中共享同一个对象时,需要避免它们指向同一块内存。
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;
}
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
三、拷贝构造函数总结
拷贝构造函数是 C++ 中的一种特殊的构造函数,与普通构造函数不同,它的参数是该类对象的引用。当一个类的对象被用于另一个类对象的初始化、传递参数或返回值时,拷贝构造函数会被调用,以实现对象之间的克隆。
下面是对拷贝构造函数的总结:
- 拷贝构造函数的定义方式为:类名(const 类名& other),其中 other 表示另一个同类对象。
- 拷贝构造函数是一种特殊的构造函数,用于在对象初始化时从现有对象中创建新对象。
- 如果程序没有提供自定义的拷贝构造函数,编译器会生成一个合成的默认拷贝构造函数,该函数将执行浅拷贝操作,即只复制对象中的指针而不复制指向的内存。
- 如果对象中存在成员变量为指针类型,则需要进行深拷贝,以避免多个对象共享同一个资源,造成意外错误。
- 拷贝构造函数的调用时机包括了对象初始化、传参和返回值三种情况,每次调用都会创建一个新的对象,并将原对象的数据拷贝到其中。
- 拷贝构造函数也可以通过其它成员函数、友元函数调用,但一般情况下不需要手动调用,否则可能会导致意外错误。
总之,拷贝构造函数是用于对象克隆的一种特殊函数,它和普通的构造函数类似,但需要注意在拷贝过程中对指针类型成员变量进行深拷贝。正确地理解和使用拷贝构造函数有助于程序的正确性和性能优化。