<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++】多态
多态是面向对象编程中的重要特性,允许通过基类引用调用派生类的具体方法,实现代码的灵活性和扩展性。其核心机制包括虚函数、动态绑定及继承。通过声明虚函数并让派生类重写这些函数,可以在运行时决定具体调用哪个版本的方法。此外,多态还涉及虚函数表(vtable)的使用,其中存储了虚函数的指针,确保调用正确的实现。为了防止资源泄露,基类的析构函数应声明为虚函数。多态的底层实现涉及对象内部的虚函数表指针,指向特定于类的虚函数表,支持动态方法解析。
32 1
|
2月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
47 2
C++入门12——详解多态1
|
2月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
89 1
|
4月前
|
编译器 C++ 索引
C++虚拟成员-虚函数
C++虚拟成员-虚函数
|
29天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
50 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
103 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
89 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
108 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
32 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
32 4