⼀个类,我们不写的情况下编译器会默认⽣成以下6个默认成员函数。
下面我们详细介绍的是构造函数和析构函数,它们的主要作用分别是初始化工作和清理工作。
构造函数
1、构造函数的概念
构造函数虽名里带着“构造”但是其实际上并不是说开辟空间创建对象,而是对象实例化的时候初始化。保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。这也方便我们不需要初始化。
下面我们可以看这个代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;class Date {
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d;d.Init(2025, 1, 19);d.Print();return 0;
}
我们可以看这个代码正常情况下:
可是当我们忘记初始化的时候:
从上述图片我们可以看到,运行结果输出了一串随机值。
2、构造函数的特性
1.函数名和类名相同
2.没有返回值
3.对象实例化时系统会⾃动调⽤对应的构造函数
4.构造函数可以重载
5.类中没有显式定义构造函数,编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
6.无参构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。我们没写编译器默认生成的构造函数都可以认为是默认成员函数。
7.我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;class Date {
public://无参构造函数/*Date(){_year = 1;_month = 1;_day = 1;}*///带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}//全缺省构造函数Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d; //调用默认构造函数Date d1(2025, 1, 19); //调用带参构造函数//d.Init(2025, 1, 19);d.Print();return 0;
}
构造函数支持函数重载
如果类中没有显式定义构造函数,则编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
下面我们来看一下这个代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;class Date {
public://全缺省构造函数/*Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}*/void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d;d.Print();return 0;
}
我们将上述代码运行会发现出现的是随机数,这是为什么呢?上面不是说了没有显式定义构造函数,编译器会自动生成一个无参的默认构造函数吗?为什么会出现是随机数呢?
这个是因为C++默认生成的构造函数对内置类型成员变量不做处理,但是对于自定义类型的成员变量才会处理。这就是为什么上面会出现随机数的情况了
那么哪种是自定义类型,哪种是内置类型呢?
内置类型是:int、char等
自定义类型就是:class、struct去定义的类型对象
下面我们再看这一串代码:
class Stack {
public:Stack(){A = nullptr;top = capacity;}
private:int* A;int top;int capacity;
};class _Queue {
public:void push(int n){}
private:Stack s1;Stack s2;
};
我们可以看到s1和s2就不需要初始化了直接就默认生成的。但是如果是栈里面没有写构造函数,那么就会像上面一样出现随机值,因为栈里面的是内置类型。
综上所述,我们可以知道如果是一个类中的成员全是自定义类型,我们就可以用默认生成的函数。如果有内置类型的成员,或者需要显示传参初始化的,那么就要自己实现构造函数。
假设我们不想写构造类型也要对内置类型处理呢?
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;class Date {
public://全缺省构造函数/*Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}*/void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year = 2025;int _month = 1;int _day = 19;
};int main()
{Date d;d.Print();return 0;
}
使用默认构造函数提前给成员变量定义好
析构函数
析构函数的概念
析构函数的功能和构造函数的功能恰恰相反,析构函数不是对完成对象的销毁,局部对象销毁工作是由编译器完成的,而对象在销毁时会自动调用析构函数完成类的一些资源清理工作。
析构函数的特性
1.析构函数名是在类名前面加上符号"~"
2.无参数返回值(这个第二点和构造函数一样)
3.一个类有且仅有一个析构函数。若未显式定义,系统会⾃动⽣成默认的析构函数。
4.对象生命周期结束时,编译器系统自动调用析构函数。
5.编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。
下面我们还是用日期类的代码举例:
class Date {
public:Date(int year = 2025, int month = 1, int day = 19){_year = year;_month = month;_day = day;}void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}~Date(){cout << "~Date()" << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d; //调用默认构造函数d.Print();return 0;
}
我们可以看到d会调用它的默认构造函数进行初始化,出了作用域后又调用其析构函数。
析构函数是完成对资源的清理,那什么才算是清理呢?答案是:对malloc、new、fopen这些进行清理。
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;
}
private:STDataType* _a;size_t _capacity;size_t _top;
};
int main()
{Stack s;
}
我们看下面这个代码:
int main()
{Stack s1;Stack s2;
}
如果是上述两个实例化对象,s1先构造呢还是s2先构造,又或者s1先析构还是s2先析构?
答案是:前者是s1先构造,后者是s2先析构。
后者为什么是s2先析构呢?这个就好比是在栈上,后进先出,s2后压栈,那它肯定是先出的。
析构函数和构造函数是一样的,对内置类型不处理,自定义类型会去调用它的析构函数。
综上所述,如果在类没有申请资源的时候析构函数是可以不用写的,直接让编译器生成默认析构函数。有资源的时候是一定要写的,否则会造成资源泄露。