① 复制构造函数
是否需要定义复制构造函数完全取决于类的直接成员,只包含类类型或内置类型的数据成员,不含指针的类一般可以使用合成操作,复制,赋值,或撤销这样的成员不需要特殊控制,具有指针成员的类一般需要定义自己的复制控制来管理这些成员。
l 基类定义复制构造函数
此时派生类中若无指针成员,可以不用定义复制构造函数,可以使用合成操作,此时合成操作可以自动的去调用基类的复制构造函数。
l 派生类定义复制构造函数
此时基类中若无指针成员,亦可以使用合成操作,但是我们在定义派生类的复制构造函数时必须显示的调用基类的复制构造函数,否则会调用基类的默认构造函数来初始化基类部分,若我们没有相应的构造函数,则会出现编译错误。
l 派生类和基类均定义复制构造函数
若基类和派生类中均无指针成员,均可使用合成操作,若其中之一用,可以使用上面两种方法,若均有指针成员我们此时必须在派生类和基类中都定义自己的复制构造函数。但我们仍必须在派生类中显示调用自己的复制构造函数。
如果派生类定义了自己的复制构造函数,该复制构造函数一般应显示的使用基类的复制构造函数来初始化基类部分,否则会自动调用基类的默认构造函数来初始化基类部分。
#include <iostream>
using namespace std;
class base
{
protected:
base() {}
base(int a):x(a){}
int x;
};
class derived:public base
{
public:
derived(int a,int b):base(a),y(b){}
derived(derived& d)
{
y = d.y;
}
int getx(){return x;}
int gety(){return y;}
private:
int y;
};
int main()
{
derived e(2,3);
derived d(e);
cout << d.getx() << " " << d.gety() << endl;
return 0;
}
输出后基类部分是未初始化的。派生类部分用3进行初始化。
② 赋值函数
赋值函数和复制构造函数类似:如果派生类定义了自己的赋值操作符,该操作符必须对基类部分进行显示赋值。这里有点注意,一般情况下若是我们使用到赋值函数,说明我们已定义了一个对象需要用到默认构造函数。
如:
#include <iostream>
using namespace std;
class base
{
protected:
base() {}
base(int a):x(a){}
int x;
};
class derived:public base
{
public:
//derived(){}
derived(int a,int b):base(a),y(b){}
derived(derived& d)
{
y = d.y;
}
derived& operator=(derived& d)
{
if (this != &d)
{
base::operator=(d);
y = d.y;
}
return *this;
}
int getx(){return x;}
int gety(){return y;}
private:
int y;
};
int main()
{
derived e(2,3);
derived d;
d = e;
cout << d.getx() << " " << d.gety() << endl;
return 0;
}
此时同样若我们未显示调用父类赋值函数,则会调用了父类的默认构造函数来初始化父类部分。注意这里我们需要检查防止自赋值。
③ 析构函数
析构函数与复制构造函数和赋值函数不同。派生类部分不负责撤销基类对象的成员,编译器总是显示调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员。
对象的撤销顺序与构造顺序相反,首先运行派生类的析构函数,然后按继承层次依次向上调用各基类析构函数。