目录
- 被 virtual 关键字修饰的成员函数称为虚函数。
- 在类中定义了虚函数就会有一个虚函数表(vftable),对象模型中就含有一个指向虚表的指针(__vfptr)。在定义对象时构造函数设置虚表指针指向虚函数表。
- 使用指针和引用调用虚函数,在编译只需要知道函数接口,运行时指向具体对象,才能关联具体对象的虚方法(通过虚函数指针查虚函数表得到具体对象中的虚方法)。
实现多态性,即通过基类访问派生类的函数。
虚函数的参数值是默认静态绑定的,在子类中 重新定义虚函数的时候,并没有重新定义继承而来的参数值;除非是在实际调用过程中传递了想用的参数。
- class Parent
- {
- public:
- virtual void fn(int a = 10)//如果虚函数有值,虚函数的参数值是默认静态绑定
- {
- cout << "parent fn a=" << a<
- }
- };
- class Child :public Parent
- {
- public:
- virtual void fn(int b = 100)
- {
- cout << "child fn b=" << b << endl;
- }
- };
- int main()
- {
- Child cc;
- Parent* p = &cc;
- //p->fn(200);//chlid fn b=200;
- p->fn();//child fn b=10;产生了动态绑定,但是虚函数的参数是默认绑定
- cc.fn();//child fn b=100;
- }
二、虚析构函数
1、什么是虚析构函数?
- 派生类的析构函数会自动调用基类的析构函数。 只要基类的析构函数是虚函数,那么派生类的析构函数不论是否用virtual关键字声明,都自动成为虚析构函数。
- 一般来说,一个类如果定义了虚函数,则最好将析构函数也定义成虚函数。 析构函数可以是虚函数,但是构造函数不能是虚函数。(第4点有具体原因说明)
- 注意:类中没有虚函数,就不要把析构函数定义为虚。
在动态分配内存时所有C++的标准库函数都采用这种格式。
2、虚析构函数的作用
为了解决基类指针指向派生类对象,并用基类的指针删除派生类对象。
3、 构造函数
构造函数是类的一个特殊的成员函数 :
- 1) 当一个对象的生命周期结束时,系统会自动调用析构函数注销该对象并进行善后工作,对象自身也可以调用析构函数;
- 2)析构函数的善后工作是:释放对象在生命期内获得的资源(如动态分配的内存,内核资源);
- 3) 析构函数也用来执行对象即将被撤销之前的任何操作。
4、为什么构造函数不能是虚函数?
根据赋值兼容规则,可以用基类的指针指向派生类对象,如果使用基类型指针指向动态创建的派生类对象,由该基类指针撤销派生类对象,则必须将析构函数定义为虚函数,实现多态性,自动调用派生类析构函数,否则可能存在内存泄漏问题。
总结:在实现运行时的多态,无论其他程序员怎样调用析构函数都必须保证不出错,所以必须把析构函数定义为虚函数。
5、构造函数和虚析构函数的联系
- 如果构造函数可以定义为虚构造函数,使用指针调用虚构造函数,
- 如果编译器采用静态联编,构造函数就不能为虚函数。
- 如果采用动态联编,运行时指针指向具体对象,使用指针调用构造函数,相当于已经实例化的对象在调用构造函数,这是不容许的调用,对象的构造函数只执行一次。
6、为什么程序员不能调构造函数,但是可以调用析构函数?
原因:
- 虚函数调用只需要"部分的"信息,即只需要知道函数接口,而不需要对象的具体类型。但是构建一个对象,却必须知道具体的类型信息。
- 如果你调用一个虚构造函数,编译器无法知道你想构建是继承树上的哪种类型,所以构造函数不能为虚。
7、虚析构函数产生多态
多态特例——虚析构函数产生多态;
类有指针作为数据成员,必须要写析构函数,如果当前类被继承了,则析构函数写成virtual
为了实现多态,将子类的空间被合理的释放,防止内存泄露 。
- class A {
- public:
- A()
- {
- cout << "A" << endl;
- m_i = new int;
- }
- virtual ~A()
- {
- cout << "~A" << endl;
- delete m_i;
- }
- private:
- int* m_i;
- };
- class B :public A
- {
- public:
- B()
- {
- cout << "B" << endl;
- m_j = new int;
- }
- virtual ~B()
- {
- cout << "~B" << endl;
- delete[]m_j;
- }
- private:
- int* m_j;
-
- };
- void test(A* pb)
- {
- ;
- }
- int main()
- {
- A* pb = new B;//new B类对象,先调用基类构造函数,再调用子类构造函数,
- delete pb;//pb指向子类对象,但是pb是A类类型指针,所以只析构了A,没有调子类类型的析构函数,存在内存泄漏。
-
- }
三、纯虚函数
1、定义:
纯虚函数(pure virtual function)是指没有具体实现的虚成员函数。它用于这样的情况:设计一个类型时,会遇到无法定义类型中虚函数的具体实现,其实现依赖于不同的派生类。
2、纯虚函数的一般格式
virtual返回类型函数名(参数表)=0;
“=0"表明程序员将不定义该虚函数实现,没有函数体,只有函数的声明;函数的声明是为了在虚函数表中保留一个位置。在虚函数()的基础上=0;“=0“体质上是将指向函数体的指针定义为nullptr。
3、例题理解
题目:
设计一个形状类——有矩形、圆形、三角形;分别计算三种图形的周长和面积;
- class Shape
- {
- public:
- virtual void Area() = 0;//纯虚函数就是在虚函数的基础上给个0
- virtual void Girth() = 0;//相当于把函数指针指向空
-
-
- };
- class Rectangle :public Shape
- {
- public:
- virtual void Area() { cout << "Rectangle area"<< m_length*m_width << endl; }
- virtual void Girth(){ cout << "Rectangle girth " << 2*(m_length+m_width)<
-
- private:
- int m_length;
- int m_width;
- };
-
- class Circle :public Shape
- {
- public:
- virtual void Area() { cout << "Circle area "<<3.14*m_ra*m_ra << endl; }
- virtual void Girth() { cout << "Circle girth"<< 2*3.14*m_ra<< endl; }
-
- private:
- int m_ra;//圆的半径
-
- };
-
- class Triangle :public Shape
- {
- public:
- virtual void Area() { cout << "Triangle area " << endl; }
- virtual void Girth() { cout << "Triangle girth "<<3*m_length << endl; }
-
- private:
- int m_length;
-
- };
- void test(Shape* p)
- {
- p->Area();
- p->Girth();
- }
-
- int main()
- {
- Shape* pf[3];
- pf[0] = new Rectangle;
- pf[1] = new Circle;
- pf[2] = new Triangle;
-
- for (int i = 0;i < 3;i++)
- {
- pf[i]-> Area();
- pf[i]->Girth();
- delete pf[i];
- pf[i] = NULL;
- }
- /*Shape s;
- s.Area();
- s.Girth();*/
- }
四、抽象类
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