<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掉父类的指针,先调动子类的析构函数,再调动父类的析构函数。


相关文章
|
10月前
|
存储 人工智能 编译器
c++--多态
上一篇文章已经介绍了c++的继承,那么这篇文章将会介绍多态。看完多态的概念,你一定会感觉脑子雾蒙蒙的,那么我们先以举一个例子,来给这朦胧大致勾勒出一个画面,在此之前,先介绍一个名词虚函数,(要注意与虚拟继承区分)重定义: 重定义(隐藏)只要求函数名相同(但要符合重载的要求,其实两者实际上就是重载);重定义下:在这种情况下,如果通过父类指针或引用调用函数,会调用父类的函数而不是子类。重定义(或称为隐藏)发生的原因是因为函数名相同但参数列表不同,导致编译器无法确定调用哪一个版本的函数。
190 0
|
存储 编译器 C++
【c++】多态(多态的概念及实现、虚函数重写、纯虚函数和抽象类、虚函数表、多态的实现过程)
本文介绍了面向对象编程中的多态特性,涵盖其概念、实现条件及原理。多态指“一个接口,多种实现”,通过基类指针或引用来调用不同派生类的重写虚函数,实现运行时多态。文中详细解释了虚函数、虚函数表(vtable)、纯虚函数与抽象类的概念,并通过代码示例展示了多态的具体应用。此外,还讨论了动态绑定和静态绑定的区别,帮助读者深入理解多态机制。最后总结了多态在编程中的重要性和应用场景。 文章结构清晰,从基础到深入,适合初学者和有一定基础的开发者学习。如果你觉得内容有帮助,请点赞支持。 ❤❤❤
1471 0
|
编译器 C++
c++中的多态
c++中的多态
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
12月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
468 12
|
10月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
251 0
|
10月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
396 0
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
233 16
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。