构造函数与析构函数
一、构造函数
(一)构造函数继承
1,如果子类没有定义构造方法,则调用父类的无参数的构造方法。
2,在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类自己提供了无参构造函数,则会调用父类自己的无参构造函数。
3,在创建子类对象时候,如果子类的构造函数没有显示调用父类的构造函数且父类只定义了自己的有参构造函数,则会出错(如果父类只有有参数的构造方法,则子类必须显示调用此带参构造方法)。
4,如果子类调用父类带参数的构造方法,需要用初始化父类成员对象的方式。
(二)explicit构造函数
1.explicit类型的构造函数和普通类型的构造函数的区别
普通的构造函数可以被显式调用和隐试调用,但是explicit的构造函数只能被显式的调用,不能被隐试的调用。
代码示例如下:
class test0{ private: int i; public: explicit test0(int n) { i = n; cout << "i = " << i << endl; } }; class test1{ private: int j; public: test1(int n) { j = n; cout << "j = " << j << endl; } }; int main(int argc, char *argv[]) { //test0 t0 = 34;//这种就是隐试调用,只能是普通的构造函数,也就是构造函数前没有加explicit,否者会编译出错 test0 t0(34);//如果explicit类型的构造函数的,只能够像这样显示的调用构造函数 test1 t1 = 55; while (1); return 0; }
2.explicit的作用
#include <iostream> #include <string> using namespace std; class test1{ public: string type; test1() :type("void"){} explicit test1(short) :type("short"){}//2.由于这里使用了explicit,所以不能够被隐试转换。于是42自动转换为int型,检查int的构造函数能否被隐试转换。 test1(int) :type("int"){}//3.没有被声明为explicit显示调用,因此调用此构造函数造出一个临时对象。最终输出结果就是int }; void show(const test1& n) { cout << n.type << endl; } int main(int argc, char *argv[]) { short s = 42; show(s);//1.s为short型,值为42,首先检查参数为short的构造函数能否被隐试转换。 while (1); return 0; }
按照默认规定,只有一个参数的构造函数也定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象。
因此,某些时候,explicit 可以有效得防止构造函数的隐式转换带来的错误或者误解。
explicit 只对构造函数起作用,用来抑制隐式转换。
二、析构函数
(一)析构函数调用时机
1、对象生命周期结束,被销毁时
#include <iostream> using namespace std; class A { public: A() { cout << "constructing A" << endl; } ~A() { cout << "destructing A" << endl; } private: int a; }; void main() { A a; }
运行结果:
constructing A
destructing A
2、主动调用delete 。
delete指向对象的指针时,或 delete指向对象的基类类型指针,而其基类析构函数是虚函数时;
如果是new的对象,即使离开了作用域也会一直存在,必须主动delete,否则只有在结束程序时才会执行析构。
#include <iostream> using namespace std; class A { public: A() { cout << "constructing A" << endl; } ~A() { cout << "destructing A" << endl; } private: int a; }; void fun() { A *a = new A(); } int main() { while (1) { fun(); } return 0; }
当离开fun时,虽然离开了作用域,但用new动态开辟空间的对象是不会析构的,你可以观察任务管理器,看到内存一直在上升。但你在其他地方却无法使用a所开辟的空间,因为a这个指针是保存在栈上的,当离开作用域后就自动析构(或者说自动消失了),但它所在分配空间是分配在堆上的,只有主动析构或程序结束,才会释放空间,也就是丢失了这块空间的地址,无法操作这块空间了 。
3、对象A是对象B的成员,B的析构函数被调用时,对象A的析构函数也被调用。
#include <iostream> using namespace std; class A { public: A() { cout << "constructing A" << endl; } ~A() { cout << "destructing A" << endl; } private: int a; }; class C { public: C() { cout << "constructing C" << endl; } ~C() { cout << "destructing C" << endl; } private: int c; }; class B : public A { public: B() { cout << "constructing B" << endl; } ~B() { cout << "destructing B" << endl; } private: int b; C c; }; void main() { B b; }
运行结果:
constructing A
constructing C
constructing B
destructing B
destructing C
destructing A
B的析构函数调用之后,又调用了B的成员c的析构函数 。
若将上边的代码中的main()函数内容改成
A* a = new B; delete a;
我们知道,这将不会调用class B的析构函数不会被调用,所以class C的析构函数也不会被调用。
运行结果:
constructing A
constructing C
constructing B
destructing A
若将class A中的析构函数声明为虚函数 ,这时class B的析构函数也会被调用,例如:
#include <iostream> using namespace std; class A { public: A() { cout << "constructing A" << endl; } virtual ~A() { cout << "destructing A" << endl; } private: int a; }; class C { public: C() { cout << "constructing C" << endl; } ~C() { cout << "destructing C" << endl; } private: int c; }; class B : public A { public: B() { cout << "constructing B" << endl; } ~B() { cout << "destructing B" << endl; } private: int b; C c; }; void main() { A* a = new B; delete a; }
运行结果:
constructing A
constructing C
constructing B
destructing B
destructing C
destructing A
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了