C++修炼之构造函数与析构函数
🌷默认成员函数
上一章中我们谈到,如果一个类中什么成员也没有,那么这个类就叫作空类。其实这么说是不太严谨的,因为一个类不可能什么都没有。
当我们定义好一个类,不做任何处理时,编译器会自动生成以下6个默认成员函数:
默认成员函数:如果用户没有手动实现,则编译器会自动生成的成员函数构造函数:主要完成初始化工作;析构函数:主要完成清理工作;拷贝构造:使用一个同类的对象初始化创建一个对象;赋值重载:把一个对象赋值给另一个对象;取地址重载:普通对象取地址操作;取地址重载(const):const对象取地址操作;
本章我们将学习两个默认成员函数——构造函数与析构函数。
🌷构造函数
🌺引例
在C语言阶段,我们实现栈的数据结构时,有一件事很苦恼,就是每当创建一个stack对象(之前叫作定义一个stack类型的变量)后,首先得调用它的专属初始化函数StackInit来初始化对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
这不免让人觉得有点麻烦。在C++中,构造函数为我们很好的解决了这一问题。
🌺构造函数的概念及特性
构造函数是一个特殊的成员函数。构造函数虽然叫作构造,但是其主要作用并不是开辟空间创建对象,而是初始化对象。
构造函数之所以特殊,是因为相比于其它成员函数,它具有如下特性:
- 函数名与类名相同;
- 无返回值;
- 对象实例化时,编译器
自动调用对应的构造函数; - 构造函数可以重载;
🌼举例🌼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
🌼特别注意🌼
- 创建对象时编译器会自动调用构造函数,若是
调用无参构造函数,则无需在对象后面使用()。否则会产生歧义:编译器无法确定你是在声明函数还是在创建对象。
🌼错误示例🌼
1 2 |
|
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
- 默认生成构造函数,对内置类型成员不作处理;对自定义类型成员,会调用它的默认构造函数;
- C++把类型分成
内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int、char、double…,自定义类型就是我们使用class、struct、union等自己定义的类型。
🌼举例🌼
🌼默认构造函数对内置类型🌼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
- 如图所示,默认构造函数的确未对内置类型做处理。
🌼默认构造函数对自定义类型🌼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
- 如图所示,在创建
queue对象时,默认构造函数对自定义成员_s做了处理,调用了它的默认构造函数stack()。
这一波蜜汁操作让很多C++使用者感到困惑与不满,为什么要针对内置类型和自定义类型做不同的处理呢?终于,在C++11中针对内置类型成员不初始化的缺陷,又打了补丁,即:
- 内置类型成员变量在类中声明时可以给默认值;
🌼举例🌼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
默认值:若不对成员变量做处理,则使用默认值。
- 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个;
🌼举例🌼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
🌷析构函数
析构函数与构造函数的特性相似,但功能有恰好相反。构造函数是用来初始化对象的,析构函数是用来销毁对象的。
- 需要注意的是,
析构函数并不是对对象本身进行销毁(因为局部对象出了作用域会自行销毁,由编译器来完成),而是在对象销毁时会自动调用析构函数,对对象内部的资源做清理(例如stack _s中的int* a)。
同样,有了析构函数,我们再也不用担心创建对象(或定义变量)后由于忘记释放内存而造成内存泄漏了。
🌼举例🌼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
🌺析构函数的特性
- 析构函数名是在类名前加上字符
~; - 无参数;
- 无返回值;
- 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数;
- 析构函数不能重载;
🌼举例🌼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
为便于观察,我们可以在析构函数内部写点儿东西。
- 编译器生成的默认析构函数,对自定类型成员调用它的析构函数;
🌼举例🌼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
|
- 这里可能有小伙伴会好奇:
为什么析构函数不像构造函数那样区分内置类型与自定义类型呢?
答案是:因为内置类型压根不需要我们担心清理工作,在其生命周期结束时会自动销毁。而自定义类型需要担心,因为自定义类型里可能含有申请资源(例如:malloc申请内存须手动释放)。
