类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题

索引

  • 六大默认成员函数
      • 构造函数
      • 析构函数
      • 拷贝构造
        • 一,.对象以值传递的方式传入函数参数、
        • 二,对象以值传递的方式从函数返回

六大默认成员函数

如果一个类中什么成员都没有,称其为空类,空类中并不是什么都没有,任何一个类在我们不写的情况下,都会自动生成六个默认成员函数。
类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题_第1张图片

内置类型:int double long int*等之前在C语言学习过的都是内置类型
自定义类型:顾名思义,就是一些class、struct定义的类型

构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次
主要任务:并不是开空间创建对象,而是初始化对象。
特征

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
    以日期为例:
    类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题_第2张图片
    注意当我们没有写构造函数时,编译器会生成默认的构造函数,该默认构造函数对内置类型不做处理,对自定义类型还是调用它本身的默认构造函数
    eg
    类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题_第3张图片

析构函数

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

特征

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
    以自己构造的栈为例
class Stack
{
public:
	//构造函数
	Stack()
	{
		int* a = (int*)malloc(sizeof(40));
		_size = 2;
		_capacity = 4;
	}
	void Print()
	{
		for (int i = 0; i < _size; i++)
			cout << _a[i] << " ";
			cout << endl;
	}
	//析构函数
	~Stack()
	{
		free(_a);//释放堆上的空间,将指针置为空
		_a = NULL;
		_size = 0;
		_capacity = 0;
	}




private:
	int* _a;
	int _size;
	int _capacity;


};
int main()
{
	Stack A;
	return 0;
}

如果我们没有在类中写析构函数,那么编译器生成默认的析构函数,该析构函数对内置类型不做处理,对自定义类型调用其自身的析构函数。

拷贝构造

对于普通类型的对象来说,他们之间的复制非常简单,例如int b = 3;int a;a = b;
而自定义类型中成员变量比较多,这时候如果要进行复制的话必须调用拷贝构造。
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
3. 一般常用const修饰

为什么会引发无穷递归?
我们以日期类为例:

class Date
{
public:
	Date()//无参构造函数
	{
		_year = 1;
		_month = 1;
		_day = 1;

	}
	Date(int year, int month, int day)//带参的构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}

	~Date()//析构函数
	{
		_year = 01;
		_month = 0;
		_day = 0;
	}
private:
	int _year;
	int _month;
	int _day;
};

先看错误的拷贝构造定义方式:
类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题_第4张图片
但是如果我们再拷贝构造的定义中加上引用的话,Date(const Date& d)此时形参d只是实参的一种别名,是不会调用拷贝构造的,所以拷贝构造的定义必须加上d
正确调用:

类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题_第5张图片

自定义类型一般有三种情况需要调用拷贝构造

一,.对象以值传递的方式传入函数参数、

class Date
{
public:
	
	Date(int year, int month, int day)//带参的构造函数
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "creat" << endl;
	}
	void Print()
			{
				cout << _year << "-" << _month << "-" << _day << endl;
			}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "copy" << endl;
	}
	~Date()//析构函数
	{
		_year = 01;
		_month = 0;
		_day = 0;
		cout << "destory:" <<this<< endl;
		//cout << (*this) << endl;
		
	}
private:
	int _year;
	int _month;
	int _day;
};
void Fun(Date C)
{
	Date d(C);

}



int main()
{
	Date B(10, 20, 20);//调用带参的构造函数
	Fun(B);
	return 0;
}

调用Fun函数时有如下几个重要步骤
1.首先B对象传入形参时,会先产生一个临时变量,暂且称为X
2.然后调用拷贝构造将X的值传给C,相当于是Date C(X)
3.接着在用C的值拷贝给d
4.调用完函数Fun后来开始析构
5,析构遵循的是栈区的规则,先进后出
6,先析构的是d,接着是c,然后再把临时变量X析构,最后将B析构

