第15章 类的继承
继承的概念
继承是面向对象编程的三大核心特性之一,它允许一个类(派生类)继承另一个类(基类)的属性和方法。通过继承,派生类可以重用基类的代码,同时添加自己特有的功能。
继承的目的
- 代码重用:避免重复编写相同的代码
- 建立层次关系:通过继承建立类之间的层次结构
- 多态性:为实现多态性提供基础
继承的基本语法
定义派生类
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Base { public: void baseMethod() { std::cout << "Base method" << std::endl; } };
class Derived : public Base { public: void derivedMethod() { std::cout << "Derived method" << std::endl; } };
|
访问控制
C++支持三种继承方式,它们决定了基类成员在派生类中的访问权限:
- public继承:基类的public成员在派生类中仍然是public,protected成员在派生类中仍然是protected,private成员在派生类中不可访问
- protected继承:基类的public和protected成员在派生类中都是protected,private成员在派生类中不可访问
- private继承:基类的public和protected成员在派生类中都是private,private成员在派生类中不可访问
1 2 3 4 5 6 7 8
| class DerivedPublic : public Base { };
class DerivedProtected : protected Base { };
class DerivedPrivate : private Base { };
|
构造函数与析构函数
构造函数的调用顺序
当创建派生类对象时,构造函数的调用顺序是:
- 基类的构造函数
- 派生类的构造函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Base { public: Base() { std::cout << "Base constructor" << std::endl; } };
class Derived : public Base { public: Derived() { std::cout << "Derived constructor" << std::endl; } };
Derived d;
|
带参数的构造函数
如果基类有带参数的构造函数,派生类必须在其构造函数中显式调用基类的构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Base { public: Base(int value) : data(value) { std::cout << "Base constructor with value: " << value << std::endl; } private: int data; };
class Derived : public Base { public: Derived(int baseValue, int derivedValue) : Base(baseValue), derivedData(derivedValue) { std::cout << "Derived constructor with value: " << derivedValue << std::endl; } private: int derivedData; };
Derived d(10, 20);
|
析构函数的调用顺序
当销毁派生类对象时,析构函数的调用顺序是:
- 派生类的析构函数
- 基类的析构函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Base { public: ~Base() { std::cout << "Base destructor" << std::endl; } };
class Derived : public Base { public: ~Derived() { std::cout << "Derived destructor" << std::endl; } };
{ Derived d; }
|
成员函数的重写
重写基类的成员函数
派生类可以重写基类的成员函数,以提供自己的实现。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Base { public: void show() { std::cout << "Base::show()" << std::endl; } };
class Derived : public Base { public: void show() { std::cout << "Derived::show()" << std::endl; } };
|
调用基类的成员函数
派生类可以通过作用域解析运算符(::)调用基类的成员函数。
1 2 3 4 5 6 7
| class Derived : public Base { public: void show() { Base::show(); std::cout << "Derived::show()" << 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
| class Base { public: Base(const Base& other) : data(other.data) { std::cout << "Base copy constructor" << std::endl; } Base& operator=(const Base& other) { if (this != &other) { data = other.data; std::cout << "Base assignment operator" << std::endl; } return *this; } private: int data; };
class Derived : public Base { public: Derived(const Derived& other) : Base(other), derivedData(other.derivedData) { std::cout << "Derived copy constructor" << std::endl; } Derived& operator=(const Derived& other) { if (this != &other) { Base::operator=(other); derivedData = other.derivedData; std::cout << "Derived assignment operator" << std::endl; } return *this; } private: int derivedData; };
|
移动构造函数和移动赋值运算符
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 30 31 32 33 34
| class Base { public: Base(Base&& other) noexcept : data(std::move(other.data)) { std::cout << "Base move constructor" << std::endl; } Base& operator=(Base&& other) noexcept { if (this != &other) { data = std::move(other.data); std::cout << "Base move assignment operator" << std::endl; } return *this; } private: std::string data; };
class Derived : public Base { public: Derived(Derived&& other) noexcept : Base(std::move(other)), derivedData(std::move(other.derivedData)) { std::cout << "Derived move constructor" << std::endl; } Derived& operator=(Derived&& other) noexcept { if (this != &other) { Base::operator=(std::move(other)); derivedData = std::move(other.derivedData); std::cout << "Derived move assignment operator" << std::endl; } return *this; } private: std::string derivedData; };
|
继承的层次结构
单继承
单继承是指一个派生类只有一个直接基类。
1 2 3
| class A { }; class B : public A { }; class C : public B { };
|
多继承
多继承是指一个派生类有多个直接基类。
1 2 3
| class A { }; class B { }; class C : public A, public B { };
|
菱形继承
菱形继承是多继承的一种特殊情况,当一个派生类从两个不同的基类继承,而这两个基类又从同一个基类继承时,就会形成菱形继承。
1 2 3 4
| class A { }; class B : public A { }; class C : public A { }; class D : public B, public C { };
|
菱形继承会导致二义性问题,因为派生类会继承基类的两个副本。可以使用虚继承来解决这个问题。
虚继承
虚继承是一种特殊的继承方式,它确保派生类只继承基类的一个副本。
1 2 3 4
| class A { }; class B : virtual public A { }; class C : virtual public A { }; class D : public B, public C { };
|
继承与访问控制
protected成员
protected成员可以被基类的成员函数、派生类的成员函数和友元访问,但不能被外部代码访问。
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
| class Base { protected: int protectedData; public: void baseMethod() { protectedData = 10; } };
class Derived : public Base { public: void derivedMethod() { protectedData = 20; } };
int main() { Base b; Derived d; return 0; }
|
private成员
private成员只能被基类的成员函数和友元访问,不能被派生类的成员函数访问。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Base { private: int privateData; public: void baseMethod() { privateData = 10; } };
class Derived : public Base { public: void derivedMethod() { } };
|
继承的最佳实践
1. 合理使用继承
- 继承是一种”is-a”关系:派生类应该是基类的一种特殊类型
- 避免过深的继承层次:继承层次应该控制在合理的范围内(一般不超过3-4层)
- 优先使用组合而非继承:当派生类和基类之间不是”is-a”关系时,应该使用组合
2. 设计良好的基类
- 提供虚析构函数:如果基类可能被继承,应该提供虚析构函数
- 使用protected成员:对于需要被派生类访问的成员,使用protected而非private
- 提供合适的构造函数:为基类提供合适的构造函数,包括默认构造函数
3. 正确使用访问修饰符
- public继承:最常用的继承方式,适用于”is-a”关系
- protected继承:适用于需要将基类的接口作为派生类内部实现的一部分
- private继承:适用于需要使用基类的实现但不希望暴露基类接口
示例:继承的应用
图形类层次结构
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
| #include <iostream> #include <memory>
class Shape { public: virtual void draw() const = 0; virtual double area() const = 0; virtual ~Shape() = default; };
class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} void draw() const override { std::cout << "Drawing a circle with radius " << radius << std::endl; } double area() const override { return 3.14159 * radius * radius; } };
class Rectangle : public Shape { private: double width; double 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; } };
int main() { std::unique_ptr<Shape> shape1 = std::make_unique<Circle>(5.0); std::unique_ptr<Shape> shape2 = std::make_unique<Rectangle>(4.0, 6.0); shape1->draw(); std::cout << "Area: " << shape1->area() << std::endl; shape2->draw(); std::cout << "Area: " << shape2->area() << std::endl; return 0; }
|
动物类层次结构
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 73 74 75
| #include <iostream> #include <string>
class Animal { protected: std::string name; public: Animal(const std::string& n) : name(n) {} virtual void makeSound() const = 0; virtual void move() const = 0; virtual ~Animal() = default; };
class Mammal : public Animal { public: Mammal(const std::string& n) : Animal(n) {} void move() const override { std::cout << name << " walks" << std::endl; } };
class Bird : public Animal { public: Bird(const std::string& n) : Animal(n) {} void move() const override { std::cout << name << " flies" << std::endl; } };
class Dog : public Mammal { public: Dog(const std::string& n) : Mammal(n) {} void makeSound() const override { std::cout << name << " barks" << std::endl; } };
class Cat : public Mammal { public: Cat(const std::string& n) : Mammal(n) {} void makeSound() const override { std::cout << name << " meows" << std::endl; } };
class Eagle : public Bird { public: Eagle(const std::string& n) : Bird(n) {} void makeSound() const override { std::cout << name << " screeches" << std::endl; } };
int main() { Dog dog("Rex"); Cat cat("Whiskers"); Eagle eagle("Baldy"); dog.makeSound(); dog.move(); cat.makeSound(); cat.move(); eagle.makeSound(); eagle.move(); return 0; }
|
总结
继承是C++面向对象编程的重要特性,它允许派生类继承基类的属性和方法,实现代码重用和建立类的层次结构。合理使用继承可以提高代码的可维护性和可扩展性,但也需要注意避免过度使用继承,特别是在复杂的系统中。在设计类层次结构时,应该遵循”is-a”关系原则,优先使用组合而非继承,并注意处理好构造函数、析构函数和访问控制等问题。