C++ <多态>详解:从概念到底层实现

发布于:2025-07-30 ⋅ 阅读:(35) ⋅ 点赞:(0)

目录

1. 多态的概念

2. 多态的定义及实现

2.1 多态的构成条件

2.2 虚函数与重写

特殊重写场景:

2.3 关键字:override 与 final

3. 纯虚函数与抽象类

4. 多态的底层原理

4.1 虚函数表指针(_vfptr)

4.2 动态绑定与静态绑定

5. 常见面试题解析

6. 总结


多态是 C++ 面向对象编程的核心特性之一,它允许不同对象对同一消息作出不同响应。理解多态不仅能写出更灵活的代码,也是面试中的高频考点。本文将从概念、实现条件、关键特性到底层原理,全面剖析 C++ 多态的精髓。

1. 多态的概念

多态(polymorphism)字面意思是 "多种形态",在 C++ 中分为两类:

  • 编译时多态:通过函数重载和函数模板实现,编译器在编译阶段根据参数类型匹配相应函数,属于静态多态。
  • 运行时多态:程序运行时根据传递的对象类型决定调用哪个函数,这是本文的重点。

运行时多态的经典案例

  • 买票行为:普通人全价、学生半价、军人优先
  • 动物叫行为:猫 "喵"、狗 "汪"
//买票的多态实例
class Person
{
public:
    virtual void BuyTicket()
    {
        cout << "买票-全价" << endl;
    }
};

class Student : public Person
{
public:
    virtual void BuyTicket()   //可以不加virtual 不规范
    {
        cout << "买票-半价" << endl;
    }
};

void Func(Person& ptr) //基类的引用或指针
{
    ptr.BuyTicket();
}

// 动物叫的多态示例
class Animal {
public:
    virtual void talk() const {}
};
class Dog : public Animal {
public:
    virtual void talk() const { cout << "汪汪" << endl; }
};
class Cat : public Animal {
public:
    virtual void talk() const { cout << "喵" << endl; }
};
// 同一函数接收不同对象,表现不同行为
void letsHear(const Animal& animal) {
    animal.talk(); 
}

 

2. 多态的定义及实现

2.1 多态的构成条件

多态需满足继承关系基础上的两个核心条件:

class Person {
public:
    virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
    // 重写基类虚函数
    virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
// 基类引用调用虚函数,满足多态条件
void Func(Person& people) {
    people.BuyTicket(); 
}
int main() {
    Person p;
    Student s;
    Func(p); // 输出"买票-全价"
    Func(s); // 输出"买票-半价"
}

 

2.2 虚函数与重写

  • 虚函数:用virtual修饰的类成员函数(非成员函数不能用virtual)。
  • 重写规则:派生类虚函数需与基类虚函数的函数名、参数列表、返回值完全一致(协变除外)。

注意:派生类重写时可省略virtual(不推荐),但仍视为虚函数。

特殊重写场景:
  1. 协变:返回值可改为基类 / 派生类的指针 / 引用(需满足继承关系)

    class A {};
    class B : public A {};
    class Person {
    public:
        virtual A* BuyTicket() { return nullptr; } // 基类指针
    };
    class Student : public Person {
    public:
        virtual B* BuyTicket() { return nullptr; } // 派生类指针(协变)
    };

    析构函数重写:基类析构函数为虚函数时,派生类析构函数自动视为重写(编译器统一处理析构函数名为destructor

    class A {
    public:
        virtual ~A() { cout << "~A()" << endl; } // 虚析构
    };
    class B : public A {
    public:
        ~B() { cout << "~B()" << endl; } // 自动重写
    };
    // 正确释放派生类资源,避免内存泄漏
    int main() {
        A* p = new B;
        delete p; // 先调用~B(),再调用~A()
    }

    2.3 关键字:override 与 final

  • override:强制检查派生类函数是否重写基类虚函数,未重写则编译报错。
  • final:修饰虚函数时,禁止派生类重写该函数;修饰类时,禁止被继承。
    // C++11提供了override,可以帮助⽤⼾检测是否重写。
    class Car
    {
    public:
        virtual Dirve()
        {}
    };
    
    class Benz : public Car
    {
    public:
        virtual Dirve() override
        {
            cout << "Benz-舒适" << endl;
        }
    };
    
    int main()
    {
        return 0;
    }
    
    class Car {
    public:
        virtual void Drive() final {} // 禁止重写
    };
    class Benz : public Car {
    public:
        virtual void Drive() {} // 编译报错:无法重写final函数
    };

    3. 纯虚函数与抽象类

  • 纯虚函数:在虚函数后加=0,无需实现(语法允许实现但无意义)。
  • 抽象类:包含纯虚函数的类,不能实例化对象,派生类需重写纯虚函数才能实例化。
    class Car {
    public:
        virtual void Drive() = 0; // 纯虚函数
    };
    class Benz : public Car {
    public:
        virtual void Drive() { cout << "Benz-舒适" << endl; } // 重写后可实例化
    };
    int main() {
        // Car car; // 报错:抽象类不能实例化
        Car* p = new Benz; // 正确
        p->Drive();
    }

    4. 多态的底层原理

    4.1 虚函数表指针(_vfptr)

    含有虚函数的类,其对象会额外包含一个虚函数表指针_vfptr),指向虚函数表(简称虚表)。虚表是存储虚函数地址的指针数组,同类型对象共享同一张虚表。

class Base {
public:
    virtual void Func1() { cout << "Func1()" << endl; }
protected:
    int _b = 1;
    char _ch = 'x';
};
int main() {
    Base b;
    cout << sizeof(b) << endl; // 32位下为12字节(4字节指针 + 4字节int + 4字节对齐)
}

 

4.2 动态绑定与静态绑定

  • 静态绑定:编译时确定函数调用地址(非虚函数或不满足多态条件时)。
  • 动态绑定:运行时通过对象的虚表查找函数地址(满足多态条件时)。

虚表结构示例

  • 基类虚表:存储基类所有虚函数地址。
  • 派生类虚表:先复制基类虚表,再用派生类重写的虚函数地址覆盖对应位置,最后添加派生类自身的虚函数地址。

5. 常见面试题解析

问题:以下程序输出结果是?

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
}

解析

  • test()是基类函数,调用func()时满足多态,实际调用派生类B::func
  • 虚函数重写仅覆盖函数体(即函数的实现),默认参数仍使用基类的(val=1),故输出B->1

6. 总结

多态是 C++ 面向对象的核心特性,通过虚函数重写虚表机制实现,允许同一接口呈现不同行为。关键要点:

  1. 运行时多态需满足:基类指针 / 引用 + 虚函数重写。
  2. 虚表存储虚函数地址,派生类虚表覆盖基类重写函数地址。
  3. 基类析构函数建议设为虚函数,避免派生类资源泄漏。
  4. 抽象类通过纯虚函数强制派生类实现特定接口,提升代码规范性。

掌握多态不仅能写出更灵活的代码,也是理解 C++ 底层机制的重要一步。实际开发中,合理使用多态可显著提高代码的可扩展性和维护性。

 


网站公告

今日签到

点亮在社区的每一天
去签到