构造函数和析构函数

佚名 / 2023-05-03 / 原文

1. 概念引入

在说明构造函数和析构函数的概念之前, 首先看一个例子

下面这段代码是栈经典的应用场景括号匹配

如图, 栈首先必须初始化,然后在每一个return false之前都需要销毁栈, 否则就会有内存泄漏

这样很繁琐, 而且有些时候初始化和销毁很容易忘记写, 所以在C++中添加了默认的成员函数,构造函数和析构函数来自动进行初始化和清理工作

 本章详细说明构造函数和析构函数 

2. 构造函数

概念与定义

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

定义构造函数:

函数名与类名相同, 无返回值,如下例

#include <iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	// 构造函数
	Stack()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 4);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = 4;
		_size = 0;
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack st1;
	return 0;
}

知道了构造函数的概念与定义, 下面解析构造函数的特性

特性解析

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

#include <iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	// 构造函数
	Stack()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 4);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = 4;
		_size = 0;
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack st1;
	return 0;
}

如图, 实例化对象st1时, 自动调用默认构造函数, 完成初始化

 

2. 构造函数支持重载

#include <iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	Stack(DataType* a, int n)
	{
		cout << "Stack(DataType* a, int n)" << endl;
		_array = (DataType*)malloc(sizeof(DataType) * n);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		memcpy(_array, a, sizeof(DataType) * n);

		_capacity = n;
		_size = n;
	}

	Stack()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 4);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = 4;
		_size = 0;
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack st1;
	return 0;
}

如图, 没有报错说明构造函数支持重载, 其原因是可能会需要多种初始化的情况

 

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

#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.Print();
}

如果类中没有显式定义构造函数,对象在实例化时会去调用默认的构造函数进行初始化, 那么为什么这里仍然是随机值 ? 

这是因为C++标准规定内置类型(char, int, 指针, 引用)不做处理,  但是自定义类型(class, struct)会去调用它的默认构造函数处理初始化, 如下例

#include <iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	// 构造函数
	Stack()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 4);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = 4;
		_size = 0;
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};

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;    

	// 自定义类型会去调用它的默认构造函数
	Stack _st;
};
int main()
{
	Date d1;
	//d1.Print();
}

如图, 内置成员变量没有进行处理, 但是自定义类型成员_st调用了它的默认构造函数

 

在C++11中, 为了处理内置类型成员不做初始化的缺陷,打了补丁, 内置类型成员变量在类中声明时可以给默认的缺省值

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() // 3. void Print(Date* this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}
private:
	// 不是初始化, 而是默认的缺省值
	int _year = 1;     
	int _month = 1;    
	int _day = 1;      
};
int main()
{
	Date d1, d2;
	d1.Print();
}

 

5. 编译器默认生成的构造函数,无参的构造函数和全缺省的构造函数都称为默认构造函数,但是默认构造函数只能有一个, 如下例

#include <iostream>
using namespace std;

class Date
{
public:
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	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() // 3. void Print(Date* this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}
private:
	// 不是初始化, 而是缺省值(默认值)
	int _year = 1;
	int _month = 1;
	int _day = 1;
};
int main()
{
	Date d1; 
	d1.Print();
}

从语法的角度看, 是没有错误的, 因为构造函数支持函数重载

但是实例化d1时, 会去调用默认构造函数, 而无参构造和全缺省构造函数都是默认构造函数, 所以调用存在歧义

下面说明构造函数的调用

3. 构造函数的调用

调用无参构造函数:  实例化对象时就会去调用无参的默认构造

调用有参数的构造:  调用有参构造函数需要在实例化对象后加上参数列表

如下例:

#include <iostream>
using namespace std;

class Date
{
public:
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() // 3. void Print(Date* this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}
private:
	// 不是初始化, 而是缺省值(默认值)
	int _year = 1;
	int _month = 1;
	int _day = 1;
};
int main()
{
	// 调用无参构造函数
	Date d1; 
	// 调用有参构造
	Date d2(2222, 2, 2);
	//d1.Print();

}

为什么无参构造在调用时不需要加(), 如 Date d1(), 而有参构造在调用时需要加参数列表?

这是因为编译器无法区分Date d1(), 是函数声明还是调用构造, 所以调用无参默认构造只能这样写Date d1

 

4. 析构函数

概念与定义

析构函数是一个默认的成员函数, 完成对象中的资源清理

需要注意的是, 析构函数不是完成对对象本身的销毁而是对象中的资源, 局部对象本身的销毁工作是由编译器完成的

 

析构函数的定义:

析构函数名是在类名前加上字符 ~ , 无参数无返回值, 如下例

#include <iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	// 构造函数
	Stack()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 4);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = 4;
		_size = 0;
	}

	// 析构函数
	~Stack()
	{
		free(_array);
		_capacity = 0;
		_size = 0;
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack st1;
	return 0;
}

下面解析析构函数的特性

特性解析

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

#include <iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	// 构造函数
	Stack()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 4);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = 4;
		_size = 0;
	}

	// 析构函数
	~Stack()
	{
		free(_array);
		_capacity = 0;
		_size = 0;
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};
int main()
{
	Stack st1;
	return 0;
}

如图, 对象st1生命周期结束, 自动调用析构

 

2. 若未显式定义,系统会自动生成默认的析构函数

3. 编译器自动生成的默认析构函数对内置类型成员不会处理, 但是对自定义类型成员会去调用它的析构函数

#include <iostream>
using namespace std;

typedef int DataType;
class Stack
{
public:
	// 构造函数
	Stack()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 4);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}

		_capacity = 4;
		_size = 0;
	}

	// 析构函数
	~Stack()
	{
		free(_array);
		_capacity = 0;
		_size = 0;
	}

private:
	DataType* _array;
	int _capacity;
	int _size;
};

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() // 3. void Print(Date* this)
	{
		cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
	}
private:
	int _year = 1;
	int _month = 1;
	int _day = 1;

	Stack st1;
};

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

如图, 编译器自动生成的默认析构函数对内置类型成员(year,month, day)不会处理, 但是对自定义类型成员st1会去调用它的析构函数