C++——多态

简介: C++——多态

文章目录

多态

函数重写(虚函数覆盖)、多态的概念

函数重写要求(虚函数覆盖条件)

多态的条件

纯虚函数、抽象类和纯抽象类

多态原理

虚析构函数

代码示例


多态

例如:实现图形库,可以用于显示多种图形


   

图形(位置/绘制)
    /     \
  矩形(长和宽/绘制)  圆形(半径/绘制)


函数重写(虚函数覆盖)、多态的概念

  • 如果将基类中的某个成员函数声明为虚函数,那么其子类中与该函数具有相同原型的成员函数也就是虚函数,并且对基类版本形成覆盖,即函数重写。
  • 此时,通过指向子类对象的基类指针,或引用子类对象的基类引用,调用虚函数,实际被执行的将是子类中的覆盖版本,而不再是基类中原始版本,这种语法现象称为多态。

函数重写要求(虚函数覆盖条件)

  • 只有类中成员函数才能被声明为虚函数,全局函数、静态成员函数、构造函数都不能为虚函数。(析构函数可以为虚函数)
  • 只有在基类中以 virtual 关键字修饰的成员函数才能作为虚函数被子类覆盖,而与子类中 virtual 无关。
  • 虚函数在子类中的版本和基类中的版本要具有相同的函数签名,即函数名、参数表、常属性一致。
  • 如果基类中的虚函数返回基本类型的数据,那么该函数在子类中的版本必须返回相同类型的数据。
  • 如果基类中的虚函数返回的是类类型的指针(A*)或引用(A&),那么允许子类的版本返回其子类类型的指针(B*)或引用(B&)——类型协变。

 

class A{};
  class B:public A{};


多态的条件

  • 多态特性除了要满足虚函数覆盖,还必须通过指针或引用去调用虚函数才能表现出来。
  • 调用虚函数的指针也可以是this指针,只要它是一个指向子类对象的基类指针,同样可以表现多态的语法特性。

例如:QT多线程的实现

class QThread{//QT官方定义表示多线程类
  protected:
  virtual void run(void){
    //线程的入口函数
  }
  public:
  void start(void){
    this->run();
  }
  };
  class MyThread:public QThread{
  protected:
  void run(void){
    //需要放到线程中执行的代码
  }
  };
  MyThread thread;
  thread.start();


纯虚函数、抽象类和纯抽象类

  • 纯虚函数
virtual 返回类型 函数名(形参表)[const] = 0;
  • 抽象类
  • 如果一个类中包含了纯虚函数,那么这个类就是抽象类,(注:抽象类不能创建对象)
  • 纯抽象类
  • 如果一个类中所有的成员函数都是纯虚函数,那么这个类就是纯抽象类(有名接口类)

多态原理

通过虚函数表和动态绑定实现:


  • 虚函数表会增加内存开销
  • 动态绑定会增加时间开销
  • 虚函数不能做内联优化

注:如果没用多态的语法要求,最好不用使用虚函数。


虚析构函数

  • 基类的析构函数不会调用子类的析构函数,对一个指向子类对象的基类指针使用 delete 操作符,实际被调用的仅是基类的析构函数,子类的析构函数不会被调用,有内存泄漏的风险。
  • 可以将基类中析构函数声明为虚函数,那么子类中的析构函数也就是虚函数,并且可以对基类中版本形成有效的覆盖,可以表现多态的语法特性;这时delete一个指向子类对象的基类指针,实际被执行的将是子类的析构函数,而子类析构函数在执行结束以后会自动调用基类的析构,避免了内存泄漏。

代码示例

  • shape.cpp
#include <iostream>
using namespace std;
class Shape{
public:
    Shape(int x=0,int y=0):m_x(x),m_y(y){}
    virtual void draw(void){//虚函数
        cout << "绘制图形:" << m_x << 
            ',' << m_y << endl;
    }
protected:
    int m_x;//坐标
    int m_y;
};
class Rect:public Shape{
public:
    Rect(int x,int y,int w,int h):
        Shape(x,y),m_w(w),m_h(h){}
    void draw(void){
        cout << "绘制矩形:" << m_x << "," <<
            m_y << "," << m_w << "," << m_h
            << endl;
    }
private:
    int m_w;
    int m_h;
};
class Circle:public Shape{
public:
    Circle(int x,int y,int r)
        :Shape(x,y),m_r(r){}
    void draw(void){
        cout << "绘制圆形:" << m_x << "," <<
            m_y << "," << m_r << endl;
    }
private:
    int m_r;
};
void render(Shape* buf[]){
    for(int i=0;buf[i]!=NULL;i++){
        //多态:根据指针实际指向的目标对象
        //类型去调用虚函数,不再由指针本身
        //类型决定
        buf[i]->draw();
    }
}
int main(void)
{
    Shape* buf[1024] = {NULL};
    buf[0] = new Rect(1,2,3,4);
    buf[1] = new Rect(11,21,33,24);
    buf[2] = new Circle(5,7,8);
    buf[3] = new Rect(1,2,3,4);
    buf[4] = new Circle(9,10,13);
    buf[5] = new Rect(1,2,3,4);
    render(buf);
    return 0;
}


  • 执行结果

20200214203919180.png

  • poly.cpp
