void fun2(Stack s)
{}
int main()
{
Stack s1;
fun2(s1);
return 0;
}
这里,fun2函数中的形参s对s1这个实参进行拷贝。但是会发生一些错误。
这里的问题其实是浅拷贝问题。
其中,栈中会有main函数的栈帧和fun2函数的栈帧。s1中的其他成员变量拷贝给s都没问题,但是其中的a是在堆区malloc出来的。此时,s1会把a这块空间的地址也赋值一份给s,让s的a也指向这块空间。
但是,我们知道,析构函数是后创建的先调用。析构函数的调用顺序为——s——s1。
也就是把a也给释放了,所以s1中的a就变成了野指针。
这时,s1再次调用析构函数对a这块空间再次释放时,就会发生代码崩溃的情况。
【C语言默认情况下不会发生,因为C语言中不会自动调用析构函数。】
对于浅拷贝问题的解决方案:
引用
void fun2(Stack &s)//引用
{}
int main()
{
Stack s1;
fun2(s1);
return 0;
}
但是引用使用后,s的改变就会影响s1(push,pop...),如果想要s的改变对s1没有影响的话,就得真正的进行拷贝。所以,我们用到了拷贝构造函数。
【BTW】:引用对象实质上是变量的一个别名,不会调用析构函数。
拷贝构造函数也是一种特殊的成员函数,特征如下:
1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须是类型对象的引用,使用传值方编译器直接报错,因为会产生无限递归,如下:
Stack(Stack s) //如果拷贝构造是直接传值
{
capacity = s.capacity;
top = s.top;
a = s.a;
}int main()
{
Stack s;
Stack s1(s);
//这里调用拷贝构造函数,当传值给形参Stack s时就得调用拷贝构造函数,所以是每当进行一个传值,就调用一次拷贝构造函数,进行无限递归。
return 0;
}
其实就是因为自定义类型传参的时候,要调用拷贝构造函数。
比如,如下调用函数
这样传值是不行的。
所以最终的拷贝构造函数的形式为:引用
Stack(const Stack& s) //加上const养成习惯,防止下面赋值赋反了。
{
capacity = s.capacity;
top = s.top;
a = s.a;
}
如上是我们自己写的有关于自定义类型的拷贝构造函数,但是如果我们没有写某些类的拷贝构造函数,系统也会自动生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
【注意】:默认的拷贝构造函数对于内置类型,会进行值拷贝(浅拷贝)。而对于自定义类型,会调用它的拷贝。
总结:Date类型不需要我们写拷贝构造函数,默认生成的就够用了。而Stack类型需要我们写拷贝构造函数来完成深拷贝,否则会在析构阶段产生错误。