C++:构造函数和析构函数

一、构造函数

1.1概念

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

不使用构造函数

cpp 复制代码
#include<iostream>
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.Init(2022, 7, 5);//每次都要调用Init函数
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();
	return 0;
}

使用构造函数

cpp 复制代码
#include<iostream>
using namespace std;
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, 7, 5);//不需要调用函数直接可以初始化
	d1.Print();
	Date d2(2022,7,6);
	d2.Print();
	return 0;
}

上面两幅图在main函数中,图1未使用构造函数,每次定义时都要调用Init函数初始化。

图2 使用了构造函数,直接定义就可以,编译器会自动调用构造函数初始化,方便了用户,也防止了用户忘记调用Init函数的情况。

1.2特性

1.构造函数是特殊的成员函数,虽然名字叫构造,但作用其实是初始化对象。

2.构造函数的函数名与类的名字相同。

3.在对象实例化时会自动调用对应的构造函数。

4.构造函数没有返回值。

5.构造函数可以重载。

cpp 复制代码
class Date
{
public:
	// 1.无参构造函数
	Date()
	{}
	// 2.带参构造函数
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
void TestDate()
{
	Date d1; // 调用无参构造函数
	Date d2(2015, 1, 1); // 调用带参的构造函数
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	// warning C4930: "Date d3(void)": 未调用原型函数(是否是有意用变量定义的?)
	Date d3();
}

所以Date d3()是错误的用法。

6.如果用户没有定义构造函数,编译器会自动生成一个无参的默认构造函数。如果用户定义了构造函数就不会生成构造函数。

cpp 复制代码
class Date
{
public:

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1; // 自动生成无参的构造函数
	return 0;
}

7.如果编译器自动生成构造函数,对象的初始化由两种情况分为内置类型(基本类型)和自定义类型。对内置类型不做处理,初始化成随机值。对自定义类型会调用其的默认成员函数

cpp 复制代码
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的默认成员函数
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

注:为了解决内置类型初始化成随机值的情况发生,C++11中规定内置类型成员变量在类的声明中可以给默认值。

cpp 复制代码
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 = 1970;//默认值
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d;
	return 0;
}

8.无参的构造函数和全缺省的构造函数都称为默认构造函数,且默认构造函数只能存在一个。

cpp 复制代码
class Date
{
public:
	Date()//无参的构造函数
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, int month = 1, int day = 1)//全缺省的函数构造
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{
	Date d1;//编译报错
}

二、析构函数

2.1概念

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

2.2特性

1.析构函数是特殊的成员函数,析构函数是在类名前加上字符~。

2.析构函数无返回值和类型。

3.一个类只能有一个析构函数,若未定义,编译器会默认生成析构函数。

4.析构函数不能重载。

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

cpp 复制代码
typedef int DataType;
class Stack
{
public:
	Stack(DataType capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_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;
};
int main() 
{
	Stack s;//自动调用Stack构造
	s.Push(1);
	s.Push(2);//自动调用~Stack销毁
	return 0;
}

6.析构函数对于内置类型成员不做资源清理,系统最后会将其内存回收。对于自定义类型会调用

其自身的析构函数。

cpp 复制代码
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;
}
// 程序运行结束后输出:~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类生成的
// 默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的
// 析构函数

7.如果类中没有资源申请(如Date),则可以不写析构函数;如果有资源申请(如Stack),则必须写析构函数。

相关推荐
行路见知1 分钟前
1.4 Go 数组
开发语言
2501_903238658 分钟前
Java 9模块开发:Eclipse实战指南
java·开发语言·eclipse·个人开发
ahardstone9 分钟前
【CS61A 2024秋】Python入门课,全过程记录P5(Week8 Inheritance开始,更新于2025/2/2)
开发语言·python
阿豪学编程24 分钟前
c++ string类 +底层模拟实现
开发语言·c++
-VE-1 小时前
myshell
linux·c++
沈韶珺1 小时前
Visual Basic语言的云计算
开发语言·后端·golang
沈韶珺2 小时前
Perl语言的函数实现
开发语言·后端·golang
嘻嘻哈哈的zl2 小时前
初级数据结构:栈和队列
c语言·开发语言·数据结构
wjs20242 小时前
MySQL 插入数据指南
开发语言
美味小鱼2 小时前
Rust 所有权特性详解
开发语言·后端·rust