
◆ 博主名称: 晓此方-CSDN博客
大家好,欢迎来到晓此方的博客。
⭐️C++系列个人专栏:
⭐️踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰
0.1概述&前言
从本文会开始,此方会为大家带来类的默认成员函数 的内容。该方面是C/C++类和对象篇章最难以理解的部分 ,构造函数和析构函数分别取代了C语言的Init函数和destory函数 ,大大提升了运行效率。默认成员函数的学习将为后续内容打下深厚的基础,本文讲解深入骨髓,细节无微不至,希望看完后能让你对这两者有深入的认识。
一,类的默认成员函数
定义:
默认成员函数就是用户没有显式实现 ,编译器会自动生成的成员函数称为默认成员函数。
一个类,我们不写的情况下编译器会默认生成以下6个默认成员函数:

需要注意的是这6个中最重要的是前4个。 最后两个取地址重载不重要 ,我们稍微了解一下即可。其次就是**C++11以后还会增加两个默认成员函数,移动构造和移动赋值,**这个我们后面再讲解。默认成员函数很重要,也比较复杂,我们要从两个方面去学习:
- 第一:我们不写时,编译器默认生成的函数行为是什么,是否满足我们的需求。
- 第二:编译器默认生成的函数不满足我们的需求,我们需要自己实现,那么如何自己实现?
二,构造函数
2.1构造函数的定义
构造函数是特殊的成员函数
2.1.1构造函数的主要任务:
构造函数的主要任务并不是开空间创建对象(我们常使用的局部对象是栈帧创建时,空间就开好了),而是对象实例化时初始化对象。
2.1.2构造函数的本质:
构造函数的本质 是要替代我们以前写的Init函数的功能,构造函数自动调用的特点就完美的替代了Init。
2.2特性与创建
2.2.1构造函数的特性
- 函数名与类名相同。
- 无返回值。(返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
- 构造函数可以重载。
2.2.2构造函数的创建
cpp
class Date
{
public:
Date ()
{
_year=0;
_month=0;
_day=0;
}
Date (int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
}
- 在类Date中创建了一个构造函数Date,构造函数的名称和类的名称一致。
- 构造函数Date没有返回类型也没有返回值。
- 构造函数Date()和Date (int year,int month,int day)构成重载。
2.3构造函数的调用
对象实例化的时候,构造函数会自动调用对应的构造函数
1,调用无参数的构造函数,直接创建Date类的时候自动调用了构造函数。
2,调用有参的构造函数后面要加括号。和传递的参数值。
2.3.1以上面的的类举例
cpp
int mian()
{
Date d1;
Date d2(2025,12,21);
return 0;
}
- Date 类型实例化一个对象d1。此时自动调用第一个无参构造函数。
- Date类型实例化一个对象d2.此时实自动传递擦参数2025、12、21.调用第二个构造函数。
Tips:无参数构造函数的调用不可以在后面加括号,原因:为了防止与函数声明产生歧义。
cpp
Date Func();
2.4默认构造函数
2.4.1全缺省构造函数
cpp
Date (int year=2025,int month=2,int day=20)
{
_year=year;
_month=month;
_day=day;
}
- 全缺省构造函数的调用不需要括号。
- 全缺省构造函数不能和无参构造函数同时存在,会产生调用歧义。
- 全缺省构造函数的使用一定程度上取代了上面的两个构造函数。
2.4.2自动生成默认构造函数
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
定义:
无参构造函数、全缺省构造函数、我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数有且只有一个存在,不能同时存在。
Tips:常见错误写法与报错类型:
cpp
Date (int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
//省略class等代码
int main()
{
Date d1;
return 0;
}
手动添加了一个非默认构造函数,**防止了编译器自动生成默认构造函数。**引发报错:

总结一下: 不传递实参的构造函数就是默认构造函数。(这个概念特别容易混淆。)
2.5构造函数对不同的类型
说明:
C++把类型分成内置类型和自定义类型 。内置类型就是语言提供的原生数据类型 ,如:int/char/double/指针等,自定义类型就是我们使用class/struct等关键字自己定义的类型。
2.5.1对内置类型
先说结论:
我们不写,编译器默认生成的构造,**对内置类型成员变量的初始化没有要求。**也就是说是否初始化是不确定的,看编译器。(实际上这里很乱。C++标准没有规定)
- 早期的编译器是都不初始化的(也就是对内置类型的成员,生成的是一个随机值)
- 后期的编译器有些会初始化,或者在有些场景下会初始化。
既然如此,为了提升代码的可移植性:我们都把他们当成内置类型不处理的情况。
**测试:VS2019,VS2022:**其他编译器也可以尝试一下。

2.5.2对自定义类型
先说结论:
对于自定义类型成员变量,要求调用这个成员变量的默认构造 函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错。
举例:这里有一个栈
cpp
class Stack{
public:
Stack(int n = 4){
_a = (STDataType*)malloc(sizeof(STDataType) * n);
_capacity = n;
_top = 0;
} // ...
private:
STDataType* _a;
size_t _capacity;
size_t _top;
};
双栈实现队列:
cpp
class MyQueue{// 两个Stack实现队列
public: //编译器默认生成MyQueue的构造函数调用了Stack的构造,完成了两个成员的初始化
private:
Stack pushst;
Stack popst;
};
int main(){
MyQueue mq;
return 0;
}
这里就不需要显示的写MyQueue的构造函数,创建mq对象时对 pushst和popst先后调用Stack的构造函数。实现初始化:

如果stack的构造函数不存在,则会报错 :现在我们没办法解决这个问题,在后面的文章中再讲:初始化列表。

2.5.3混合特殊情况
这里让内置类型和自定义类型混合 ,初始化了内置类型。这是C++标准和编译器的问题。所以这个size是一个大坑,因为C++标准没有规定,有的编译器不会处理,问题就大了。因此size的初始化构造函数得自己写。

2.6总结
大多数情况下,构造函数都要我们自己写,少数情况下,类似Myqueue,构造函数会自动调用。
二,析构函数
2.1析构函数定义
析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,
C++规定对象在销毁时(声明周期结束)会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比我们之前Stack实现的Destroy功能。
2.1.1为什么析构函数不是完成对对象本身的销毁
对象存在栈帧,函数结束栈帧销毁并释放
cpp
int main()
{
Myqueue mq;
Stack ST;
return 0;
}
对象本身的空间在main函数的栈帧 里面。main结束后栈帧一块儿销毁,所以对象本身的空间的释放和销毁不是析构函数完成的。
2.1.2没有空间申请的类不需要析构函数
如日期类:
cpp
class Date
{
public:
//.....................
private:
int _year;
int _month;
int _day;
}
成员变量都是内置类型,不需要手写析构函数,同时编译器自动生成的析构函数也没什么用。
2.2析构函数的创建
- 在构造函数的前面加上"~":"~"在C语言中是按位取反,有"与构造函数相反"的意味。
- 无参数无返回值。(也不需要void)
2.2.1以栈举例
cpp
int main()
{
Stack st1;
Stack st2;
return 0;
}
cpp
~Stack()
{
free(_a);
_a=nullptr;
_top=_capacity=0;
}
2.3析构函数的特性
相对于destory,最大的特性:自动
- 对象声明周期结束,系统会自动调用默认的析构函数。
- 有资源申请的类需要写析构函数。
- 有多个对象需要析构的时候后创建的先析构。因为栈帧后进先出。
2.3.1以上面的栈类型析构举例
cpp
~Stack()
{
free(_a);
_a=nullptr;
_top=_capacity=0;
}
执行到达return语句,声明周期结束,开始调用析构函数。析构函数先后调用两次,为st2和st1析构。
调试结果:

Tips:调试技巧,在类里面想要看成员变量,可以直接输入this。
2.4编译器的默认操作
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。默认生成的析构函数不会管内置类型。
- 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型成员不做处理 ,自定类型成员会调用他的析构函数 (如果没有找到会报错)。
2.5对自定义类型的特殊规定
需要注意的是我们显示写析构函数,对于自定义类型成员也会调用他的析构,也就是说自定义类型成员无论什么情况都会自动调用析构函数。
cpp
// 两个Stack实现队列
class MyQueue
{
public:
~MyQueue()
{
free(str);
str=nullptr;
}
private:
Stack pushst;
Stack popst;
int *str;
}
首先,编译器会去调用析构函数 ~MyQueue() 。(调用 ~MyQueue()并不是因为有str指针)然后再会去调用栈类型的析构函数。

所以对于自定义类型的成员,不写也好,写也好,他都会去调用这个自定义成员的自己的析构函数。目的是防止内存泄漏。
2.4总结
如果类中没有申请资源 时,析构函数可以不写,直接使用编译器生成的默认析构函数, 如Date;如果默认生成的析构就可以用,也就不需要显示写析构,如MyQueue;但是有资源申请时,一定要自己写析构,否则会造成资源泄漏,如Stack。