在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。
相当于就是把自己复制一遍,内置类型如int,char这些要实现复制很简单只需要:
int a = 10;
int b = a;
那在创建类对象时,如何创建一个与已存在对象一摸一样的新对象呢?
答案是拷贝构造。
class Date
{
public:
Date(int year = 2024, int month = 1, int day = 25)//全缺省构造函数
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷贝构造
{
_year = d._year;
_month = d._month;
_day = d._day;
}
~Date()//析构函数
{
//cout << "~Time()" << endl;
}
void show()//普通函数
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date a(2000, 1, 1);
Date b = a; //注意这里的对象初始化要调用拷贝构造函数,而非赋值
b.show();
return 0;
}
从以上代码可以看出系统为对象 B 分配了内存并完成了与对象 A 的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。
具体过程是当编译器执行到Date b = a这一行代码时会自动调用b的拷贝构造函数,拷贝构造的参数为对象a,并在函数中完成赋值操作。
同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或称拷贝是完全可行的。==这个拷贝过程只需要拷贝数据成员,==而函数成员是共用的(只有一份拷贝)。在建立对象时可用同一类的另一个对象来初始化该对象的存储空间,这时所用的构造函数称为拷贝构造函数。
拷贝构造函数本质上来说也是构造函数,是构造函数的一个重载。
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
class Date
{
public:
Date(int year = 2024, int month = 1, int day = 25)//全缺省构造函数
{
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;
}
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
浅拷贝就是在对象复制时,仅仅只对对象中的数据成员进行简单的赋值,默认的拷贝构造就是浅拷贝,虽然大多数情况下浅拷贝就已经够用了,但如果出现资源申请的情况(申请内存),浅拷贝就会出现一些问题。
以下程序是我们自定义实现了一个栈,其中有个成员变量是指针DataType* _array;,在构造函数中我们在堆区申请了内存,让指针_array指向了内块内存。我们在主函数中写了Stack s2(s1);利用构造函数构造了一个s2,但默认的构造函数时是浅拷贝,在s2的构造函数中相当于有这么一句代码_array=s1._array;。这造成了两个指针指向了同一块内存空间,但这俩指针在不同的对象s1,s2中,在俩对象析构的时候会导致同一块内存空间释放两次,导致错误。
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;
}
为了解决上述问题 我们就需要给s2中的_array也开辟和s1中的_array一样大小的空间,所以我们就需要深拷贝 。
在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:
手动写一个深拷贝的拷贝构造,各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。
Stack(const Stack& s)
{
//s1空间有多大就申请多大的空间
_array= (DataType*)malloc(s._capacity * sizeof(DataType));
if (_array == NULL)
{
exit(-1);
}
for (int i = 0; i < s._size; i++)
{
_array[i] = s._array[i];
_size++;
}
_capacity = s._capacity;
}