继承
继承基础
继承与派生
cpp
class Base
{
// ... base class members
};
class Derived: access-specifier Base
{
// ... derived class members
};
其中 access-specifier 可以是 public(这是最常见的,表示派生类是一个基类)、private 或 protected(表示派生类有一个基类)。
下面的继承层次结构表明,Carp 类是从 Fish 类派生而来的:
cpp
class Fish // base class
{
// ... Fish's members
};
class Carp:public Fish // derived class
{
// ... Carp's members
};
阅读介绍继承的文献时,您将遇到"从...继承而来"和"从...派生而来"等术语,它们的含义相同。
同样,基类也被称为超类;从基类派生而来的类称为派生类,也叫子类。
访问限定符protected
与 public 和 private 一样,protected 也是一个访问限定符。将属性声明为 protected 的时,相当于允许派生类和友元类访问它,但禁止在继承层次结构外部(包括 main( ))访问它。
一种更好的 Fish 类设计,它使用关键字 protected 将成员属性只暴露给派生类:
cpp
0: #include <iostream>
1: using namespace std;
2:
3: class Fish
4: {
5: protected:
6: bool isFreshWaterFish; // accessible only to derived classes
7:
8: public:
9: void Swim()
10: {
11: if (isFreshWaterFish)
12: cout << "Swims in lake" << endl;
13: else
14: cout << "Swims in sea" << endl;
15: }
16: };
17:
18: class Tuna: public Fish
19: {
20: public:
21: Tuna()
22: {
23: isFreshWaterFish = false; // set protected member in base
24: }
25: };
26:
27: class Carp: public Fish
28: {
29: public:
30: Carp()
31: {
32: isFreshWaterFish = false;
33: }
34: };
35:
36: int main()
37: {
38: Carp myLunch;
39: Tuna myDinner;
40:
41: cout << "About my food" << endl;
42:
43: cout << "Lunch: ";
44: myLunch.Swim();
45:
46: cout << "Dinner: ";
47: myDinner.Swim();
48:
49: // uncomment line below to see that protected members
50: // are not accessible from outside the class hierarchy
51: // myLunch.isFreshWaterFish = false;
52:
53: return 0;
54: }
基类初始化,向基类传递参数
包含初始化列表的派生类构造函数:
cpp
0: #include <iostream>
1: using namespace std;
2:
3: class Fish
4: {
5: protected:
6: bool isFreshWaterFish; // accessible only to derived classes
7:
8: public:
9: // Fish constructor
10: Fish(bool isFreshWater) : isFreshWaterFish(isFreshWater){}
11:
12: void Swim()
13: {
14: if (isFreshWaterFish)
15: cout << "Swims in lake" << endl;
16: else
17: cout << "Swims in sea" << endl;
18: }
19: };
20:
21: class Tuna: public Fish
22: {
23: public:
24: Tuna(): Fish(false) {} // constructor initializes base
25: };
26:
27: class Carp: public Fish
28: {
29: public:
30: Carp(): Fish(true) {}
31: };
32:
33: int main()
34: {
35: Carp myLunch;
36: Tuna myDinner;
37:
38: cout << "About my food" << endl;
39:
40: cout << "Lunch: ";
41: myLunch.Swim();
42:
43: cout << "Dinner: ";
44: myDinner.Swim();
45:
46: return 0;
47: }
为了最大限度地提高安全性,对于派生类不需要访问的基类属性,别忘了将其声明为私有的。
在派生类中覆盖基类的方法
如果派生类实现了从基类继承的函数,且返回值和特征标相同,就相当于覆盖了基类的这个方法,如下面的代码所示:
cpp
class Base
{
public:
void DoSomething()
{
// implementation code... Does something
}
};
class Derived:public Base
{
public:
void DoSomething()
{
// implementation code... Does something else
}
};
因此,如果使用 Derived 类的实例调用方法 DoSomething( ),调用的将不是 Base 类中的这个方法。
要调用 Base::DoSomething( ),只能在 main( )中使用作用域解析运算符显式地调用它。
调用基类中被覆盖的方法
派生类 Tuna 通过实现 Swim( )覆盖了 Fish 类的 Swim( )函数,其结果如下:
cpp
Tuna myDinner;
myDinner.Swim(); // will invoke Tuna::Swim()
如果要在 main( )中调用 Fish::Swim( ),需要使用作用域解析运算符(::),如下所示:
cpp
myDinner.Fish::Swim(); // invokes Fish::Swim() using instance of Tuna
在派生类中调用基类的方法
通常,Fish::Swim( )包含适用于所有鱼类(包括金枪鱼和鲤鱼)的通用实现。如果要在 Tuna::Swim( )和 Carp::Swim( )的实现中重用 Fish::Swim( )的通用实现,可使用作用域解析运算符(::),如下面的代码所示:
cpp
class Carp: public Fish
{
public:
Carp(): Fish(true) {}
void Swim()
{
cout << "Carp swims real slow" << endl;
Fish::Swim(); // invoke base class function using operator::
}
};
在派生类中隐藏基类的方法
覆盖的一种极端情形是,Tuna::Swim( )可能隐藏 Fish::Swim( )的所有重载版本,使得调用这些重载版本会导致编译错误(因此称为被隐藏),
Tuna::Swim( )隐藏了重载方法 Fish::Swim(bool) :
cpp
0: #include <iostream>
1: using namespace std;
2:
3: class Fish
4: {
5: public:
6: void Swim()
7: {
8: cout << "Fish swims... !" << endl;
9: }
10:
11: void Swim(bool isFreshWaterFish) // overloaded version
12: {
13: if (isFreshWaterFish)
14: cout << "Swims in lake" << endl;
15: else
16: cout << "Swims in sea" << endl;
17: }
18: };
19:
20: class Tuna: public Fish
21: {
22: public:
23: void Swim()
24: {
25: cout << "Tuna swims real fast" << endl;
26: }
27: };
28:
29: int main()
30: {
31: Tuna myDinner;
32:
33: cout << "About my food" << endl;
34:
35: // myDinner.Swim(false);//failure: Tuna::Swim() hides Fish::Swim(bool)
36: myDinner.Swim();
37:
38: return 0;
39: }
要通过 Tuna 实例调用 Fish::Swim(bool),可采用如下解决方案。
- 解决方案 1:在 main( )中使用作用域解析运算符(::):
cpp
myDinner.Fish::Swim();
- 解决方案 2:在 Tuna 类中,使用关键字 using 解除对 Fish::Swim( )的隐藏:
cpp
class Tuna: public Fish
{
public:
using Fish::Swim; // unhide all Swim() methods in class Fish
void Swim()
{
cout << "Tuna swims real fast" << endl;
}
};
- 解决方案 3:在 Tuna 类中,覆盖 Fish::Swim( )的所有重载版本(如果需要,可通过 Tuna::Fish(...) 调用方法 Fish::Swim( )):
cpp
class Tuna: public Fish
{
public:
void Swim(bool isFreshWaterFish)
{
Fish::Swim(isFreshWaterFish);
}
void Swim()
{
cout << "Tuna swims real fast" << endl;
}
};
C++ 继承中构造、析构完整执行顺序
一、单一继承(父类 → 子类)
1. 构造函数执行顺序(先父后子)
- 基类(父类)构造函数 先执行
- 子类自身成员变量按声明顺序构造
- 派生类(子类)构造函数体 执行
原理:子类依赖父类的成员,必须先把父类完整构造好,再构造子类。
cpp
#include <iostream>
using namespace std;
class Base {
public:
Base() { cout << "基类构造\n"; }
};
class Son : public Base {
public:
Son() { cout << "子类构造\n"; }
};
int main() {
Son s;
return 0;
}
输出:
基类构造
子类构造
2. 析构函数执行顺序(先子后父,和构造完全相反)
- 派生类(子类)析构函数体 先执行
- 子类成员变量逆序销毁
- 基类(父类)析构函数 最后执行
cpp
// 接上例,增加析构
class Base {
public:
Base() { cout << "基类构造\n"; }
~Base() { cout << "基类析构\n"; }
};
class Son : public Base {
public:
Son() { cout << "子类构造\n"; }
~Son() { cout << "子类析构\n"; }
};
int main() {
Son s;
return 0;
}
输出:
基类构造
子类构造
子类析构
基类析构
二、包含成员对象(类中有其他类成员)
规则:先构造成员,再执行自身构造函数体;析构相反
执行流程:
- 父类构造(如有继承)
- 类内所有成员变量,按声明从上到下依次构造(和初始化列表顺序无关)
- 当前类构造函数体
析构完全逆序: - 当前类析构函数体
- 成员变量按声明倒序销毁
- 父类析构(如有继承)
示例:
cpp
struct A { A(){cout<<"A构造";} ~A(){cout<<"A析构";} };
struct B { B(){cout<<"B构造";} ~B(){cout<<"B析构";} };
class Test {
A a; // 先声明
B b; // 后声明
public:
Test() { cout << "Test构造"; }
~Test() { cout << "Test析构"; }
};
构造顺序:A → B → Test
析构顺序:Test → B → A
三、多重继承(多个父类)
构造顺序:按继承声明顺序,不是初始化列表顺序
cpp
class A{};
class B{};
// 继承顺序:先A,后B
class C : public A, public B {
public:
// 哪怕列表先写B(),构造依然先A后B
C():B(),A(){}
};
构造:A → B → C
析构:C → B → A
四、虚继承(菱形继承)
公共虚基类最先构造 ,只构造一次;其余普通父类按继承顺序。
析构时虚基类最后销毁。
五、多态重点考点:虚析构函数
如果用父类指针指向子类对象 ,父类析构不加virtual只会调用父类析构,子类析构不执行,内存泄漏!
cpp
class Base {
public:
virtual ~Base(){} // 必须virtual
};
class Son : public Base {};
int main() {
Base* p = new Son();
delete p; // 先Son析构,再Base析构
return 0;
}
私有继承与保护继承
注:派生类不能继承父类的private,不论是什么继承方式。
前面介绍的都是公有继承,私有继承的不同之处在于,指定派生类的基类时使用关键字 private:
cpp
class Base
{
// ... base class members and methods
};
class Derived: private Base // private inheritance
{
// ... derived class members and methods
};
私有继承意味着在派生类的实例中,基类的所有公有成员和方法都是私有的------不能从外部访问。
换句话说,即便是 Base 类的公有成员和方法,也只能被 Derived 类使用,而无法通过 Derived 实例来使用它们。
一、基础规则(所有继承通用)
任何派生类,永远无法直接访问基类 private 私有成员 ;
仅能直接访问基类 public、protected 成员。
基类private成员会存在于子类内存,只能通过基类提供的接口间接操作。
二、三种继承方式权限转换(父类Motor → 子类Car)
| 继承方式 | Motor::public 在Car内 | Motor::protected 在Car内 | Motor::private |
|---|---|---|---|
| public 公有继承 | public | protected | 不可访问 |
| protected 保护继承 | protected | protected | 不可访问 |
| private 私有继承 | private | private | 不可访问 |
记忆
- public继承:protected/public权限原样保留,不收紧
- protected继承:protected/public 进子类统一变成protected
- private继承:protected/public降级为子类私有,阻断下层派生类访问
三、多层继承访问规则(Motor → Car → RaceCar)
场景1:Car : private Motor(题目示例)
- Motor的public/protected → 全部变为Car的私有成员
- 无论RaceCar以何种方式继承Car,都不能访问Car的私有成员
- 最终结论:RaceCar 无法访问 Motor 的任何 public/protected
- 核心原理:继承链中最严格的private继承会锁住上层所有成员,下层派生类无法穿透
场景2:Car : public Motor
- Motor的public保留为Car的public,Motor的protected保留为Car的protected
- RaceCar继承Car后,仍可正常访问Motor的public/protected
- 整条链路无权限收紧,访问权限可向下传递
只有基类自己的成员函数,能直接操作自身 private;
子类想操作基类 private,只能调用基类提供的public/protected成员函数中转;
三种继承的区别,只影响基类 public/protected 在子类中的访问等级,完全不改变基类 private 的访问限制。
切除问题
如果程序员像下面这样做,结果将如何呢?
cpp
Derived objDerived;
Base objectBase = objDerived;
如果程序员像下面这样做,结果又将如何呢?
cpp
void UseBase(Base input);
...
Derived objDerived;
UseBase(objDerived); // copy of objDerived will be sliced and sent
它们都将 Derived 对象复制给 Base 对象,一个是通过显式地复制,另一个是通过传递参数。在这
些情形下,编译器将只复制 objDerived 的 Base 部分,即不是整个对象。换句话说,Derived 的数据成员包含的信息将丢失。这种无意间裁减数据,导致 Derived 变成 Base 的行为称为切除(slicing)。
要避免切除问题,不要按值传递参数,而应以指向基类的指针或 const 引用的方式传递。
多继承
使用多继承模拟具备哺乳动物、鸟类和爬行动物特征的鸭嘴兽:
cpp
0: #include <iostream>
1: using namespace std;
2:
3: class Mammal
4: {
5: public:
6: void FeedBabyMilk()
7: {
8: cout << "Mammal: Baby says glug!" << endl;
9: }
10: };
11:
12: class Reptile
13: {
14: public:
15: void SpitVenom()
16: {
17: cout << "Reptile: Shoo enemy! Spits venom!" << endl;
18: }
19: };
20:
21: class Bird
22: {
23: public:
24: void LayEggs()
25: {
26: cout << "Bird: Laid my eggs, am lighter now!" << endl;
27: }
28: };
29:
30: class Platypus: public Mammal, public Bird, public Reptile
31: {
32: public:
33: void Swim()
34: {
35: cout << "Platypus: Voila, I can swim!" << endl;
36: }
37: };
38:
39: int main()
40: {
41: Platypus realFreak;
42: realFreak.LayEggs();
43: realFreak.FeedBabyMilk();
44: realFreak.SpitVenom();
45: realFreak.Swim();
46:
47: return 0;
48: }
使用 final 禁止继承
从 C++11 起,编译器支持限定符 final。被声明为 final 的类不能用作基类。如下:Platypus 类表示一种进化得很好的物种,因此您可能想将其声明为 final 的,从而禁止继承它。可像下面这样做:
cpp
class Platypus final: public Mammal, public Bird, public Reptile
{
public:
void Swim()
{
cout << "Platypus: Voila, I can swim!" << endl;
}
};
除用于类外,还可将 final 用于成员函数来控制多态行为。