继承和派生是密切相关的两个概念,从不同的视角来看待父类与子类的关系。继承是从父类(基类)角度描述的,它强调的是子类可以复用父类的成员。换句话说,父类为子类提供了功能,并且这种功能可以被扩展或修改;派生是从子类角度描述的,它强调的是子类是从父类生成的,并在父类的基础上扩展或修改功能。

1. 基本语法

1
2
3
4
5
6
7
8
class 基类名 {
// 基类的成员
};

class 派生类名 : 访问权限 基类名 {
// 派生类的成员
};

访问控制符可以是public、protected、private,分别表示公有继承、保护继承、私有继承。

2. 继承方式

继承方式决定了父类成员在子类中的访问权限,包括公有继承、保护继承、私有继承。

  • public继承:基类的公有成员和保护成员成为派生类的公有成员和保护成员,基类的私有成员派生类不可访问。
  • protected继承:基类的公有成员和保护成员成为派生类的保护成员,基类的私有成员派生类不可访问。
  • private继承:基类的公有成员和保护成员成为派生类的私有成员,基类的私有成员派生类不可访问。

3. 继承与派生中的构造函数

  1. 构造函数的调用顺序
    当创建一个派生类对象时,先调用基类的构造函数,再调用派生类的构造函数。
    这是因为派生类继承了基类的成员,因此派生类对象的创建需要先初始化基类部分。

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

// 基类
class Animal {
public:
Animal() {
cout << "Animal's constructor called!" << endl;
}

Animal(string name) {
cout << "Animal's parameterized constructor called with name: " << name << endl;
}

void eat() {
cout << "I can eat!" << endl;
}
};

// 派生类
class Dog : public Animal {
public:
Dog() : Animal("Dog") { // 显式调用基类构造函数
cout << "Dog's constructor called!" << endl;
}

void bark() {
cout << "Woof woof!" << endl;
}
};

int main() {
Dog myDog;
myDog.eat();
myDog.bark();
return 0;
}
/*
输出
Animal's parameterized constructor called with name: Dog
Dog's constructor called!
I can eat!
Woof woof!
/*

4. 继承与派生中的析构函数

  1. 析构函数的调用顺序
    当派生类对象销毁时,先调用派生类的析构函数,再调用基类的析构函数。
    这是为了确保派生类对象的资源(如动态内存)能够先释放,再释放基类资源。

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

// 基类
class Animal {
public:
Animal() {
cout << "Animal's constructor called!" << endl;
}

virtual ~Animal() { // 虚析构函数
cout << "Animal's destructor called!" << endl;
}
};

// 派生类
class Dog : public Animal {
public:
Dog() {
cout << "Dog's constructor called!" << endl;
}

~Dog() { // 重载析构函数
cout << "Dog's destructor called!" << endl;
}
};

int main() {
Animal* animal = new Dog(); // 基类指针指向派生类对象
delete animal; // 删除时会调用派生类和基类的析构函数
return 0;
}

/*输出
Animal's constructor called!
Dog's constructor called!
Dog's destructor called!
Animal's destructor called!
*/

5. 继承和派生中的复用

复用 是继承和派生的重要特点之一,它允许派生类直接使用基类已有的代码,而无需重新编写。

5.1. 复用

派生类会继承基类的属性(成员变量)和方法(成员函数),根据访问权限,派生类可以直接复用基类的成员,或者通过间接访问实现复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

class Animal {
public:
void eat() { cout << "I can eat!" << endl; }
void sleep() { cout << "I can sleep!" << endl; }
};

class Dog : public Animal {
public:
void bark() { cout << "I can bark! Woof woof!" << endl; }
};

int main() {
Dog dog;
dog.eat(); // 复用基类方法
dog.sleep(); // 复用基类方法
dog.bark(); // 扩展新功能
return 0;
}

5.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
class Engine {
public:
void start() { cout << "Engine started" << endl; }
};

class Car {
private:
Engine engine; // 组合:Car 包含 Engine
public:
void drive() {
engine.start(); // 复用 Engine 的功能
cout << "Car is driving" << endl;
}
};

int main() {
Car car;
car.drive();
return 0;
}

/* 输出
Engine started
Car is driving
*/

6. 继承和派生中的重载

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

class Base {
public:
void display() {
cout << "Base: display()" << endl;
}

void display(int x) {
cout << "Base: display(int x) with x = " << x << endl;
}
};

class Derived : public Base {
public:
void display(double y) {
cout << "Derived: display(double y) with y = " << y << endl;
}
};

int main() {
Derived obj;
obj.display(); // 错误!派生类隐藏了基类的同名函数
obj.display(10); // 错误!派生类隐藏了基类的同名函数
obj.display(3.14); // 正确!调用派生类的重载函数
return 0;
}

如果没有特别处理,基类的同名函数会被派生类隐藏,即使参数不同。
为了解决这个问题,可以显式地引入基类的函数:
1
2
3
4
5
6
7
class Derived : public Base {
public:
using Base::display; // 显式引入基类的 display() 方法
void display(double y) {
cout << "Derived: display(double y) with y = " << y << endl;
}
};

