C++:多态

系列文章目录

C++内存存储模型

C++引用以及函数的占位、重载

C++封装与对象特性

C++对象特性及友元 

C++运算符重载及继承

C++多态

C++文件操作

C++模板


文章目录

前言

一、多态的基本语法

 1.动态绑定实现的条件:

2.动态绑定的实现

二.多态的深入剖析

三.纯虚函数和抽象类

1.纯虚函数的作用

2.纯虚函数的实现

四.虚析构和纯虚析构

1.虚析构和纯虚析构的概念

 2.虚析构和纯虚析构的实现

总结


前言

多态是面向对象编程中的一个核心概念,它允许我们通过基类指针或引用来操作派生类对象。在 C++ 中,多态主要通过虚函数(Virtual Function)和继承来实现。

多态有两种形式:静态多态(编译时多态)和动态多态(运行时多态)。

  • 1. 静态多态:在编译时就确定了函数调用的地址,主要通过函数重载和运算符重载来实现。
  • 2. 动态多态:在运行时确定函数调用的地址,主要通过虚函数来实现。

多态的主要优点是提高了代码的可扩展性和可维护性。通过使用基类指针或引用,你可以编写能够处理任何派生类对象的通用代码,而不需要知道这些对象的具体类型。这使得你可以添加新的派生类而不需要修改处理基类的代码,从而提高了代码的可扩展性。同时,多态也使得代码更加模块化,提高了代码的可维护性。

在接下来的文章中,我们将详细介绍 C++ 的多态特性,包括如何使用虚函数来实现动态多态,以及多态的一些使用场景和注意事项。


一、多态的基本语法

为了实现多态,我们引入一个动态绑定的操作

在 C++ 中,动态绑定(也称为晚绑定或运行时多态)是通过虚函数实现的。当你在基类中声明一个函数为 virtual,并在派生类中重写这个函数,那么你就可以通过基类的指针或引用来调用这个函数,实际执行的将是派生类中的版本。

 1.动态绑定实现的条件:

  • 继承:必须有一个基类和至少一个派生类。
  • 虚函数:基类中需要有一个或多个虚函数,这些函数在派生类中被重写。
  • 基类指针或引用:你需要使用基类的指针或引用来指向派生类的对象。

补充:重写允许派生类改变基类中的函数行为,这使得我们可以通过基类的指针或引用来操作派生类的对象。

2.动态绑定的实现

//动物类
class Aninal
{
public:
    virtual void speak()
    {
        cout << "动物在说话" << endl;   
    }
};

//猫类
class Cat : public Aninal
{
public:
    void speak()
    {
        cout << "小猫在说话" << endl;
    }
};

//执行说话的函数
//地址早绑定,在编译阶段确定函数地址
//如果想执行让猫说话,这个函数的地址不能提前绑定,需要在运行阶段进行绑定,地址晚绑定
void doSpeak(Aninal &animal)
{
    animal.speak();
}

void test01()
{
    Cat cat;
    doSpeak(cat);
}

int main()
{
    test01();
    return 0;
}

二.多态的深入剖析

C++ 中的多态是通过虚函数表(vtable)和虚函数表指针(vptr)来实现的。每个包含虚函数的类(或者从这样的类派生的类)都有一个虚函数表,这个表中存储了虚函数的地址。每个这样的对象都有一个虚函数表指针,这个指针指向该对象所属类的虚函数表。

当你通过基类指针或引用调用虚函数时,编译器会生成代码来获取虚函数表指针,然后通过这个指针找到虚函数表,再从表中获取虚函数的地址,最后调用这个地址对应的函数。这个过程发生在运行时,所以称为动态绑定。

class Base {
public:
    virtual void foo() { std::cout << "Base::foo" << std::endl; }
};

class Derived : public Base {
public:
    void foo() override { std::cout << "Derived::foo" << std::endl; }
};

int main() {
    Base* ptr = new Derived();
    ptr->foo();  // 输出 "Derived::foo"
    delete ptr;
    return 0;
}

三.纯虚函数和抽象类

