• 【C++心愿便利店】No.5---构造函数和析构函数



    前言

    在这里插入图片描述

    👧个人主页:@小沈YO.
    😚小编介绍:欢迎来到我的乱七八糟小星球🌝
    📋专栏:C++ 心愿便利店
    🔑本章内容:类和对象
    记得 评论📝 +点赞👍 +收藏😽 +关注💞哦~


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、类的6个默认成员函数

    如果一个类中什么成员都没有,简称为空类。
    空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
    默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
    请添加图片描述

    二、构造函数

    1. 构造函数的概念

    如下Date类,没有初始化打印出来就会是随机值,同时对于栈没有初始化,就会报错
    那如果想能否在创建对象的同时,就将信息设置进去呢。因此,就有了构造函数。以Date类为例:

    #include
    using namespace std;
    class Date
    {
    public:
    	void Init(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;
    	d1.Print();//没有调用Init初始化函数
    	Date d2;
    	d2.Init(2022, 7, 6);//调用Init初始化函数
    	d2.Print();
    	return 0;
    }
    
    • 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

    请添加图片描述

    对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

    2. 构造函数的定义

    🌟构造函数是一个特殊的成员函数,名字与类名相同, 创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

    3. 构造函数的特性

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

    • 🌏函数名与类名相同。
    • 🌏无返回值。(不需要写void)
    • 🌏对象实例化时编译器自动调用对应的构造函数。

    对于上述代码所运行后的结果没有初始化d1结果是随机值,然后对比下述代码(同样没有初始化d1)及结果;运行结果自动初始化为1/1/1不是随机值且打印了Date(),这就说明对象实例化时编译器自动调用对应的构造函数

    #include
    using namespace std;
    class Date
    {
    public:
    	构造函数
    	Date()
    	函数名与类名相同
    	且无返回值
    	{
    		cout << "Date()" << endl;
    		_year = 1;
    		_month = 1;
    		_day = 1;
    	}
    	void Init(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;
    	d1.Print();
    	return 0;
    }
    
    • 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

    请添加图片描述

    • 🌏构造函数可以重载。(本质可以写多个构造函数,提供多种初始化方式)
    #include
    using namespace std;
    class Date
    {
    public:
    	1. 无参的构造函数
    	Date()
    	{}
    	也可以写成下面这种
    	Date()//函数名与类名相同且无返回值
    	{
    		cout << "Date()" << endl;
    		_year = 1;
    		_month = 1;
    		_day = 1;
    	}
    	2. 有参的构造函数
    	Date(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	3. 全缺省的构造函数
    	Date(int year=1, int month=1, int day=1)
    	无参和全缺省的不能同时存在会存在调用歧义
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	void Init(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;调用无参的构造函数
    	//Date func();
    	这也可以是一个函数声明所以为了区分不能加()
    	d1.Print();
    	Date d2(2023,8,28);调用带参的构造函数
    	d2.Print();
    
    	对于全缺省的构造函数使用更灵活可以传一个参数,两个等
    	Date d3(2023);
    	d3.Print();
    	Date d4(2023, 8);
    	d4.Print();
    	return 0;
    }
    
    • 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
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    🌟注意:如果通过无参构造函数创建对象时,对象后面不用跟括号(例如Date d1() 是错误的 ),否则就成了函数声明以下代码的函数:声明了d1函数,该函数无参,返回一个日期类型的对象warning C4930: “Date d1(void)”: 未调用原型函数(是否是有意用变量定义的?)
    请添加图片描述

    • 🌏如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
     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;
     return 0;
     }
    
    • 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

    将Date类中构造函数注释后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数;将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用

    • 🌏不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??

    🌟解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,所有类型的指针都是内置类型

    #include
    using namespace std;
    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;
    }
    
    • 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

    在这里插入图片描述
    🌟注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

    #include
    using namespace std;
    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 = 2023;
    	int _month = 9;
    	int _day = 5;
    	// 自定义类型
    	Time _t;
    };
    int main()
    {
    	Date d;
    	return 0;
    }
    
    • 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

    在这里插入图片描述

    无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
    注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
    是默认构造函数。它们都有一个共同的特点:可以不用传参。默认构造函数只能有一个,前面两个,在语法上可以构成函数重载,但是在无参调用的时候,会发生歧义,出现调用不明确。

    注意:要把默认构造函数和默认成员函数区分清楚,默认成员函数是我们不写编译器会自动生成的,默认构造函数是不需要传参的构造函数。编译器生成的构造函数,既是默认构造函数,同时也是默认成员函数。
    🌟为什么上述说内置类型用的缺省值

    #include
    using namespace std;
    class Date
    {
    public:
    	Date()
    	{
    		这里_year没有给值而_month _day给了值打印出来是2023/2/1所以声明那给的是缺省值
    		
    		_month = 2;
    		_day = 1;
    	}
    	void Print()
    	{
    		cout << _year << "/" << _month << "/" << _day << endl;
    	}
    private:
    	int _year = 2023;
    	int _month = 9;
    	int _day= 5;
    };
    
    int main()
    {
    
    	Date d1;
    	d1.Print();
    	return 0;
    }
    
    • 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

    在这里插入图片描述

    🌟编译器生成的默认构造的特点:

    1. 不写才会生成,写了任意一个构造函数就不会生成了
    2. 内置类型也叫基本类型(语言用关键字定义的类型);内置类型的成员不会处理(根据编译器有的会有的不会)
    3. 自定义类型的成员才会处理,会去调用这个成员的构造函数

    🌟总结:
    一般情况下,都需要我们自己写构造函数,决定初始化方式;成员变量全是自定义类型,可以考虑不写构造函数

    • 🌏无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个多个并存在会存在调用的二义性。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数(不传参就可以默认调用的)

    三、析构函数

    1. 析构函数的概念

    析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

    2. 析构函数的特性

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

    • 🌏析构函数名是在类名前加上字符 ~。
    • 🌏无参数无返回值类型。
    • 🌏一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
    • 🌏对象生命周期结束时,C++编译系统系统自动调用析构函数

    温馨提示:析构函数不能重载。
    🌟后定义先析构

    #include
    #include
    using namespace std;
    class Date
    {
    public:
    	Date(int year = 1, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	~Date()
    	{
    		cout << "Date()" << endl;
    	}
    private:
    	int  _year;
    	int _month;
    	int _day;
    };
    class Stack
    {
    public:
    	Stack(size_t n=4)
    	{
    		cout << "Stack(size_t n=4)" << endl;
    		if (n == 0)
    		{
    			a = nullptr;
    			top = capacity = 0;
    		}
    		else
    		{
    			a = (int*)malloc(sizeof(int) * n);
    			if (a == nullptr)
    			{
    				perror("realloc fail");
    				exit(-1);
    			}
    			top = 0;
    			capacity = n;
    		}
    	}
    	void Init()
    	{
    		a = nullptr;
    		top = capacity = 0;
    	}
    	void Push(int x)
    	{
    		if (top == capacity)
    		{
    			size_t newcapacity = capacity == 0 ? 4 : capacity * 2;
    			int*tmp = (int*)realloc(a,sizeof(int) * newcapacity);
    			if (tmp == nullptr)
    			{
    				perror("realloc fail");
    				exit(-1);
    			}
    			if (tmp == a)
    			{
    				cout << capacity << "原地扩容" << endl;
    			}
    			else
    			{
    				cout << capacity << "异地扩容" << endl;
    			}
    			a = tmp;
    			capacity = newcapacity;
    		}
    		a[top++] = x;
    	}
    	~Stack()
    	{
    		cout << "~Stack()" << endl;
    		free(a);
    		a = nullptr;
    		top = capacity = 0;
    	}
    	int Top()
    	{
    		return a[top - 1];
    	}
    	void Pop()
    	{
    		assert(top > 0);
    		--top;
    	}
    	void Destroy()
    	{
    		free(a);
    		a = nullptr;
    		top = capacity = 0;
    	}
    	bool Empty()
    	{
    		return top == 0;
    	}
    private:
    	int* a;
    	int top;
    	int capacity;
    };
    int main()
    {
    	Date d1;
    
    	Stack st1;
    	Stack st2;//后定义的先析构
    	return 0;
    }
    
    • 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
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112

    在这里插入图片描述
    Stack中的成员变量a、capacity、top都是内置类型,对象st1生命周期结束要销毁的时候,a和capacity和top是在栈上不需要资源清理,最后由系统将其内存回收,而a指向的空间是在堆区上申请的,这块空间不会随着对象生命周期的结束而自动释放,所以会造成内存泄漏,因此在对象销毁前,要通过析构函数去释放成员变量a指向的空间,这就是析构函数的作用。

    • 🌏 关于编译器自动生成的析构函数,是否会完成一些事情呢?

    下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数

    #include
    using namespace std;
    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;
    }
    
    • 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

    程序运行结束后输出:~Time()
    在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
    因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。
    但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
    注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数;如果类中没有申请资源时(在堆上申请空间),析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date(日期)类;有资源申请时,一定要写,否则会造成内存泄漏,比如Stack类


  • 相关阅读:
    ELK技术栈简介
    select服务器和poll客户端
    Reactor And Gev 详解 通俗易懂
    计算机系统6-> 计组与体系结构3 | MIPS指令集(中)| MIPS汇编指令与机器表示
    Class加载过程
    dubbo(3): 启动时检查/超时/重试/幂等
    《向量数据库指南》——AI原生向量数据库Milvus Cloud 2.3 Enhancement
    react中使用腾讯地图
    SpringCloud学习(一)——微服务介绍
    页面标题组件
  • 原文地址:https://blog.csdn.net/ljq_up/article/details/132523981