初识C++之虚函数

虚函数的概念是在基类中展开的,即把基类中须求定义为虚函数的成员函数表明为virtual。当基类中的某些成员函数被声称为虚函数后,就足以在派生类中被再次定义。在派生类中再度定义时,其函数原型,包涵重临类型、函数名、参数个数和等级次序,参数的逐一都必须要与基类中的原型完全意气风发致。

C++的多态性用一句话回顾正是:在基类的函数前增加virtual关键字,在派生类中重写该函数,运转时将会依赖目的的实际类型来调用相应的函数。假如目的类型是派生类,就调用派生类的函数;假诺指标类型是基类,就调用基类的函数

初识C++之虚函数

1、什么是虚函数
  在基类中用virtual关键字修饰,并在一个或八个派生类中被重新定义的成员函数,用法格式为:
  virtual 函数再次来到类型 函数名(参数表)
   {
    函数体
   }
  
虚函数是完毕多态性的重要,通过指向派生类的基类指针或引用,访谈派生类中同名覆盖成员函数。
  
看三个例子:
①未有概念基类的Fun函数为虚函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include 
using namespace std;

class Base
{
public:
    void Fun()
    {
        cout << "Base::Fun()" << endl;
    }
private:
    int data;
};

class Derived : public Base
{
public:
    void Fun()
    {
        cout << "Derived::Fun()" << endl;
    }
private:
    int data;
};

int main()
{
    Derived d;
    Base* pb = &d;
    pb->Fun();
    return 0;
}

图片 1
可以见见那儿基类指针固然针对的是派生类,但依然调用的是基类的Fun函数。<
喎?” target=”_blank”
class=”keylink”>vcD4NCjxwPqLatqjS5cHLu/nA4LXERnVuuq/K/c6q0Om6r8r9o7o8L3A+DQo8cHJlIGNsYXNzPQ==”brush:java;”>
#define _CRT_SECURE_NO_WARNINGS 1 #include using namespace std; class Base { public: virtual void Fun() //注意这儿加了关键字virtual { cout << "Base::Fun()" << endl; } private: int data; }; class Derived : public Base { public: void Fun() { cout << "Derived::Fun()" << endl; } private: int data; }; int main() { Derived d; Base* pb = &d; pb->Fun(); return 0; }

图片 2
能够看看那儿基类指针指向派生类后,其调用Fun函数时正是调用派生类的Fun函数了,从此时也能看出虚函数是促成多态的要害。

2、虚函数的完成
①在基类中用virtual关键字修饰成员函数,使之形成二个虚函数,在派生类中重写该函数,使之变成新的效能。

②派生类中重写该虚函数时,必要重写的函数的函数名、参数列表、重回值都与基类虚函数相似,函数体依据须求采用是还是不是重写。若不重写,则一直到位后续于基类。
PS:C++规定,当二个分子函数被定义为虚函数时,其派生类中的同名函数都活动造成虚函数,因而,派生类在再次定义该虚函数时,可加virtual关键字,也能够不加,但为了等级次序的明明白白与代码的易读性,最佳还是增加(那认为前面说的都以废话)。

③定义三个基类的指针,使之指向同风姿洒脱类族中必要调用的函数所在类的对象。

④经过该指针调用虚函数,那个时候调用的就是指针指向的靶子的同名函数了。

用多少个实例来验证上述达成机制:

#define _CRT_SECURE_NO_WARNINGS 1
#include 
using namespace std;

class Base
{
public:
    virtual void Fun()     //加virtual关键字
    {
        cout << "Base::Fun()" << endl;
    }
private:
    int data;
};

class Derived : public Base
{
public:
    void Fun()
    {
        //重写Fun函数的函数体,当然,这儿不是必须要重写的
        cout << "Derived::Fun()" << endl;
    }
private:
    int data;
};

class Derived_Derived : public Derived
{
public:
    void Fun()
    {
        //重写Fun函数的函数体,当然,这儿不是必须要重写的
        cout << "Derived_Derived::Fun()" << endl;
    }
private:
    int data;
};

int main()
{
    Derived_Derived d;
    Derived* pb = &d;
    pb->Fun();
    return 0;
}

