第14章 类继承

继承的概念与原理

继承是面向对象编程的核心特性之一,它允许从已有类(基类/父类)派生出新类(派生类/子类),实现代码重用和类型层次结构的建立。

继承的核心原理

  1. 代码重用(Code Reuse):派生类自动继承基类的成员变量和成员函数,避免重复代码
  2. 层次结构(Hierarchy):建立类的层次关系,反映现实世界的分类体系
  3. 类型关系(Type Relationship):派生类与基类之间形成”is-a”关系
  4. 多态基础(Polymorphism Foundation):为运行时多态提供基础

继承的语法与实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 基类定义
class Base {
protected:
int value;
public:
Base(int v) : value(v) {
std::cout << "Base constructor called" << std::endl;
}

virtual ~Base() {
std::cout << "Base destructor called" << std::endl;
}

void showValue() const {
std::cout << "Base value: " << value << std::endl;
}

virtual void doSomething() {
std::cout << "Base::doSomething()" << std::endl;
}
};

// 派生类定义
class Derived : public Base {
private:
std::string name;
public:
// 派生类构造函数
Derived(int v, const std::string& n) : Base(v), name(n) {
std::cout << "Derived constructor called" << std::endl;
}

~Derived() {
std::cout << "Derived destructor called" << std::endl;
}

// 新增成员函数
void showName() const {
std::cout << "Derived name: " << name << std::endl;
}

// 重写基类虚函数
void doSomething() override {
std::cout << "Derived::doSomething()" << std::endl;
Base::doSomething(); // 调用基类方法
}
};

// 使用示例
void useInheritance() {
Derived d(42, "Derived");
d.showValue(); // 继承自基类
d.showName(); // 派生类新增
d.doSomething(); // 多态调用
}

继承的类型

C++支持三种继承方式,不同的继承方式会影响基类成员在派生类中的可访问性。

公有继承(public inheritance)

核心特性:基类的public成员在派生类中仍然是publicprotected成员仍然是protectedprivate成员不可访问。

应用场景:体现”is-a”关系,是最常用的继承方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Shape {
public:
virtual double area() const = 0;
virtual void draw() const = 0;
};

class Circle : public Shape {
private:
double radius;
public:
explicit Circle(double r) : radius(r) {}

double area() const override {
return M_PI * radius * radius;
}

void draw() const override {
std::cout << "Drawing a circle with radius " << radius << std::endl;
}
};

保护继承(protected inheritance)

核心特性:基类的publicprotected成员在派生类中都变为protectedprivate成员不可访问。

应用场景:体现”is-implemented-in-terms-of”关系,用于实现细节的重用。

私有继承(private inheritance)

核心特性:基类的publicprotected成员在派生类中都变为privateprivate成员不可访问。

应用场景:体现”is-implemented-in-terms-of”关系,用于完全隐藏基类接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 私有继承示例:实现组合的一种方式
class String {
private:
std::vector<char> buffer; // 组合方式
public:
// 接口实现
};

// 等价的私有继承方式
class String : private std::vector<char> {
public:
// 接口实现,显式暴露需要的方法
void push_back(char c) {
std::vector<char>::push_back(c);
}

size_t size() const {
return std::vector<char>::size();
}
};
```。

## 构造函数与析构函数的继承

### 构造函数的调用顺序

**核心规则**:派生类对象创建时,构造函数的调用顺序是从基类到派生类。

1. **基类构造函数**:先调用最顶层基类的构造函数
2. **成员变量构造函数**:按照声明顺序调用成员变量的构造函数
3. **派生类构造函数**:最后调用派生类自身的构造函数

```cpp
class Base1 {
public:
Base1() {
std::cout << "Base1 constructor" << std::endl;
}
};

class Base2 {
public:
Base2() {
std::cout << "Base2 constructor" << std::endl;
}
};

class Derived : public Base1, public Base2 {
private:
std::string name; // 成员变量
public:
Derived() : name("Derived") {
std::cout << "Derived constructor" << std::endl;
}
};

// 调用顺序:Base1 → Base2 → std::string → Derived
void testConstructorOrder() {
Derived d;
}

析构函数的调用顺序

核心规则:派生类对象销毁时,析构函数的调用顺序与构造函数相反。

  1. 派生类析构函数:先调用派生类自身的析构函数
  2. 成员变量析构函数:按照声明顺序的逆序调用成员变量的析构函数
  3. 基类析构函数:最后调用基类的析构函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Base {
public:
virtual ~Base() { // 虚析构函数
std::cout << "Base destructor" << std::endl;
}
};

