深入理解C++中的继承与多态:从原理到实践
一、继承:代码复用与层次化设计
1.1 继承的基本概念
继承是面向对象编程(OOP)的核心特性之一,它允许一个类(派生类/子类)继承另一个类(基类/父类)的属性和方法,从而实现代码复用和层次化设计。
示例:
class Animal {
protected:
string name;
public:
Animal(string n) : name(n) {}
void eat() { cout << name << " is eating." << endl; }
};
class Dog : public Animal {
public:
Dog(string n) : Animal(n) {}
void bark() { cout << name << " is barking." << endl; }
};
1.2 继承方式
C++支持三种继承方式:
- public:基类的public/protected成员在子类中保持原有访问权限。
- protected:基类的public/protected成员在子类中变为protected。
- private:基类的所有成员在子类中变为private。
示例:
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
class PublicDerived : public Base {
// publicVar -> public
// protectedVar -> protected
// privateVar 不可访问
};
class ProtectedDerived : protected Base {
// publicVar -> protected
// protectedVar -> protected
// privateVar 不可访问
};
class PrivateDerived : private Base {
// publicVar -> private
// protectedVar -> private
// privateVar 不可访问
};
1.3 构造函数与析构函数
- 构造顺序:先调用基类构造函数,再调用子类构造函数。
- 析构顺序:与构造顺序相反,先调用子类析构函数,再调用基类析构函数。
示例:
class Base {
public:
Base() { cout << "Base constructor" << endl; }
~Base() { cout << "Base destructor" << endl; }
};
class Derived : public Base {
public:
Derived() { cout << "Derived constructor" << endl; }
~Derived() { cout << "Derived destructor" << endl; }
};
// 输出结果:
// Base constructor
// Derived constructor
// Derived destructor
// Base destructor
二、多态:接口统一与行为分化
2.1 静态多态(编译时多态)
通过函数重载和模板实现,在编译阶段确定调用的函数版本。
函数重载示例:
class Calculator {
public:
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
};
模板示例:
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
2.2 动态多态(运行时多态)
通过虚函数和基类指针/引用实现,在运行时确定调用的函数版本。
虚函数示例:
class Shape {
public:
virtual void draw() { cout << "Drawing a shape." << endl; }
};
class Circle : public Shape {
public:
void draw() override { cout << "Drawing a circle." << endl; }
};
class Rectangle : public Shape {
public:
void draw() override { cout << "Drawing a rectangle." << endl; }
};
// 使用基类指针调用虚函数
void render(Shape* shape) {
shape->draw(); // 运行时根据实际对象类型决定调用哪个版本
}
int main() {
Circle circle;
Rectangle rectangle;
render(&circle); // 输出:Drawing a circle.
render(&rectangle); // 输出:Drawing a rectangle.
return 0;
}
2.3 纯虚函数与抽象类
- 纯虚函数:在基类中声明但不实现的虚函数,格式为
virtual void func() = 0;
。 - 抽象类:包含至少一个纯虚函数的类,不能实例化,只能作为基类被继承。
示例:
class Animal {
public:
virtual void sound() = 0; // 纯虚函数
};
class Dog : public Animal {
public:
void sound() override { cout << "Woof!" << endl; }
};
class Cat : public Animal {
public:
void sound() override { cout << "Meow!" << endl; }
};
三、继承与多态的结合应用
3.1 实现多态数组
通过基类指针数组存储不同子类对象,实现统一操作。
示例:
int main() {
Shape* shapes[2];
shapes[0] = new Circle();
shapes[1] = new Rectangle();
for (int i = 0; i < 2; i++) {
shapes[i]->draw(); // 动态绑定,调用实际对象的draw()
delete shapes[i]; // 释放内存
}
return 0;
}
3.2 虚析构函数
当通过基类指针删除子类对象时,为确保子类析构函数被调用,需将基类析构函数声明为虚函数。
示例:
class Base {
public:
virtual ~Base() { cout << "Base destructor" << endl; }
};
class Derived : public Base {
public:
~Derived() { cout << "Derived destructor" << endl; }
};
int main() {
Base* ptr = new Derived();
delete ptr; // 输出:Derived destructor → Base destructor
return 0;
}
四、继承与多态的注意事项
4.1 隐藏与覆盖
- 函数覆盖(Override):子类重新定义基类的虚函数,函数签名必须完全相同。
- 函数隐藏(Hiding):子类定义与基类同名但参数不同的函数,基类函数被隐藏。
示例:
class Base {
public:
virtual void func() { cout << "Base::func()" << endl; }
};
class Derived : public Base {
public:
void func(int x) { cout << "Derived::func(int)" << endl; } // 隐藏基类的func()
};
4.2 菱形继承与虚基类
- 菱形继承:多个子类继承同一个基类,而这些子类又被一个子类继承,导致数据冗余。
- 虚基类:使用
virtual
关键字声明基类,解决菱形继承中的数据冗余问题。
示例:
class Animal { public: int age; };
class Mammal : virtual public Animal {};
class Bird : virtual public Animal {};
class Bat : public Mammal, public Bird {}; // Bat只包含一个age成员
五、继承与多态的优缺点
5.1 优点
- 代码复用:减少重复代码,提高开发效率。
- 可扩展性:通过继承和多态,方便添加新功能。
- 接口统一:通过基类定义统一接口,子类实现具体行为。
5.2 缺点
- 耦合度高:过度使用继承会导致类之间的耦合度增加,修改基类可能影响所有子类。
- 复杂性增加:多态机制可能使代码难以理解和调试。
- 性能开销:虚函数调用涉及运行时查找,有一定性能开销。
六、总结
继承和多态是C++面向对象编程的两大核心机制:
- 继承:实现代码复用和层次化设计,通过基类派生出具有共同特性的子类。
- 多态:通过虚函数实现“一个接口,多种实现”,提高代码的灵活性和可扩展性。
合理使用继承和多态,可以构建出结构清晰、可维护性强的大型软件系统。但需注意避免滥用,保持设计的简洁性和合理性。