【C++】构造函数与析构函数

写在前面

构造函数与析构函数都是属于类的默认成员函数!
默认成员函数是程序猿不显示声明定义,编译器会中生成。

构造函数和析构函数的知识需要建立在有初步类与对象的基础之上的,关于类与对象不才在前面笔记中有详细的介绍:点我跳转


文章目录


一、构造函数的特性

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

其特征如下:

1.1、函数名与类名相同。

1.2、 无返回值。

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

cpp 复制代码
class stack {
public:
	stack() {//构造函数
		cout << "this is stack()" << endl;
	}
	
	void Init(int defintCapacity) {
		_arr = (int*)calloc(defintCapacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = defintCapacity;
		_size = 0;
	}

	void push(int x) {
		//....扩容等
		_arr[_size++] = x;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};

int main() {
	stack s1;

	return 0;
}

程序运行结果:

  • 在上述代码中,不才创建了一个默认构造函数stack,在构造函数中,我们只让其打印字符串this is stack(),之后,我们在s1对象中,并没有显示的调用构造函数,但是字符串就被打印出来了,这就说明的对象实例化时编译器自动调用对应的构造函数

这时候,我们就可以把stack的初始化函数设置放入构造函数中,每当我们创建一个对象时,通过构造函数自动初始化数据。如下:

cpp 复制代码
class stack {
public:
	stack(int defintCapacity = 4) {
		_arr = (int*)calloc(defintCapacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = defintCapacity;
		_size = 0;
	}

	void push(int x) {
		//....扩容等
		_arr[_size++] = x;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};

int main() {
	stack s1;

	return 0;
}

运行结果:

  • 这时候,我们就不用每次都显示的初始化数据了,而且也不怕忘记初始化。

1.4、构造函数可以重载。

构造函数也是函数,是函数就可以重载

cpp 复制代码
class stack {
public:
	stack(int defintCapacity = 4) {
		_arr = (int*)calloc(defintCapacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = defintCapacity;
		_size = 0;
	}

	stack(int* arr, int defintCapacity) {
		if (nullptr == arr) {
			perror("malloc申请空间失败");
			return;
		}
		_arr = arr;
		_capacity = defintCapacity;
		_size = 0;
	}

	void push(int x) {
		//....扩容等
		_arr[_size++] = x;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};

int main() {
	int* arr = (int*)calloc(2, sizeof(int));

	stack s1(arr, 2);

	return 0;
}

程序运行结果:

和函数重载一样的逻辑,编译器会根据符号名去调用对应的构造函数。
需要注意,调用默认构造函数不需要加括号,因为加上括号后,编译器会认为是函数

举个栗子:
stack s1:这时s1代表的是调用stack的默认构造函数的对象
stack s1():这时s1就被当做,返回值是stack类且没有形参的函数。

有参调用就和普通函数一样,只不过是对象+参数列表stack s1(arr, 2)


1.5、如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

默认构造函数在C++中有特殊定义,在C++标准中,默认构造函数不会对内置类型进行处理,自定义类型会调用它的默认构造函数 。但是现在有些编译器会对内置类型进行初始化,但这是该编译器自己的行为,C++标准中是不进行处理的。

内置类型/基本类型 :语言本身定义的基础类型(如intchar、指针、double等)
自定义类型 :使用classstruct等定义的类型

cpp 复制代码
class stack {
public:
	stack(int defintCapacity = 4) {
		_arr = (int*)calloc(defintCapacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = defintCapacity;
		_size = 0;
	}

	stack(int* arr, int defintCapacity) {
		if (nullptr == arr) {
			perror("malloc申请空间失败");
			return;
		}
		_arr = arr;
		_capacity = defintCapacity;
		_size = 0;
	}

	void push(int x) {
		//....扩容等
		_arr[_size++] = x;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};

class Date
{
public:

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main() {
	Date d1;

	return 0;
}

程序运行结果:(在vs2022环境下)

在默认构造函数中,并不会对定义类型进行任何操作,貌似不能证明默认构造函数的存在,但是我们把Date类设置为,下程序时:

cpp 复制代码
class stack {
public:
	stack(int defintCapacity = 4) {
		_arr = (int*)calloc(defintCapacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = defintCapacity;
		_size = 0;
	}

	stack(int* arr, int defintCapacity) {
		if (nullptr == arr) {
			perror("malloc申请空间失败");
			return;
		}
		_arr = arr;
		_capacity = defintCapacity;
		_size = 0;
	}

	void push(int x) {
		//....扩容等
		_arr[_size++] = x;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};

class Date
{
public:

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	//内置类型
	int _year;
	int _month;
	int _day;
	//自定义类型
	stack _st;
};

int main() {
	Date d1;

	return 0;
}

运行结果:(在VS2013 编译器中)

  • 在vs2013中,我们可以清晰看出内置类型不会进行处理的 ,而自定义类型会调用其默认构造函数

但是我们在VS2022中尝试一下

  • 我们发现在vs2022编译环境下,有自定义类型情况中,内置类型会被初始化为0 ,在上例中,我们也发现,在没有自定义类型情况中,内置类型是不会处理的

所以,不才推荐在C++中类中,我们默认内置类型是未被处理的,自定义类型是会调用其默认构造函数的,这样不会出现程序运行错误。

C++11后,对成员变量做了一个补丁,可以在声明成员变量时给定一个缺省值。

这里不才以内置类型为例,

cpp 复制代码
class Date
{
public:
	Date(){}
	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	//内置类型 
	//这里不是初始化,而是声明
	//这里给的是默认缺省值,给编译器生成默认构造函数时使用
	int _year = 1;
	int _month = 1;
	int _day = 1;

};

int main() {
	Date d1;
	d1.Print();
	return 0;
}

程序运行结果:


如果我们调用默认构造函数,那么内置类型的值就是程序猿给定的缺省值。如果我们调用不是默认构造函数,那么使用的就是自定义构造函数的值 ,如下图。

什么情况下可以直接使用默认构造函数:

  • 内置类型成员都有缺省值,且初始化符合要求
  • 全部都是自定义类型成员,且这些类型都定义了默认构造函数。

1.6、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

虽然在语法中,无参的构造函数和全缺省的构造函数形参了函数重载,编译不会有错,但是在对象初始化时,无参调用存在歧义。

cpp 复制代码
class Date
{
public:
	Date() {}

	Date(int year = 2035, int month = 1, int day = 1) {
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;

};

int main() {
	Date d1;
	d1.Print();
	return 0;
}

程序运行结果:

  • 无参构造函数和全缺省的构造函数都是不需要传参调用的,所以在函数调用时,就会报错对重载函数的调用不明确

无参构造函数全缺省构造函数、我们没写编译器默认生成的构造函数,只要不传参就可以调用的,都可以认为是默认构造函数,而默认构造函数只能存在一个!

(未完...)

二、析构函数

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

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

2.1、析构函数名是在类名前加上字符 ~

2.2、无参数无返回值类型

2.3、一个类只能有一个析构函数。

若未显式定义,系统会自动生成 默认的析构函数。注意:析构函数没有形参所以不能重载

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

cpp 复制代码
class Date
{
public:
	Date() {
		cout << "Date()" << endl;
	}

	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	~Date() {
		cout << "~Date()" << endl;
	}

private:
	int _year = 1;
	int _month = 1;
	int _day = 1;

};

int main() {
	Date d1;
	{//创建了代码块用于验证:对象生命周期结束时,C++编译系统系统是否会自动调用析构函数
		Date d2;
		d2.Print();
		printf("\n");

		cout << &d2 << endl;
	}
	printf("\n");

	d1.Print();
	return 0;
}

程序运行结果:


2.5、编译器生成的默认析构函数,对自定类型成员调用它的析构函数

cpp 复制代码
class stack {
public:
	stack(int defintCapacity = 4) {
		_arr = (int*)calloc(defintCapacity, sizeof(int));
		if (nullptr == _arr)
		{
			perror("malloc申请空间失败");
			return;
		}
		_capacity = defintCapacity;
		_size = 0;

		cout << "stack()" << endl;
	}

	stack(int* arr, int defintCapacity) {
		if (nullptr == arr) {
			perror("malloc申请空间失败");
			return;
		}
		_arr = arr;
		_capacity = defintCapacity;
		_size = 0;
	}

	void push(int x) {
		//....扩容等
		_arr[_size++] = x;
	}

	~stack() {
		free(_arr);
		_arr = nullptr;
		cout << "~stack()" << endl;
	}
private:
	int* _arr;
	int _size;
	int _capacity;
};

class Date
{
public:
	Date() {
		cout << "Date()" << endl;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	//内置类型 给定缺省值
	int _year = 1;
	int _month = 1;
	int _day = 1;
	//自定义类型
	stack _st;
};

int main() {
	Date d1;
	d1.Print();
	return 0;
}

程序运行结果:

  • main方法中创建了Date对象d1,而d1中包含4个成员变量,其中_year, _month,_day三个是内置类型 成员,销毁时不需要资源清理,最后系统直接将其内存回收即可。
  • 但是_ststack类的对象,所以在d1销毁时,要将其内部包含的stack类的_st对象销毁,所以要调用stack类的析构函数
  • main函数中不能直接调用stack类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数 ,而Date没有显式提供,则编译器会给Date生成一个默认的析构函数 ,目的是在其内部调用stack类的析构函数 ,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁

2.6、如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数

析构函数的使用:

  1. 一般情况下,有动态申请资源 ,就需要显示写析构函数释放资源
  2. 没有动态申请资源,不需要写析构函数
  3. 需要释放资源的类型都是自定义类型,在该类中就不需要写析构函数。因为默认生成的析构函数遇到自定义类型会自动调用自定义类型的析构函数
  4. 特殊场景特殊使用

以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 如果对大家有用的话,就请多多为我点赞收藏吧~~~💖💖

ps:表情包来自网络,侵删🌹

相关推荐
Pandaconda5 分钟前
【Golang 面试题】每日 3 题(二十三)
开发语言·后端·面试·golang·go·channel
sun0077008 分钟前
C++中,typename
开发语言·c++
C++小厨神23 分钟前
Go语言的数据库交互
开发语言·后端·golang
毒丐43 分钟前
GCC使用说明
linux·c语言·c++
强大的RGG1 小时前
从源码编译Qt5
开发语言·c++·qt
(❁´◡`❁)Jimmy(❁´◡`❁)1 小时前
3103: 【基础】既生瑜,何生亮!
c++
s.feng1 小时前
00_basic_gemm
c++
Channing Lewis1 小时前
python实现,outlook每接收一封邮件运行检查逻辑,然后发送一封邮件给指定邮箱
开发语言·python·outlook
编程小筑1 小时前
TypeScript语言的软件工程
开发语言·后端·golang
꧁坚持很酷꧂2 小时前
Qt天气预报系统鼠标拖动窗口
开发语言·qt·计算机外设