C++编程之多态的使用

 更新时间:2025年06月07日 10:27:11   作者:倔强老吕  
这篇文章主要介绍了C++编程之多态的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

多态是面向对象编程的三大特性之一(封装、继承、多态),它允许使用统一的接口来处理不同类型的对象。在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 c1(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 {
public:
    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类对象内存布局:
+----------------+
| 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 析构函数中的vptr处理

  • 在进入析构函数体之后,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++面向对象编程中非常强大的特性,合理使用可以提高代码的灵活性和可扩展性。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • C++类中const修饰的成员函数及日期类小练习

    C++类中const修饰的成员函数及日期类小练习

    将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,表明在该成员函数中不能对类的任何成员进行修改,下面这篇文章主要给大家介绍了关于C++类中const修饰的成员函数及日期类小练习 的相关资料,需要的朋友可以参考下
    2023-01-01
  • C语言预处理详解

    C语言预处理详解

    这篇文章主要给大家介绍了关于C语言之预处理的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-10-10
  • 如何给随机数加密

    如何给随机数加密

    随机数加密的简单算法,需要的朋友可以参考一下
    2013-03-03
  • 通过c++11改进我们的模式之改进命令模式

    通过c++11改进我们的模式之改进命令模式

    这篇我要讲的是如何使用c++11改进命令模式,感兴趣的朋友可以看下
    2013-11-11
  • C++中声明、定义、初始化、赋值区别介绍

    C++中声明、定义、初始化、赋值区别介绍

    在 C++ 中,声明、定义、初始化、赋值是变量的四个基本操作,很多朋友不清楚他们之间有什么区别,今天通过本文给大家介绍下C++中声明、定义、初始化、赋值区别,感兴趣的朋友一起看看吧
    2023-05-05
  • 详解在C++中显式默认设置的函数和已删除的函数的方法

    详解在C++中显式默认设置的函数和已删除的函数的方法

    这篇文章主要介绍了在C++中显式默认设置的函数和已删除的函数的方法,文中讲到了C++11标准中的新特性,需要的朋友可以参考下
    2016-01-01
  • C++实现LeetCode(87.搅乱字符串)

    C++实现LeetCode(87.搅乱字符串)

    这篇文章主要介绍了C++实现LeetCode(87.搅乱字符串),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C语言的进制转换及算法实现教程

    C语言的进制转换及算法实现教程

    这篇文章主要介绍了C语言的进制转换及算法实现的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01
  • C#和C++编程语言中的类浅析

    C#和C++编程语言中的类浅析

    在本篇文章里我们给大家分析了C#和C++编程语言中的类的相关知识点,正在学习的朋友们跟着操作下。
    2019-02-02
  • C++的动态内存管理你真的了解吗

    C++的动态内存管理你真的了解吗

    这篇文章主要为大家详细介绍了C++的动态内存管理,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-02-02

最新评论