-
2022-03-24 09:26:54
多态在使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。
解决方式:将父类的中析构函数修改为虚析构或者纯虚析构。虚析构与纯虚析构共性:
1)可以解决父类指针释放子类对象。
2)都需要有具体的函数实现。虚析构与纯虚析构区别:
如果纯虚析构,该类属于抽象类,无法实例化对象。#include <iostream> using namespace std; class Animal{ public: Animal() { cout <<"Animal的构造函数的调用" << endl; } virtual void speak() = 0; //纯虚函数 //析构 ~Animal() { cout << "Animal的析构函数的调用" << endl; } }; class Cat:public Animal{ public: Cat(string name) { cout << "Cat的构造函数的调用" << endl; m_Name = new string(name); } //子类必须重写父类的纯虚函数 virtual void speak() { cout << *m_Name << "小猫在说话" << endl; } ~Cat() { cout << "Cat的析构函数的调用" << endl; if(m_Name != NULL) { delete m_Name; m_Name = NULL; } } string *m_Name; }; int main(int argc,char *argv[]) { Animal *animal = new Cat("Tom"); animal->speak(); delete animal; /* 当用户通过基类指针,释放派生类的堆空间时,系统只会调用 基类的析构函数,不会调用派生类的析构函数*/ return 0; } //子类析构未调用 /************************** PS C:\Users\32781\Desktop\love_you\02> g++ .\demo1.cpp PS C:\Users\32781\Desktop\love_you\02> .\a.exe Animal的构造函数的调用 Cat的构造函数的调用 Tom小猫在说话 Animal的析构函数的调用 PS C:\Users\32781\Desktop\love_you\02> ***************************/
虚析构语法: virtual ~类名() { } 纯虚析构语法: virtual ~类名() = 0; 类名::~类名() { }
#include <iostream> using namespace std; //虚析构与纯虚析构 class Animal{ public: Animal() { cout <<"Animal的构造函数的调用" << endl; } virtual void speak() = 0; //纯虚函数 //解决办法1: 将父类的析构函数修改为虚析构 /* virtual ~Animal() { cout << "Animal的析构函数的调用" << endl; } */ //解决办法2: 将父类的析构函数修改为纯虚析构。 virtual ~Animal() = 0; }; Animal::~Animal() { cout << "Animal的析构函数的调用" << endl; } class Cat:public Animal{ public: Cat(string name) { cout << "Cat的构造函数的调用" << endl; m_Name = new string(name); } //子类必须重写父类的纯虚函数 virtual void speak() { cout << *m_Name << "小猫在说话" << endl; } ~Cat() { cout << "Cat的析构函数的调用" << endl; if(m_Name != NULL) { delete m_Name; m_Name = NULL; } } string *m_Name; }; int main(int argc,char *argv[]) { Animal *animal = new Cat("Tom"); animal->speak(); delete animal; //Animal a;//如果纯虚析构,该类属于抽象类,无法实例化对象 return 0; } /******* PS C:\Users\32781\Desktop\love_you\02> g++ .\demo1.cpp PS C:\Users\32781\Desktop\love_you\02> .\a.exe Animal的构造函数的调用 Cat的构造函数的调用 Tom小猫在说话 Cat的析构函数的调用 Animal的析构函数的调用 PS C:\Users\32781\Desktop\love_you\02> *******/
更多相关内容 -
C++ --虚析构和纯虚析构
2022-03-13 23:48:01虚析构和纯虚析构 前言: 当使用父类指针指向子类对象,delete指针时,只会调用父类的析构函数,而不会调用子类的析构函数。这就存在子类对象没被释放的问题,导致内存泄漏 父类上使用虚析构和纯虚析构都能解决上面...虚析构和纯虚析构
前言: 当使用父类指针指向子类对象,delete指针时,只会调用父类的析构函数,而不会调用子类的析构函数。这就存在
子类对象没被释放
的问题,导致内存泄漏- 父类上使用
虚析构
和纯虚析构
都能解决上面的问题,但是需要注意:纯虚析构也要有具体实现;且如果包含了纯析构函数,则变成了一个抽象类。 - 形式:virtual ~父类名() {}
//虚析构和纯虚析构 //场景:当使用父类指针指向子类对象,delete指针时,只会调用父类的析构函数,而不会调用子类的析构函数。这就存在子类对象没被释放的问题,导致内存泄漏 //父类上使用虚析构和纯虚析构都能解决上面的问题,但是需要注意:纯虚析构也要有具体实现;且如果包含了纯析构函数,则变成了一个抽象类。 //形式:virtual ~父类名() {} class Base { public: virtual void function() = 0; Base() { cout << "Base构造函数\n"; } //虚析构 virtual ~Base() { cout << "Base析构函数\n"; } }; class Son : public Base { public: //属性创建在堆上 int* p_age; void function() { cout << "Son is working " << *p_age << endl; } Son(int age) { p_age = new int(age); cout << "Son构造函数\n"; } ~Son() { if (p_age != NULL) { delete p_age; p_age = NULL; } cout << "Son析构函数\n"; } }; void test() { Base* b = new Son(20); b->function(); delete b; } int main() { test(); return 0; }
- 父类上使用
-
C++ 虚表与虚析构
2022-01-26 23:24:51虚析构 #include using namespace std; class parent { public: parent() { cout 父类构造" ; } ~parent() { cout 父类析构" ; } }; class son:public parent { public: son() { cout 子类构造" ; }...在C++中,多态性的实现和联编(也称绑定)这一概念有关。一个源程序经过编译、链接,成为可执行文件的过程是把可执行代码联编(或称装配)在一起的过程。其中在运行之前就完成的联编成为静态联编(前期联编);而在程序运行之时才完成的联编叫动态联编(后期联编)。
静态联编支持的多态性称为编译时多态性(静态多态性)。在C++中,编译时多态性是通过函数重载和模板实现的。利用函数重载机制,在调用同名函数时,编译系统会根据实参的具体情况确定索要调用的是哪个函数。
动态联编所支持的多态性称为运行时多态(动态多态)。在C++中,运行时多态性是通过虚函数来实现的。
一个动态联编的例子:
场景:码农小A为银行后天做开发,需要设计两个类,一个是没有透支的类,一个是透支的类,两个类都有一个展示数据的功能show,但透支的类会展示透支的数据。会计大妈业务繁忙,需要可以查看上万人的账户情况,想让会计通过小A开发的软件简便去查看数据,只需要输入1查看没有透支的账户,输入2查看透支的账户。
解决:因为透支类成员包含未透支类的成员,且有额外的透支金额等成员,故透支类继承未透支的类,将show在基类中声明为虚,在各自类中定义其功能。定义一个指向基类的指针数组同时管理未透支的类和透支的类,其中每个指针指向一个账户。
用for循环遍历整个指针数组,当会计大妈输入1时将当前指针指向未透支的类,输入2时指向透支的类,每次输入后根据指向的对象类型立即调用对应的show函数。
分析:程序运行前编译器不知道指针指向什么类型的成员,即不知道会计大妈想看透支的类还是未透支的类。因此编译器在程序运行时根据用户的输入选择正确的虚方法,这就是动态联编。
评价:动态联编虽然给予了更多的灵活性,但相较于静态联编增加了额外的开销,因此只将那些预期被重新定义的方法声明为虚的。
01,动态多态性的实现
声明虚函数只用在父类对应函数处声明为虚,子类中相应的函数自动变成虚的#include<iostream> using namespace std; class father { public: virtual void show(); }; class son:public father { public: void show(); }; void father::show() { cout << "father" << endl; } void son::show() { cout << "son" << endl; } int main() { father f1; father* pt = &f1; pt->show(); son s1; pt = &s1; pt->show(); }
可以看到,虽然pt为指向father类型的指针,但调用的虚函数所属的类是根据当前pt指向的类决定的2.为什么C++ 中空类的大小是1个字节?
1、对于结构体和空类大小是 1个字节 这个问题,首先这是一个C++问题,在C语言下空结构体大小为0 (当然这是编译器相关的)。这里的空类和空结构体是指类或结构体中没有任何成员。
2、在C++下,空类和空结构体的大小是1(编译器相关),这是为什么呢?为什么不是0?
3、这是因为,C++标准中规定,“no object shall have the same address in memory as any other variable” ,就是任何不同的对象不能拥有相同的内存地址。 如果空类大小为0,若我们声明一个这个类的对象数组,那么数组中的每个对象都拥有了相同的地址,这显然是违背标准的。
3.查看空类大小,然后添加虚函数再查看
定义一个空类(没有显式的任何成员)class kong { }; int main() { kong k1; cout << sizeof(k1)<<endl; cout << sizeof(kong)<<endl; }
可以看到类与其实例化后的对象所占空间大小一样,都为一个字节
为其添加一个非虚函数,并使用同样的main函数:class kong { void kong1() {}; };
结果也是如此
添加虚函数:class kong { void kong1() {}; virtual void kongv() {}; };
添加int型(64位系统下为4字节)数据成员:class kong { void kong1() {}; virtual void kongv() {}; int a; };
由此我们可以猜想类的内存模型为虚函数表指针与数据成员:
其中vtptr占了四个字节,i为其数据成员4.验证内存模型
先为father类添加一个数据成员a和一个虚函数show2()class father { public: virtual void show1(); virtual void show2(); int a=1; }; void father::show2() { cout << "father2" << endl; }
int main() { father f1; father* pt = &f1; pt->show1(); cout << *(unsigned int*)(&f1) << endl; father f2; cout << *(unsigned int*)(&f2) << endl; cout << *((unsigned int*)(&f2)+1) << endl; }
(&f1)为取内存模型的地址,再解引用即是取其中第一个成员,+1再解引用就是取第二个成员
f1内存模型中第一个成员,即虚函数表指针,显示出来的即为虚表的首地址
可以看到,同一个类的不同实例化的对象,其虚表的地址一样int main() { father f1; father* pt = &f1; cout << (unsigned int*)(&f1) << endl; father f2; cout << (unsigned int*)(&f2) << endl; }
可以看到,即使是同一个类的对象,他们模型存放的地址也不同(00EFFB74和00EFFB58),但其中放的指向虚表的指针是同一个(15571964)(画图时重新跑了代码,和上面贴出的运行结果略有不同,但原理一样)
由此我们得出结论,虚函数表指针与对象一一对应,虚表与类一一对应示意图如下:
5.查看虚函数表内存模型
由于void类型函数的地址无法转换并打印,因此先将上述show1,show2换成int型的
并在父类中添加虚函数show2(),并为其增加一个子类son:class father { public: virtual int show1(); virtual int show2(); int a=9; }; class son:public father { public: virtual int show1(); };
int main() { father f1; father* pt = &f1; son s1; cout << "f1内存模型地址"<<(unsigned int*)(&f1) << endl; cout << "s1内存模型地址" << (unsigned int*)(&s1) << endl; cout <<"father类虚表地址"<< *(unsigned int*)(&f1) << endl; cout << "son类虚表地址" << *(unsigned int*)(&s1) << endl; cout << "通过father类虚函数表查看father类中show1()函数地址" << *(unsigned int*)*(unsigned int*)(&f1)<< endl; cout << "通过father类虚函数表查看father类中show2()函数地址"<<*(unsigned int*)(*(unsigned int*)(&f1)+1) << endl; cout << "通过son类虚函数表查看father类中show1()函数地址" << *(unsigned int*)*(unsigned int*)(&s1) << endl; cout << "通过son类虚函数表查看father类中show2()函数地址" << *(unsigned int*)(*(unsigned int*)(&s1)+1) << endl; }
可以通过访问虚函数表的第二个元素查看的父类和派生类对象的虚函数表访问的同一个虚函数地址相同。
这是因为在派生类son中没有重新定义虚函数show2(),但虚表中需要保存该对象的所有虚函数,故将show2的原始版本保存在表中。
示意图如下:
6.虚函数的使用
再回头分析虚函数的使用int main() { father f1; father* pt = &f1; pt->show(); son s1; pt = &s1; pt->show(); }
pt指向f1时,此时调用虚函数show1()将先通过f1的虚函数表指针找到虚函数表,再通过虚函数表的show1()的地址找到Father类的show1
当pt指向s1时,此时的虚函数表为son类的虚函数表,将调用son的show1();
7.虚析构
#include<iostream> using namespace std; class parent { public: parent() { cout << "父类构造" << endl; } ~parent() { cout << "父类析构" << endl; } }; class son:public parent { public: son() { cout << "子类构造" << endl; } ~son() { cout << "子类析构" << endl; } }; int main() { son s1; }
当直接通过实例化构造时,调用顺序如下:
不会出现问题
当通过new一个子类对象动态分配内存时int main() { son* s1 = new son; }
将不会自动调用析构函数,将会造成内存泄漏:
因此需要手动删除int main() { son* s1 = new son; delete s1; }
可以看到没啥问题
若是通过父类指针来new
int main() { parent* p1 = new son; delete p1; }
可以看到没有调用子类的析构函数,
这是因为delete p1时调用析构函数的类取决于p1的类型,直接调用父类析构,子类比父类多的东西没有删除,将造成内存泄漏为了解决这个父类指针动态开辟内存后删除该指针会造成内存泄漏这个问题,引入虚析构
#include<iostream> using namespace std; class parent { public: parent() { cout << "父类构造" << endl; } virtual ~parent() { cout << "父类析构" << endl; } }; class son:public parent { public: son() { cout << "子类构造" << endl; } ~son() { cout << "子类析构" << endl; } }; int main() { //son* s1 = new son; parent* p1 = new son; delete p1; }
将不会造成内存泄漏
这是因为将析构函数声明为虚函数后,析构函数会放到虚函数表中
示意图如下:(内存模型用的上面例子的,修改麻烦就没有修改)
可以看到即使p1的类型的指向parent指针,但他指向了子类对象,将根据子类对象的虚函数表指针找到子类的虚函数表,调用子类析构,随后调用父类析构,不会造成内存泄漏 -
C++中虚析构和纯虚析构的区别
2022-04-30 15:40:24首先虚析构和纯虚析构都是为了解决多态中子类有堆区数据,父类释放时无法释放子类的堆区数据而导致内存泄露的问题。 虚析构和纯虚析构都能解决上述问题,但是虚析构和纯虚析构有很大的不同。 1.语法不同 虚析构...首先虚析构和纯虚析构都是为了解决多态中子类有堆区数据,父类释放时无法释放子类的堆区数据而导致内存泄露的问题。
虚析构和纯虚析构都能解决上述问题,但是虚析构和纯虚析构有很大的不同。
1.语法不同
虚析构语法: virtual~ 类名(){}; 虚析构也需要实现内部函数
纯虚析构语法: virtual~类名() = 0; 纯虚析构也需要实现内部函数,但是是在内外
实现方式 作用域::~类名(){};
2.纯虚析构函数写了之后,这个类也属于抽象类,而无法实例化对象。
下面是具体代码实例:
class Animal { public: Animal() { cout << "Animal的构造函数调用" << endl; } //利用虚析构能够解决父类指针释放子类对象时不干净的问题 //virtual ~Animal() //{ // cout << "Animal的虚析构函数调用" << endl; //} //纯虚析构 需要声明 也需要实现 //有了纯虚析构之后,这个类也属于抽象类无法实例化对象 virtual ~Animal() = 0; //纯虚函数 virtual void Speak() = 0; }; Animal:: ~Animal() { cout << "Animal的纯虚析构函数调用" << endl; } class Cat :public Animal { public: Cat(string name) { cout << "Cat的构造函数调用" << endl; m_Name = new string(name); } ~Cat() { assert(m_Name); cout << "Cat的析构函数调用" << endl; delete m_Name; m_Name = NULL; } virtual void Speak() { cout << *m_Name <<"小猫在说话" << endl; } string* m_Name; }; void Test1() { Animal* animal = new Cat("Tom"); animal->Speak(); //父类的指针在析构的时候 不会调用子类中析构函数,导致子类如果有堆区属性,会出现内存泄露 delete animal; } int main() { Test1(); system("pause"); return 0; }
输出结果:
-
虚析构
2021-03-18 19:37:15虚析构用于释放子类中的指针成员 虚析构函数使得在删除指向子类对象的基类指针时,可以调用子类的析构函数来实现释放子类中堆内存的目的,从而防止内存泄漏。 示例: .h #pragma once #include<iostream> ... -
C++类与对象笔记十四:多态六:虚析构和纯虚析构
2022-01-25 18:31:04解决方案:将父类中的析构函数改为虚析构或者纯虚析构。 虚析构和纯虚析构共性: 可以解决父类指针释放子类对象 都需要有具体的函数实现。 虚析构和纯虚析构区别: 如果是纯虚析构,该类属于抽象类,无法实例化... -
C++中的虚析构
2019-04-20 15:39:05首先什么是虚析构,虚析构就是析构函数为虚函数。 那么为什么要用虚析构呢,是为了delete基类指针指向派生类时防止子类得数据不会被释放造成内存泄露。 我们看一下下面的例子: 首先我们定义一个数据类,MyData ... -
虚析构和纯虚析构原理
2020-07-15 14:29:30虚析构和纯虚析构原理 直接上代码:请根据编号查看代码说明。 先总结: 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构 拥有纯虚析构函数的类也... -
c++虚析构和纯虚析构
2020-12-26 10:51:264.7.5 虚析构和纯虚析构 纯虚析构的使用场景:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。(1.父类指针指向子类对象,2.子类在堆区有数据) 解决方式:将父类中的析... -
窥视C++细节-父类为什么要虚析构
2021-03-14 18:39:15文章目录环境单继承下的虚析构析构的顺序子类没有发生析构将父类析构器置为虚函数多继承下的虚析构this指针的调整多继承下的虚析构原理 本文将分析,父类的析构函数为什么必须为虚函数,如果不是虚函数会造成什么... -
C++学习之多态-虚析构和纯虚析构
2021-09-29 20:49:53解决方式:将父类中的析构函数改为虚析构或者纯虚析构 先看一个案例,子类在堆区开辟了空间: #include<iostream> #include<string> using namespace std; class animal { public: animal() { ... -
C++: 虚析构和纯虚析构
2020-12-27 23:36:41虚析构和纯虚析构 //每日心得:独上高楼,望尽天涯路 //日期:12.27 //学习内容:虚析构和纯虚析构 //重点: //1、子类有属性 开辟到堆区,则父类指针 无法调用子类的析构函数 //2、解决:将父类的析构函数改为== 虚... -
C++智能指针与虚析构
2021-08-31 20:56:58智能指针与虚析构虚析构智能指针注意智能指针与普通指针的转换问题 虚析构 众C++er所周知,在多态发生时,如果父类的虚函数不是虚函数的话,子类指针指向父类对象,在其生命周期结束进行delete时,子类对象只会调用... -
C++ 虚函数、虚析构、虚构造(原创纯手码)
2021-04-21 16:46:08虚析构虚构造 1. 前言 虚函数的概念是在类继承中提出的,为了让派生类重新实现基类的方法。当对象指针/引用指向的类型来调用具体的方法。 在基类声明中使用关键字virtual标记可以被重写的方法,派生类中可以使用该... -
虚析构和纯虚析构及C ++实现
2021-05-10 10:52:30虚析构和纯虚析构----->解决子类析构函数调不到的问题 在多态的使用时,如果子类中有属性开辟到堆区,那么父类子针在释放时无法调用到子类的析构代码 解决方式:将父类中的析构函数改为虚析构或者纯虚析构 析构和... -
C++的虚析构详解及实例代码
2020-12-25 21:51:05C++的虚析构 最近准备复习一遍所有的知识点,先从基础开始做起,用几分钟写个继承和析构吧。 父类为A,子类为B,代码如下: class A { public: A() { cout << "构造A"<< endl; } ~A() { ... -
C++中关于 虚函数、虚析构、虚继承和虚基类
2018-04-01 18:48:28//(1)基类A的析构未加virtual,即不是虚析构 { cout "A的析构" ; } }; class B : public A { public : ~B() { cout "B的析构" ; } }; int main() { A *pa1 = new A; delete pa1; //... -
C++ 虚析构和纯虚析构
2021-08-02 18:35:06若子类中有些属性是通过new操作符开辟到堆区的话,之后再通过delete操作符析构父类的对象却无法调用到子类的析构函数,必然存在内存泄漏的隐患。 创建一个名为Animal的父类 在类里,为构造函数,析构函数以及虚... -
C++学习笔记之虚析构和纯虚析构/虚析构和纯虚析构的学习/虚析构和纯虚析构学习
2020-08-03 09:57:43C++学习笔记之虚析构和纯虚析构/虚析构和纯虚析构的学习/虚析构和纯虚析构学习 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。 解决办法: 将父类中的析构函数改为虚析构... -
c++ 抽象类为什么要实现虚析构?
2020-03-19 10:22:11c++ 抽象类为什么要实现虚析构? 这个问题是面试官经常会问的 可能c++基础不扎实的会懵逼 连什么是抽象类都不清楚 什么是抽象类? 先看一下抽象类的定义 抽象类:声明了纯虚函数的类,都成为抽象类 主要特点:抽象类... -
C++虚函数,纯虚函数,虚析构和纯虚析构
2020-09-16 11:27:13C++虚函数 定义为虚函数是为了允许用基类的指针来调用子类的这个函数,是需要具体实现的 virtual void funtion1() { cout<<"A::foo() is called"<<endl; } C++纯虚函数 一、定义 纯虚函数是在... -
多态-虚析构和纯虚析构
2019-11-04 20:47:59解决办法:将父类中的析构函数改为虚析构或者纯虚析构 虚析构和纯虚析构共性: 1.可以解决父类指针释放子类对象 2.都需要有具体的函数实现 虚析构和纯虚析构的区别: 1.如果是纯虚析构,则该类属于抽象类,无法实例... -
C++虚函数和虚析构及标准C语言读取数值矩阵
2019-03-21 19:26:18C++虚函数及其纯虚函数学习。...虚析构:基类必须用虚析构函数这样子类析构(消失灭亡)时候才会释放基类申请的空间。 标准C语言读取数值矩阵:标准C语言从txt读取数值,分隔符是逗号和空格。 具体代码使用方式如下。 -
虚析构和纯虚析构函数
2021-10-25 11:30:295.1 虚析构 5.1.1 virtual ~类名() {} 5.1.2 解决问题: 通过父类指针指向子类对象释放时候不干净导致的问题 5.2 纯虚析构函数 5.2.1 写法 virtual ~类名() = 0 5.2.2 类内声明 类外实现 5.2.3 如果出现了纯虚析构... -
20、C++多态—(纯虚函数,抽象类,虚析构,纯虚析构)
2021-08-23 20:40:21动态多态:派生类和虚函数实现运行时多态 区别: 静态多态函数地址早就绑定 -编译阶段确定函数地址 静态多态函数地址晚绑定 - 运行阶段确定函数地址 ———————————————— class Animal { public: ...