图片 3
自身在Derived类中并从未在Fun函数前加virtual关键字,但pb指针调用的却是Derived_Derived类中的Fun函数,而非Derived类中的,那就表达了风流倜傥旦基类的积极分子函数被定义为虚函数时,派生类的同名函数就能够变成虚函数。

瞩目:虚函数只能出现在类的持续档期的顺序中,只可以定义类的成员函数为虚函数,无法定义类外的常备函数为虚函数:

#define _CRT_SECURE_NO_WARNINGS 1
#include 
using namespace std;

virtual void Fun()     //注意这儿加了关键字virtual
{
    cout << "Base::Fun()" << endl;
}

int main()
{
    Fun();
    return 0;
}

如上代码会报错:
图片 4

3、虚析构函数
  析构函数实行时先调用派生类的析构函数,其次才调用基类的析构函数。借使析构函数不是虚函数,而程序奉行时又要通过基类的指针去销毁派生类的动态指标(用new运算符创立的),那么用delete销毁对象时,只调用了基类的析构函数,未调用派生类的析构函数。那样会招致销毁对象不完全。

看例子:

#define _CRT_SECURE_NO_WARNINGS 1 
#include  
using namespace std; 

class Base 
{ 
public: 
  Base()
  { 
    cout << "Base()" << endl; 
  }
  
  ~Base() 
  {
    cout << "~Base()" << endl; 
  } 
private: 
  int data; 
};

class Derived: public Base 
{
public: 
  Derived()
  {
    cout << "Derived()" << endl;
  }
  
  ~Derived()
  {
    cout << "~Derived()" << endl;
  }
private: 
  int data;
}; 

int main()
{ 
  Base *pb = new Derived;
  delete pb; 
  return 0; 
}

图片 5
能够见到,确实是只调用了基类的析构函数,派生类的析构函数并不曾被调用。

当自家把基类的析构函数改成这样:

virtual ~Base() 
{
  cout << "~Base()" << endl;
} 

再运营时,结果如下:
图片 6

可以看看,那个时候无论是是基类依然派生类的的析构函数都被调用了,为啥吗?
解析如下:
要是Derive类public世袭自Base类,况且基类的析构函数未有概念为虚函数,犹如下调用:
Base* pb = new Derive;
delete pb;
这儿,delete
pb时,编写翻译器会把pb当成Base的一个指标看待,因为pb是Base类型的,所以直接去调用基类的析构函数;
若将基类的析构函数写成virtual函数,那么基类和派生类的析构函数会分别寄存在本人的虚表中,当时再实行delete
pb时,会调用析构函数,但以后杜撰函数是虚函数,所以会到虚表中去找出,而当时pb指向的适逢其会是一个派生类对象,所以经过虚表查找就找到了派生类的虚函数,进而调用派生类的析构函数。

4、纯虚函数
①定义:
  纯虚函数是在基类中注明的虚函数,它在基类中绝非定义,即未有函数体,但要求任何派生类都要贯彻团结的函数体。在基类中落实纯虚函数的办法是在函数原型后加“=0;“。
virtual void funtion()=0 ;

②引进原因 :
  为了方便使用多态特性,大家日常要求在基类中定义虚构函数。
但在广大情况下,基类自身生成对象是不符情理的。举个例子,动物作为一个基类能够派生出老虎、孔雀等子类,但动物本人生成对象分明不合常理。
为了缓慢解决上述难点,引进了纯虚函数的定义,将函数定义为纯虚函数,则编写翻译器须求在派生类中必得给与重写以落到实处多态性。那样就很好地化解了上述多个难题。

③作用:
  纯虚函数的效能是在基类中为其派生类保留二个函数的名字,以便派生类遵照供给对其进行贯彻,进而完毕多态性。

④抽象类
  不用来定义对象,只是充作黄金年代种基本项目用作世襲的类,称为抽象类,由于抽象类平常作为基类,所以也叫做抽象基类。包括纯虚函数的类正是抽象类,它无法生成靶子。

注意:
①纯虚函数没有函数体;
②纯虚函数证明方式最终的“=0;”并不是再次来到值是0,它只是起二个方式上的机能,告诉编译器那是虚函数;
③虚函数的注明情势是二个口舌,最终要充足“;”。
④纯虚函数独有扬言,未有函数体,所以它不具有函数成效,由此无法被调用,能够称其为“华而不实”。
⑤万意气风发四个类中宣称了虚函数,且在其派生类中并未有被达成,那么在派生类中它仍是纯虚函数。

