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