目录
玩法4:构造函数不仅能够完成对象成员变量的初始化,还可以做判断功能:
如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
- 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 d1(2015, 1, 1);
- Date d2(2022,9,1);
- d1.Print();
- d2.Print();
- }
当Date类创建d1,d2对象时,会自动调用构造函数,实参2015传给形参year,1传给month,1传给day......完成d1,d2对象的成员变量初始化赋值。

- class Date
- {
- public:
-
- //构造函数
- Date(int year, int month, int day)
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- //无参构造函数
- Date() {
- _year = 1999;
- _month = 10;
- _day = 13;
- }
-
-
- void Print() {
- cout << _year << "-" << _month << "-" << _day << endl;
- }
-
- private:
- int _year;
- int _month;
- int _day;
- };
-
- int main() {
- Date d1(2015, 1, 1);
- Date d2(2022, 9, 1);
- d1.Print();
- d2.Print();
- Date d3;
- Date d4;
- d3.Print();
- d4.Print();
- return 0;
- }
注:创建d3,d4对象时,不带参数所以自动调用无参构造函数Date()

- class Date
- {
- public:
- //全缺省构造函数
- Date(int year=1, int month=1, int day=1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- //无参构造函数
- Date() {
- _year = 1999;
- _month = 10;
- _day = 13;
- }
-
-
- void Print() {
- cout << _year << "-" << _month << "-" << _day << endl;
- }
-
- private:
- int _year;
- int _month;
- int _day;
- };
-
- int main() {
-
- Date d5;
- Date d6;
- d5.Print();
- d6.Print();
- return 0;
- }

产生错误的原因:会引发二义性,对于不带参数的对象,既可以调用全缺省构造,也可以调用无参构造,让编译器为难,报错。对于这两种构造函数,可以同时存在,没有语法错误,但会产生歧义,不知道调用哪个。
构造函数不仅能够完成对象成员变量的初始化,还可以做判断功能:
- class Date {
- public:
- Date(int year = 1199, int month = 12, int day = 15) {
-
- //可以对对象的实参做判断功能
- if(!(year >= 1 && (month >= 1 && month <= 12) && (day >= 1 && day <=
- GetMonthDay(year, month)))) {
-
- cout << "该日期为非法日期" << endl;
- }
- else {
- cout << "该日期为合法日期" << endl;
- }
- _year = year;
- _month = month;
- _day = day;
- cout << _year << "-" << _month << "-" << _day << endl;
- }
-
- //获得月份天数
- int GetMonthDay(int year, int month) {
- int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
- if ((month == 2) && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) {
- return 29;
- }
- else {
- return MonthDay[month];
- }
- }
- private:
- int _year;
- int _month;
- int _day;
- };
-
- int main() {
- Date d1(2022, 2, 40);
- Date d2(-1999, 15, 30);
- Date d3(2022, 10, 15);
- }

- #include
- #include
-
- typedef struct Stack
- {
- int* array;
- int capacity;
- int size;
- }Stack;
-
- //初始化函数
- void StackInit(Stack* ps,int capacity)
- {
- ps->array = (int*)malloc(sizeof(int) * 4);
- if (NULL == ps->array)
- {
- perror("malloc fail");
- return;
- }
- ps->capacity = 4;
- ps->size = 0;
- }
-
- //插入数据函数
- void StackPush(Stack* ps, int data)
- {
- //...扩容
- ps->array[ps->size] = data;
- ps->size++;
- }
-
- int main(){
- Stack st;
- StackInit(&st,4);
- StackPush(&st,1);
- StackPush(&st,2);
- StackPush(&st,3);
- return 0;
- }
- class Stack{
- public:
-
- //构造函数
- Stack(int capa=4){
- _array = (int*)malloc(sizeof(int) * capa);
- if (NULL == _array)
- {
- perror("malloc fail");
- return;
- _capacity = 4;
- _size = 0;
- }
-
- void StackPush(int data){
- //...扩容
- _array[_size] = data;
- _size++;
- }
-
- private:
- int* _array;
- int _capacity;
- int _size;
- };
-
- int main(){
- Stack st; //创建对象后自动调用构造函数——完成初始胡操作
- StackPush(1);
- StackPush(2);
- StackPush(3);
- return 0;
- }
总结:C++就是针对于我们可能在实现代码的过程中忘记去写初始化构造函数,从而研发出构造函数,在后面介绍的析构函数也是如此。
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦 用户显式定义编译器将不再生成。
默认构造函数有三种形式:
1、无参构造(没有形参的构造函数)称之为默认构造函数
2、全缺省构造(形参都有缺省值)也称之为默认构造函数
3、程序员自己不写构造,编译器自动生成的默认构造函数
重点介绍一下编译器自己生成的:
- class Date
- {
- public:
-
- //没有写构造函数
-
- void Print()
- {
- cout << _year << "-" << _month << "-" << _day << endl;
- }
-
- private:
- int _year;
- int _month;
- int _day;
- };
-
- int main()
- {
- Date d1;
- return 0;
- }
当创建d1对象时,自动调用构造函数,因为我们没有亲自去写构造函数,所以编译器自动生成一个默认的无参构造函数,这种构造函数既不会让其成员变量置成零,也不会做其他任何事情,它生成的构造函数只为确保程序能够正常运行,赋值的事情不归它管,该类中的成员变量值经过构造后会变成随机值。

有的小伙伴会问了,系统生成的默认构造函数没什么用,毕竟这些成员变量并不能被赋我们想要的具体的值,最终还是得要我们自己去写它,那么创造出这个机制的意义在哪里?
其实C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,而自定义类型就是我们使用class/struct/union等自己定义的类型。对于内置类型的成员变量,编译器不会管,所以成员变量成为随机值(让其自生自灭),对于自定义类型的成员变量,编译器会调用其自定义类型的构造函数,也就是说编译器只处理自定义类型的成员变量。如下代码:
- class Time{
- public:
- //构造函数
- 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;
- };
-
- int main(){
- Date d;
- return 0;
- }
运行结果:

解析:_year,_month,_day都是随机值,而Time _t这个变量是class Time自定义类型,编译器会走到Time类中,调用该类的构造函数。
- typedef int DataType;
- class Stack
- {
- public:
- Stack(size_t capa = 4)
- {
- _array = (DataType*)malloc(sizeof(DataType) * capa);
- if (NULL == _array)
- {
- perror("malloc申请空间失败!!!");
- return;
- }
- _capacity = capa;
- _size = 0;
- }
- void Push(DataType data)
- {
- // CheckCapacity();
- _array[_size] = data;
- _size++;
- }
- // 其他方法...
-
- //析构函数
- ~Stack()
- {
- if (_array)
- {
- free(_array);
- _array = NULL;
- _capacity = 0;
- _size = 0;
- }
- }
- private:
- DataType* _array;
- int _capacity;
- int _size;
- };
-
-
- class MyQue {
- private:
- //自定义类型
- Stack _pushST;
- Stack _popST;
- };
-
- int main() {
- MyQue q;
- return 0;
- }
运行结果:
对于MyQue类来说,它的成员变量全是自定义类型,编译会处理——去Stack类中调用Stack的构造函数,从而得到上图右上角的各变量初始值,MyQue不需要自己写构造函数,全靠调用Stack类的即可。
案例总结:Date类和Stack类的构造函数都需要自己去写,而MyQue类的构造使用默认构造即可,因为编译生成的构造函数并不能满足我们的需求。
在之前,我写过很多用C语言实现链表、通讯录、栈、队列的代码,发现都需要用到数据结构的销毁函数Destory,于是析构函数因此诞生,即使我们忘记写了,系统也会帮助我们清理。
析构函数与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由 编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
析构函数是特殊的成员函数,其特征如下:
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
- class Stack
- {
- public:
- //构造函数
- Stack(int capa = 4){
- _array = (DataType*)malloc(sizeof(DataType) * capa);
- if (NULL == _array)
- {
- perror("malloc申请空间失败!!!");
- return;
- }
- _capacity = capacity;
- _size = 0;
- }
-
-
- void Push(DataType data){
- // 扩容
- _array[_size] = data;
- _size++;
- }
-
- //析构函数
- ~Stack(){
- if (_array){
- free(_array);
- }
- _array = NULL;
- _capacity = 0;
- _size = 0;
- }
-
- int main(){
- Stack st; //创建对象后自动调用构造函数——完成初始化操作
- StackPush(1);
- StackPush(2);
- StackPush(3);
- return 0;
- }
析构函数的调用是在执行return 0后,函数即将销毁时自动调用它,然后堆区空间就会被释放,且其他变量清零。
默认析构函数与默认构造函数同理,都是我们不生成时,编译器自动生成一个析构函数,但编译器生成的也只能处理一些简单类型的成员变量,例如日期类:
- 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 d1(2022, 10, 5);
- Date d2(2021, 9, 1);
- return 0;
- }

对于复杂类型的成员变量,编译器自动生成的析构函数并不能完成最后的清理工作,比如:对于堆区空间的生成,使用fopen函数打开的文件,最后需要手动释放(fclose/free),可是默认析构函数并不能做到这么智能,所以还是得自己去写析构函数!!!
所以我总结出一个道理:若编译器默认生成就可以满足需求的话,就不用自己写,不满足需求就自己写。