【C++ 6.析构函数和拷贝构造函数】

6 篇文章 0 订阅
5 篇文章 0 订阅

1. 析构函数

a. 概念

i. 析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作. 构造函数是在对象销毁时,有编译器会自动调用,,,目的:将对象中的资源拿走

b. 特性

1.析构函数是在类名称前面加上字符~。

2.无参数和返回值。

~stack()
	{
	}

3.一个类有且只有一个析构函数。

若未显示定义,系统会自动生默认的显示的析构函数。析构函数不能有参数,因此析构函数是不能发生重载的。

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

(反汇编查看),我们可以通过反汇编看到系统自动调用了析构哈函数。

#include <malloc.h>
#include <assert.h>
typedef int DataType;
class stack
{
public:
	//析构函数
	//析构函数不能有参数
	//~stack(int a)编译报错
	//~stack(void) 编译通过--一般不会使用
	~stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
	//构造函数
	stack()
	{
		//StackDestroy(&s);-->在C语言中的做法
		_array = (DataType*)malloc(sizeof(DataType) * 10);
		if (NULL == _array)
		{
			assert(0);
			return;
		}
		_capacity = 10;
		_size = 0;
	}
	void Push(const DataType& data)
	{
		_array[_size] = data;
		_size++;
	}
	void Pop()
	{
		if (Empty())
			return;
		--_size;
	}
	DataType Top()
	{
		return _array[_size-1];
	}
	size_t Size()
	{
		return _size;
	}
	DataType Empty()
	{
		return 0 == _size;
	}
private:
	DataType* _array;
	size_t _capacity;
	size_t _size;

};
void Test()
{
	stack s;
	s.Push(5);
	s.Push(4);
	s.Push(3);
	s.Push(2);
	cout << s.Size() << endl;
	cout << s.Top() << endl;

	s.Pop();
	cout << s.Size() << endl;
	cout << s.Top() << endl;


	//在C语言中需要使用StackDestroy(&s)销毁栈
}
//C++中,对象生命周期结束时,C++编译系统自动调用析构函数
int main()
{
	Test();
	return 0;
}
4
2
3
3

//反汇编调用了析构函数
//00007FF796911D49  lea         rcx, [s]
//00007FF796911D4D  call        stack::~stack(07FF796911375h)
//00007FF796911D52  lea         rcx, [rbp - 20h]
//00007FF796911D56  lea         rdx, [__xt_z + 160h(07FF79691AC00h)]
//00007FF796911D5D  call        _RTC_CheckStackVars(07FF796911348h)
//00007FF796911D62  mov         rcx, qword ptr[rbp + 168h]

5.编译器生成的默认析构函数,对会自定义类型成员调用他的析构函数

1.编译器是否生成默认的构造,析构函数
这种没有生成构造和析构函数,运行速度就快了很多。
默认的成员函数到底会不会生成,语法是一定会生成,,实际具体的编译器,比如vs2022是不一定生成的
有些情况下会生成,有些编译器判断自己需要时才会生成

class Date
{
	//语法:构造函数没有显式定义,则编译器会自动生成一份无参的默认构造函数
	
	//析构函数没有显式定义,则编译器会自动生成一份析构函数
public:
	void Print()
	{
		cout << _year << "-" << _month << "_" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	return 0;
}
	//Date d1;
	//return 0;
	//00007FF747381A4C xor eax, eax

2.那么编译器为什么给日期生成构造和析构函数?
因为日期类包含了自定义对象 Time _t,日期类型对象内部包含了时间类型对象。当给日期类生成一份构造函数时,会调用时间类的构造函数,当出了函数作用域时,会将时间类的析构函数放在日期类中,及调用日期类析构函数。通过反汇编看到。

给内置类型发送随机值,都是可行的;
但是不能给对象发送随机值。

//时间
class Time
{public:
	Time()
	{

	}
	~Time()
	{

	}
private:
	int _hour;
	int _minute;
	int _second;
};
//日期
class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "_" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d1;
	return 0;
}
	//Date d1;
	//00007FF7BC4719FD  lea         rcx, [d1]
	//00007FF7BC471A01  call        Date::Date(07FF7BC47146Ah)
	//return 0;
	//00007FF7BC471A06  mov         dword ptr[rbp + 0F4h], 0
	//00007FF7BC471A10  lea         rcx, [d1]
	//00007FF7BC471A14  call        Date::~Date(07FF7BC471460h)
	//00007FF7BC471A19  mov         eax, dword ptr[rbp + 0F4h]

由以上可知:
有些类的析构函数实现出来没有任何意义——单独的Date类,有些类的析构函数时必须给出的,否则程序中会存在内存泄漏。类中如果不涉及到资源管理时,比如Date类,则析构函数不用写,一旦涉及到资源管理师,则析构函数一定要给出

