💓博主CSDN主页:杭电码农-NEO💓
⏩专栏分类:C++初阶之路⏪
🚚代码仓库:NEO的学习日记🚚
🌹关注我🫵带你学习C++
🔝🔝
1. 前言
本章重点:
本篇文章着重讲解类中的
两个默认函数,分别为:
并且介绍类的六个默认函数
(其他三个在后面章节讲解)
我们平时写数据结构时,比如:栈和队列
经常忘记写或者调用初始化函数
使得栈类中的变量是随机值,易出错
有时忘记调用销毁函数,导致内存泄漏
非常的不方便,不好用!
于是C++引入了这几个函数
可以有效的解决这些问题!
2. 构造函数
构造函数,顾名思义是用于初始化的函数
特性:
- 函数名与类名相同
- 无返回值
- 对象实例化时自动调用对应的构造函数
- 构造函数可以重载
需要注意的点:
- 构造函数是特殊的成员函数
不能将它与普通函数对比 - 构造函数的任务是初始化对象
而不是开辟空间创造对象
举例说明:
class Date { public: Date(int year, int month, int day)//构造函数 { _year = year; _month = month; _day = day; } Date()//无参的构造函数 { _year = 1900; _month = 1; _day = 1; } private: int _year; int _month; int _day; }; int main() { Date d1; // 调用无参构造函数 Date d2(2023, 7, 24);//调用含参的构造 }
注:构造函数是实例化对象时就调用
对象后面跟一个括号来调用!
3. 构造函数的特性
如果使用者没有显示写构造函数
系统就会自动生成一个默认构造函数
比如:
class Date { public: /* // 如果用户显式定义了构造函数,编译器将不再生成 Date(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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数 // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成 // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用 Date d1; return 0; }
对代码的解释:
屏蔽掉自己写的构造函数时
编译器会自动生成一个,d1在
实例化时就会去调用编译器生成的然而当放开自己写的构造函数后
会报错,因为自己实现的构造函数
没有缺省值,并且d1实例化时没有传参
4. 对默认构造函数的理解
可能你们会疑惑:
既然编译器会自己生成构造函数
那我是不是写不写构造函数都可以了?
带着此疑问引出一个新概念:
内置类型和自定义类型
- 内置类型是C++语言提供的类型
比如: int/char类型 - 自定义类型是用户使用class类
定义出来的类型,如:Date类(日期类)
这个新概念有什么用?
- 编译器自动生成的构造函数
不会处理内置类型,它们是随机值 - 然而自动生成的构造会处理自定义类型
它会去调用自定义类型的默认构造
举例说明:
class Time { public: Time()//Time类的构造函数 { cout << "Time()" << endl; _hour = 0; _minute = 0; _second = 0; } private: int _hour; int _minute; int _second; }; class Date { private: // 基本类型(内置类型) int _year; int _month; int _day; // 自定义类型 Time _t; };
对代码的理解:
Date类没有显示写构造函数
所以编译器会自动生成一个构造函数
此构造函数不会处理内置类型
所以成员变量:
year,month,day都是随机值然而此构造函数会处理自定义类型
它会去调用Time类的默认构造函数
将成员变量_t初始化
5. 对默认构造函数的补充
你可能会疑惑:上面的代码中
Time类显示写了构造函数
为啥还能被称为默认构造函数被调用?
默认构造函数可以是下面的类别:
- 编译器自动生成的默认构造
- 显示写的无参的构造函数
- 显示写的全缺省的构造函数
请看下面的代码:
class Date { public: Date()//默认构造函数 { _year = 1900; _month = 1; _day = 1; } Date(int year = 1900, int month = 1, int day = 1)//默认构造函数 { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
上面两种写法都是默认构造函数!
但是它们不能同时存在
因为当实例化对象时没有传参,系统
不知道是调用全缺省函数还是无参的函数
6. 析构函数
现在我们知道一个对象是怎么被初始化的
那么一个对象又是怎么被销毁的呢?
析构函数的概念:
与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作
析构函数的特性:
- 析构函数名是在类名前加上字符 ~
- 析构函数无参数无返回值类型
- 一个类只有一个析构函数,若未显式定义
系统会自动生成默认的析构函数 - 析构函数不能重载!
- 对象生命周期结束时
C++编译系统系统自动调用析构函数
注:析构函数和构造函数一样
是特殊的函数,不能将它与普通函数相比
7. 对析构函数的理解
有了前面构造函数的铺垫
析构函数就容易理解了,和我们想的一样
编译器自动生成的默认析构函数
只处理自定义类型,而内置类型不会管
那你可能会问:
既然默认析构函数不会处理内置类型
那么内置类型是不是不会销毁?
答案是: 不!
内置类型会在对象生命周期结束时
将它在栈区的空间还给操作系统
所以析构函数不处理在栈区的变量
也没有问题但是有些变量的指针指向堆区
有由动态开辟出来的空间
这份空间不会主动还给操作系统
需要我们手动写析构函数来释放!
请看以下代码:
typedef int DataType; class Stack { public: Stack(size_t capacity = 3)//构造函数 { _array = (DataType*)malloc(sizeof(DataType) * capacity); if (NULL == _array) { perror("malloc申请空间失败!!!"); return; } _capacity = capacity; _size = 0; } ~Stack()//析构函数 { if (_array) { free(_array); _array = NULL; _capacity = 0; _size = 0; } } private: DataType* _array; int _capacity; int _size; }; void TestStack() { Stack s; }
这段代码中,存在在堆区申请的空间
所以不能使用编译器默认生成的析构
而是要用自己写的析构函数去free掉
这块堆区的空间
8. 对默认析构函数的理解
和构造函数一样,默认析构函数
会去调用自定义类型的析构函数
可以用下面这段代码来验证一下:
class Time { public: ~Time() { cout << "~Time()" << endl; } private: int _hour; int _minute; int _second; }; class Date { private: // 基本类型(内置类型) int _year = 1970; int _month = 1; int _day = 1; // 自定义类型 Time _t; }; int main() { Date d; return 0; }
当d的生命周期结束时
系统会自动调用析构函数
而Date类没有显示写析构函数
就会使用编译器自动生成的析构
此析构函数会去调用Time的析构函数
所以屏幕上就会打印:~Time()
9. 总结以及拓展
构造函数是析构函数是对立的
一个用于初始化,一个用于销毁对象调用
掌握它们对后面类和对象的学习很重要
拓展1:
类的六个默认函数:
现在已经学了构造和析构函数!
拓展2:
C++11新增内容:
C++11新增了一个功能:
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值
例如:
class Time { public: Time() { cout << "Time()" << endl; _hour = 0; _minute = 0; _second = 0; } private: int _hour = 1;//声明的时候给缺省值 int _minute = 1; int _second = 1; };
如果用户没有显示传参
那么hour,minute,second
的值都会初始化为1
🔎 下期预告:拷贝构造函数 🔍