class Derived : public Base {
private:
std::string name;
public:
~Derived() override {
std::cout << "Derived destructor" << std::endl;
}
};

// 调用顺序:Derived → std::string → Base
void testDestructorOrder() {
Base* ptr = new Derived();
delete ptr; // 多态调用析构函数
}

虚析构函数的重要性

核心原则:当使用基类指针指向派生类对象时,基类的析构函数必须声明为virtual,以确保派生类的析构函数被正确调用。

未使用虚析构函数的风险

  • 只调用基类析构函数,不调用派生类析构函数
  • 可能导致资源泄漏(如文件句柄、内存等)
  • 程序行为未定义

构造函数的继承(C++11+)

核心特性:C++11引入了继承构造函数,允许派生类继承基类的构造函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Base {
private:
int value;
public:
Base() : value(0) {}
Base(int v) : value(v) {}
Base(const std::string& s) : value(std::stoi(s)) {}
};

class Derived : public Base {
private:
std::string name;
public:
// 继承基类的构造函数
using Base::Base;

// 额外的构造函数
Derived(int v, const std::string& n) : Base(v), name(n) {}

// 默认构造函数不会被继承,需要显式定义
Derived() : Base(), name("Default") {}
};

// 使用示例
void useInheritedConstructors() {
Derived d1(42); // 使用继承的 Base(int)
Derived d2("123"); // 使用继承的 Base(const std::string&)
Derived d3(100, "Custom"); // 使用派生类自定义构造函数
}

函数覆盖与隐藏

函数覆盖(Function Overriding)

核心特性:派生类重新实现基类的虚函数,实现运行时多态。

实现条件

  • 基类函数必须声明为virtual
  • 派生类函数必须使用override关键字(C++11+)
  • 函数签名(返回类型、函数名、参数列表)必须完全相同
  • 返回类型可以是协变类型(covariant return type)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Base {
public:
virtual void doSomething() {
std::cout << "Base::doSomething()" << std::endl;
}

virtual Base* clone() const {
return new Base(*this);
}
};

class Derived : public Base {
public:
void doSomething() override {
std::cout << "Derived::doSomething()" << std::endl;
}

// 协变返回类型:返回Derived*而不是Base*
Derived* clone() const override {
return new Derived(*this);
}
};

函数隐藏(Function Hiding)

核心特性:派生类定义了与基类同名的函数,隐藏了基类的函数(无论是否为虚函数)。

注意事项

  • 函数隐藏会阻止基类函数的调用,即使参数列表不同
  • 可以使用using声明来显式引入基类函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Base {
public:
void show(int x) {
std::cout << "Base::show(int): " << x << std::endl;
}

void show(double x) {
std::cout << "Base::show(double): " << x << std::endl;
}
};

class Derived : public Base {
public:
// 隐藏基类的show函数
void show(const std::string& s) {
std::cout << "Derived::show(string): " << s << std::endl;
}

// 显式引入基类的show函数
using Base::show;
};

// 使用示例
void testFunctionHiding() {
Derived d;
d.show(42); // 调用基类的show(int)
d.show(3.14); // 调用基类的show(double)
d.show("Hello"); // 调用派生类的show(string)
}

多重继承与虚拟继承

多重继承的概念

核心特性:C++允许一个派生类从多个基类继承,实现更复杂的类型层次结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Interface1 {
public:
virtual void method1() = 0;
};

class Interface2 {
public:
virtual void method2() = 0;
};

class ConcreteClass : public Interface1, public Interface2 {
public:
void method1() override {
std::cout << "ConcreteClass::method1()" << std::endl;
}

void method2() override {
std::cout << "ConcreteClass::method2()" << std::endl;
}
};

菱形继承问题

核心问题:当一个类从两个不同的路径继承同一个基类时,会产生重复的基类子对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 菱形继承结构
class Base {
public:
int value;
Base() : value(42) {}
};

class Derived1 : public Base { /* ... */ };
class Derived2 : public Base { /* ... */ };
class FinalDerived : public Derived1, public Derived2 { /* ... */ };

// 问题:FinalDerived包含两个Base子对象
void testDiamondProblem() {
FinalDerived obj;
// obj.value = 100; // 歧义错误
obj.Derived1::value = 100; // 需要显式指定
obj.Derived2::value = 200; // 需要显式指定
}

虚拟继承(Virtual Inheritance)

