C++编程之多态的使用
目录
- 1. 多态的基本概念
- 2. 静态多态
- 2.1 函数重载
- 2.2 运算符重载
- 3. 动态多态(运行时多态)
- 3.1 虚函数
- 3.2 纯虚函数和抽象类
- 4. 虚析构函数
- 5. override和final关键字(C++11)
- 6. 多态的实现原理
- 1. 虚函数表(vtable)机制
- 2. 多态调用的底层过程
- 3. 构造和析构过程中的vptr
- 4. 多继承下的虚函数表
- 5. 虚继承的虚函数表
- 6. RTTI(运行时类型信息)
- 7. 总结
多态是面向对象编程的三大特性之一(封装、继承、多态),它允许使用统一的接口来处理不同类型的对象。在C++中,多态主要通过虚函数和继承机制来实现。
1. 多态的基本概念
多态分为两种:
- 编译时多态(静态多态):通过函数重载和运算符重载实现
- 运行时多态(动态多态):通过虚函数和继承实现
2. 静态多态
2.1 函数重载
class Print { public: void show(int i) { cout << "整数: " << i << endl; } void show(double f) { cout << "浮点数: " << f << endl; } void show(string s) { cout << "字符串: " << s << endl; } }; int main() { Print obj; obj.show(5); // 调用show(int) obj.show(3.14); // 调用show(double) obj.show("Hello"); // 调用show(string) return 0; }
2.2 运算符重载
class Complex { private: double real, imag; public: Complex(double r = 0, double i = 0) : real(r), imag(i) {} Complex operator + (const Complex& obj) { return Complex(real + obj.real, imag + obj.imag); } void display() { cout << real << " + " << imag << "i" << endl; } }; int main() { Complex phpc1(3, 4), c2(5, 6); Complex c3 = c1 + c2; // 运算符重载 c3.display(); // 输出: 8 + 10i return 0; }
3. 动态多态(运行时多态)
动态多态通过虚函数和继承实现,是C++中最常用的多态形式。
3.1 虚函数
class Base { public: virtual void show() { // 虚函数 cout << "Base class show()" << endl; } void print() { // 非虚函数 cout << "Base class print()" << endl; } 编程客栈}; class Derived : public Base { public: void show() override { // 重写虚函数 cout << "Derived class show()" << endl; } void print() { // 隐藏基类的print() cout << "Derived class print()" << endl; } }; int main() { Base* bptr; Derived d; bptr = &d; // 运行时多态,根据实际对象类型调用函数 bptr->show(); // 输出: Derived class show() // 非虚函数,根据指针类型调用函数 bptr->print(); // 输出: Base class print() return 0; }
3.2 纯虚函数和抽象类
class Shape { // 抽象类 public: virtual float area() = 0; // 纯虚函数 virtual void draw() = 0; // 纯虚函数 }; class Circle : public Shape { private: float radius; public: Circle(float r) : radius(r) {} float area() override { return 3.14 * radius * radius; } void draw() override { cout << "Drawing Circle" << endl; } }; class Square : public Shape { private: float side; public: Square(float s) : side(s) {} float area() override { return side * side; } void draw() override { cout << "Drawing Square" << endl; } }; int main() { Shape* shapes[2]; shapes[0] = new Circle(5); shapes[1] = new Square(4); for (int i = 0; i < 2; i++) { shapes[i]->draw(); cout << "Area: " << shapes[i]->area() << endl; } delete shapes[0]; delete shapes[1]; return 0; }
4. 虚析构函数
当基类指针指向派生类对象时,如果基类析构函数不是虚函数,删除该指针只会调用基类的析构函数,可能导致内存泄漏。
class Base { public: Base() { cout << "Base constructor" << endl; } virtual ~Base() { cout << "Base destructor" << endl; } // 虚析构函数 }; class Derived : public Base { public: Derived() { cout << "Derived constructor" << endl; } ~Derived() { cout << "Derived destructor" << endl; } }; int main() { Base* b = new Derived(); delete b; // 会调用Derived的析构函数,然后是Base的析构函数 return 0; }
5. override和final关键字(C++11)
override
:显式指明函数重写基类虚函数final
:禁止派生类重写虚函数或禁止类被继承
class Base { publandroidic: virtual void foo() {} virtual void bar() final {} // 不能被子类重写 }; class Derived : public Base { public: void foo() override {} // 正确:重写基类虚函数 // void bar() override {} // 错误:bar是final的 }; class FinalClass final {}; // 不能被继承 // class TryInherit : public FinalClass {}; // 错误
6. 多态的实现原理
多态是C++面向对象编程的核心特性之一,其底层实现机制非常精妙。
C++通过虚函数表(virtual table,简称vtable)和虚指针(vptr)实现运行时多态:
- 每个包含虚函数的类都有一个虚函数表
- 每个对象有一个指向虚函数表的指针(vptr)
- 调用虚函数时,通过vptr找到虚函数表,再找到实际要调用的函数
这种机制虽然有一定开销,但提供了强大的运行时多态能力,是C++面向对象编程的基石,现代CPU的预测执行可以部分缓解这种开销。
1. 虚函数表(vtable)机制
1.1 基本结构
虚函数表(vtable):
- 编译器为每个包含虚函数的类创建一个虚函数表
- 表中按声明顺序存储该类所有虚函数的地址
- 是一个静态数组,在编译时确定
虚指针(vptr):
- 每个对象内部包含一个隐藏的指针成员(vptr)
- vptr指向该对象所属类的虚函数表
- 由编译器自动添加和维护
1.2 内存布局示例
class Base { public: virtual void func1() { cout << "Base::func1" << endl; } virtual void func2() { cout << "Base::func2" << endl; } void func3() { cout << "Base::func3" << endl; } int a; }; class Derived : public Base { public: void func1() override { cout << "Derived::func1" << endl; } virtual void func4() { cout << "Derived::func4" << endl; } int b; };
内存布局示意图:
Base类对象内存布局:
+----------------+| vptr | --> 指向Base的vtable+----------------+| int a |+----------------+Base的vtable:
+----------------+| &Base::func1 |+----------------+| &Base::func2 |+----------------+Derived类对象内存布局:
+-javascript---------------+| vptr | --> 指向Derived的vtable+----------------+| int a (继承) |+----------------+| int b |+----------------+Derived的vtable:
+----------------+| &Derived::func1| // 重写的func1+----------------+| &Base::func2 | // 未重写的func2+----------------+| &Derived::func4| // 新增的func4+----------------+
2. 多态调用的底层过程
当通过基类指针或引用调用虚函数时:
Base* ptr = new Derived(); ptr->func1(); // 多态调用
实际执行步骤:
- 通过ptr找到对象的vptr(编译器知道vptr在对象中的偏移量)
- 通过vptr找到虚函数表
- 在虚函数表中找到func1对应的条目
- 调用该地址处的函数
3. 构造和析构过程中的vptr
3.1 构造函数中的vptr初始化
- 在进入构造函数体之前,编译器插入代码初始化vptr
- 在构造过程中,vptr会随着构造的进行而改变
Derived::Derived() { // 1. 首先初始化Base部分,此时vptr指向Base的vtable // 2. 然后初始化Derived成员,vptr改为指向Derived的vtable // 3. 最后执行构造函数体 }
3.2 析构函数中的vp编程tr处理
- 在进入析构函数体之后,vptr首先指向当前类的vtable
- 析构完成后,vptr会被设置为指向基类的vtable
Derived::~Derived() { // 1. 执行析构函数体(此时vptr指向Derived的vtable) // 2. 析构Derived特有成员 // 3. vptr改为指向Base的vtable // 4. 调用Base的析构函数 }
4. 多继承下的虚函数表
多继承情况下,虚函数表会更复杂:
class Base1 { public: virtual void f1() {} int a; }; class Base2 { public: virtual void f2() {} int b; }; class Derived : public Base1, public Base2 { public: void f1() override {} void f2() override {} virtual void f3() {} int c; };
内存布局:
Derived对象:
+----------------+| Base1::vptr | --> 指向Derived的Base1 vtable+----------------+| Base1::a |+----------------+| Base2::vptr | --> 指向Derived的Base2 vtable+----------------+| Base2::b |+----------------+| Derived::c |+----------------+Derived的Base1 vtable:
+----------------+| &Derived::f1 |+----------------+| &Derived::f3 |+----------------+Derived的Base2 vtable:
+----------------+| &Derived::f2 |+----------------+
5. 虚继承的虚函数表
虚继承(virtual inheritance)会使得虚函数表更加复杂,通常会引入额外的虚基类指针。
6. RTTI(运行时类型信息)
dynamic_cast和typeid也依赖于虚函数表,通常vtable的第一个条目指向类型信息。
7. 总结
C++多态的实现依赖于:
- 每个类有自己的虚函数表
- 每个对象有自己的虚指针
- 调用虚函数时通过虚指针间接查找
- 继承关系反映在虚函数表的布局中
性能考虑:
多态调用相比普通函数调用有以下开销:
- 通过vptr间接访问虚函数表
- 通过虚函数表间接调用函数
- 通常无法内联
多态的应用场景:
- 实现接口与实现的分离
- 设计模式(如工厂模式、策略模式等)
- 回调函数
- 容器存储不同类型的对象但统一处理
多态是C++面向对象编程中非常强大的特性,合理使用可以提高代码的灵活性和可扩展性。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。
精彩评论