前言

本节内容,将会用到前面类的相关知识以及C++基础教程以及C++类&对象,需要你具备前面的知识,如果你还有不会的或者说不熟悉的地方,请重新温习一下

C++ 继承

继承是面向对象编程中一个非常重要的概念。通过继承,一个类(称为派生类)可以直接使用另一个类(称为基类)的属性和方法,而无需重新编写。这种方式不仅能重用代码,还能让程序更易扩展和维护。


什么是继承?

  1. 基类和派生类:

    • 基类:已有的类,其属性和方法可以被其他类继承。
    • 派生类:继承自基类的类,它可以拥有基类的所有公开和保护成员,同时还可以定义自己的成员。
  2. 继承的核心思想:

    • 基类定义了一些通用的功能或特性。
    • 派生类可以直接使用这些功能,同时根据需要添加新的功能或特性。

继承的现实类比

继承可以看作是“是一种(is-a)关系”。例如:

  • 哺乳动物是一种动物。
  • 狗是一种哺乳动物。
  • 因此,狗也是一种动物。

这种逻辑反映在代码中,可以通过继承轻松实现:


继承的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 定义基类(父类)
class Animal {
public:
void eat() {
cout << "动物正在吃东西。" << endl;
}

void sleep() {
cout << "动物正在睡觉。" << endl;
}
};

// 定义派生类(子类)
class Dog : public Animal {
public:
void bark() {
cout << "狗正在汪汪叫。" << endl;
}
};

代码解析:

  1. 基类 Animal:

    • 包含了通用的方法 eat()sleep(),它们适用于所有的动物。
  2. 派生类 Dog:

    • 通过 : public Animal 继承了 Animal 类的所有公开成员。
    • Dog 类中添加了一个新的方法 bark(),用来描述狗特有的行为。
  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
#include <iostream>
using namespace std;

// 定义基类
class Animal {
public:
void eat() {
cout << "动物正在吃东西。" << endl;
}

void sleep() {
cout << "动物正在睡觉。" << endl;
}
};

// 定义派生类
class Dog : public Animal {
public:
void bark() {
cout << "狗正在汪汪叫。" << endl;
}
};

int main() {
Dog myDog;

// 使用继承的方法
myDog.eat(); // 基类的 eat 方法
myDog.sleep(); // 基类的 sleep 方法

// 使用派生类自己的方法
myDog.bark(); // Dog 类的 bark 方法

return 0;
}

输出结果:

1
2
3
动物正在吃东西。
动物正在睡觉。
狗正在汪汪叫。

继承的细节

  1. 访问权限控制:

    • 使用 public 继承时,基类的 public 成员仍然是派生类的 public 成员。
    • 使用 private 继承时,基类的成员即使是 public,在派生类中也会变成 private
  2. is-a 关系的本质:

    • 派生类对象可以看作是基类对象的一种。例如:
      1
      2
      Animal* animal = new Dog();
      animal->eat(); // 可以通过基类指针调用基类的方法
  3. 重载与扩展:

    • 派生类可以覆盖基类的方法,提供更具体的实现。

小总结

  • 继承是为了实现代码复用和逻辑组织。
  • 基类提供通用功能,派生类可以直接使用这些功能,同时扩展自己特有的功能。
  • 继承建立的是一种“是一种(is-a)”的关系,比如“狗是一种动物”。

类的继承详解:基类与派生类

在编程中,一个类可以从其他类继承,这种机制让我们可以复用代码并增强类的功能。继承关系中,被继承的类称为“基类”继承的类称为“派生类”


定义派生类

派生类继承基类的数据和函数。定义派生类的格式如下:

1
2
3
class 派生类名: 访问修饰符 基类名 {
// 派生类的成员
};

关键点:

  1. 访问修饰符publicprotectedprivate):控制基类成员在派生类中的访问权限。
  2. 默认访问修饰符:如果没有写访问修饰符,默认是 private

一个例子:Shape 和 Rectangle