末段用二个实例来分析:

class Animal
{
public:
    virtual void Sleep() = 0;   //这里声明Sleep为纯虚函数
public:
    char sex[5];
    int age;
};

class Person: public Animal
{
public:
    virtual void Sleep()
    {
        cout << "Person lying down to sleep!" << endl;
    }
public:
    char notionality[20];   //国籍
    char name[10];
};

class Horse: public Animal
{
public:
    virtual void Sleep()
    {
        cout << "Horse sleep standing up!" << endl;
    }
public:
    char rase[20];          //种族
};

int main()
{
    Animal a;
    return 0;
}

图片 7
那会儿用包涵纯虚函数Sleep的抽象类Animal来创立对象时,会报错。

笔者把主函数改为调用抽象类Animal中的Sleep函数:

int main()
{
    Animal::Sleep();
    return 0;
}

图片 8
调用多少个纯虚函数,会报错。

自笔者把Person改善了,去掉它对Sleep函数的贯彻:

class Person: public Animal
{
public:
    char notionality[20];   //国籍
    char name[10];
};

图片 9
当时Person类中从不对Sleep函数实行落实,因而,Person类中的Sleep函数仍然是虚函数,会报错。

 

 

1、什么是虚函数
在基类中用virtual关键字修饰,并在一个或多个派生类中被另行定义的积极分子函数,用法格式为:
virtual 函数…

当Fish类的fh对象组织完毕后,当中间的虚表指针也就被最早化为指向Fish类的虚表。在类型转变后,调用an-breathe,由于an实际指向的是Fish类的靶子,该指标内部的虚表指针指向的是Fish类的虚表,由此最后调用的是Fish类的breathe函数。

  1:用virtual关键字注脚的函数叫做虚函数,虚函数断定是类的分子函数。  

 
2:存在虚函数的类都有四个大器晚成维的虚函数表叫做虚表,类的对象有二个照准虚表开端的虚指针。虚表是和类对应的,虚表指针是和对象对应的。
 

 
3:多态性是三个接口多样贯彻,是面向对象的主导,分为类的多态性和函数的多态性。
 

  4:多态用虚函数来得以完成,结合动态绑定.  

  5:纯虚函数是虚函数再加多 = 0;  

  6:抽象类是指包含最少叁个纯虚函数的类。

纯虚函数:virtual void
fun(卡塔尔=0;即抽象类!必得在子类达成那一个函数,即先盛名称,未有内容,在派生类达成内容。

咱俩先看个例证

图片 10

 1 #include "stdafx.h"
 2 #include <iostream> 
 3 #include <stdlib.h>
 4 using namespace std; 
 5 
 6 class Father
 7 {
 8 public:
 9     void Face()
10     {
11         cout << "Father's face" << endl;
12     }
13 
14     void Say()
15     {
16         cout << "Father say hello" << endl;
17     }
18 };
19 
20 
21 class Son:public Father
22 {
23 public:     
24     void Say()
25     {
26         cout << "Son say hello" << endl;
27     }
28 };
29 
30 void main()
31 {
32     Son son;
33     Father *pFather=&son; // 隐式类型转换
34     pFather->Say();
35 }

图片 11

出口的结果为:
图片 12

我们在main(卡塔尔国函数中第一定义了贰个Son类的对象son,接着定义了三个指向Father类的指针变量pFather,然后利用该变量调用pFather->Say(State of Qatar.测度很四人往往将这种景观和c++的多态性搞混淆,以为son实际上是Son类的目的,应该是调用Son类的Say,输出”Son
say hello”,然则结果却不是.

 

  从编写翻译的角度来看:

    c++编写翻译器在编写翻译的时候,要规定各个对象调用的函数(非虚函数)之处,那称为开始的一段时期绑定,当大家将Son类的对象son的地址赋给pFather时,c++编译器进行了类型调换,当时c++编写翻译器以为变量pFather保存的正是Father对象的地点,当在main函数中施行pFather->Say(卡塔尔国,调用的本来就是Father对象的Say函数

 从内部存款和储蓄器角度看

    图片 13

Son类对象的内部存款和储蓄器模型如上航海用体育场所

发表评论

电子邮件地址不会被公开。 必填项已用*标注