• C++——虚函数、虚析构函数、纯虚函数、抽象类


    目录

    一、虚函数

    1、什么是虚函数?

    2、虚函数的作用

     3、虚函数的参数

    4、虚函数代码示例

    二、虚析构函数

    1、什么是虚析构函数?

    2、虚析构函数的作用

     3、 构造函数

    4、为什么构造函数不能是虚函数?

    5、构造函数和虚析构函数的联系

    6、为什么程序员不能调构造函数,但是可以调用析构函数?

    7、虚析构函数产生多态

    三、纯虚函数

    1、定义:

    2、纯虚函数的一般格式

     3、例题理解

    四、抽象类

    1、抽象类的定义

    2、抽象类的主要作用 

    一、虚函数

    1、什么是虚函数?

    •   被 virtual 关键字修饰的成员函数称为虚函数。
    •  在类中定义了虚函数就会有一个虚函数表(vftable),对象模型中就含有一个指向虚表的指针(__vfptr)。在定义对象时构造函数设置虚表指针指向虚函数表。
    • 使用指针和引用调用虚函数,在编译只需要知道函数接口,运行时指向具体对象,才能关联具体对象的虚方法(通过虚函数指针查虚函数表得到具体对象中的虚方法)。

    2、虚函数的作用

         实现多态性,即通过基类访问派生类的函数。

     3、虚函数的参数

      虚函数的参数值是默认静态绑定的,在子类中 重新定义虚函数的时候,并没有重新定义继承而来的参数值;除非是在实际调用过程中传递了想用的参数。 

    4、虚函数代码示例

    1. class Parent
    2. {
    3. public:
    4. virtual void fn(int a = 10)//如果虚函数有值,虚函数的参数值是默认静态绑定
    5. {
    6. cout << "parent fn a=" << a<
    7. }
    8. };
    9. class Child :public Parent
    10. {
    11. public:
    12. virtual void fn(int b = 100)
    13. {
    14. cout << "child fn b=" << b << endl;
    15. }
    16. };
    17. int main()
    18. {
    19. Child cc;
    20. Parent* p = &cc;
    21. //p->fn(200);//chlid fn b=200;
    22. p->fn();//child fn b=10;产生了动态绑定,但是虚函数的参数是默认绑定
    23. cc.fn();//child fn b=100;
    24. }

    二、虚析构函数

    1、什么是虚析构函数?

    •       派生类的析构函数会自动调用基类的析构函数。 只要基类的析构函数是虚函数,那么派生类的析构函数不论是否用virtual关键字声明,都自动成为虚析构函数
    •      一般来说,一个类如果定义了虚函数,则最好将析构函数也定义成虚函数。 析构函数可以是虚函数,但是构造函数不能是虚函数。(第4点有具体原因说明)
    • 注意:类中没有虚函数,就不要把析构函数定义为虚。
      在动态分配内存时所有C++的标准库函数都采用这种格式。

    2、虚析构函数的作用

    为了解决基类指针指向派生类对象,并用基类的指针删除派生类对象。  

     3、 构造函数

    构造函数是类的一个特殊的成员函数 :

    • 1)  当一个对象的生命周期结束时,系统会自动调用析构函数注销该对象并进行善后工作,对象自身也可以调用析构函数;
    • 2)析构函数的善后工作是:释放对象在生命期内获得的资源(如动态分配的内存,内核资源);
    • 3)  析构函数也用来执行对象即将被撤销之前的任何操作。

    4、为什么构造函数不能是虚函数?

           根据赋值兼容规则,可以用基类的指针指向派生类对象,如果使用基类型指针指向动态创建的派生类对象,由该基类指针撤销派生类对象,则必须将析构函数定义为虚函数,实现多态性,自动调用派生类析构函数,否则可能存在内存泄漏问题

    总结:在实现运行时的多态,无论其他程序员怎样调用析构函数都必须保证不出错,所以必须把析构函数定义为虚函数。

    5、构造函数和虚析构函数的联系

    • 如果构造函数可以定义为虚构造函数,使用指针调用虚构造函数,
    • 如果编译器采用静态联编,构造函数就不能为虚函数。
    • 如果采用动态联编,运行时指针指向具体对象,使用指针调用构造函数,相当于已经实例化的对象在调用构造函数,这是不容许的调用,对象的构造函数只执行一次。 

    6、为什么程序员不能调构造函数,但是可以调用析构函数?

    原因:

    •      虚函数调用只需要"部分的"信息,即只需要知道函数接口,而不需要对象的具体类型。但是构建一个对象,却必须知道具体的类型信息。
    •      如果你调用一个虚构造函数,编译器无法知道你想构建是继承树上的哪种类型,所以构造函数不能为虚。

    7、虚析构函数产生多态

    多态特例——虚析构函数产生多态;
    类有指针作为数据成员,必须要写析构函数,如果当前类被继承了,则析构函数写成virtual
    为了实现多态,将子类的空间被合理的释放,防止内存泄露 。

    1. class A {
    2. public:
    3. A()
    4. {
    5. cout << "A" << endl;
    6. m_i = new int;
    7. }
    8. virtual ~A()
    9. {
    10. cout << "~A" << endl;
    11. delete m_i;
    12. }
    13. private:
    14. int* m_i;
    15. };
    16. class B :public A
    17. {
    18. public:
    19. B()
    20. {
    21. cout << "B" << endl;
    22. m_j = new int;
    23. }
    24. virtual ~B()
    25. {
    26. cout << "~B" << endl;
    27. delete[]m_j;
    28. }
    29. private:
    30. int* m_j;
    31. };
    32. void test(A* pb)
    33. {
    34. ;
    35. }
    36. int main()
    37. {
    38. A* pb = new B;//new B类对象,先调用基类构造函数,再调用子类构造函数,
    39. delete pb;//pb指向子类对象,但是pb是A类类型指针,所以只析构了A,没有调子类类型的析构函数,存在内存泄漏。
    40. }

    三、纯虚函数

    1、定义:

           纯虚函数(pure virtual function)是指没有具体实现的虚成员函数。它用于这样的情况:设计一个类型时,会遇到无法定义类型中虚函数的具体实现,其实现依赖于不同的派生类。

    2、纯虚函数的一般格式

                              virtual返回类型函数名(参数表)=0;
    “=0"表明程序员将不定义该虚函数实现,没有函数体,只有函数的声明;函数的声明是为了在虚函数表中保留一个位置。在虚函数()的基础上=0;“=0“体质上是将指向函数体的指针定义为nullptr。

     3、例题理解

     题目:
    设计一个形状类——有矩形、圆形、三角形;分别计算三种图形的周长和面积;

    1. class Shape
    2. {
    3. public:
    4. virtual void Area() = 0;//纯虚函数就是在虚函数的基础上给个0
    5. virtual void Girth() = 0;//相当于把函数指针指向空
    6. };
    7. class Rectangle :public Shape
    8. {
    9. public:
    10. virtual void Area() { cout << "Rectangle area"<< m_length*m_width << endl; }
    11. virtual void Girth(){ cout << "Rectangle girth " << 2*(m_length+m_width)<
    12. private:
    13. int m_length;
    14. int m_width;
    15. };
    16. class Circle :public Shape
    17. {
    18. public:
    19. virtual void Area() { cout << "Circle area "<<3.14*m_ra*m_ra << endl; }
    20. virtual void Girth() { cout << "Circle girth"<< 2*3.14*m_ra<< endl; }
    21. private:
    22. int m_ra;//圆的半径
    23. };
    24. class Triangle :public Shape
    25. {
    26. public:
    27. virtual void Area() { cout << "Triangle area " << endl; }
    28. virtual void Girth() { cout << "Triangle girth "<<3*m_length << endl; }
    29. private:
    30. int m_length;
    31. };
    32. void test(Shape* p)
    33. {
    34. p->Area();
    35. p->Girth();
    36. }
    37. int main()
    38. {
    39. Shape* pf[3];
    40. pf[0] = new Rectangle;
    41. pf[1] = new Circle;
    42. pf[2] = new Triangle;
    43. for (int i = 0;i < 3;i++)
    44. {
    45. pf[i]-> Area();
    46. pf[i]->Girth();
    47. delete pf[i];
    48. pf[i] = NULL;
    49. }
    50. /*Shape s;
    51. s.Area();
    52. s.Girth();*/
    53. }

    四、抽象类

    1、抽象类的定义

    • 含有纯虚函数的类是抽象类。
    • 抽象类是一种特殊的类,它是为抽象的目而建立的,它处于继承层次结构的较上层。
    • 抽象类不能实例化对象,因为纯虚函数没有实现部分,所以含有纯虚函数类型不能实例化对象;
    • 注意:1)在定义纯虚函数时,不能定义虚函数的实现部分;

      2)在没有重新定义这种纯虚函数之前,是不能调用这种函数的。

    2、抽象类的主要作用 

    • 将相关的类型组织在一个继承层次结构中,抽象类为派生类型提供一个公共的根,相关的派生类型。
    •  为了派生子类,作为当前类族最上面的基类出现,则在子类中必须要重写基类中的纯虚函数(具体类)。
    • 如果在子类中没有实现纯虚函数,则子类也是抽象类。

    如有错误,敬请指正。

    您的收藏与点赞都是对我最大的鼓励和支持!

  • 相关阅读:
    Flask全套知识点从入门到精通,学完可直接做项目
    GO-unioffice实现word编辑
    Dockerfile 安装python3.7到tensorflow1.15.0镜像中
    haas506 2.0开发教程-高级组件库-modem.sms(仅支持2.2以上版本)
    vue.js—v-bind绑定Class与Style
    冒泡排序、选择排序、直接插入排序、二分法查找
    Docker容器内存限制
    『现学现忘』Docker基础 — 33、Docker数据卷容器的说明与共享数据原理
    老卫带你学---leetcode刷题(96. 不同的二叉搜索树)
    vulnhub靶机DC8
  • 原文地址:https://blog.csdn.net/x20020402/article/details/128134956