核心解决方案:使用虚拟继承解决菱形继承问题,确保最终派生类只包含一个共享的基类子对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base {
public:
int value;
Base() : value(42) {}
};

// 使用虚拟继承
class Derived1 : public virtual Base { /* ... */ };
class Derived2 : public virtual Base { /* ... */ };
class FinalDerived : public Derived1, public Derived2 { /* ... */ };

// 解决方案:FinalDerived只包含一个Base子对象
void testVirtualInheritance() {
FinalDerived obj;
obj.value = 100; // 无歧义,直接访问
std::cout << "Base value: " << obj.value << std::endl;
}

虚拟继承的构造函数调用

核心规则:在虚拟继承中,虚拟基类的构造函数由最终派生类直接调用,而不是由中间派生类调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class VirtualBase {
public:
VirtualBase(int v) : value(v) {
std::cout << "VirtualBase constructor: " << value << std::endl;
}
int value;
};

class Derived1 : public virtual VirtualBase {
public:
Derived1() : VirtualBase(100) { // 非最终派生类时调用
std::cout << "Derived1 constructor" << std::endl;
}
};

class Derived2 : public virtual VirtualBase {
public:
Derived2() : VirtualBase(200) { // 非最终派生类时调用
std::cout << "Derived2 constructor" << std::endl;
}
};

class FinalDerived : public Derived1, public Derived2 {
public:
// 最终派生类直接调用虚拟基类构造函数
FinalDerived() : VirtualBase(300), Derived1(), Derived2() {
std::cout << "FinalDerived constructor" << std::endl;
}
};

// 调用顺序:VirtualBase(300) → Derived1 → Derived2 → FinalDerived
void testVirtualBaseConstructor() {
FinalDerived obj;
std::cout << "VirtualBase value: " << obj.value << std::endl;
}

继承的最佳实践

1. 优先使用组合而非继承

核心原则:当派生类与基类之间不是”is-a”关系时,应该优先使用组合而非继承。

组合的优势

  • 降低类之间的耦合度
  • 避免继承带来的复杂性(如菱形继承)
  • 更灵活的代码组织方式
  • 符合单一职责原则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 继承方式(不推荐,因为Car不是一个Engine)
class Engine {
public:
void start() { /* 启动引擎 */ }
void stop() { /* 停止引擎 */ }
};

class Car : public Engine { // 错误:Car不是一个Engine
public:
void drive() { /* 驾驶汽车 */ }
};

// 组合方式(推荐)
class Car {
private:
Engine engine; // Car有一个Engine
public:
void start() {
engine.start();
}

void stop() {
engine.stop();
}

void drive() { /* 驾驶汽车 */ }
};

2. 合理设计继承层次

核心原则:继承层次应该保持合理的深度,避免过深的继承链。

最佳实践

  • 继承层次通常不超过3-4层
  • 每一层都应该有明确的职责和抽象
  • 使用接口(纯虚类)定义行为契约
  • 避免在继承层次中混用不同的继承方式

3. 正确使用访问控制

核心原则:根据成员的用途选择合适的访问修饰符。

最佳实践

  • public:只用于类的接口方法
  • protected:用于需要被派生类访问的内部实现
  • private:用于完全内部的实现细节
  • 避免使用friend关键字,除非确实必要

4. 实现多态时的注意事项

核心原则:正确实现虚函数,确保多态行为的正确性。

最佳实践

  • 基类析构函数应该声明为virtual
  • 派生类重写虚函数时使用override关键字
  • 避免在构造函数和析构函数中调用虚函数
  • 考虑使用final关键字防止不必要的覆盖

5. 处理菱形继承

核心原则:当需要多重继承时,正确使用虚拟继承解决菱形继承问题。

最佳实践

  • 只在必要时使用多重继承
  • 优先使用接口(纯虚类)作为多重继承的基类
  • 对于有状态的基类,使用虚拟继承避免重复子对象
  • 明确最终派生类对虚拟基类的初始化责任

继承的实际应用场景

1. 图形系统

核心需求:创建一个支持多种图形的系统,每种图形都有共同的行为(如绘制、计算面积)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Shape {
public:
virtual ~Shape() = default;
virtual void draw() const = 0;
virtual double area() const = 0;
virtual std::string name() const = 0;
};

class Circle : public Shape {
private:
double radius;
public:
explicit Circle(double r) : radius(r) {}

void draw() const override {
std::cout << "Drawing a circle with radius " << radius << std::endl;
}

double area() const override {
return M_PI * radius * radius;
}

std::string name() const override {
return "Circle";
}
};

