构造函数与析构函数
构造函数
初始化和清理
当我们创建对象的时候,这个对象应该有一个初始状态,当对象销毁之前应该销毁自己创建的一些数据。对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始化的时候,对其使用后果是未知的,同时使用完一个变量,没有清理的话,也会造成一定的安全问题。C++为了给我们提供这种问题的解决防范,构造函数和析构函数,这两个函数会被编辑器自动调用,完成对象初始化和对象清理的工作。无论你是否喜欢,对象的初始化和清理工作是编辑器强制我们要做的事情,即使你不提供初始化操作和清理操作,编辑器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类就应该顺便提供初始化函数。
构造函数的概述
类实例化对象的时候,系统会自动调用构造函数,完成对象的初始化
如果用户不提供构造函数,编辑器会自动添加一个默认的构造函数(空函数)
构造函数的定义方法
构造函数名和类名相同,没有返回值类型,可以有参数(可以重载)
先给对象开辟空间(实例化),然后调用构造函数初始化,一般是定义在 pubblic: ,权限为 public
。
class Data
{
public :
int mA;
public :
// 无参构造函数
Data()
{
mA = 0;
cout << "无参构造函数" << endl;
}
// 有参构造函数
Data(int a)
{
mA = a;
cout << "有参构造函数" << endl;
}
}
int main()
{
// 隐式调用无参构造函数 (推荐)
Data ob1;
// 显示调用无参构造函数
Data ob2 = Data();
// 隐式调用有参构造函数(推荐)
Data ob3(10);
// 显示调用有参构造函数
Data ob4 = Data(10);
// 匿名对象
Data();
Data(10);
}
tip
注意设计任何一个类,无参和有参的构造函数我们都是要去写的
提供构造函数的影响
如果用户不提供任何构造函数,编辑器默认提供一个空的无参构造
如果用户定义了构造函数(不管是有参、无参),编辑器不在提供默认构造函数
析构函数
析构函数的定义方式
函数名和类名称相同,在函数名前加~,没有返回值类型,没有函数行参。(不能被重载)
当对象生命周期结束的时候,系统自动调用析构函数。 先调用析构函数,再释放对象的空间
class Data
{
public :
int mA;
public :
// 无参构造函数
Data()
{
mA = 0;
cout << "无参构造函数" << endl;
}
// 有参构造函数
Data(int a)
{
mA = a;
cout << "有参构造函数" << endl;
}
// 析构函数
~Data()
{
cout << "析构函数" << endl;
}
};
tip
一般情况下,系统默认的析构函数就足够了,但是如果一个类中有指针成员,这个类必须写析构函数,释放指针成员所指向的空间
拷贝构造函数
拷贝构造:本质上是构造函数
拷贝构造的调用时机:旧对象 初始化 新对象
的时候才会调用拷贝构造
如果用户不提供拷贝构造,编辑器会自动提供一个默认的拷贝构造(完成赋值动作——浅拷贝)
class Data
{
public :
int mA;
public :
// 无参构造函数
Data()
{
mA = 0;
cout << "无参构造函数" << endl;
}
// 有参构造函数
Data(int a)
{
mA = a;
cout << "有参构造函数" << endl;
}
// 拷贝构造函数
Data(const Data &d)
{
mA = d.mA;
cout << "拷贝构造函数" << endl;
}
};
如果用户不提供拷贝构造 编辑器会自动提供一个默认的拷贝构造
拷贝构造和无参构造以及有参构造的关系
如果用户定义了拷贝构造或者有参构造,都会屏蔽无参构造。
如果用户定义了无参构造或者有参构造 不会屏蔽拷贝构造
拷贝构造几种调用形式
1、旧对象给新对象初始化 调用拷贝构造
Data ob1(10);
Data ob2(ob1); // 调用拷贝构造
2、给对象取别名,不会调用拷贝构造
```c++
Data ob1(10);
Data &ob2 = ob1; // 不会调用拷贝构造
3、普通对象作为函数传参,调用函数时候,会调用拷贝构造
void func(Data d)
{
}
int main()
{
Data ob1(10);
func(ob1); // 调用拷贝构造
}
拷贝构造的浅拷贝和深拷贝
默认的拷贝构造,都是浅拷贝
如果类中没有指针成员,不用实现构造函数和析构函数
如果类中有指针成员,且指向堆区空间,必须重新实现析构函数释放指针的堆区空间,必须实现拷贝构造函数,实现深拷贝
class Data
{
public :
int mA;
int *mB;
public :
// 无参构造函数
Data()
{
mA = 0;
mB = NULL;
cout << "无参构造函数" << endl;
}
// 有参构造函数
Data(int a)
{
mA = a;
mB = new int(a);
cout << "有参构造函数" << endl;
}
// 拷贝构造函数
Data(const Data &d)
{
mA = d.mA;
mB = new int(*d.mB);
cout << "拷贝构造函数" << endl;
}
// 析构函数
~Data()
{
if (mB != NULL)
{
delete mB;
mB = NULL;
}
cout << "析构函数" << endl;
}
};
初始化列表
对象成员
在类中定义的数据成员一般是基本的数据类型,但是类中的成员也可以是对象,叫做对象成员。
先调用对象成员的构造函数,再调用本身的构造函数。析构函数和构造函数调用顺序相反,先构造,后析构
类会自动调用对象成员的无参构造。
class A{
public:
A(){
cout << "A()" << endl;
}
A(int a){
cout << "A(int a)" << endl;
}
~A(){
cout << "~A()" << endl;
}
};
class B
{
public:
int mB;
A ob; // 成员对象
B(){
cout << "B()" << endl;
}
// 初始化列表 成员对象 必须使用对象名 + ()的方式初始化
B(int a, int b): ob(a)
{
cout << "B(int b)" << endl;
}
~B(){
cout << "~B()" << endl;
}
};
explicit 关键字
C++ 提供了关键字 explicit, 禁止通过构造函数进行隐式转换, 声明为 explicit 的构造函数不能用于隐式转换。
注意 explicit 用于修饰构造函数,防止隐式转换。是针对单参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造)而言。
class MyString{
public:
explicit MyString(int n){
cout << "MyString(int n)" << endl;
}
MyString(const char *str){
cout << "MyString(const char *str)" << endl;
}
};
int main()
{
// 给字符串复制,还是初始化 ?
MyString str1 = 10; // MyString(int n)
MyString str2(10);
// 寓意非常明确,给字符串赋值
MyString str3 = "hello"; // MyString(const char *str)
MyString str4("hello");
return 0;
}