2. 拷贝构造函数

a. 概念

构造拷贝函数:只有单个形参,该形参是对本类类型对象的引用(一般是常用const修饰),再用已存在的类类型对象,创建新对象是由编译器自动调用。

//日期
class Date
{
public:
	Date(int year=2000,int month=2,int day=2)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)//一般情况下加const不需要要改变d1 ,使用引用传参
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2008,8,8);
	d1.Print();

	Date d2(d1);
	d2.Print();
	return 0;
}
2008-8-8
2008-8-8

需求:创建的d2与d1的内容一样,既然创建一个新对象就需要调用构造函数。
并且 拷贝构造函数,参数只有一个,是构造函数的一个重载形式

b. 特征

1.拷贝构造函数是构造函数的一个重载。

2.拷贝构造函数的参数只有一个且必须使用引用传参

如果使用传值方式,会引发无穷递归调用。

3.若未显示定义,系统会生成默认的拷贝构造函数

默认拷贝的拷贝构造函数对象按内存存储字节序完成拷贝,这种拷贝称为浅拷贝,或者值拷贝。
1.编译器默认拷贝构造
浅拷贝后果;多个对象共用同一份资源,当这些对象销毁时,资源会被释放多次而引起程序崩溃.

4.编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了

是否直接说明了:直接使用编译器生成的默认拷贝构造函数就可以了呢?
答案;对于日期类这种没有涉及到资源管理的类是可以的
一旦涉及到资源管理,拷贝构造函数就一定要实现。以下,,在调用析构函数时报错

3.详解编译器会生成一份默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储字节序完成拷贝(将d1原封不动的拷贝到d2中,即内部指针指向同一对象),及调用析构函数,通过d2中的指针_array将堆上的空间释放一次,但是d1中的指针_array将堆上的空间再次释放一次。程序报错(d1中的_array成为野指针了。)

#include <stdlib.h>
#include <assert.h>
typedef int DataType;
class stack
{
public:

	~stack()
	{
		if (_array)
		{
			free(_array);
			_array =nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
	stack()
	{
		_array = (DataType*)malloc(sizeof(DataType) * 10);
		if (NULL == _array)
		{
			assert(0);
			return;
		}
		_capacity = 10;
		_size = 0;
	}
	void Push(const DataType& data)
	{
		_array[_size] = data;
		_size++;
	}
	void Pop()
	{
		if (Empty())
			return;
		--_size;
	}
	DataType Top()
	{
		return _array[_size - 1];
	}
	size_t Size()
	{
		return _size;
	}
	DataType Empty()
	{
		return 0 == _size;
	}
private:
	DataType* _array;
	size_t _capacity;
	size_t _size;

};

void Test()
{
	stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	s1.Push(5);

	stack s2(s1);
	//刚开始创建了s1,有三个成员变量。在Test的栈帧中创建通过构造函数在堆上创建内存空间
	//s2,内部也有三个成员变量。调用拷贝构造函数完成,但是Stack的拷贝构造函数并没有实现
	//编译器会生成一份默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储字节序完成拷贝(将d1原封不动的拷贝到d2中,即内部指针指向同一对象)
	//及调用析构函数,通过d2中的指针_array将堆上的空间释放一次,但是d1中的指针_array将堆上的空间再次释放一次。程序报错(d1中的_array成为野指针了。)

	cout << s2.Size() << endl;
	cout << s2.Top() << endl;

}
int main()
{
	Test();
	return 0;
}

5.作为自定义类型作为参数或者返回值时,能传递引用尽量传递引用

1.用一个对象直接构造一个新对象。
2.以类类型方式传参–以值传参。
3.以类类型方式作为函数的返回值–以值的方式返回的
4. 创建一个匿名对象: 及没有名字的对象。编译器在返回时,编译器不会再通过拷贝构造函数创建一个临时对象返回,而是将匿名对象直接返回,编译器会优化。

//日期
class Date
{
public:
	Date(int year = 2000, int month = 2, int day = 2)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
//2.以类类型方式传参--以值传参
void Test1(Date d)
{}
//3.以类类型方式作为函数的返回值--以值的方式返回的
Date Test2()
{
	Date d(2022, 2, 2);
	return d;

}
Date Test3()
{
	//创建一个匿名对象:及没有名字的对象
	//编译器在 返回时,编译器不会再通过拷贝构造函数创建一个临时对象返回
	//而是将匿名对象直接返回,编译器会优化
	return Date(2002,2,2);
}
int main()
{
	//1.用一个对象直接构造一个新对象
	Date d1(2000, 2, 2);
	Date d2(d1);

	Test1(d1);
	d2 = Test2();
	d2 = Test3();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值