class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}

void draw() const override {
std::cout << "Drawing a rectangle with width " << width
<< " and height " << height << std::endl;
}

double area() const override {
return width * height;
}

std::string name() const override {
return "Rectangle";
}
};

// 使用多态
void processShapes(const std::vector<std::unique_ptr<Shape>>& shapes) {
for (const auto& shape : shapes) {
shape->draw();
std::cout << "Area: " << shape->area() << std::endl;
}
}

2. 插件系统

核心需求:创建一个支持插件的系统,插件可以扩展系统的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
class Plugin {
public:
virtual ~Plugin() = default;
virtual std::string getName() const = 0;
virtual void initialize() = 0;
virtual void shutdown() = 0;
virtual void execute() = 0;
};

class TextEditorPlugin : public Plugin {
private:
std::string pluginName;
public:
explicit TextEditorPlugin(const std::string& name) : pluginName(name) {}

std::string getName() const override {
return pluginName;
}

void initialize() override {
std::cout << "Initializing " << pluginName << std::endl;
}

void shutdown() override {
std::cout << "Shutting down " << pluginName << std::endl;
}

void execute() override {
std::cout << "Executing " << pluginName << std::endl;
}
};

class SyntaxHighlightPlugin : public TextEditorPlugin {
public:
SyntaxHighlightPlugin() : TextEditorPlugin("Syntax Highlight") {}

void execute() override {
std::cout << "Applying syntax highlighting" << std::endl;
}
};

class AutoCompletePlugin : public TextEditorPlugin {
public:
AutoCompletePlugin() : TextEditorPlugin("Auto Complete") {}

void execute() override {
std::cout << "Providing auto completion suggestions" << std::endl;
}
};

// 插件管理器
class PluginManager {
private:
std::vector<std::unique_ptr<Plugin>> plugins;
public:
void addPlugin(std::unique_ptr<Plugin> plugin) {
plugin->initialize();
plugins.push_back(std::move(plugin));
}

void executeAll() {
for (const auto& plugin : plugins) {
plugin->execute();
}
}

void shutdownAll() {
for (const auto& plugin : plugins) {
plugin->shutdown();
}
}
};

3. 异常层次结构

核心需求:创建一个异常层次结构,用于不同类型的错误处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
class Exception {
private:
std::string message;
public:
explicit Exception(std::string msg) : message(std::move(msg)) {}

virtual ~Exception() = default;

virtual std::string what() const {
return message;
}
};

class RuntimeException : public Exception {
public:
explicit RuntimeException(std::string msg) : Exception(std::move(msg)) {}
};

class IOException : public Exception {
private:
std::string filename;
public:
IOException(std::string msg, std::string file)
: Exception(std::move(msg)), filename(std::move(file)) {}

std::string what() const override {
return Exception::what() + " (File: " + filename + ")";
}
};

class FileNotFoundException : public IOException {
public:
explicit FileNotFoundException(std::string file)
: IOException("File not found", std::move(file)) {}
};

class PermissionDeniedException : public IOException {
public:
explicit PermissionDeniedException(std::string file)
: IOException("Permission denied", std::move(file)) {}
};

// 使用异常层次结构
void readFile(const std::string& filename) {
if (!fileExists(filename)) {
throw FileNotFoundException(filename);
}

if (!hasPermission(filename)) {
throw PermissionDeniedException(filename);
}

// 读取文件...
}

void process() {
try {
readFile("data.txt");
} catch (const FileNotFoundException& e) {
std::cerr << "Error: " << e.what() << std::endl;
// 处理文件不存在的情况
} catch (const PermissionDeniedException& e) {
std::cerr << "Error: " << e.what() << std::endl;
// 处理权限被拒绝的情况
} catch (const Exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
// 处理其他异常
}
}

总结

继承是C++面向对象编程的核心特性之一,它通过代码重用、层次结构建立、类型关系定义和多态基础提供,为构建复杂的软件系统提供了强大的工具。

关键要点

  • 正确理解和使用三种继承方式(公有、保护、私有)
  • 掌握构造函数和析构函数的调用顺序
  • 理解函数覆盖与隐藏的区别
  • 正确处理多重继承和菱形继承问题
  • 优先使用组合而非继承
  • 遵循继承的最佳实践,保持代码的清晰性和可维护性

通过合理应用继承,开发者可以创建更加模块化、可扩展和可维护的C++代码,充分发挥面向对象编程的优势。