C++ OOP 面试高频知识点 - 05
继承:单继承、多继承、菱形继承、虚继承
1. 继承的基本概念
继承(Inheritance) 是面向对象编程的核心原则之一,它允许一个类(派生类/子类)继承另一个类(基类/父类)的属性和行为。继承提高了代码的复用性,并建立了类之间的层次关系。
// 基类
class Animal {
public:
void eat() {
cout << "Animal is eating" << endl;
}
void sleep() {
cout << "Animal is sleeping" << endl;
}
};
// 派生类
class Dog : public Animal {
public:
void bark() {
cout << "Dog is barking" << endl;
}
};
int main() {
Dog dog;
dog.eat(); // 继承自 Animal
dog.sleep(); // 继承自 Animal
dog.bark(); // Dog 自己的方法
return 0;
}
2. 继承类型
2.1 单继承(Single Inheritance)
单继承是指一个类只继承自一个基类。这是最简单和最常见的继承方式。
class Shape {
public:
virtual double area() const = 0;
};
class Rectangle : public Shape {
public:
Rectangle(double w, double h) : width(w), height(h) {}
double area() const override {
return width * height;
}
private:
double width, height;
};
class Circle : public Shape {
public:
Circle(double r) : radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
private:
double radius;
};
2.2 多继承(Multiple Inheritance)
多继承是指一个类继承自多个基类。它可以组合多个类的功能,但也会带来一些复杂性。
class Swimmer {
public:
void swim() {
cout << "Swimming" << endl;
}
};
class Flyer {
public:
void fly() {
cout << "Flying" << endl;
}
};
class Duck : public Swimmer, public Flyer {
public:
void quack() {
cout << "Quacking" << endl;
}
};
int main() {
Duck duck;
duck.swim(); // 继承自 Swimmer
duck.fly(); // 继承自 Flyer
duck.quack(); // Duck 自己的方法
return 0;
}
2.3 菱形继承(Diamond Inheritance)
菱形继承是一种特殊的多继承情况,其中一个类继承自两个类,而这两个类又继承自同一个基类,形成菱形结构。
class Animal {
public:
string name;
};
class Mammal : public Animal {};
class Bird : public Animal {};
class Bat : public Mammal, public Bird {}; // 菱形继承
int main() {
Bat bat;
// bat.name = "Bat"; // 错误:歧义
bat.Mammal::name = "Bat"; // 必须明确指定继承路径
cout << bat.Mammal::name << endl;
return 0;
}
2.4 虚继承(Virtual Inheritance)
虚继承是解决菱形继承中基类成员重复继承问题的方法。通过在继承时使用 virtual 关键字,可以确保基类在派生类中只存在一个实例。
class Animal {
public:
string name;
};
class Mammal : virtual public Animal {}; // 虚继承
class Bird : virtual public Animal {}; // 虚继承
class Bat : public Mammal, public Bird {}; // 菱形继承,但只有一个 Animal 实例
int main() {
Bat bat;
bat.name = "Bat"; // 现在没有歧义了
cout << bat.name << endl;
return 0;
}
3. 继承时的访问权限
继承时的访问权限取决于继承方式(public、protected、private)和基类成员的访问权限:
| 基类成员访问权限 | public 继承 | protected 继承 | private 继承 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可访问 | 不可访问 | 不可访问 |
4. 继承的优缺点
4.1 优点
- 代码复用:避免重复编写相似的代码
- 层次结构:建立清晰的类层次关系
- 多态性:支持动态多态(虚函数)
- 可扩展性:可以通过继承添加新功能
4.2 缺点
- 复杂性:继承层次过深会使代码难以理解和维护
- 紧耦合:子类与父类耦合度高,修改父类可能影响子类
- 限制:单继承限制了功能组合的灵活性(Java 等语言)
- 菱形继承问题:需要使用虚继承解决
5. 常见问题和回答
问题 1:什么时候应该使用继承?
当类之间存在 "is-a" 关系时,应该使用继承。例如,Dog is an Animal。
问题 2:什么时候不应该使用继承?
当类之间存在 "has-a" 关系时,应该使用组合而不是继承。例如,Car has an Engine。
// 不推荐:继承
class Car : public Engine {};
// 推荐:组合
class Car {
private:
Engine engine;
};
问题 3:如何解决菱形继承问题?
使用虚继承可以解决菱形继承问题,确保基类在派生类中只存在一个实例。
问题 4:继承和组合的区别是什么?
- 继承:表示 "is-a" 关系,子类继承父类的属性和行为
- 组合:表示 "has-a" 关系,对象包含其他对象
6. 最佳实践
- 保持继承层次简单:继承层次不超过 3-4 层
- 优先使用组合:在不确定时,优先使用组合
- 设计好访问权限:合理使用 public、protected、private 访问控制
- 使用虚继承:在需要时使用虚继承解决菱形继承问题
总结
继承是面向对象编程的核心原则之一,它提供了代码复用和层次结构建立的能力。C++ 支持单继承、多继承、菱形继承和虚继承等多种继承方式。在使用继承时,我们应该注意继承与组合的选择,保持继承层次的简单性,并正确解决菱形继承问题。
练习建议: 1. 设计一个几何图形继承体系,包含形状、矩形、圆形、三角形等 2. 实现虚函数和多态 3. 测试菱形继承和虚继承的行为 4. 比较继承和组合在不同场景下的应用