C++基础——构造函数与析构函数

目录

一.类的六大特殊成员函数

二.构造函数

1.定义:

2.其特征如下:

3.构造函数的玩法1: 

玩法2:提供多种构造函数,完成多种初始化

 玩法3:二义性调用

玩法4:构造函数不仅能够完成对象成员变量的初始化,还可以做判断功能:

4.C++实现栈的好处:

C语言实现栈代码(部分):

C++实现: 

5.默认构造函数

三.析构函数

1.定义:

2 特性:

3.析构练习: 

4.默认析构函数



一.类的六大特殊成员函数

如果一个类中什么成员都没有,简称为空类。

空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员 函数。

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

二.构造函数

1.定义:

        构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。

2.其特征如下:

1. 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

3.构造函数的玩法1: 

 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对象的成员变量初始化赋值。 

玩法2:提供多种构造函数,完成多种初始化

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() 

 玩法3:二义性调用

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;
}

        产生错误的原因:会引发二义性对于不带参数的对象,既可以调用全缺省构造,也可以调用无参构造,让编译器为难,报错对于这两种构造函数,可以同时存在,没有语法错误,但会产生歧义,不知道调用哪个。 

玩法4:

构造函数不仅能够完成对象成员变量的初始化,还可以做判断功能:

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);
}

4.C++实现栈的好处:

C语言实现栈代码(部分):

#include<stdlib.h>
#include<stdio.h>

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;
}

C++实现: 

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++就是针对于我们可能在实现代码的过程中忘记去写初始化构造函数,从而研发出构造函数,在后面介绍的析构函数也是如此。

5.默认构造函数

        如果类中没有显式定义构造函数,则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等自己定义的类型。对于内置类型的成员变量,编译器不会管,所以成员变量成为随机值(让其自生自灭),对于自定义类型的成员变量,编译器会调用其自定义类型的构造函数,也就是说编译器只处理自定义类型的成员变量。如下代码:

案例1:

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类中,调用该类的构造函数。 

案例2:

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 特性:

        析构函数是特殊的成员函数,其特征如下:

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载。

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

3.析构练习: 

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后,函数即将销毁时自动调用它,然后堆区空间就会被释放,且其他变量清零。 

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(2022, 10, 5);
    Date d2(2021, 9, 1);
    return 0;
}

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

        所以我总结出一个道理:若编译器默认生成就可以满足需求的话,就不用自己写,不满足需求就自己写。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橙予清的zzz~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值