假设我们有一个表示形状的基类 Shape,它可以设置宽度和高度;然后,我们定义一个 Rectangle 类继承自 Shape,并添加计算面积的功能。

代码示例:

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
#include <iostream>
using namespace std;

// 基类:Shape
class Shape {
public:
// 设置宽度和高度
void setWidth(int w) { width = w; }
void setHeight(int h) { height = h; }

protected:
int width; // 受保护的成员
int height;
};

// 派生类:Rectangle
class Rectangle : public Shape {
public:
// 计算矩形的面积
int getArea() { return (width * height); }
};

int main() {
Rectangle rect;

rect.setWidth(5); // 设置宽度
rect.setHeight(7); // 设置高度

// 输出矩形的面积
cout << "Total area: " << rect.getArea() << endl;

return 0;
}

运行结果:

1
Total area: 35

访问权限与继承规则

成员访问权限:

基类的成员根据访问修饰符分为三类:publicprotectedprivate。这会影响派生类和外部类能否访问这些成员。

访问范围 public protected private
同一个类 可以 可以 可以
派生类 可以 可以 不可以
外部类 可以 不可以 不可以

注意:

  • private 成员只能在基类内部访问,派生类和外部类无法访问。
  • protected 成员可以被派生类访问,但不能被外部类直接访问。
  • public 成员任何地方都可以访问。

访问修饰符对派生类的影响:

基类的访问修饰符在派生类中继承的访问权限,取决于派生类的定义方式:

派生方式 基类 public 成员 基类 protected 成员 基类 private 成员
public 保持 public 保持 protected 不可访问
protected 降为 protected 保持 protected 不可访问
private 降为 private 降为 private 不可访问

特殊情况:派生类中不能继承的内容

即使派生类继承了基类的大部分内容,但以下几种特殊情况除外:

  1. 基类的构造函数、析构函数和拷贝构造函数:这些函数不能被派生类直接继承。
  2. 基类的运算符重载函数:例如,重载的 operator+,派生类不会直接继承。
  3. 基类的友元函数:友元函数只对声明它们的类开放,不会被派生类直接继承。

小结

  • 继承让代码复用更高效:基类定义通用的功能,派生类在此基础上扩展。
  • 控制访问权限:通过访问修饰符确保数据的安全性。
  • 继承有例外:构造函数等特殊成员无法继承,需要在派生类中重新定义。

这个机制强大而灵活,是面向对象编程的重要特性!

继承类型

想象一下,我们有一个家族,基类就像是家族中的长辈,而派生类则是晚辈。长辈的财产和技能可以传给晚辈,但是传给的方式不同,晚辈对这些财产和技能的拥有和使用方式也会有所不同。

  1. 公有继承(public)

    • 这就像是长辈公开宣布:“我所有的财产和技能,我的孩子们都可以自由使用。”在这种情况下,如果一个类(我们称之为子类)是从另一个公开的类(基类)继承来的,那么基类中所有公开的成员(比如财产和技能)在子类中也是公开的,可以被外界直接访问。同时,基类中的保护成员(比如一些只有家族内部才能使用的技能)在子类中也保持保护状态,只能被家族内部(也就是子类及其派生类)访问。
  2. 保护继承(protected)

    • 这就像是长辈说:“我有一些财产和技能,只允许我的孩子们使用,外人不能直接使用,但我的孩子们可以自由地使用。”在保护继承中,基类中的公开和保护成员都会变成子类的保护成员。这意味着这些成员不能被外界直接访问,但是可以在子类及其派生类中被访问。
  3. 私有继承(private)

    • 这就像是长辈说:“我有一些东西,只留给我的孩子们,而且他们也只能自己使用,不能给外人用,也不能让他们的孩子(也就是孙子辈)使用。”在私有继承中,基类中的公开和保护成员都会变成子类的私有成员。这意味着这些成员只能在子类内部使用,不能被外界或子类的派生类访问。