#include <iostream>
using namespace std;
class Base{
public:
    virtual int cal(int a,int b){
        return a + b;
    }
    /* func是基类中成员函数,里面this指针类型
     * 一定是Base*.
     * 但是func函数的调用对象可以是子类对象,
     * this又指向调用对象,这时this就是一个指
     * 向子类对象的基类指针,再通过它去调用虚
     * 函数,也可以表现多态的特性
     * */
    void func(void){
        cout << cal(10,20) << endl;//200
    }/*
    void func(Base* this=&d){
        cout << this->cal(10,20) << endl;
    }*/
};
class Derived:public Base{
public:
    int cal(int a,int b){
        return a * b;
    }
};
int main(void)
{
    Derived d;
    Base b = d;
    cout << b.cal(10,20) << endl;
    d.func();//func(&d)
    return 0;
}


  • 执行结果

20200214204301953.png

  • 02poly.cpp
#include <iostream>
using namespace std;
class Base{
public:
    Base(void){
        cout << "Base的构造函数" << endl;
    }
    //虚析构函数
    virtual ~Base(void){
        cout << "Base的析构函数" << endl;
    }
};
class Derived:public Base{
public:
    Derived(void){
        cout << "Derived的构造函数" << endl;
    }
    ~Derived(void){
        cout << "Derived的析构函数" << endl;
    }
};
int main(void)
{
    Base* pb = new Derived;
    //...
    //pb->析构函数
    delete pb;
}


  • 执行结果

20200214204640481.png

  • typeid.cpp
#include <iostream>
#include <typeinfo>
using namespace std;
class A{ virtual void foo(void){} };
class B:public A{ void foo(void){} };
class C:public A{ void foo(void){} };
void func(A& ra){
    if(typeid(ra) == typeid(B)){
        cout << "针对B子类对象的处理" 
            << endl;
    }
    else if(typeid(ra) == typeid(C)){
        cout << "针对C子类对象的处理"
            << endl;
    }
}
int main(void)
{
    int i;
    cout << typeid(int).name() << endl;//i
    cout << typeid(i).name() << endl;//i
    int* a1[10];
    int (*a2)[10];
    cout << typeid(a1).name() 
        << endl;//A10_Pi
    cout << typeid(a2).name() 
        << endl;//PA10_i
    cout << typeid(int (*[10])(char)).name()
        << endl;//A10_PFicE
    B b;
    func(b);
    C c;
    func(c);
    return 0;
}

执行结果

20200214205203801.png

相关文章
|
10月前
|
存储 人工智能 编译器
c++--多态
上一篇文章已经介绍了c++的继承,那么这篇文章将会介绍多态。看完多态的概念,你一定会感觉脑子雾蒙蒙的,那么我们先以举一个例子,来给这朦胧大致勾勒出一个画面,在此之前,先介绍一个名词虚函数,(要注意与虚拟继承区分)重定义: 重定义(隐藏)只要求函数名相同(但要符合重载的要求,其实两者实际上就是重载);重定义下:在这种情况下,如果通过父类指针或引用调用函数,会调用父类的函数而不是子类。重定义(或称为隐藏)发生的原因是因为函数名相同但参数列表不同,导致编译器无法确定调用哪一个版本的函数。
201 0
|
存储 编译器 C++
【c++】多态(多态的概念及实现、虚函数重写、纯虚函数和抽象类、虚函数表、多态的实现过程)
本文介绍了面向对象编程中的多态特性,涵盖其概念、实现条件及原理。多态指“一个接口,多种实现”,通过基类指针或引用来调用不同派生类的重写虚函数,实现运行时多态。文中详细解释了虚函数、虚函数表(vtable)、纯虚函数与抽象类的概念,并通过代码示例展示了多态的具体应用。此外,还讨论了动态绑定和静态绑定的区别,帮助读者深入理解多态机制。最后总结了多态在编程中的重要性和应用场景。 文章结构清晰,从基础到深入,适合初学者和有一定基础的开发者学习。如果你觉得内容有帮助,请点赞支持。 ❤❤❤
1517 0
|
编译器 C++
c++中的多态
c++中的多态
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
206 2
C++入门12——详解多态1
|
存储 编译器 数据安全/隐私保护
【C++】多态
多态是面向对象编程中的重要特性,允许通过基类引用调用派生类的具体方法,实现代码的灵活性和扩展性。其核心机制包括虚函数、动态绑定及继承。通过声明虚函数并让派生类重写这些函数,可以在运行时决定具体调用哪个版本的方法。此外,多态还涉及虚函数表(vtable)的使用,其中存储了虚函数的指针,确保调用正确的实现。为了防止资源泄露,基类的析构函数应声明为虚函数。多态的底层实现涉及对象内部的虚函数表指针,指向特定于类的虚函数表,支持动态方法解析。
201 1
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
263 1
|
存储 编译器 C++
【C++】深度解剖多态(下)
【C++】深度解剖多态(下)
206 1
【C++】深度解剖多态(下)
|
存储 编译器 C++
|
机器学习/深度学习 算法 C++
C++多态崩溃问题之为什么在计算梯度下降时需要除以批次大小(batch size)
C++多态崩溃问题之为什么在计算梯度下降时需要除以批次大小(batch size)