专栏名称: 嵌入式微处理器
关注这个时代最火的嵌入式微处理器,你想知道的都在这里。
目录
相关文章推荐
51好读  ›  专栏  ›  嵌入式微处理器

必知必会的C++多态机制

嵌入式微处理器  · 公众号  ·  · 2024-04-18 12:00

正文

大家好,今天我们聊一聊C++中的多态机制。

unset unset 一、多态 unset unset

在 C++ 中, 多态 (Polymorphism)是一种面向对象编程的重要概念,它允许不同类的对象对同一消息做出不同的响应。具体来说,多态性允许基类的指针或引用在运行时指向派生类的对象,并且根据对象的实际类型来调用相应的成员函数。

多态性是通过虚函数来实现的。当一个基类的成员函数被声明为虚函数时,派生类可以通过覆盖(重写)这个函数来提供自己的实现。在运行时,调用这个虚函数的时候,实际上调用的是指向对象的实际类型的版本。

C++ 中的多态性有两种形式:静态多态(编译时多态)和动态多态(运行时多态)。

  1. 静态多态(编译时多态): 主要是通过函数重载和模板实现的,例如,同一个函数名可以有多个版本,根据参数的类型和数量来决定调用哪个版本的函数。这种多态性在编译时就已经确定了。

  2. 动态多态(运行时多态): 主要是通过虚函数和继承实现的,例如,基类指针指向派生类对象,并调用虚函数。在运行时,根据对象的实际类型来决定调用哪个版本的函数。这种多态性在运行时才会确定。

静态多态

静态多态(也称为编译时多态或早期多态)是指在编译时就确定函数调用的方式,主要通过函数重载和模板来实现。在静态多态中,编译器在编译时根据函数的签名(函数名称和参数列表)来确定调用哪个函数版本。

静态多态主要有两种形式:

  1. 函数重载: 函数重载允许在同一作用域内声明多个函数,它们具有相同的名称但参数列表不同。在调用函数时,编译器根据传递的参数的数量、类型和顺序来选择匹配的函数。

    #include 

    // 函数重载示例
    void print(int x) {
        std::cout <"Integer: " endl;
    }

    void print(double x) {
        std::cout <"Double: " endl;
    }

    int main() {
        print(5);       // 调用第一个 print 函数
        print(3.14);    // 调用第二个 print 函数
        return 0;
    }
  2. 模板: 模板是一种通用编程技术,允许编写与特定类型无关的代码。通过使用模板,可以在不同类型的参数上执行相同的操作,而无需为每种类型编写不同的函数。

    #include 

    // 模板示例
    template <typename T>
    void print(T x) {
        std::cout <"Value: " endl;
    }

    int main() {
        print(5);       // 实例化一个 int 类型的 print 函数
        print(3.14);    // 实例化一个 double 类型的 print 函数
        print("Hello"); // 实例化一个 const char* 类型的 print 函数
        return 0;
    }

在静态多态中,函数调用的决定在编译时完成,因此性能更高。然而,静态多态的缺点是在编写代码时必须明确指定每个函数的具体版本,如果有大量的重载或模板,可能会导致代码量增加和可读性降低。

动态多态

动态多态(也称为运行时多态或晚期多态)是指在程序运行时根据对象的实际类型来决定调用哪个函数版本。动态多态性通过虚函数和继承来实现,在编译时无法确定函数调用的具体版本,而是在运行时根据对象的类型动态确定。

动态多态的实现需要满足以下两个条件:

  1. 基类中声明虚函数: 在基类中将函数声明为虚函数,这样编译器就会在运行时进行函数调用的动态绑定。
  2. 派生类重写虚函数: 派生类中可以通过重写(覆盖)基类中的虚函数来提供自己的实现。在调用这个虚函数时,会根据对象的实际类型来决定调用哪个版本的函数。

下面是一个简单的示例,演示了动态多态的用法:

#include 

// 基类
class Animal {
public:
    // 虚函数
    virtual void makeSound() {
        std::cout <"Animal makes a sound" endl;
    }
};

// 派生类
class Dog : public Animal {
public:
    // 重写基类的虚函数
    void makeSound() override {
        std::cout <"Dog barks" endl;
    }
};

// 派生类
class Cat : public Animal {
public:
    // 重写基类的虚函数
    void makeSound() override {
        std::cout <"Cat meows" endl;
    }
};

int main() {
    // 创建派生类对象
    Dog dog;
    Cat cat;

    // 基类指针指向派生类对象
    Animal* ptr1 = &dog;
    Animal* ptr2 = &cat;

    // 通过基类指针调用虚函数,实现多态
    ptr1->makeSound(); // 调用的是派生类 Dog 的 makeSound() 函数
    ptr2->makeSound(); // 调用的是派生类 Cat 的 makeSound() 函数

    return 0;
}

Animal 类有一个虚函数 makeSound() ,而 Dog Cat 类分别继承自 Animal 类并重写了 makeSound() 函数。在 main() 函数中,我们创建了 Dog Cat 类的对象,并将基类指针指向这些对象,然后通过基类指针调用虚函数 makeSound() 。由于 makeSound() 是虚函数,所以在运行时根据对象的实际类型来决定调用哪个版本的函数,从而实现了动态多态性。

unset unset 二、父类指针指向子类对象 unset unset

在 C++ 中,可以使用父类的指针来指向子类的对象,这是实现多态的一种常见方式。这种行为被称为向上转型(upcasting),它允许您通过基类的接口来操作派生类的对象。这在面向对象编程中是非常有用的,因为它使代码更加灵活和可扩展。

下面是一个简单的示例说明了如何使用父类的指针来指向子类的对象:

#include 

// 基类
class Base {
public:
    virtual void display() {
        std::cout <"Base class display() called" endl;
    }
};

// 派生类
class Derived : public Base {
public:
    void display() override {
        std::cout <"Derived class display() called" endl;
    }
};

int main() {
    // 创建派生类对象
    Derived derivedObj;

    // 使用基类指针指向派生类对象
    Base* basePtr = &derivedObj;

    // 通过基类指针调用虚函数,实现多态
    basePtr->display(); // 调用的是派生类的 display() 函数

    return 0;
}

Base 是基类, Derived 是派生类。 Base 类有一个虚函数 display() Derived 类重写了 display() 函数。在 main() 函数中,我们创建了 Derived 类的对象 derivedObj ,然后使用 Base 类的指针 basePtr 指向了 derivedObj 。最后,通过 basePtr 调用 display() 函数,由于 display() 函数是虚函数,所以调用的是 Derived 类中的版本,实现了多态行为。

方法调用

在 C++ 中,如果父类通过指针或引用调用一个虚函数,而这个虚函数在子类中被重写(override),那么调用的实际方法将取决于指针或引用所指向的对象的类型。这就是多态的体现。

具体来说,如果父类指针或引用指向的是子类对象,那么调用的方法将是子类中重写的版本;如果指针或引用指向的是父类对象,那么调用的方法将是父类中的版本。

下面是一个示例来说明这一点:

#include 

// 基类






请到「今天看啥」查看全文