【类与对象 -2】学习类的6个默认成员函数中的构造函数与析构函数

目录

1.类的6个默认成员函数

2.构造函数

2.1概念

2.2特性

3.析构函数

3.1析构函数的概念

3.2特性


1.类的6个默认成员函数

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

下面我们来分别学习一下这6个函数~

2.构造函数

2.1概念

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.Init(2022, 7, 5);
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();
	return 0;
}

在日常使用时,我们可能会忘记对对象进行初始化,程序报错后反而检查半天才发现问题。在为了避免这种失误出现,C++中引入了构造函数。

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

2.2特性

构造函数在函数名上看起来好像是创建对象,实际上是完成对象的初始化

构造函数特征如下:

①函数名和类名相同;

②没有返回值,不是在前加void,而是压根不需要写;

③对象实例化时编译器自动调用对应的构造函数;

④构造函数可以重载;

上面代码的构造函数形式如下:

(1)不带参数的构造函数,由于编译器自动调用构造函数,在定义对象时就已经初始化了,

(2)带参数的构造函数,在定义对象的时候就要将初始化的值加在后面。

调用带参的构造函数时在括号内加入要初始化的值跟在对象后,那调用无参的构造函数时可以直接跟括号在对象后吗?(为什么调用无参的构造函数时后面不用加括号)

不能。这种形式就成了函数声明。

如下图,声明了一个d3函数,该函数没有参数,返回一个Date类型对象。

以理论上两种形式可以同时存在,构成了函数重载,但是一般情况下我们不会同时写出这两个,因为调用存在歧义。

这两种形式可以合为一种,利用我们之前学过的缺省参数来修改,如下面图中的形式:

⑤如果在类中忘记显式定义构造函数也不用担心,C++编译器会自动生成一个无参的默认构造函数。

我们忘记定义构造函数时执行程序,发现可以正常运行,这是因为编译器会自动生成一个默认构造函数,但是看运行结果截图我们可以发现编译器没有对参数进行初始化,_year、_month、_day都是随机值。那么编译器生成的默认构造函数有什么用呢?

C++中把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,像int/char/double/指针等;自定义类型就是我们使用的struct、class等自己定义的类型。

C++98中规定对默认生成的构造函数对内置类型不作处理,对自定义类型则调用它的默认构造函数。如果自定义类型没有默认构造函数就会报错。

其实自定义类型的尽头就是内置类型。

以上Date类中的_year、_month、_day就是int类型,所以不作处理,是随机值,接下来我们验证一下自定义类型。

•有些编译器可能会对内置类型进行处理(int初始化为0,double初始化为0.0,指针初始化为nullptr),但C++标准并没有规定内置类型要处理。

C++11中对内置类型不处理的缺陷又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

分析一个类型成员和初始化要求,如果需要些构造函数我们就自己写,不需要时就靠编译器自动生成。结论:大多情况下都需要自己实现构造函数

⑥无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为
是默认构造函数。

不止是编译器生成的构造函数叫默认构造函数,我们自己显式定义的无参构造函数,全缺省构造函数都是默认构造函数。即不需要传参就能调用的构造函数,就叫默认构造函数

默认生成的构造函数不能与其他两种同时存在;无参构造函数不能与全缺省构造函数同时存在,会产生调用歧义。

一般情况下,我们建议使用全缺省构造函数

3.析构函数

3.1析构函数的概念

前面我们学习了对对象进行初始化构造函数(类似于栈中的初始化函数Init),那么怎样清理资源呢,这就要提到我们接下来要学习的析构函数(栈中的销毁函数Destory)。

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

3.2特性

析构函数特征:

①析构函数名是在类名前加上字符 ~。(C语言中“~”作用是按位取反,析构函数前加表示该函数与构造函数作用相反)
②无参数无返回值类型。
③一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构
函数不能重载
默认生成的析构函数与构造函数类似,对内置类型不作处理,自定义类型则调用它的析构函数。
(Date类没有显式定义析构函数,系统将自动生成,Date类中定义了自定义类型Time类型的对象t,调用它的析构函数~Time()。)
对象生命周期结束时,C++编译系统系统自动调用析构函数。
以下面的日期类为例,定义一个析构函数~Date()。(其实日期类的析构函数没什么有用的,因为它的_year等都是属于对象,并没有额外开辟空间,也就没有什么资源需要清理,此处只是用来验证它的特性)
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	~Date()
	{
		cout << this << endl;
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void func()
{
	Date d2;
}
int main()
{
	func();
	Date d1;
	d1.Print();
	return 0;
}

如果类中没有申请资源时,析构函数可以不写 ,直接使用编译器生成的默认析构函数,比如
Date类; 有资源申请时,一定要写, 否则会造成资源泄漏,比如Stack类。

如果我们写了一个栈,忘记写析构函数会发生内存泄漏。虽然函数结束栈帧销毁,但是在堆上额外开辟了一块空间没有释放。C语言中避免出现内存泄漏需要在函数结束前调用Destory函数来销毁,但是这种方法在使用时极有可能忘记,因而C++中引入了析构函数。

class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = (int*)malloc(sizeof(int) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(int data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
private:
	int* _array;
	int _capacity;
	int _size;
};
int main()
{
    Stack S;
	return 0;
}

对以上代码加上析构函数,结果如下

**先定义的先调用构造函数,后定义的先调用析构函数

class Date
{
public:
	Date(int year)
	{
		_year = year;
		cout << "Date()->" << _year << endl;
	}
	~Date()
	{
		cout << "~Date()->" << _year << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;
};
Date d5(5);
static Date d6(6);
void func()
{
	Date d4(4);
}
int main()
{
	Date d1(1);
	static Date d2(2);
	Date d3(3);
	func();
	return 0;
}

分析上面代码,猜测结果是什么?

我们先分析一下各个对象是全局还是局部变量。d1、d2、d3、d4是局部变量,d4是在函数 func() 内定义,函数 func() 在main函数内调用,所以d4的生命周期先结束。d2又被static修饰(称为静态局部变量),改变了它的存储位置,使得它的生命周期变长,直至程序结束,生命周期才结束,在销毁之前应先将main函数内的局部变量先销毁(后定义先析构)再销毁该静态局部变量。d5、d6是全局变量,虽然d6被static修饰(静态全局变量),但它与全局变量的存储位置没有区别,生命周期仍是整个程序的执行期间,也是后定义的先析构。

所以调用析构函数的顺序是d4-d3-d1-d2-d6-d5

结果如下:

总结:

调用析构函数的顺序:局部对象(后定义的先析构)->静态局部对象->全局对象(后定义的先析构)

        构造函数和析构函数的学习就到这了,下篇我们将一起学习其他的默认成员函数,谢谢观看!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

今天学习了吗•

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

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

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

打赏作者

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

抵扣说明:

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

余额充值