1.纯虚函数的作用

  • 定义接口:纯虚函数允许你定义一个接口,这个接口在基类中没有具体的实现,而是在派生类中实现。这使得你可以编写与具体实现无关的代码,只需要依赖接口。
  • 禁止实例化包含纯虚函数的类被称为抽象类,抽象类不能被实例化。这可以确保只有实现了所有纯虚函数的派生类才能被实例化。
  • 强制派生类实现:如果一个类从抽象类派生,并且想要成为一个可以实例化的类,那么它必须实现基类中的所有纯虚函数。这确保了派生类必须提供某些特定的行为。

2.纯虚函数的实现

//纯虚函数及抽象类
class test
{
public:
    //子类类必须重写父类中的纯虚函数,否则也是抽象类
    //抽象类不能实例化对象
    virtual void print() = 0;
};

class test1 : public test
{
public:
    virtual void print()
    {
        cout << "test1" << endl;
    }
};

void test3()
{
    test * p = new test1;
    p->print();
    delete p;
}

int main()
{
    test3();
    return 0;
}

四.虚析构和纯虚析构

1.虚析构和纯虚析构的概念

  1. 虚析构函数:如果你有一个指向派生类对象的基类指针,并且通过这个指针删除对象,那么如果基类的析构函数不是虚函数,就只会调用基类的析构函数,而不会调用派生类的析构函数,这可能会导致资源泄漏。如果基类的析构函数是虚函数,那么就会先调用派生类的析构函数,然后再调用基类的析构函数,这样就可以正确地清理资源。

  2. 纯虚析构函数:纯虚析构函数是一种特殊的虚析构函数,它在基类中没有实现(或者说,它的实现被声明为 0)。包含纯虚析构函数的类是抽象类,不能被实例化。纯虚析构函数必须在基类中提供定义,否则在删除派生类对象时会出错。

 2.虚析构和纯虚析构的实现

//虚析构和纯虚析构
class Animal
{
public:
    Animal()
    {
        cout << "Animal的构造函数调用" << endl;
    }
    //虚析构和纯虚析构就是用来解决通过父类指针释放子类对象
    //如果子类中有属性开辟了堆区数据,那么父类指针释放的时候就无法调用到子类的析构代码
    //导致子类出现内存泄漏
    //virtual ~Animal()
    //{
    //	cout << "Animal的析构函数调用" << endl;
    //}
    virtual ~Animal() = 0;
    virtual void speak() = 0;
};

Animal::~Animal()
{
    cout << "Animal的纯虚析构函数调用" << endl;
}

class Cat : public Animal
{
public:
    Cat(string name)
    {
        cout << "Cat的构造函数调用" << endl;
        m_Name = new string(name);
    }
    virtual void speak()
    {
        cout << *m_Name << "小猫在说话" << endl;
    }
    ~Cat()
    {
        if (m_Name != NULL)
        {
            cout << "Cat的析构函数调用" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }
    string *m_Name;
};

void test01()
{
    Animal *animal = new Cat("Tom");
    animal->speak();
    delete animal;
}

int main()
{
    test01();
    return 0;
}

总结

  • 1. 多态是面向对象编程的核心概念,可以通过基类指针或引用操作派生类对象。C++中主要通过虚函数实现多态。
  • 2. 多态分为静态多态(编译时多态)和动态多态(运行时多态)。静态多态主要通过函数重载和运算符重载实现。动态多态主要通过虚函数实现。
  • 3. 多态的主要优点是提高代码的可扩展性和可维护性。使用基类指针或引用可以编写能处理任何派生类对象的通用代码,而无需知道对象的具体类型。
  • 4. 为了实现动态多态,需要满足三个条件:有一个基类和至少一个派生类,基类中有一个或多个虚函数,并在派生类中被重写,使用基类的指针或引用来指向派生类的对象
  • 5. C++实现动态多态主要通过虚函数表(vtable)和虚函数表指针(vptr)。虚函数的运行过程是动态绑定的。
  • 6. 纯虚函数和抽象类用来定义接口,其中定义的函数在基类中没有具体实现,而在派生类中实现。抽象类不能被实例化,只能通过派生类进行实现。
  • 7. 虚析构和纯虚析构用于在删除基类指针时,能正确析构派生类对象,防止资源泄漏。

你可能感兴趣的:(C++学习笔记,c++,开发语言,学习)