总结一下,继承类型决定了基类的成员在派生类中的可见性和可访问性。公有继承让基类的成员在派生类中保持原有的访问级别;保护继承将基类的成员都变为派生类的保护成员;私有继承则将基类的成员变为派生类的私有成员。在实际编程中,公有继承是最常用的,因为它允许派生类最大程度地利用基类的功能,同时也保持了灵活性。

多继承

什么是多继承?

多继承指的是一个子类可以同时继承多个父类,从而获得这些父类的属性和方法。在 C++ 中,允许通过多继承来创建更强大的类。这种特性可以通过以下语法实现:

1
2
3
4
class <派生类名>:<继承方式1> <基类名1>, <继承方式2> <基类名2>, …
{
<派生类内容>
};

其中:

  • 继承方式可以是 publicprotectedprivate,它决定了从父类继承过来的成员在子类中的访问权限。
  • 基类名是父类的名字。
  • 多个父类之间用逗号分隔。

一个简单的例子

假设我们要设计一个程序,涉及计算矩形的面积,并估算将矩形涂上油漆的总花费。这种情况下,可以通过多继承实现如下功能:

  1. **一个基类 Shape**:用来保存矩形的宽度和高度,并提供设置宽高的方法。
  2. **另一个基类 PaintCost**:用来计算油漆的花费。
  3. **派生类 Rectangle**:同时继承自 ShapePaintCost,可以直接使用这两个基类的功能,并添加自己的功能,比如计算面积。

代码如下:

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
#include <iostream>

using namespace std;

// 基类 Shape:处理形状的宽和高
class Shape
{
public:
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width; // 宽度
int height; // 高度
};

// 基类 PaintCost:计算油漆的总花费
class PaintCost
{
public:
int getCost(int area)
{
return area * 70; // 每平方米花费 $70
}
};

// 派生类 Rectangle:继承 Shape 和 PaintCost
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return width * height; // 计算矩形的面积
}
};

int main(void)
{
Rectangle Rect;
int area;

// 设置矩形的宽和高
Rect.setWidth(5);
Rect.setHeight(7);

// 计算矩形的面积
area = Rect.getArea();

// 输出面积
cout << "Total area: " << Rect.getArea() << endl;

// 输出油漆总花费
cout << "Total paint cost: $" << Rect.getCost(area) << endl;

return 0;
}

程序运行的结果

当运行上述代码时,会输出以下内容:

1
2
Total area: 35
Total paint cost: $2450

详细解释代码的逻辑

  1. Shape 类的功能

    • 它包含两个成员变量:widthheight,分别表示矩形的宽度和高度。
    • 提供了两个公共方法:setWidth(int w)setHeight(int h),用来设置宽度和高度的值。
  2. PaintCost 类的功能

    • 包含一个方法 getCost(int area),它接受面积作为参数,并返回涂上油漆的总费用。
    • 每平方米油漆的费用为 $70
  3. Rectangle 类的功能

    • 它继承了 ShapePaintCost,因此可以直接使用 Shape 类的方法(如 setWidthsetHeight),以及 PaintCost 类的方法(如 getCost)。
    • 它还定义了一个自己的方法 getArea(),用来计算矩形的面积。
  4. main() 函数的执行流程

    • 创建一个 Rectangle 对象 Rect
    • 调用 setWidth(5)setHeight(7),设置矩形的宽度为 5、高度为 7。
    • 调用 getArea() 方法,计算矩形的面积为 ( 5 \times 7 = 35 )。
    • 调用 getCost(area),计算油漆费用为 ( 35 \times 70 = 2450 )。
    • 最终输出面积和总费用。

为什么多继承很有用?

在本例中,通过继承 ShapePaintCostRectangle 类不需要重新定义宽度、高度的管理方法或油漆费用的计算逻辑,这样可以减少代码的重复,同时增加了代码的可读性和可维护性。这种功能组合的方式是多继承的一个典型应用场景。