7.析构函数

7.面向对象-封装-析构函数


7.1 知识点

注意事项

  • 在 C++ 中,结构体中的析构函数规则和类中基本一致
  • 由于我们之前已经学习过结构体析构函数
  • 因此本节课,我们主要用相关知识对类的析构函数进行一个快速讲解即可

析构函数

析构函数基本概念

析构函数是对象被释放时调用的方法,主要用于处理堆内存回收工作。由于堆内存需手动回收,其核心作用是在类对象释放时清理堆上分配的内容,避免内存泄漏。

析构函数基本规则

  • 声明定义在类中
  • 没有返回值,没有参数
  • 函数名与类名相同,且前面加 ~ 符号(如 ~ClassName()
  • 只能有一个析构函数,不能重载
  • 若类中未定义析构函数,编译器会自动生成默认析构函数,该函数会依次调用所有成员的析构函数,但不会释放动态资源(如堆上 new 的成员)

析构函数的调用机制

析构函数无需手动调用,会在对象生命周期结束时自动触发。对象释放时机如下:

  • 栈中对象:超出作用域时释放
  • 堆中对象:使用 delete 删除时释放

实例

#pragma once
class Lesson07
{
public:
    Lesson07();
    ~Lesson07();

    int* i;
};
#include "Lesson07.h"
#include <iostream>
using namespace std;

Lesson07::Lesson07()
{
    //手动在堆上分配内容
    i = new int(10);
    cout << "构造函数" << endl;
}

Lesson07::~Lesson07()
{
    delete i;
    i = nullptr;

    cout << "析构函数" << endl;
}

void Test()
{
    //栈上分配
    Lesson07 l;
}
Test();
// 构造函数
// 析构函数

// 堆上
Lesson07* l = new Lesson07();
delete l;
l = nullptr;
// 构造函数
// 析构函数

总结

  • 析构函数虽然不需要我们手动调用,但是它的存在是非常重要的

  • 对象的生命周期结束时会自动调用析构函数

    • 栈:语句块结束后,对象会自动释放,此时调用析构函数
    • 堆:手动 delete 时,会调用析构函数
  • 我们只需要把重点放在析构函数中,去释放类或结构体中声明在堆上分配内存的成员


7.2 知识点代码

Lesson07.h

#pragma once
class Lesson07
{
public:
    Lesson07();
    ~Lesson07();

    int* i;
};

Lesson07.cpp

#include "Lesson07.h"
#include <iostream>
using namespace std;
Lesson07::Lesson07()
{
    //手动在堆上分配内容
    i = new int(10);
    cout << "构造函数" << endl;
}


Lesson07::~Lesson07()
{
    delete i;
    i = nullptr;

    cout << "析构函数" << endl;
}

Lesson07_面向对象_封装_析构函数.cpp

#include <iostream>
#include "Lesson07.h"
using namespace std;


void Test()
{
    //栈上分配
    Lesson07 l;
}

int main()
{
    #pragma region 注意事项
    //在C++中,结构体中的析构函数规则和类中基本一致
    //由于我们之前已经学习过结构体析构函数
    //因此本节课,我们主要用相关知识对类的析构函数进行一个快速讲解即可
    #pragma endregion

    #pragma region 知识点 析构函数(结构体中讲解过的内容)
    //1.基本概念
    //	析构函数 是当对象被释放时会调用的方法
    //	也就意味着在析构函数中主要处理的就是堆内存回收相关的工作
    //	因为堆内存空间需要我们自己手动回收
    //	它的主要作用就是用来当类对象被释放时
    //	能够将其中在堆上分配的内容清理掉,避免内存泄漏

    //2.基本规则
    //	声明定义在类中
    //	2-1.没有返回值,没有参数
    //	2-2.函数名必须和类名相同,并且在前面加~符号
    //	2-3.只能有一个析构函数,不能重载
    //	2-4.如果类中没有定义析构函数,编译器会自动生成一个默认的析构函数,该函数会依次调用所有成员的析构函数
    //		注意:默认析构函数不会释放动态资源(即堆上new的成员)

    //3.析构函数不需要我们手动调用
    //	它是自动调用的函数
    //	当对象的生命周期结束时,析构函数会自动被调用
    //	对象释放时机:
    //	3-1.栈中的内容,当对象超出作用域时就会释放
    //	3-2.堆中的内容,delete 删除对象时就会释放

    Test();
    //构造函数
    //析构函数

    //堆上
    Lesson07* l = new Lesson07();
    delete l;
    l = nullptr;
    //构造函数
    //析构函数

    int i;
    cout << "按任意键结束程序" << endl;
    cin >> i;

    #pragma endregion

    //总结
    //析构函数 虽然不需要我们手动调用 但是它的存在是非常重要的
    //对象的生命周期结束时会自动的去调用
    //栈:语句块结束后 对象会自动释放 这时就会调用析构函数
    //堆:手动delete时 就会去调用析构函数

    //我们就只需要把重点放在析构函数中 去释放类或结构体中声明在堆上分配内存的成员
}

7.3 练习题

析构函数缺失释放内存导致的内存泄漏

class A {
private:
    int* ptr;
public:
    A() { ptr = new int(100); }
    ~A() { } 
};

int main() {
    A obj;
    return 0;
}

该代码是否存在内存泄漏?为什么?如果存在应该怎么修改代码?

存在内存泄漏,因为在析构函数中没有去释放掉类中堆上分配的成员变量 ptr

在析构函数中添加释放堆内存的内容:

~A() {
    delete ptr;
    ptr = nullptr;
}

互相包含成员对象导致的递归类型和循环包含

class A {
public:
    B bObj;
    A() {  }
    ~A() {  }
};

class B {
public:
    A aObj;
    B() {  }
    ~B() {  }
};

int main() {
    B obj;
    return 0;
}

此段代码是否存在问题?具体是什么问题?

  • 类 A 和类 B 互相包含对方的成员对象,导致“你中有我、我中有你”的递归类型,无限递归构造。
  • 同时头文件也会出现循环包含的问题。

解决方法:

  • B.h 中不要 #include "A.h",改用前向声明 class A;
  • B 类中的 A aObj; 改为指针 A* aObj;
    这样既避免了递归构造,也消除了头文件循环包含。
// B.h
#pragma once
class A;  // 前向声明

class B {
public:
    A* aObj;  // 改用指针
    B() { aObj = nullptr; }
    ~B() { delete aObj; }
};

7.4 练习题代码

A.h

#pragma once
#include "B.h"
class A
{
public:
    B bObj;

    A() {};
    ~A() {};
};

B.h

#pragma once
//#include "A.h"

class A;

class B
{
public:
    A* aObj;

    B() {};
    ~B() {};
};

Lesson07_练习题.cpp

#include <iostream>
#include "B.h";
int main()
{
    #pragma region 练习题一
    /*class A {
    private:
        int* ptr;
    public:
        A() { ptr = new int(100); }
        ~A() {  }
    };

    int main() {
        A obj;
        return 0;
    }
    该代码是否存在内存泄漏?为什么?


    存在内存泄漏,因为在析构函数中没有去释放掉 类中堆上分配内存的成员变量

    如果存在应该怎么修改代码?


    在析构函数中 添加释放堆内存的 内容
    delete ptr;
    ptr = nullptr;*/

    #pragma endregion

    #pragma region 练习题二
    /*class A {
    public:
        B bObj;
        A() {  }
        ~A() {  }
    };

    class B {
    public:
        A aObj;
        B() {  }
        ~B() {  }
    };

    int main() {
        B obj;
        return 0;
    }

    此段代码是否存在问题?具体是什么问题?*/

    //1.类似递归构造函数 你中有我我中有你
    //2.循环包含的问题

    // 解决循环包含的方法 
    // 让B.h不#include "A.h" 只是声明class A; 
    // 同时B类中的A对象改成A的指针对象	A* aObj;
    // 就可以一次性解决两个问题了


    B obj;
    #pragma endregion
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