注意由于构造一个临时变量后马上利用临时变量进行拷贝构造编译器会直接优化称为直接构造,所以上述中线构造临时变量X,后利用临时变量X进行拷贝构造会直接被编译器优化成为直接构造C,所以在编译器编译上述代码时候会发现,拷贝构造只调用了两次,分别是Date C(B)实参传递给形参,还有Date d©,然后进行析构的时候只有三次析构,分别是析构d,C,B;
类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题_第6张图片

二,对象以值传递的方式从函数返回

class Date
{
public:
	
	Date(int year, int month, int day)//带参的构造函数
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "creat" << endl;
	}
	void Print()
			{
				cout << _year << "-" << _month << "-" << _day << endl;
			}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "copy" << endl;
	}
	~Date()//析构函数
	{
		_year = 01;
		_month = 0;
		_day = 0;
		cout << "destory:" <<this<< endl;
		//cout << (*this) << endl;
		
	}
private:
	int _year;
	int _month;
	int _day;
};
Date Fun(Date C)
{
	Date d(C);
     return d;
}

将上述全局函数的返回值改变一下,前面全部都不变,拷贝构造函数的调用次数不变,刚开始产生的临时变量也不变,区别在于执行到return时,先回产生一个临时变量暂且称为XXXX,然后调用拷贝构造将d的值传给XXXX相当于是Date XXXX(d),在函数执行到最后先析构局部变量,再析构临时变量XXXX。
如果仅仅是这样调用

int main()
{
	Date B(10, 20, 20);//调用带参的构造函数
	Fun(B);
	return 0;
}

那么上述临时变量XXXX的拷贝构造可以被显示出来,也就是进行了四次拷贝构造,分别是
Date C(B), Date d©,Date XXXX(d)然后析构的时候进行了四次析构
类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题_第7张图片

但是如果是这样调用的话

int main()
{
	Date B(10, 20, 20);//调用带参的构造函数

	Date G = Fun(B);
	return 0;
}

此时拷贝构造的此时应该是五次,相较于上面的函数调用,此时应该多了最后一次将Date G(XXXX)也就是将临时变量拷贝给刚初始化定义的G。但是用编译器编译上述代码发现,编译器只给我们提供了三次拷贝构造,Date C(B), Date d©,这两次拷贝构造不变,但是最后两次拷贝构造也就是Date XXXX(d),Date G(XXXX)直接被优化成了Date G(d)
因为在编译器看来,构造了一个临时变量后直接拷贝构造给一个未初始化的值,会被优化成直接拷贝构造给那个未初始化的值。
类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题_第8张图片

但是如果将代码改成下面

class Date
{
public:
	
	Date(int year = 1, int month = 1, int day = 1)//带参的构造函数
	{
		_year = year;
		_month = month;
		_day = day;
		cout << "creat" << endl;
	}
	void Print()
			{
				cout << _year << "-" << _month << "-" << _day << endl;
			}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "copy" <<this<< endl;
	}
	~Date()//析构函数
	{
		_year = 01;
		_month = 0;
		_day = 0;
		cout << "destory:" <<this<< endl;
		//cout << (*this) << endl;
		
	}
private:
	int _year;
	int _month;
	int _day;
};
Date Fun(Date C)
{
	Date d(C);
	return d;

}



int main()
{
	Date B(10, 20, 20);//调用带参的构造函数

	Date G;
	G = Fun(B);
	return 0;
}

可以发现此时此时的编译器此时显示拷贝构造的此时不变还是三次,但此时的三次经过编译器调试可以发现分别是Date C(B), Date d©,Date XXXX(d)。G = Fun(B);这一句代码并没有拷贝构造,这一句代码涉及到后面的赋值运算符重载
所以可见如果不是直接用临时变量拷贝构造给未初始化的值,是不会被编译器所优化的。
类和对象(二)类成员构造函数+拷贝构造详解涉及临时对象编译器优化问题_第9张图片

三,.对象需要通过另一个对象进行初始化

你可能感兴趣的:(C++,C++语法分析,c++,开发语言)