C++ 继承和派生
继承和派生
一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。
从父类产生一个子类,称为派生。
基类与派生类的关系:派生类是基类的具体化,而基类是派生类的抽象。
定义基类和派生类
定义基类
例:一个矩形类。
/*矩形类*/
class Rectangle
{
protected:/*被保护成员*/
double L = 0;
double W = 0;
public: /*共有成员*/
Rectangle() = default;
Rectangle(double a, double b) : L{ a }, W{ b } {}
double GetArea() const /*矩形面积*/
{
return L * W;
}
double GetGirth() const /*矩形周长*/
{
return 2 * (L + W);
}
};
基类成员的访问属性
- 共有成员(public member)可以在程序的任何地方被访问。
- 私有成员(private member)可以被类的成员函数(或友元函数和友元类)使用,类外不能使用。
- 被保护成员(protected member)可以被类的成员函数(或友元函数和友元类)使用,类外不能使用,但可以被派生类的成员函数使用。
如果省略成员访问限定符,则系统默成员都是私有的。
public、private、protected 关键字被称为成员访问限定符。
定义派生类
派生类必须通过使用类派生列表(class derivation list)明确指出它是从哪个(或哪些)基类继承而来的。
类派生列表的形式是:
class 派生类名:[继承方式] 基类名
{
派生类新增加的成员
};
首先是一个冒号,后面紧跟以逗号分隔的基类列表。
其中每个基类前面可以有以下三种访问说明符中的一个:public、protected 或者 private。
派生类的构造函数
派生类可以接收基类全部的成员,但是不能继承构造函数和析构函数。
派生类必须使用基类的构造函数来初始化它的基类部分。
例:
/*长方体类*/
class Cuboid : public Rectangle
{
private:
double H = 0;
public:
Cuboid() = default;
Cuboid(double a, double b, double c) : Rectangle{ a,b }, H{ c } {}
// Rectangle{ a,b }是基类的构造函数
};
执行派生类构造函数的顺序:
先调用基类构造函数,对基类数据成员初始化;
然后执行派生类构造函数本身,对派生类数据成员初始化。
派生类的拷贝构造和移动构造函数
在默认情况下,基类默认构造函数初始化派生类对象的基类部分。
如果我们想拷贝((或移动)基类部分,则必须在派生类的构造函数初始值列表中显式地使用基类的拷贝(或移动)构造函数。
合成的拷贝、赋值、移动操作
某些定义基类的方式也可能导致有的派生类成员成为被删除的函数:
- 如果基类中的默认构造函数、拷贝构造函数、拷贝赋值运算符或析构函数是被删除的函数或者不可访问,则派生类中对应的成员将是被删除的。
- 如果在基类中有一个不可访问或删除掉的析构函数,则派生类中合成的默认和拷贝构造函数将是被删除的。
- 如果基类的移动操作是删除的或不可访问的,则派生类的移动操作也将是被删除的。
- 如果基类的析构函数是删除的或不可访问的,则派生类的移动构造函数也将是被删除的。
继承的构造函数
C++11标准允许派生类继承基类构造函数的方式是提供一条注明了(直接)基类名的 using 声明语句。
/*正方形类*/
class Square
{
protected:
double L;
public:
Square() :L{ 4 } {};
Square(double a) :L{ a } {}
};
/*立方体类*/
class Cube : public Square
{
public:
Square::Square;
/*
等价于下列构造函数:
Cube() :Square() {};
Cube(double a) :Square{ a } {}
*/
};
一个构造函数的 using 声明不会改变该构造函数的访问级别。
一个 using 声明语句不能指定 explicit 或 constexpr。
继承方式
通过指定继承方式,可以改变基类成员在派生类中的访问属性。
公用继承
公用继承(public inheritance)基类的共有成员和被保护成员在派生类中保持原有访问属性。
派生类可以访问基类的共有成员和被保护的成员。
class Cuboid : public Rectangle
{
private:
double H = 0;
public:
Cuboid() = default;
Cuboid(double a, double b, double c) : Rectangle{ a,b }, H{ c } {}
double GetVolume() const /*长方体体积*/
{//访问基类被保护成员L、W
double V = L * W * H; //底面积乘高
return V;
}
double GetSurfaceArea() const /*长方体表面积*/
{//访问基类共有成员GetArea(),GetGirth()
double S = 2 * GetArea() + H * GetGirth(); //底面积 + 侧面积
return S;
}
};
派生类的对象可以访问基类的共有成员和本类的共有成员。
Cuboid Cu{ 3,3,3 };
cout << "底面积:" << Cu.GetArea() << endl;
cout << "体积:" << Cu.GetVolume() << endl;
cout << "底周长:" << Cu.GetGirth() << endl;
cout << "表面积:" << Cu.GetSurfaceArea() << endl;
私有继承和受保护的继承
受保护的继承(protected inheritance)基类的共有成员和被保护成员在派生类中成了被保护成员。
私有继承(private inheritance)基类的共有成员和被保护成员在派生类中成了私有成员。
派生类可以访问基类的共有成员和被保护的成员。
class Cuboid : protected Rectangle
{
private:
double H = 0;
public:
Cuboid() = default;
Cuboid(double a, double b, double c) : Rectangle{ a,b }, H{ c } {}
double GetVolume() const /*长方体体积*/
{//访问基类被保护成员L、W
double V = L * W * H; //底面积乘高
return V;
}
double GetSurfaceArea() const /*长方体表面积*/
{//访问基类共有成员GetArea(),GetGirth()
double S = 2 * GetArea() + H * GetGirth(); //底面积 + 侧面积
return S;
}
};
如果基类的共有成员在派生类中成了被保护或私有成员,则派生类的对象只可以访问本类的共有成员。
Cuboid Cu{ 3,3,3 };
cout << "体积:" << Cu.GetVolume() << endl;
cout << "表面积:" << Cu.GetSurfaceArea() << endl;
改变个别成员的可访问性
有时我们需要改变派生类继承的某个名字的访问级别,通过使用 using 声明可以达到这一目的:
/*长方体类*/
class Cuboid : public Rectangle
{
private:
double H = 0;
public:
using Rectangle::W; //受保护的成员变成共有成员
Cuboid() = default;
Cuboid(double a, double b, double c) : Rectangle{ a,b }, H{ c } {}
};
派生类到基类的隐式类型转换
- 派生类对象能向基类对象赋值。(派生类增加的部分会被切掉。)
- 派生类对象能向基类对象的引用进行赋值或初始化。
- 派生类对象的地址能赋给指向基类对象的指针变量。
- 如果函数参数是基类的对象或基类对象的引用,相应的实参能用派生类对象。
通过基类的对象、基类对象的引用或指向基类的指针,只能访问派生类中基类部分的共有成员,而不能访问派生类增加的成员。
不存在基类向派生类的隐式类型转换。
防止继承的发生
有时我们会定义这样一种类,我们不希望其他类继承它,或者不想考虑它是否适合作为一个基类。C++11新标准提供了一种防止继承发生的方法,即在类名后跟一个关键字 final。
/*矩形类*/
class Rectangle final //不允许被继承
{
protected:/*被保护成员*/
double L = 0;
double W = 0;
public: /*共有成员*/
Rectangle() = default;
Rectangle(double a, double b) : L{ a }, W{ b } {}
};