c++学习笔记(5)-多态

本文详细介绍了C++中静态多态(包括函数重载、模板和隐藏)与动态多态(通过虚函数实现)的概念、实现方法、原理及应用。展示了如何通过模板和继承机制实现CRTP,以及它们在提高代码灵活性和可扩展性上的作用。
摘要由CSDN通过智能技术生成

1、引言

多态是面向对象编程的一种特性,简单来说就是调用同名函数,根据具体的对象类型、或者参数类型,执行不同的方法实现。在C++中多态有两种实现方式,即静态多态(编译时多态)和动态多态(运行时多态)。

2、静态多态

2.1、概念

也称编译期多态。静态多态是指在编译时就能够确定函数调用的具体实现,主要包括函数重载、模板和隐藏3种实现方式。函数重载是指在同一个作用域内,名称相同的函数参数类型或参数个数不同,从而可以根据调用时提供的实参来确定实际调用的函数。模板多态是指编译时进行代码生成,根据传递的模板参数不同,生成不同的代码。隐藏是指在子类中定义父类同名同参数函数。

2.2、实现方法

C++中的静态多态可以通过函数重载、模板以及隐藏实现,以下分别展示各自的使用例子。

2.2.1、通过模板实现静态多态:

#include <iostream>

template<typename T>
T max(T a, T b){
    return a > b ? a : b;
}

int main()
{
    // 输出20,调用max<int>函数
    std::cout << max(10, 20) << std::endl;  
    //输出3.14,调用max<double>函数
    std::cout << max(1.2, 3.14) << std::endl;  
    return 0;
}

2.2.2、通过函数重载实现静态多态:

#include <iostream>

void print(int num){
    std::cout << "The integer is " << num << std::endl;
}

void print(double num){
    std::cout << "The double is " << num << std::endl;
}

int main(){
    print(10);  // 调用第一个print函数
    print(3.14);  // 调用第二个print函数
    return 0;
}

2.2.3、通过隐藏实现静态多态——在子类中定义父类同名同参数函数。

class Rectangle{
public:
    void name() {
        std::cout<<"矩形"<<std::endl;
    }
};

class Circle {
public:
    void name() { 
        std::cout<<"圆形"<<std::endl;
    }
};

template<typename T>
void print(T shape){
    shape.name();
}

int main() {
    print<Rectangle>();//矩形
    print<Circle>();//圆形
    return 0;
}

2.3、原理

静态多态都是通过name mangling实现的。name mangling是一种编译器技术,用于为程序中的变量和函数生成唯一的内部名称,这个过程涉及改变原始名称的形式,从而避免了变量和函数之间名称的冲突。name mangling规则的具体实现方式因编译器而不同,下面是一些常见编译器的name mangling规则:

  • MSVC编译器:在函数名前加上下划线,然后加上函数名,以及加上所有参数的数据类型与长度信息,最终生成修饰过的函数名。例如,函数名foo(int, float)会被修饰为_foo@8。其中8是函数参数的总长度,指示了函数的参数类型。
  • G++编译器:以函数名和参数类型组成的字符串作为函数的新名字。例如,函数名foo(int, float)会被编译器重命名为_Z3fooif,其中Z表示这是一个C++函数,3表示该函数名长度为3,f和i分别表示两个参数的数据类型为float和int。
  • Clang编译器:类似于GCC,使用函数名和参数类型生成一个字符串作为新的函数名,不过字符串的格式可能略有不同。

编译前,不同名称空间内的同名函数、同名但参数不同的函数,经过name mangling处理,都有唯一名称,从而能够被区分。

2.4、CRTP

CRTP是Curiously Recurring Template Pattern(奇怪的重复模板模式)的缩写,是一种利用继承和模板技术实现的编程模式,用于在编译时实现静态多态,也称为根据类型递归静态多态,它是静态多态使用案例中的一种最佳实践。CRTP通过继承一个模板类,并在模板参数中传递自身类型,来实现静态多态,举个例子:

template<typename T> 
class Base {
public:
  void foo() { static_cast<T *>(this)->impl_foo(); }
};

class Derived1 : public Base<Derived1> {
public:
  void impl_foo() { cout << "Derived1 foo" << endl; }
};

class Derived2 : public Base<Derived2> {
public:
  void impl_foo() { cout << "Derived2 foo" << endl; }
};

3、动态多态

3.1、概念

也称运行时多态。C++中的动态多态是通过虚函数(virtual function)实现的。其基本使用是:在父类中定义一个虚函数,在子类中重写这个虚函数。在程序运行时,通过基类指针或引用指向子类对象,并使用基类对象调用虚函数时,会自动调用子类的重写版本。

3.2、使用方法

以下是一个使用动态多态的例子。假设我们需要实现一个图形类Shape,包含一个虚函数 say() 用于表面该图形的形状。我们要实现两个派生类 Circle 和 Rectangle,分别用于表示圆形和矩形,它们重写了基类 Shape 中的虚函数 say()。

class Shape {
public:
    virtual void say() = 0;
};

class Rectangle: public Shape {
public:
    void say() {
        std::cout<<"矩形"<<std::endl;
    }
};

class Circle: public Shape {
public:
    void say() { 
        std::cout<<"圆形"<<std::endl;
    }
};

接下来就可以通过基类指针调用同名函数的不同实现了。

int main() 
{
   Shape *shape;
   Rectangle rec;
   Circle cir;

   shape = &rec;
   shape->say();//矩形

   shape = &cir;
   shape->say();//圆形
   
   return 0;
}

3.3、原理

C++动态多态是通过虚函数和虚函数表实现的。C++编译器为每一个有虚函数的类生成一个虚函数表,虚函数表是一个指向各个虚函数的函数指针数组,类实例化成对象后,对象首地址处存放有一个指针vptr指向虚函数表。
image.png
类继承的情况下,每个子类以及当前类都会有一个虚函数表。每个子类的虚函数表,会存储子类和父类所有的虚函数地址,如果子类与父类有同名虚函数,则只填子类中的同名虚函数地址。
image.png
当程序运行时,基类指针或引用调用虚函数,会根据对象的实际类型(即运行时类型),选择一个虚函数表,并在虚函数表中查找对应的函数指针,然后调用指向的具体实现代码。

4、总结

总之,多态是面向对象编程的一个重要特性,通过多态可以增强代码的灵活性和可扩展性、提高代码的可复用性、简化代码的设计和维护以及改善代码的可读性和可理解性,包括静态多态和动态多态,两者互有优劣,适用于不同场景。
image.png

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值