编程参考 - C++的析构函数的个数只能有一个
C和C++的变量或对象内存生命周期管理区别
C里面的变量,按照生存周期来分,分为静态变量和局部变量。
静态变量是程序在启动时就分配了空间,并且其地址在程序执行中保持不变。静态变量按照访问范围来分,包括全局变量或模块内静态变量。
而局部变量,是函数里在栈上自动分配空间,每次调用函数时,局部变量分配的地址可能不同。
而堆空间的使用,使用单独的库函数来完成。
在C++多了类的使用,类的实例我们通常称之为对象。
关于对象的生存周期管理,和C有所不同。
C语言里,函数内部的局部变量,在函数退出执行后其生命周期就结束了。如果函数内创建了仅在函数内访问的堆指针,函数退出前要释放堆内存。
而C++中,函数内部定义的类对象,在函数退出执行后其生命周期也结束,但在对象的生命周期结束时,要调用析构函数。加入析构函数,方便程序员更好的管理类对象的生命周期,尤其是在类对象生命周期结束时,经常需要对类所使用的资源进行释放。比如,类里面使用了堆内存,那类对象消亡前就应该释放资源,而这部分处理就可以放在析构函数里,而不用另外操作,使类操作更方便统一。类对象里的普通成员,是不需要特殊处理的,比如整型变量,在类对象消亡时,这部分变量占用资源会直接释放。
使用new生成的堆里的类对象,原理一样,当释放这个类指针资源时,也会调用类的析构函数。类对象本身占用的资源比如整型变量等会释放,如果类对象内部还是用了其他资源比如堆指针,在类的析构函数里进行释放操作。使用new给类对象分配内存,此时调用类的构造函数,当类对象不需要时,使用delete进行资源释放,此时类的析构函数被调用。
综上,C++类对象的构造和析构(constructor & destructor 或者 ctor & dtor),在创建类对象和删除类对象时使用。在创建类对象时,可以有多种初始状态,所以对象的构造函数有参数,并且有多个构造函数对应不同的参数列表。
构造函数和析构函数的名字和类名一样,析构函数前面加了一个位取反符。没有返回值。
类的析构函数只能有一个
类对象析构时,此时已穷途末路,别无选择,此时类对象的状态是稳定可知的,不需要参数来干预,添加更多的析构函数配以不同参数列表显然作用不大,所以C++标准里就是一个类只有一个析构函数。
在创建对象时可以传入不同参数列表,由编译器来调用不同构造函数。而析构函数则由编译器自动调用,当类对象将要被消灭掉时调用。
析构函数也可以手动调用,但情况很少,比较特殊,这里不深入介绍。尤其是局部变量的类对象,如果手动调用析构函数,函数退出时会再调用一次,假如有资源要释放,可能会因为释放两次而出错,总之析构函数调用两次这种情况就已经是非正常的,要避免。如下所示:
#include <stdio.h>
class A
{
public:
~A(){printf("~A\n");}
};
int main()
{
A a;
a.~A();
return 0;
}
$ g++ -o main main.cpp
$ ./main
~A
~A
类的析构函数的访问权限
如果一个类的析构函数是private的,那么编译器没法自动调用其析构函数。在类对象析构时,就会报错。
#include <stdio.h>
class A
{
private:
~A(){printf("~A\n");}
};
int main()
{
A a;
return 0;
}
$ g++ -o main main.cpp
main.cpp: In function ‘int main()’:
main.cpp:14:4: error: ‘A::~A()’ is private within this context
14 | A a;
| ^
main.cpp:7:2: note: declared private here
7 | ~A(){printf("~A\n");}
| ^
而且,如果另一个类继承这个类,也会报错。
如果这个类的析构函数是protected的,那别的类可以继承它。
但编译器还是不能自动调用其析构函数,当这个类要析构时,会报错。
注意,当析构函数是private或protected时,外部无法调用析构函数,导致这个对象不能正常删除。但可以使用类的成员函数或友元来做这件事。这表示这个类的生命周期由其他类来负责。
同理,对构造函数也是一样,如果构造函数声明为private或protected,那么类对象则不能在外部创建,只有类的成员函数或友元才能创建类对象。
析构函数和virtual
C++支持多态,所以父类的虚构函数应该都用virtual来声明。
如下所示,如果基类的析构函数不声明为虚函数,则子类对象就不会调用子类的析构函数。
如果基类的析构函数声明为虚函数,则使用父类的指针访问子类对象,调用析构函数时,就会先调用子类析构函数,再调用基类析构函数。
#include <stdio.h>
class A
{
public:
~A(){printf("~A\n");}
};
class B : public A
{
public:
~B(){printf("~B\n");}
};
int main()
{
A* a = new B();
delete a;
return 0;
}
$ g++ -o main main.cpp
$ ./main
~A
#include <stdio.h>
class A
{
public:
virtual ~A(){printf("~A\n");}
};
class B : public A
{
public:
~B(){printf("~B\n");}
};
int main()
{
A* a = new B();
delete a;
return 0;
}
$ g++ -o main main.cpp
$ ./main
~B
~A
注意,如果基类一个成员函数声明为virtual,则子类的同名函数默认为virtual,与是否加virtual修饰符无关。但这个virtual声明是向下传染的,不会影响到当前类的基类。
但析构函数是特殊的,虽然函数名不同,但基类的析构函数声明为virtual,则子类的析构函数也是virtual的,如上面的例子所示。
参考:
相关文章
- qt实现web服务器加载vue应用进行C++和html混合编程-连载【6】-企业级系统开发实战连载系列 -技术栈(vue、element-ui、qt、c++、sqlite)
- 回调函数(callback) python / c++ 演示
- 【侯捷】C++STL标准库与泛型编程(第二讲)
- 10 C++ - struct类型加强(比较C语言)
- 《C++编程剖析:问题、方案和设计准则》——第一章泛型编程与C++标准库1.1:vector的使用
- 《C++游戏编程入门(第4版)》——1.2 编写第一个C++程序
- 《C++游戏编程入门(第4版)》——1.5 声明和初始化变量
- 《C++编程风格(修订版)》——2.1 编程风格示例:string 类
- 《C++ 黑客编程揭秘与防范(第2版)》——6.5 破解基础知识及调试API函数的应用
- 《C++面向对象高效编程(第2版)》——2.7 数据封装注意事项
- 《C++面向对象高效编程(第2版)》——2.16 识别成员函数的目标对象
- 《C++面向对象高效编程(第2版)》——3.15 函数返回值
- 《C++编程惯用法——高级程序员常用方法和技巧》导读
- [第十一届蓝桥杯省赛C++B组]走方块
- C++编程——常函数与常对象
- c++编程——函数提高
- C++编程规范之19:总是初始化变量
- C++面向对象三大特性
- C++ 用libcurl库进行http通讯网络编程
- 编程参考 - C++ 术语说明 Defined Terms - 1