既然我们可以通过传确定的参数来完成初始化,那么相同的两个类之间能不能通过拷贝的方式来完成初始化
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,1,26);
Date d2(d1);
return 0;
}
以上代码中d1拷贝给了d2,并完成了初始化,里面成员变量的值和d1完全一样,这说明我们是可以传相同的类来初始化,但其实在d2完成初始化之前编译器帮你完成了一件事:拷贝构造。这里并没有显示的写拷贝构造函数,是因为编译器自动生成了一个默认拷贝构造,这种只发生在内置类型上的拷贝称为浅拷贝(也叫值拷贝)。
如果要将这里的拷贝构造函数补充完整应该是怎样的呢?
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(Date d)//这样吗?
{
_year = d.year;
_month = d.month;
_day = d.day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024,1,26);
Date d2(d1);
return 0;
}
假如我们用一个自定义类型来接收参数,类型确实是匹配了,但遗憾的是,代码编译到这就不会往下走了,因为在你调用这个函数之前必须传参,而传参数必定又会调用新的拷贝构造....如此循环,就会发生无穷递归。
这时候就只能借助引用&,这样形参就是实参的别名,所以就不会引发新的拷贝构造。
此外,由于我们只是想把d2的值拿d1的值来初始化,也就是说d2的值可以改变而d1的值我们不希望它改变,故在接收时也可以用const修饰。
Date(const Date& d)
可能就会有人说了,既然编译器会帮我们做我们干嘛还要自己写拷贝构造函数
是的,这样的情况下我们的确不需要写拷贝构造,编译器自动生成的拷贝构造也能满足我们的需求,因为这里面只有int型。但我们要是想初始化一个顺序表呢,浅拷贝还能否完成
class my_vector
{
int *_a;
int _capacity;
int _size;
public:
my_vector(int n=4)
{
_a = (int*)malloc(sizeof(int)*n);
if (_a == NULL)
{
perror("malloc failed");
return;
}
_size = 0;
_capacity = n;
}
~my_vector()
{
free(_a);
_a=nullptr;
_size=_capacity=0;
}
};
int main()
{
my_vector v1(4);
my_vector v2(v1);
return 0;
}
可以看到,v2的确完成了拷贝,每个成员变量都和v1完全相同。看似完成了拷贝,而且非常合理,可是编译后却报异常,到底是哪里出现了问题
这里有个细节很容易被忽略:_a指向的空间地址也是完全相同。可是,不就是指向同一个地址嘛,怎么就出现异常呢
别忘了,这是动态申请出来的空间,到最后是要释放的,而这里肯定是要调用析构函数来释放。当v1的生命周期结束时,_a将被释放,v2的生命周期结束时,_a又会被释放一次,同一块空间被释放两次必然会引发异常。
那有没有什么办法让_a指向不同的空间地址,又让成员变量的值相同?
有,深拷贝。想要这样做靠编译器生成的默认拷贝构造肯定不行,那就只能自己写拷贝构造,让它完成深拷贝。
my_vector(const my_vector& v)
{
_a = (int*)malloc(sizeof(int)*v._capacity);
if (_a == NULL)
{
perror("malloc failed");
return;
}
memcpy(_a, v._a, sizeof(int) *v._capacity;
_size = v._size;
_capacity = v._capacity;
}
如此,便完成了深拷贝。
什么时候用深拷贝什么时候浅拷贝?
这个根据情况而定。有时我们只有值拷贝,不涉及到动态空间申请时,用浅拷贝就够了,毕竟编译器能够自动帮我们生成,但凡涉及到动态内存申请,我们应该使用深拷贝,否则就会出现如上错误