1. 多态的基本概念
1.1 什么是多态?
多态(polymorphism)是面向对象编程的三大特性之一,指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。
两种多态类型:
-
编译时多态(静态多态):函数重载、模板
-
运行时多态(动态多态):通过虚函数和继承实现
1.2 实际应用场景
-
买票行为:普通人全价,学生半价,军人优先
-
动物叫声:猫"喵喵",狗"汪汪"
-
交通工具:汽车行驶,飞机飞行
2. 多态的实现条件
2.1 必须满足的条件
cpp
class Person {
public:
virtual void BuyTicket() { // 1. 必须是虚函数
cout << "买票-全价" << endl;
}
};
class Student : public Person {
public:
virtual void BuyTicket() override { // 2. 派生类必须重写虚函数
cout << "买票-半价" << endl;
}
};
void Func(Person& people) { // 3. 必须是基类的指针或引用
people.BuyTicket(); // 4. 调用虚函数
}
2.2 为什么必须是基类指针/引用?
基类指针/引用可以:
-
指向基类对象
-
指向派生类对象(向上转型)
-
实现统一接口,不同实现
3. 虚函数详解
3.1 虚函数声明
cpp
class Base {
public:
virtual void func(); // 虚函数声明
};
void Base::func() { // 类外定义不加virtual
// 实现
}
3.2 虚函数重写规则
-
函数签名必须完全相同(返回值类型、函数名、参数列表)
-
基类函数必须有
virtual关键字 -
派生类
virtual可省略(但不推荐)
3.3 协变(特殊情况)
cpp
class A {};
class B : public A {};
class Base {
public:
virtual A* create() { return new A; }
};
class Derived : public Base {
public:
virtual B* create() override { return new B; } // 协变:返回值类型不同
};
4. 虚函数表原理
4.1 虚函数表结构
cpp
class Base {
public:
virtual void func1() {}
virtual void func2() {}
int a;
};
int main() {
Base b;
cout << sizeof(b) << endl; // 输出:8(32位)或16(64位)
// 包含:虚表指针 + 成员变量
return 0;
}
4.2 内存布局
text
Base对象:
+--------------+
| vptr | -> 指向虚函数表
+--------------+
| int a | -> 成员变量
+--------------+
虚函数表:
+--------------+
| &Base::func1 |
+--------------+
| &Base::func2 |
+--------------+
4.3 派生类虚表
cpp
class Derived : public Base {
public:
virtual void func1() override {} // 重写
virtual void func3() {} // 新增虚函数
};
// Derived虚表:
// 1. &Derived::func1(覆盖基类)
// 2. &Base::func2(继承)
// 3. &Derived::func3(新增)
5. 重要特例
5.1 虚析构函数
cpp
class Base {
public:
virtual ~Base() { // 虚析构函数
cout << "~Base()" << endl;
}
};
class Derived : public Base {
public:
~Derived() { // 自动成为虚函数
cout << "~Derived()" << endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 正确调用~Derived()和~Base()
return 0;
}
5.2 纯虚函数与抽象类
cpp
class Animal { // 抽象类
public:
virtual void makeSound() = 0; // 纯虚函数
// 纯虚函数可以有实现(但很少用)
virtual void sleep() = 0 {
cout << "Animal sleeping" << endl;
}
};
class Dog : public Animal {
public:
virtual void makeSound() override {
cout << "汪汪" << endl;
}
virtual void sleep() override {
Animal::sleep(); // 调用基类实现
cout << "Dog dreaming" << endl;
}
};
6. C++11新特性
6.1 override关键字
cpp
class Base {
public:
virtual void func(int) {}
};
class Derived : public Base {
public:
virtual void func(int) override {} // 正确
// virtual void func(double) override {} // 错误:不是重写
};
6.2 final关键字
cpp
class Base {
public:
virtual void func() final {} // 禁止重写
};
class Derived : public Base {
public:
// virtual void func() {} // 错误:不能重写final函数
};
class Base2 final {}; // 禁止继承
// class Derived2 : public Base2 {}; // 错误
7. 重载、重写、隐藏对比
| 特性 | 重载 (Overload) | 重写/覆盖 (Override) | 隐藏 (Hide) |
|---|---|---|---|
| 作用域 | 同一作用域 | 不同作用域(继承) | 不同作用域(继承) |
| 函数名 | 相同 | 相同 | 相同 |
| 参数列表 | 必须不同 | 必须相同 | 可以不同 |
| 返回值 | 可以不同 | 必须相同(协变除外) | 可以不同 |
| virtual | 不需要 | 基类必须有virtual | 不需要 |
| 访问权限 | 可以不同 | 可以不同 | 可以不同 |
8. 常见面试题
8.1 选择题示例
cpp
class A {
public:
virtual void func(int val = 1) {
cout << "A->" << val << endl;
}
virtual void test() { func(); }
};
class B : public A {
public:
void func(int val = 0) {
cout << "B->" << val << endl;
}
};
int main() {
B* p = new B;
p->test(); // 输出:B->1
return 0;
}
解析 :调用test()时,this指向B对象,调用func()使用动态绑定。但默认参数使用静态绑定,所以使用A的默认值1。
8.2 内存布局题
cpp
class Base {
public:
virtual void f1() {}
virtual void f2() {}
int a;
char b;
};
int main() {
Base b;
cout << sizeof(b) << endl;
// 32位:4(vptr) + 4(int) + 1(char) + 3(对齐) = 12
// 64位:8(vptr) + 4(int) + 1(char) + 3(对齐) = 16
return 0;
}
8.3 多态原理题
cpp
class Animal {
public:
virtual void speak() const {
cout << "Animal sound" << endl;
}
};
class Cat : public Animal {
public:
virtual void speak() const override {
cout << "Meow" << endl;
}
};
void makeSound(const Animal& animal) {
animal.speak(); // 动态绑定
}
int main() {
Cat cat;
makeSound(cat); // 输出:Meow
return 0;
}
9. 最佳实践
9.1 何时使用多态?
-
需要处理多种类型对象,但有统一接口
-
需要在运行时决定调用哪个函数
-
需要扩展性,添加新类型不影响现有代码
9.2 设计建议
-
基类析构函数设为虚函数
-
使用
override明确重写意图 -
接口类使用纯虚函数
-
避免在构造函数/析构函数中调用虚函数
9.3 性能考虑
-
虚函数调用有额外开销(虚表查找)
-
虚函数不能内联(通常)
-
虚表增加对象大小(一个指针)
10. 总结
多态是C++面向对象编程的核心特性,通过虚函数和继承实现运行时多态。理解多态需要掌握:
-
实现条件:虚函数 + 重写 + 基类指针/引用
-
底层原理:虚函数表和虚表指针
-
关键语法 :
virtual、override、final -
特殊场景:虚析构函数、纯虚函数、协变
-
设计原则:开闭原则、里氏替换原则
多态使得代码更加灵活、可扩展,是设计复杂系统的重要工具。正确使用多态可以提高代码的复用性和可维护性,但也需要注意性能开销和正确性问题。