精华内容
下载资源
问答
  • 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>
    *******/

    更多相关内容
  • 析构

    2020-06-18 19:19:26
    析构操作是一个对象的成员函数,在该对象离开作用域或通过delete操作明确的销毁时,被自动调用。析构操作与类同名,前面加个波浪符 (~)。例如,String类的析构操作的声明是:~String()。 当没有定义析构操作时,...

    https://docs.microsoft.com/en-us/cpp/cpp/destructors-cpp?view=vs-2019

    • 概述

    析构操作是一个对象的成员函数,在该对象离开作用域或通过delete操作明确的销毁时,被自动调用。析构操作与类同名,前面加个波浪符 (~)。例如,String类的析构操作的声明是:~String()。

    当没有定义析构操作时,编译器会自动生成一个默认的;很多情况下,它是足够的。当类中占用了需要释放的系统资源或者它们拥有的指针占用着内存。

    看看如下的一个String类:

    // spec1_destructors.cpp
    #include <string>

    class String {
    public:
       String( char *ch );  // Declare constructor
       ~String();           //  and destructor.
    private:
       char    *_text;
       size_t  sizeOfText;
    };

    // Define the constructor.
    String::String( char *ch ) {
       sizeOfText = strlen( ch ) + 1;

       // Dynamically allocate the correct amount of memory.
       _text = new char[ sizeOfText ];

       // If the allocation succeeds, copy the initialization string.
       if( _text )
          strcpy_s( _text, sizeOfText, ch );
    }

    // Define the destructor.
    String::~String() {
       // Deallocate the memory that was previously reserved
       //  for this string.
       delete[] _text;
    }

    int main() {
       String str("The piper in the glen...");
    }

    在上面的类String中的析构操作 String::~String使用了delete操作符来释放动态分配的内存。

    • 析构操作的声明

    析构操作与类同名,前面加个波浪符 (~)。

    析构操作的声明有几条基本规则:

    1. 没有参数
    2. 没有返回值
    3. 不能被声明带有限定符 const, volatile, or static。然而,它们可以被带有限定符 const, volatile, or static的对象在析构时调用。
    4. 可以带有virtual。当使用virtual 析构操作时,不需要知道对象的具体类型---该对象对应的正确的析构操作会被自动调用,这是通过virtual 函数机制实现的。说明,在一个抽象类中,析构操作可以被声明成纯虚函数。
    • 使用析构

    如下几个事件之一发生时就会调用析构操作:

    1. 局部对象离开作用域
    2. 使用new操作符生成的对象,通过明确使用delete来释放。
    3. 一个临时变量的生命期结束了。
    4. 程序运行完了,全局或静态对象存在。
    5. 使用析构函数的全称来明确调用。

    析构操作有两个限制:

    1. 不能获取它的地址
    2. 子类不能继承父类的析构操作
    • 析构操作的调用顺序

    当一个对象要被释放时,它的完整的析构操作的事件序列如下:

    1. 类的析构操作被调用,析构函数被执行。
    2. 非静态的成员对象的析构操作被按出现在类声明顺序的相反顺序执行。
    3. 对于“ non-virtual base classes”的析构操作按照声明的反方向顺序执行。
    4. 对于“virtual base classes ”的析构操作按照声明的反方向顺序执行。

    举例如下:
    // order_of_destruction.cpp
    #include <cstdio>

    struct A1      { virtual ~A1() { printf("A1 dtor\n"); } };
    struct A2 : A1 { virtual ~A2() { printf("A2 dtor\n"); } };
    struct A3 : A2 { virtual ~A3() { printf("A3 dtor\n"); } };

    struct B1      { ~B1() { printf("B1 dtor\n"); } };
    struct B2 : B1 { ~B2() { printf("B2 dtor\n"); } };
    struct B3 : B2 { ~B3() { printf("B3 dtor\n"); } };

    int main() {
       A1 * a = new A3;
       delete a;
       printf("\n");

       B1 * b = new B3;
       delete b;
       printf("\n");

       B3 * b2 = new B3;
       delete b2;
    }

    Output: A3 dtor
    A2 dtor
    A1 dtor

    B1 dtor

    B3 dtor
    B2 dtor
    B1 dtor

    • 虚拟基类

    对于“virtual base classes ”的析构操作按照声明(有向无环图)的反方向顺序执行,规则是“深度优先,从左到右,后序遍历”。下面的图描述了继承关系。

    下面显示了声明关系:
    class A
    class B
    class C : virtual public A, virtual public B
    class D : virtual public A, virtual public B
    class E : public C, public D, virtual public B

    为了决定一个E类型的对象的析构操作顺序,编译器通过如下算法建立一个列表:

    1. 遍历图的左侧,从图中最深的点开始,在这个实例中是从E开始.
    2. 执行左侧遍历,直到左侧路径所有节点被访问过。记录当前节点的名称。
    3. 再次访问它的上一个节点(向下或者向右)来确认一下已经被记下的节点是否是一个虚拟基类。
    4. 如果已经被记下的节点是一个虚拟基类,看看list中是否已经存在该节点。如果不是虚拟基类,则忽略。
    5. 如果已经被记下的节点没有在list中,则把它添加到list的底部。
    6. 向图的向上的路径和右侧的路径遍历。
    7. 如果图的向上的路径没有穷尽,则跳转到第2步。
    8. 当最后一条向上的路径被穷尽,记录当前节点名称。
    9. 如果底部节点没有再次出现在当前节点,则跳转到第3步。
    10. 如果底部节点 再次出现在当前节点,则遍历结束。

    因此,对于类E,list的排序是:

    The virtual base class A.

    The virtual base class B.

    The non-virtual base class C.

    The non-virtual base class D.

    The non-virtual base class E.

    析构操作顺序为:

    The non-virtual base class E.

    The non-virtual base class D.

    The non-virtual base class C.

    The virtual base class B.

    The virtual base class A

    简单描述一下该list的构造过程。

    1)首先定位到点E。

    2)根据最左边优先遍历,定位到点A。记录下A节点。

    3)A的上一个节点是C不是虚拟基类,忽略。

    4)把A记录到list中

    5)向C的另一个上侧路径遍历,定位到B。。记录下B节点。

    6)B的上一个节点是C不是虚拟基类,忽略。

    7)把B记录到list中
    8)节点C向上的路径都穷尽了,记录C节点。

    9)访问D节点,D不是虚拟基类,忽略。

    10)把C记录到list中

    10)把D记录到list中

    这种在一个继承关系图中存在类之间有相互依赖关系是很危险的。因为后面的子类会改变哪个是最左边路径,因此,它们也会改变构造和析构的顺序。

    • 非virtual基类

    The destructors for non-virtual base classes are called in the reverse order in which the base class names are declared. Consider the following class declaration:
    class MultInherit : public Base1, public Base2
    ...
    In the preceding example, the destructor for Base2 is called before the destructor for Base1.

    • Explicit destructor calls

    Calling a destructor explicitly is seldom necessary. However, it can be useful to perform cleanup of objects placed at absolute addresses. These objects are commonly allocated using a user-defined new operator that takes a placement argument. The delete operator cannot deallocate this memory because it is not allocated from the free store (for more information, see The new and delete Operators). A call to the destructor, however, can perform appropriate cleanup. To explicitly call the destructor for an object, s, of class String, use one of the following statements:
    s.String::~String();     // non-virtual call
    ps->String::~String();   // non-virtual call

    s.~String();       // Virtual call
    ps->~String();     // Virtual call
    The notation for explicit calls to destructors, shown in the preceding, can be used regardless of whether the type defines a destructor. This allows you to make such explicit calls without knowing if a destructor is defined for the type. An explicit call to a destructor where none is defined has no effect.

    • Robust programming

    A class needs a destructor if it acquires a resource, and to manage the resource safely it probably has to implement a copy constructor and a copy assignment.

    If these special functions are not defined by the user, they are implicitly defined by the compiler. The implicitly generated constructors and assignment operators perform shallow, memberwise copy, which is almost certainly wrong if an object is managing a resource.

    In the next example, the implicitly generated copy constructor will make the pointers str1.text and str2.text refer to the same memory, and when we return from copy_strings(), that memory will be deleted twice, which is undefined behavior:
    void copy_strings()
    {
       String str1("I have a sense of impending disaster...");
       String str2 = str1; // str1.text and str2.text now refer to the same object
    } // delete[] _text; deallocates the same memory twice
      // undefined behavior
    Explicitly defining a destructor, copy constructor, or copy assignment operator prevents implicit definition of the move constructor and the move assignment operator. In this case, failing to provide move operations is usually, if copying is expensive, a missed optimization opportunity.

    展开全文
  • 主要介绍了C++类成员构造和析构顺序示例,看了这个例子大家就可以明白c++构造析构的奥秘
  • 调用构造函数和析构函数的顺序:先构造的后析构,后构造的先折构。它相当于一个栈,先进后出。 代码如下:#include<iostream>#include<string>using namespace std;class Student{ public: Student(string,string,...
  • C# 的析构以及垃圾回收实例分析 看书时,自己写的例子代码,了解到几个知识点,记载下来。同时发现自己手写代码的能力比较弱,还是得多写一下。 using System; namespace ConsoleApplication { public class ...
  • 自动析构时是先析构后构造的. //普通(非模板)类的成员模板 class DebugDelete{ public: DebugDelete(ostream &s = cerr) :os(s){} template <typename>void operator()(T*p)const { os << "deleting ...
  • 构造析构的顺序

    2019-01-17 14:01:21
    编程思想的第十四章,代码,本人亲测通过后才上传的 这段代码介绍的是构造和析构的顺序
  • 本篇文章对c++中临时对象的析构时机进行了详细的分析介绍,需要的朋友参考下
  • 主要介绍了Python类方法__init__和__del__构造、析构过程分析,本文分析了什么时候构造、什么时候析构、成员变量如何处理、Python中的共享成员函数如何访问等问题,需要的朋友可以参考下
  • cpp代码-构造和析构调用顺序
  • 本篇文章是对c++中参数对象与局部对象的析构顺序进行了详细的分析介绍,需要的朋友参考下
  • C++类的构造和析构

    2022-03-19 13:47:45
    } 构造和析构顺序问题 普通对象,构造顺序和析构顺序是相反 new出来的对象,delete会直接调用析构函数 static对象,当程序关闭的时候,生命周期才结束,所以是最后释放 #include #include using namespace std;...

    构造函数

    • 我们如果要了解类的构造函数是什么,就需要问自己以下几个问题。

    • 构造函数长什么样子?

      • 1,函数名和类名相同

      • 2,没有返回值(函数类型是 void类型)

      • 3,如果不写构造函数,任何类中都存在一个默认的构造函数

        • 默认的构造函数是无参的。

        • 当我们自己写了构造函数,默认的构造函数就不存在

      • 构造函数在构造对象的时候调用

      • delete可以用来删掉默认的函数

      • 指定使用默认的无参构造函数,用default说明

      • 允许构造函数调用另一个构造函数,只是要用初始化参数列表的写法

      • 初始化参数列表 : 只有构造函数有

        构造函数名(参数1,参数2,...):成员1(参数1),成员2(参数2),...{}

        • 避免形参名和数据成员名相同的导致问题

    • 构造函数干嘛的?

      • 构造函数用来构造对象

      • 构造函数更多是用来初始化数据成员

    • 思考题?

      • 为什么不写构造函数可以构造对象? ——是因为存在一个默认的无参构造函数,所以可以构造无参对象

      • 构造函数重载为了什么? ——为了构造不同的对象。

     接下来我们就要通过写代码的形式来解释上面这几个特点

    #include <iostream>
    using namespace std;
    class MM
    {
    public:
    	void print()
    	{
    		cout << name << " " << age << endl;
    	}
    protected:
    	string name = "Lisa";
    	int age = 18;
    };
    int main() {
    	MM mm;         /*如果我们没有写构造函数,
                       那么定义类类型变量的时候和结构体是一样的  */
        mm.print();
    
    	/*打印结果
    	Lisa 18
    	*/
    	return 0;
    }
    • 当我们在类中加入 类型()=delete(MM()=delete)时就已经把默认构造函数删除,也就可以看见在main函数中的类类型变量的定义发生了报错。

     

    class MM
    {
    public:
    	MM(string mmName, int mmAge)  //这是自己写的构造函数,那么类类型的定义也要发生变化
    	{
    		name = mmName;
    		age = mmAge;
    		cout << "带参构造函数" << endl;
    	}
    	void print()
    	{
    		cout << name << " " << age << endl;
    	}
    protected:
    	string name = "Lisa";
    	int age = 18;
    };
    int main() {
    	MM mm("小红",20);  /*当我们自己加入了构造函数之后,
                             默认的构造函数也就失去的效果
                             所以像结构体那样的定义也会报错*/
    	mm.print();
    
    	/*打印结果
    	带参构造函数
        小红 20
    	*/
    	return 0;
    }
    • 但是如果我们在自己定义了构造函数之后还想用无参数的构造函数怎么办呢?

                这个时候我们有两种解决办法,第一种是自己创建一个无参数的构造函数;第二种是把原来默认的构造函数拿回来 

    class MM
    {
    public:
    	MM(){    //这是通过自己写无参数构造函数的方法
    	    cout << "无参构造函数" << endl;
    	}
    	void print()
    	{
    		cout << name << " " << age << endl;
    	}
    protected:
    	string name = "Lisa";
    	int age = 18;
    };
    int main() {
    	MM mm;
    	mm.print();
    	/*打印结果
    	无参构造函数
        Lisa 18
    	*/
    	return 0;
    }
    class MM
    {
    public:
    	MM(string mmName, int mmAge)  
    	{
    		name = mmName;
    		age = mmAge;
    		cout << "带参构造函数" << endl;
    	}
    	MM() = default;   /*这样的操作就可以在已经有构造函数的情况下
                                再次调用原来的默认构造函数*/
    	void print()
    	{
    		cout << name << " " << age << endl;
    	}
    protected:
    	string name = "Lisa";
    	int age = 18;
    };
    int main() {
    	MM mm("小红",20);
    	MM girl;   //不报错
    	mm.print();
    	girl.print();
    	/*打印结果
    	带参构造函数
        小红 20
        Lisa 18
    	*/
    	return 0;
    }

    但是我们建议使用第二种方式,因为默认的构造函数比我们自己写的无参数构造函数速度快 

    • 我们写的构造函数也可以是重载函数或者是缺省函数,目的是为了构造不同的对象
      //为了能够构造不同长相的对象,我们会给构造函数缺省处理
      class Boy
      {
      public:
      	//Boy(string mname="我是默认值", int mage=19) 
      	//{
      	//	name = mname;
      	//	age = mage;
      	//}
      	// 
      	//上面函数 等效可以实现下面三个函数的功能
      
      	/*简单的说就是缺省函数的处理就是为了让函数有更多的调用形式
      	但是我们不建议用以下三种的方式去代替缺省函数,因为如果函数过多的话容易搞混*/
      	Boy() {}
      	Boy(string mName) { name = mName; }
      	Boy(string mName, int mage) { name = mName; age = mage; }
      	void print() {
      		cout << name << "\t" << age << endl;
      	}
      protected:
      	string name= "我是默认值";
      	int age= 19;
      };
      int main() {
      	Boy boy1;
      	Boy boy2("流浪之子");
      	Boy boy3("王子", 18);
      	boy1.print();
      	boy2.print();
      	boy3.print();
      	/*打印结果
      	我是默认值      19
          流浪之子        19
          王子    18
      	*/
      	return 0;
      }

    • 接下来我们来说一下什么是初始化参数列表,初始化参数列表也是用来构造对象的,并且它可以避免形参名和数据成员名相同的问题
      class TT
      {
      public:
      	TT(string name, int age) :name(name), age(age) {}  
          /*我们可以看到形参名与数据成员名是一样的,
          但是不用担心,它是可以自己去区别的,所以不会报错*/
      
      	//委托构造:允许构造函数调用另一个构造函数
      	TT() :TT("默认", 18) {}     //没有给数据初始化时,用这个默认值来赋初值
      	void print()
      	{
      		cout << name << "\t" << age << endl;
      	}
      protected:
      	string name;
      	int age;
      };
      int main() {
      	TT mm{ "小红",20 };
      	mm.print();
      	TT girl;   //此变量没有初始化,所以就会用默认值
      	girl.print();
      	/*打印结果
      	小红    20
          默认    18
      	*/
      	return 0;
      }
    • 对于初始化参数列表呢,它主要是用在继承和类的组合
    • 还有一点,当我们在使用初始化参数列表给数据成员赋值的时候,可以用到全局变量,不一定非得用形参来赋值。

    析构函数

    • 析构函数长什么样子?

      • 无返回值

      • 无参数

      • 函数名: ~类名

      • 不写的话会存在默认的析构函数

      • 析构函数不需要自己 调用,对象死亡的之前会调用析构函数

    • 析构函数用来干嘛?(什么时候需要自己手动写析构函数)

      • 当类中的数据成员是指针,并且动态申请内存就需要手写析构

      • 析构函数用来释放数据成员申请动态内存

    class MM
    {
    public:
    	MM(const char* mname, int age):age(age) { 
    		name = new char[20];
    		strcpy(name, mname);
    	}
    	void print()
    	{
    		cout << name << "\t" << age << endl;
    	}
    	~MM() {
    		cout << "我是析构函数" << endl;
    		delete[]name;
    	}
    protected:
    	char* name;
    	int age;
    };
    int main() {
    	{
    		MM mm{ "小红",20 };
    		mm.print();
    	}
    	cout << "我是主函数" << endl;  /*由打印结果可以看出,
    							         当mm的作用域结束后,就会自动调用析构函数*/
    	/*打印结果
    	* 小红    20
          我是析构函数
          我是主函数
    	*/
    	return 0;
    }
    • 要注意,析构函数不需要我们手动调用,如果手动调用可能会导致内存重复释放的问题
    class MM
    {
    public:
    	MM(const char* mname, int age):age(age) {
    		name = new char[20];
    		strcpy(name, mname);
    	}
    	void print()
    	{
    		cout << name << "\t" << age << endl;
    	}
    	~MM() {
    		cout << "我是析构函数" << endl;
    		delete[]name;
    	}
    protected:
    	char* name;
    	int age;
    };
    int main() {
    	MM* mm = new MM{ "小红",20 };
    	mm->print();
    	delete mm;
    	cout << "我是主函数" << endl;  /*由打印结果可以看出,
    							         如果定义类类型指针,一旦对内存进行释放
    									 就会立刻调用析构函数*/
    	/*打印结果
    	* 小红    20
          我是析构函数
          我是主函数
    	*/
    	return 0;
    }

     

    构造和析构顺序问题

    • 普通对象,构造顺序和析构顺序是相反

    • new出来的对象,delete会直接调用析构函数

    • static对象,当程序关闭的时候,生命周期才结束,所以是最后释放

      #include <iostream>
      #include <string>
      using namespace std;
      class MM
      {
      public:
      	MM(string name = "x") :name(name) {
      		cout << name;
      	}
      	~MM() {
      		cout << name;
      	}
      protected:
      	string name;
      };
      int main()
      {
      	{
      		MM mm1("A");			//A
      		static MM mm2("B");		//B   程序关闭时候才死亡,最后析构
      		MM* p3 = new MM("C");	//C
      		MM mm4[4];				//xxxx
      		delete p3;				//C  delete 直接调用析构
      		p3 = nullptr;
      	}
      	/*打印结果
      	 ABCxxxxCxxxxAB
      	*/
      	return 0;
      }

      如果大家将上面这个例子的打印结果搞清楚,那么构造与析构的顺序问题就可以掌握了

    展开全文
  • Swift 析构过程

    2021-01-06 02:48:58
    Swift 析构过程 在一个类的实例被释放之前,析构函数被立即调用。用关键字deinit来标示析构函数,类似于初始化函数用init来标示。析构函数只适用于类类型。 析构过程原理 Swift 会自动释放不再需要的实例以释放资源...
  • 析构和纯虚析构 前言: 当使用父类指针指向子类对象,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;
    }
    
    展开全文
  • 构造函数是一种特殊方法,构造函数没有返回值,连void也不能写,必须public 构造函数的名称必须和类名一样
  • 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码。 即:父类指针无法调用子类实例对象内的析构函数。造成堆区内存泄漏。 解决方案:将父类中的析构函数改为虚析构或者纯虚...
  • python类析构

    2020-12-17 06:23:47
    python __del__转自:...2、什么时候析构?3、成员变量如何处理?4、Python中的共享成员函数如何访问...文章ghost丶桃子2016-05-202086浏览量绕开“陷阱“,阿里专家带...
  • 派生类的构造与析构派生类的构造派生类的构造与派生类的构造与析构析构析构派生类的构造与析构
  • 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码 解决方式:将父类中的析构函数改为虚析构或者纯虚析构 先看一个案例,子类在堆区开辟了空间: #include<iostream> #...
  • 析构和纯虚析构原理 直接上代码:请根据编号查看代码说明。 先总结: 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构 拥有纯虚析构函数的类也...
  • 构造函数与析构函数是一个类中看似较为简单的两类函数,但在实际运用过程中总会出现一些意想不到的运行错误。本文将较系统的介绍构造函数与析构函数的原理及在C#中的运用,以及在使用过程中需要注意的若干事项。
  • C++的虚析构  最近准备复习一遍所有的知识点,先从基础开始做起,用几分钟写个继承和析构吧。  父类为A,子类为B,代码如下: class A { public: A() { cout << "构造A"<< endl; } ~A() { ...
  • 【C++】构造-析构详解

    2022-01-09 15:59:27
    文章目录(一)构造函数的用途(二)构造函数的特点(三)构造函数的调用情况(1)花括号式的初始化(2)圆括号的初始化(四)析构函数(四)理解构造、析构思想 (一)构造函数的用途 创建对象 初始化对象的属性 ...
  • C++ 虚析构和纯虚析构

    2021-08-02 18:35:06
    若子类中有些属性是通过new操作符开辟到堆区的话,之后再通过delete操作符析构父类的对象却无法调用到子类的析构函数,必然存在内存泄漏的隐患。 创建一个名为Animal的父类 在类里,为构造函数,析构函数以及虚...
  • 析构和纯虚析构 //每日心得:独上高楼,望尽天涯路 //日期:12.27 //学习内容:虚析构和纯虚析构 //重点: //1、子类有属性 开辟到堆区,则父类指针 无法调用子类的析构函数 //2、解决:将父类的析构函数改为== 虚...
  • C# 析构方法

    2022-04-17 10:16:35
    作用:释放对象 谁在使用:GC垃圾回收机制在调用 垃圾回收机制: 1.都是系统级别的,不能随便操作 不能进行自动释放,需要手动操作 ...看是否是托管资源还是非托管资源,判断是否有DIspose()方法,如果有,则...
  • 先不介绍概念,肯定和前面学构造函数和析构函数中的析构函数有关系。先通过引出问题,然后介绍这两个概念和特点。 1.多态基本代码 在前面例子我们可写出一下代码,也没有什么问题 #include <iostream> #...
  • 析构方法是对象在销毁之前最后一个被对象自动调用的方法,这也是我们在对象中使用析构方法的原因。所以通常使用构造方法完成一些对象的初始化工作,使用析构方法完成一些对象在销毁之前的清理工作。 1.构造方法 在...
  • Python之析构方法

    2022-02-26 15:45:42
    析构方法: 释放内存,做释放内存前的准备工作 触发机制:析构方法会在对象被销毁时自动触发 作用:关闭一些开发的资源 注意:是对象被销毁时触发了这个方法,而不是这个方法销毁了对象 对象会在哪些情况下被销毁...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 146,323
精华内容 58,529
关键字:

析构

友情链接: LeetCode .zip