结果
1
2
3
4
Derived obj;
obj.display(); // 调用 Base 的 display()
obj.display(10); // 调用 Base 的 display(int)
obj.display(3.14); // 调用 Derived 的 display(double)

6.2. 函数重写 (动态多态)

函数重写 指派生类重新定义基类中的虚函数

  • 重写要求函数名、参数列表和返回类型完全一致。
  • 基类中的函数必须声明为 virtual,才能被派生类重写。
  • 函数重写是多态的基础,派生类的实现可以通过基类指针或引用调用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

class Base {
public:
virtual void show() { // 基类中的虚函数
cout << "Base: show()" << endl;
}
};

class Derived : public Base {
public:
void show() override { // 覆盖基类的虚函数,使用override表明是重写函数
cout << "Derived: show()" << endl;
}
};

int main() {
Base* obj = new Derived(); // 基类指针指向派生类对象
obj->show(); // 调用 Derived 的 show()
delete obj;
return 0;
}

如果基类中的虚函数没有具体实现,可以将其声明为纯虚函数,派生类必须重写该函数。

6.3. 运算符重载 (静态多态的一种形式)

运算符重载是函数重载的一种特殊形式,派生类可以重载基类的运算符。

7. 多继承

多继承 是指一个派生类同时继承多个基类,从而能够复用多个基类的功能。C++ 支持多继承,这是与一些其他编程语言(如 Java)的重要区别之一。

7.1. 多继承的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base1 {
public:

};

class Base2 {
public:

};

// Derived 从 Base1 和 Base2 继承
class Derived : public Base1, public Base2 {
public:

};

7.2. 多继承中的冲突

7.2.1. 名字冲突

如果基类之间有相同名字的成员函数或变量,派生类中访问这些成员可能会产生二义性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Base1 {
public:
void show() { cout << "Base1 show" << endl; }
};

class Base2 {
public:
void show() { cout << "Base2 show" << endl; }
};

class Derived : public Base1, public Base2 {
};

int main() {
Derived obj;
// obj.show(); // 编译错误:二义性
obj.Base1::show(); // 通过基类名解决二义性
obj.Base2::show(); // 指定调用 Base2 的 show
return 0;
}

7.2.2. 菱形继承问题

菱形继承指的是派生类从两个基类继承,而这两个基类又继承自同一个基类。这会导致基类的成员在派生类中存在多份副本,可能引发冲突。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A {
public:
void show() { cout << "A show" << endl; }
};

class B : public A {
};

class C : public A {
};

class D : public B, public C {
};

int main() {
D obj;
// obj.show(); // 编译错误:二义性,A 的成员有两份
obj.B::show(); // 指定调用 B 中的 A
obj.C::show(); // 指定调用 C 中的 A
return 0;
}

为了解决这个问题,C++ 提供了虚继承(virtual inheritance)机制,使得派生类只继承基类的一份成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {
public:
void show() { cout << "A show" << endl; }
};

class B : virtual public A {
};

class C : virtual public A {
};

class D : public B, public C {
};

int main() {
D obj;
obj.show(); // 正确,调用 A 的 show
return 0;
}

7.2.3. 虚基类

虚基类 是 C++ 中用于解决菱形继承问题和确保唯一实例的机制。它通过使一个基类在派生类中只保留一份实例,避免了重复继承引发的冲突和资源浪费。

虚基类通过在继承声明中使用 virtual 关键字来指定。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A {
public:
void show() { cout << "A show" << endl; }
};

class B : virtual public A {
};

class C : virtual public A {
};

class D : public B, public C {
};

int main() {
D obj;
obj.show(); // 正确,调用 A 的 show
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
class A {
public:
int value;
A(int v) : value(v) { cout << "A constructor with value = " << value << endl; }
};

class B : virtual public A {
public:
B() : A(0) { cout << "B constructor" << endl; }
};

class C : virtual public A {
public:
C() : A(0) { cout << "C constructor" << endl; }
};

class D : public B, public C {
public:
D() : A(42), B(), C() { cout << "D constructor" << endl; } // 初始化基类成员
};

int main() {
D obj;
cout << "D's A::value = " << obj.value << endl;
return 0;
}

/* 输出
A constructor with value = 42
B constructor
C constructor
D constructor
D's A::value = 42
*/

7.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 A {
public:
A() { cout << "A constructor" << endl; }
};

class B : public A {
public:
B() { cout << "B constructor" << endl; }
};

class C : public A {
public:
C() { cout << "C constructor" << endl; }
};

class D : public B, public C {
public:
D() { cout << "D constructor" << endl; }
};

int main() {
D obj;
return 0;
}

/* 输出
A constructor
B constructor
A constructor
C constructor
D constructor
*/

可以看到,每个基类都调用 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
28
29
30
31
class A {
public:
A() { cout << "A constructor" << endl; }
};

class B : virtual public A {
public:
B() { cout << "B constructor" << endl; }
};

class C : virtual public A {
public:
C() { cout << "C constructor" << endl; }
};

class D : public B, public C {
public:
D() { cout << "D constructor" << endl; }
};

int main() {
D obj;
return 0;
}

/* 输出
A constructor
B constructor
C constructor
D constructor
*/

虚基类的构造函数由最派生类负责调用,避免重复初始化。