<c++>虚函数与多态 | 虚函数与纯虚函数 | 多态的实现原理 | 虚析构函数

简介: <c++>虚函数与多态 | 虚函数与纯虚函数 | 多态的实现原理 | 虚析构函数

前言


在上一篇文章中,我们介绍了c++中类与对象的继承,继承可以根据一个或多个类来定义一个新的类,减少代码量,使得开发和维护一个应用程序变得更加的容易。本文将介绍c++继承的重要应用 —— 多态。


一、多态


Q:什么是多态?

A:多态是同一个事物在不同场景下的多种形式,具体讲就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。


多态是c++面向对象的三大特性之一,有着动态改变程序的功能,一般分为两类:静态多态和动态多态。


1、静态多态


Q:什么是静态多态?

A:静态多态,又称为编译期多态,它的特点就是函数地址早绑定,即在系统编译期间就可以确定程序将要执行哪个函数。函数重载和运算符重载,都是静态多态。


#include<iostream>
using namespace std;
class base1 {
public:
  void show() {
  cout << "父类中的show函数" << endl;
  }
};
class son1 : public base1 {
public:
  void show() {
  cout << "子类son1中的show函数" << endl;
  }
};
class son2 : public base1 {
public:
  void show() {
  cout << "子类son2中的show函数" << endl;
  }
}; 
int main() {
  base1 *a,*b;
  a = new son1;
  b = new son2;
  a -> show();
  b -> show();
  return 0;
}


7e8c60b6eaaafcfba194526f2c9a98b3_ac37e013da0947b7a96f30ef7b0bd2c3.png

在这个例子中,我们在父类base1与两个子类son1 son2中都实现一个show方法,我们分别调用两个子类的show方法,最终输出的都是 父类中的show函数。这说明在静态多态中,在系统编译期间就可以确定程序将要执行哪个函数,函数地址绑定父类中的show函数地址。


2、动态多态


Q:什么是动态多态?

A:动态多态的各种实现方法与静态多态一致,只不过多了一个virtual关键字来修饰,动态多态通过派生类和虚函数在运行时实现。


#include<iostream>
using namespace std;
class base1 {
public:
  virtual void show() {
  cout << "父类中的show函数" << endl;
  }
};
class son1 : public base1 {
public:
  void show() {
  cout << "子类son1中的show函数" << endl;
  }
};
class son2 : public base1 {
public:
  void show() {
  cout << "子类son2中的show函数" << endl;
  }
}; 
int main() {
  base1 *a,*b;
  a = new son1;
  b = new son2;
  a -> show();
  b -> show();
  return 0;
}


25dbc68415098dfdb09da780608f306e_693a6f2704f342b09e036cc8e86c9609.png

在这个例子中,我们使用了动态多态,可以成功调用两个子类中的show函数。


二、虚函数与纯虚函数


1、虚函数


Q:什么是虚函数?

A:我们将被virtual修饰过的函数称为虚函数.

从上面案例的运行结果来看,用基类的指针指向一个派生类时,如果调用了虚函数,则会调用派生类对应的虚函数而不是基类本身所拥有的虚函数。


2、纯虚函数


Q:什么是纯虚函数?

A:纯虚函数与虚函数相同,就是一个被virtual修饰过的函数,但是没有函数体,直接等于 0。当一个类中有了纯虚函数,这个类就称为抽象类。抽象类无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类。


三、多态的实现原理


1、虚函数表


Q:什么是虚函数表?

A:虚函数表中存储着虚函数的地址。当派生类重新定义虚函数时,则将该函数的地址添加到虚函数表中。


2、虚函数指针


Q:什么是虚函数指针?

A:当使用虚函数的时候,类空间对象会占用更大的内存空间。编译器会给每一个对象添加一个隐藏变量:指向虚函数表的指针。


注意:无论类的对象中定义了多少个虚函数,虚函数指针只有一个。


3、多态的实现原理


当调用虚函数时,由于派生类对象重写了派生类对应的虚函数表项,基类在调用时会调用派生类的虚函数,从而产生多态。


四、虚析构函数


当通过delete关键字删除有派生类对象的基类指针时,只会调用基类的析构函数,派生类的空间并没有被释放,会造成内存泄漏。为了避免内存泄露,需要使用虚析构函数在删除基类指针时可以调用子类的析构函数释放子类中堆内存,防止内存泄露。


我们来看看下面这个例子:


#include<iostream>
using namespace std;
class base1 {
public:
    base1() {}
    ~base1() { 
  cout<<"delete base1"<<endl; 
  }
    virtual void show() { 
  cout << "show base1" << endl;  
  }
};
class son1 : public base1 {
public:
    son1() {}
    ~son1() { 
  cout << "delete son1" << endl;  
  }
    void show() { 
  cout << "show son1" << endl;  
  }
};
int main() {
    base1 *p = new son1;
    p -> show();
    delete p;
    return 0;
}


bbbcf5f298f11dc1bce9a4ab2980d8ae_ddedbf42ed2944059f411a87a1a93b0e.png

在这个例子中,我们通过delete关键字删除指针时,没有调用派生类的析构函数。我们试试将基类中的析构函数改为虚析构函数。


#include<iostream>
using namespace std;
class base1 {
public:
    base1() {}
    virtual ~base1() { 
  cout<<"delete base1"<<endl; 
  }
    virtual void show() { 
  cout << "show base1" << endl;  
  }
};
class son1 : public base1 {
public:
    son1() {}
    ~son1() { 
  cout << "delete son1" << endl;  
  }
    void show() { 
  cout << "show son1" << endl;  
  }
};
int main() {
    base1 *p = new son1;
    p -> show();
    delete p;
    return 0;
}


d16861e988a19b0fb8b3528e9ec19336_4b5942c89fa04e85aa7c8e7fe6a642c2.png

可以发现,成功调用了派生类的析构函数,没有造成内存泄漏。


总结一下:


如果父类的析构函数不加上virtual关键字

当父类的析构函数不为虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数。

如果父类的析构函数加上virtual关键字

当父类的析构函数为虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数。


相关文章
|
1月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
38 2
C++入门12——详解多态1
|
1月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
79 1
|
3月前
|
存储 编译器 C++
|
3月前
|
存储 编译器 C++
C++多态实现的原理:深入探索与实战应用
【8月更文挑战第21天】在C++的浩瀚宇宙中,多态性(Polymorphism)无疑是一颗璀璨的星辰,它赋予了程序高度的灵活性和可扩展性。多态允许我们通过基类指针或引用来调用派生类的成员函数,而具体调用哪个函数则取决于指针或引用所指向的对象的实际类型。本文将深入探讨C++多态实现的原理,并结合工作学习中的实际案例,分享其技术干货。
74 0
|
9天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
36 4
|
10天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
33 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
23 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)