c++ 构造、拷贝构造、析构

概要

本篇文章主要内容有 构造函数、析构函数、拷贝构造函数。

构造函数

类实例化对象时,会调用构造函数。构造函数的目的是声明对象。即使是一个空类也会有编译器自动生成的构造函数。

    #include <iostream>
    using namespace std;
    class Date
    {
    private:
    	int _year;
    	int _month;
    	int _day;
    public:
    	Date(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	Date()
    	{}
    	void SetDate(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    };
    int main()
    {
    	Date d1, d2, d3;
    	d1.SetDate(2018, 11,10);
    	d2.SetDate(2018, 11, 11);
    	d3.SetDate(2018, 11, 12);
		Date d4(2018,11,13);
    }

如果不在类中定义析构函数,那么编译器会创造一个类似于第二个Date()的构造函数。第一个构造函数的功能类似于 SetDate() 所以构造函数的功能就是设置初始值。但是构造函数可以在定义函数的时候初始化。

构造函数的特点

1.构造函数的名称与类名一样。

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

3.对象的构造函数在对象生成时自动调用,只调用一次,设置初始值。

4.构造函数可以被重载。

5.放在类里的函数编译器可能会将其作为内联函数处理

6.如果类中没有构造函数,编译器会默认创造无参的构造函数。换句话说,如果显式定义定义了构造函数,默认的构造函数将不会合成。

析构函数

析构函数的作用是在对象被销毁的是时候,调用析构函数清理对象占用的空间,整理内存。它的定义方式 ~Date() ,~类名。

	~Date()
	{
		cout << "~Date :: Called !" << endl;
	}

析构函数会在对象创建的时候,如果没有在类中显式的定义,那么系统会自动调用。虽然以上函数似乎没什么用。但是如果考虑堆上开辟空间。析构函数就非常有用了。

插入图片1

析构函数的特点

1.对象销毁的时候,由编译器自动调用

2.无参数返回值

3.一个类只能有一个析构函数

4.析构函数的调用时刻,是对象生命周期结束时。

拷贝构造函数

类似于内置类型,如果创建对象的时候需要使用别的对象来初始化,这个时候编译器会调用一个拷贝构造函数将一个已有的对象去初始化新对象。

    #include <iostream>
    using namespace std;
    class Date
    {
    private:
    	int _year;
    	int _month;
    	int _day;
    public:
    	Date(int year, int month, int day)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	~Date()
    	{
    		cout << "~Date :: Called !" << endl;
    	}
    	Date(Date & d1)
    	{
    		cout << "Date :: Date (Date &d1) is called !" << this << endl;
    		_year = d1._year;
    		_month = d1._month;
    		_day = d1._day;
    	}
    };
    int main()
    {
    	int a = 10;
    	int b(a);
    	Date d1(2018, 11, 16);
    	Date d2(d1);
    	system("pause");
    	return 0;
    }

以上 在 main()函数中 创建int b的时候使用了 a 中的变量进行初始化。同样的,在创建对象 Date d2 的时候使用了 d1 的成员变量进行初始化。

拷贝构造函数的特点

1.拷贝构造函数是构造函数的重载形式

2.拷贝构造函数必须是引用的形式 Date(Date &d1)。

3.如果没有吸纳是定义系统,系统会定义默认的构造函数

问题:为什么拷贝构造函数必须是引用的形式?

    	Date(Date & d1)
	  //Date(Date d1)
    	{
    		cout << "Date :: Date (Date &d1) is called !" << this << endl;
    		_year = d1._year;
    		_month = d1._month;
    		_day = d1._day;
    	}

如果是注释中的形式会造成死递归。如果在main()函数使用注释中的形式,
Date(Date d1) 那么Date d1 会再次创建一个拷贝构造函数,将主函数中的 d1 拷贝到 形参 d1中,而形参 d1 继续调用拷贝构造函数 再创建一个形参的形参d1。如此无限递归。

浅拷贝 和 深拷贝

如果编译器会默认创建拷贝构造函数,那么为什么要给用户一个接口来显式定义呢?

编译器设定的默认拷贝构造函数只是基于内存拷贝的浅拷贝。

    #include <iostream>
    using namespace std;
    
    class SeqList
    {
    private:
    	int* _PDate;
    	int  _Capacity;
    	int  _size;
    public:
    	SeqList( int size)
    	{
    		_PDate = (int *)malloc(size * sizeof(int));
    		_size = size;
    		_Capacity = _Capacity;
    	}
    	~SeqList()
    	{
    		_size = 0;
    		_Capacity = _Capacity;
    		free(_PDate);
    	}
    };
    int main()
    {
    	SeqList List1(10);
    	SeqList List2(List1);
    	system("pause");
    	return 0;
    }

以上代码在运行时会报错和中断。代码运行过程如下图:

插入图片2

此代码在执行时,首先 List2 的生命周期结束了,调用析构函数释放了 List2 的堆上空间。 然后,List1 的生命周期结束了,调用了析构函数发现
0x003498c8d地址已经被释放过了。再次释放时已经找不到 free 区间的首位标志了。所以出错了。

插入图片3

这个时候如果显示定义拷贝构造函数。那么就能够很好的处理好堆上的关系。

	SeqList(SeqList &S1)
	{
		_size = S1._size;
		_PDate = (int *)malloc(S1._size * sizeof(int));
		_Capacity = S1._Capacity;
	}

插入图片4

猜你喜欢

转载自blog.csdn.net/H_Strong/article/details/83928656