拷贝构造函数
使用之前构造与析构函数的日期类为例子:
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;
}
private:
int _year;
int _month;
int _day;
};
概念
那在创建对象时,可以创建一个与已存在对象一某一样的新对象。
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存
在的类类型对象创建新对象时由编译器自动调用。
特征
拷贝构造函数也是特殊的成员函数,其特征如下:
1、拷贝构造函数是构造函数的一个重载形式。
按照前面的文章中构造函数可以写出如下的代码:
Date(Date d) // 错误写法:编译报错,会引发无穷递归
{
_year = d._year;
_month = d._month;
_day = d._day;
}
但是上述的写法会下面的报错(会引起无穷递归)普通类型可以直接传递,但是自定义类型不行:
2、拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
拷贝构造表现形式:
Date(const Date& d)// 添加const防止对需要拷贝的数据进行修改
{
cout << "Date(Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
3、若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
在上面的日期类之中可以使用编译器给我们生成的默认拷贝构造函数,因为其中都是内置类型。而在下面栈的例子中,由于其中是自定义类型,默认生成的拷贝构造函数就会发生错误。
typedef int DataType;
class Stack
{
public:
Stack(int capacity = 10)
{
cout << "Stack(size_t capacity = 10)" << endl;
_a = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _a)
{
perror("malloc申请空间失败");
exit(-1);
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_a[_size] = data;
_size++;
}
//...
~Stack()
{
cout << "~Stack()" << endl;
if (_a)
{
free(_a);
_a = nullptr;
_capacity = 0;
_size = 0;
}
}
//private:
// 成员变量
int* _a;
int _size;
int _capacity;
};
int main()
{
Stack s1;
//s1.Push(1);
Stack s2(s1);
return 0;
}
编译器可以承担内置类型的拷贝,但是无法承担自定义类型拷贝,在有些场景下就会出现问题。
可以看出两者的地址是一致的:
其中的原因上面我们也提到过一些,因为栈中的_a指针指向了同一块空间,这样就会引发一些问题1、插入删除数据会互相影响;2、析构两次,程序崩溃,当析构函数首先释放掉了s2,但此时指向s1的空间的指针就会变成了野指针。
因此,需要我们自己实现深拷贝:
Stack(const Stack& st)
{
cout << "Stack(const Stack& st)" << endl;
_a = (DataType*)malloc(sizeof(DataType)*st._capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败");
exit(-1);
}
memcpy(_a, st._a, sizeof(DataType)*st._size);
_size = st._size;
_capacity = st._capacity;
}
让两个栈都有拥有自己的空间就可以解决上述的问题。
在自己实现了析构函数释放空间的情况下,就需要实现拷贝构造函数。 因此在C++中内置类型可以直接拷贝,自定义类型需要调用拷贝构造。