详解构造函数和析构函数【建议收藏】
🎈 作者:Linux猿
🎈 简介:CSDN博客专家🏆,华为云享专家🏆,C/C++、面试、刷题、算法尽管咨询我,关注我,有问题私聊!
🎈 欢迎小伙伴们点赞👍、收藏⭐、留言💬
这篇文章对C++构造函数和析构函数做一下总结。
一、构造函数:构造函数用于为类对象进行初始化,如果没有显示定义构造函数,会生成一个默认的构造函数。
1.1 形式:
class 类名{
类名(形式参数)
构造体
};
1.2 规则
(1)在对象创建时自动调用,完成初始化相关工作,不能显示调用。
(2)无返回值,与类名相同。
(3)可以重载,可默认参数,不能为虚构造函数。
(4)默认构造函数没有参数的空构造体,构造函数如果有实现,默认不复存在。
1.3 初始化列表
class Cont{
public:
Cont(int _a, int _c, string _str):c(_c),a(_a),str(_str){
cout<<"Cont(int _a, int _c, int _str)"<<endl;
}
void display(){
cout<<"a = "<<a<<endl;
cout<<"c = "<<c<<endl;
cout<<"str = "<<str<<endl;
}
private:
int a;
int c;
string str;
};
int main(int argc, char *argv[])
{
//Cont temp; //false
Cont temp(1, 2, "ABC");
temp.display();
return 0;
}
必须使用初始化列表的情况:
(a)非静态const 成员或引用类型的成员
用初始化列表进行初始化,参数初始化的顺序和初始化列表的顺序无关,与它们在类中声明的顺序有关。非静态const数据成员必须用初始化列表来初始化。
使用初始化列表效率高的原因(这里针对的是非内置类型):
(1)在构造函数体内赋值:先调用此成员变量的构造函数,在调用它的赋值构造函数。
(2)使用初始化列表:只调用一次成员变量的构造函数。
2.拷贝构造函数
2.1 定义
由已经存在的对象创建新对象,也就是说新对象不由构造函数来构造,而是由拷贝构造函数来构造。
2.2 形式
class 类名{
类名(const 类名& another)
拷贝构造体
};
2.3 规则
1. 系统提供默认的拷贝构造器,一经实现,不复存在。
2. 系统提供的是等位拷贝,也就是所谓的浅浅的拷贝。
3. 要实现深拷贝必须自己定义。
4. 参数的传递用采用引用传递,如果采用值传递,会造成递归调用。
2.4 拷贝构造发生的时机
(1)以一个对象去建立另一个对象
Cont a(1, 2);
Cont b(a); //拷贝构造函数
Cont c = a; //拷贝构造函数
(2)作为函数的参数
class Cont{……};
void display(Cont Obj)
(3)作为函数的返回值
class Cont{……};
Cont display(){
return Obj;
}
2.5 深拷贝和浅拷贝
拷贝构造函数又分为深拷贝和浅拷贝。系统提供的拷贝构造函数是等位拷贝,即浅拷贝。如果类中包含的数据元素全部在栈上,浅拷贝可以满足需求。但如果堆上的数据,则会发生多次析构行为,要防止这种错误就需要自己定义拷贝构造函数,即深拷贝,如果用户自己定义,则系统不再提供浅拷贝。
一下四中情况不需要按位拷贝:
(1)class内部成员变量声明有显示的拷贝构造函数。
(2)class的基类有显示的拷贝构造函数。
(3)class中有虚基类,因为编译器要为对象设置虚函数表,所以无法按位拷贝。
(4)class继承虚基类。
2.6 C++默认为类创建的成员函数
包括默认构造函数、拷贝构造函数、析构函数、拷贝赋值函数。
//个人编写
class Cont{};
//编译器实现
class Cont{
public:
Cont(){}; //默认构造函数
Cont(const Cont& another){} //拷贝构造函数
~Cont(){} //析构函数
Cont& operator=(const Cont& another){} //拷贝赋值操作符
};
二、析构函数
1.1 定义
用来实现与构造函数相反的操作:释放对象使用的资源,并销毁非static成员。
1.2 形式
class 类名{
~类名()
析构体
};
1.3 规则
对象销毁时,自动调用。完成销毁的善后工作。
无返回值,与类名相同,无参数,不可以重载,不可以有默认参数。
系统提供默认析构器,一经实现,不复存在。
析构函数的作用不是要删除对象,而是在对象销毁前完成一些清理工作。
1.4 虚析构函数
虚析构函数一般是指子类继承基类,基类的析构函数一般申请为虚析构函数。这是为什么呢?是在子类中有指针成员变量时,为了防止内存泄漏而写成虚函数。也就是说虚析构函数使得在删除指向子类对象的基类指针时,可以调用子类的析构函数来释放子类中堆内存的目的,从而防止内存泄漏。
举个栗子:
class Base{
public:
Base(){
cout<<"Base()"<<endl;
}
virtual ~Base(){
cout<<"~Base()"<<endl;
}
virtual void Solve(){
cout<<"Base::Solve()"<<endl;
}
};
class Node : public Base{
public:
Node(){
cout<<"Node()"<<endl;
p = new int;
}
~Node(){
cout<<"~Node()"<<endl;
}
void Solve(){
cout<<"Node::Solve()"<<endl;
delete p;
p = NULL;
}
private:
int *p;
};
int main()
{
Base *b = new Node;
b->Solve();
delete b;
return 0;
}
输出结果:
上面的栗子中,如果基类的析构函数不申请为虚函数的话,delete b 的时候就不会调用子类的析构函数,这样子类在堆上申请的内存就无法释放,从而造成内存泄漏。这里需要注意的是:基类的虚函数被声明为虚析构函数,但是子类的析构函数仍然无法覆盖基类的析构函数,这是由于基类的析构函数是子类无法继承的。
参考文献:
[1] https://www.cnblogs.com/yetuweiba/p/4231870.html
[2] http://www.cnblogs.com/yetuweiba/p/3390853.html
[3] https://blog.csdn.net/baiyq369/article/details/54926983/
[3] http://www.runoob.com/cplusplus/cpp-constructor-destructor.html
[4] https://www.cnblogs.com/raichen/p/4752025.html
[5] https://www.cnblogs.com/MrListening/p/5567762.html
🎈 有任何疑问欢迎交流!
🎈 欢迎小伙伴们点赞👍、收藏⭐、留言💬
- 点赞
- 收藏
- 关注作者
评论(0)