C++中构造函数,拷贝构造函数,析构函数

C++中默认构造函数就是没有形参的构造函数。准确的说法,按照《C++ Primer》中定义:只要定义一个对象时没有提供初始化式,就是用默认构造函数。为所有 的形参提供默认实参的构造函数也定义了默认构造函数。

合成的默认构造函数,即编译器自动生成的默认构造函数。《C++ Primer》中的说明:一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数。这条规则的根据是,如果一个类再某种情况下需要控制对象初始化,则该类很可能在所有情况下都需要控制。只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。

需要注意的是自定义的构造函数包括拷贝构造函数。就是说在下面这种情况下,编译器也不会自动生成默认构造函数,代码会出错。把拷贝构造函数删除掉后就没有问题了。

编译器为类自动生成的函数包括默认构造函数,拷贝构造函数,析构函数,赋值函数。但是如果自己定义了拷贝构造函数,那么默认构造函数编译器就不会自动生成了。但是自己定义了默认构造函数时其他函数都会自动生成。

注意拷贝构造函数的参数类型为const Base &类型。&是必须的很容易理解,需要传递引用才行。至于const参数在g++中不加在编译时会报错,在vs中能通过编译。不过建议还是加上,因为传递的是引用,根据原对象构造新的对象时当然不希望将原对象改变。

#include <iostream>

using namespace std;

class Base{
public:
	//构造函数
	//Base(){
	//	cout<<"default constructor invoke!"<<endl;
	//}
	//Base(int d){
	//	this->data=d;
	//	cout<<"constructor invoke!"<<endl;
	//}
	//拷贝构造函数
	Base(const Base &b){
		cout<<"copy constructor invoke!"<<endl;
		this->data=b.data;
	}
	//赋值操作符
	Base & operator=(const Base &b){
		cout<<"operator constructor invoke!"<<endl;
		this->data=b.data;
	}
	//7构函数
	~Base(){
		cout<<"deconstructor invoke!"<<endl;
	}
private:
	int data;

};

int main(){

	Base b;
	return 0;
}


首先使用下面的代码测试,默认构造函数,构造函数,拷贝构造函数,赋值操作符的调用:

#include <iostream>

using namespace std;

class Base{
public:
	//构造函数
	Base(){
		cout<<"default constructor invoke!"<<endl;
	}
	Base(int d){
		this->data=d;
		cout<<"constructor invoke!"<<endl;
	}
	//拷贝构造函数
	Base(const Base &b){
		cout<<"copy constructor invoke!"<<endl;
		this->data=b.data;
	}
	//赋值操作符
	void  operator=(const Base &b){
		cout<<"operator constructor invoke!"<<endl;
		this->data=b.data;
	}
	//析构函数
	~Base(){
		cout<<"deconstructor invoke!"<<endl;
	}

private:
	int data;

};

int main(){

	Base b(1);//构造函数调用

	Base b2=b;//拷贝构造函数调用

	Base b3;//默认构造函数调用
	b3=b;//赋值操作符调用

	return 0;
}
执行结果如下:

在g++和vs中执行的结果都是一样的。

然后添加一个函数调用来测试:

#include <iostream>

using namespace std;

class Base{
	public:
		//构造函数
		Base(){
			cout<<"default constructor invoke!"<<endl;
		}
		Base(int d){
			this->data=d;
			cout<<"constructor invoke!"<<endl;
		}
		//拷贝构造函数
		Base(const Base &b){
			cout<<"copy constructor invoke!"<<endl;
			this->data=b.data;
		}
		//赋值操作符
		void  operator=(const Base &b){
			cout<<"operator constructor invoke!"<<endl;
			this->data=b.data;
		}
		//7构函数
		~Base(){
			cout<<"deconstructor invoke!"<<endl;
		}

		Base play(Base b){
			return b;
		}
	private:
		int data;

};

int main(){

	Base b(1);//构造函数调用

	Base b2=b;//拷贝构造函数调用

	Base b3;//默认构造函数调用
	b3=b;//赋值操作符调用

	b.play(2);//构造函数调用,拷贝构造函数调用(由于函数返回引起的)

	return 0;
}
结果如下:函数b.play(2)执行时,首先会使用构造函数(即接受一个int型变量的构造函数)进行隐式的类型转换,即调用一次构造函数。注意,如果play的形参是B&类型的话,就不可以这样做。否则会出错。


然后使用直接调用时:

#include <iostream>

using namespace std;

class Base{
	public:
		//构造函数
		Base(){
			cout<<"default constructor invoke!"<<endl;
		}
		Base(int d){
			this->data=d;
			cout<<"constructor invoke!"<<endl;
		}
		//拷贝构造函数
		Base(const Base &b){
			cout<<"copy constructor invoke!"<<endl;
			this->data=b.data;
		}
		//赋值操作符
		void  operator=(const Base &b){
			cout<<"operator constructor invoke!"<<endl;
			this->data=b.data;
		}
		//7构函数
		~Base(){
			cout<<"deconstructor invoke!"<<endl;
		}

		Base play(Base  b){
			return b;
		}
	private:
		int data;

};

int main(){

	Base b(1);//构造函数调用

	Base b2=b;//拷贝构造函数调用

	Base b3;//默认构造函数调用
	b3=b;//赋值操作符调用

	b.play(2);//构造函数调用,拷贝构造函数调用(由于函数返回引起的)
	b.play(b);//<span style="font-size: 11.8181819915771px; font-family: Arial, Helvetica, sans-serif;">第一次拷贝构造函数调用是构造形参,第二次拷贝构造函数调用是函数返回值</span>

	return 0;
}
输出结构如下:


最开始也不明白为什么是两次拷贝构造函数调用。那么打印出每个对象的地址吧。

vs中的结果:


g++中的结果,可以发现g++中的结果参数进栈的顺序还是有规律的,但是vs好像就没有规律了。

这样还是看不明白,但是和下面代码的这种情况进行比较就比较容易理解了:

#include <iostream>

using namespace std;

class Base{
public:
	//构造函数
	Base(){
		cout<<"default constructor invoke!"<<(int *)this<<endl;
	}
	Base(int d){
		this->data=d;
		cout<<"constructor invoke!"<<(int *)this<<endl;
	}
	//拷贝构造函数
	Base(const Base &b){
		cout<<"copy constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//赋值操作符
	void  operator=(const Base &b){
		cout<<"operator constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//7构函数
	~Base(){
		cout<<"deconstructor invoke!"<<(int *)this<<endl;
	}

	Base play(Base  b){
		return b;
	}
private:
	int data;

};

int main(){

	Base b(1);//构造函数调用

	Base b2=b;//拷贝构造函数调用

	Base b3;//默认构造函数调用
	b3=b;//赋值操作符调用

	b.play(2);//构造函数调用,拷贝构造函数调用(由于函数返回引起的)
	b.play(b);//第一次拷贝构造函数调用是构造形参,第二次拷贝构造函数调用是函数返回值

	cout<<"---------------"<<endl;
	Base b4=b.play(2);
	cout<<"---------------"<<endl;
	Base b5=b.play(b);
	cout<<"---------------"<<endl;
	return 0;
}
g++中执行结果为:

可以发现Base b4=b.play(2);和b.play(2);这行代码相比,析构函数的位置发生了变化,之前是连续两次析构,现在换成了先析构一次,然后函数结束后又析构一次。并且是第二次拷贝构造函数创建的对象在函数结束后析构的(根据对象的地址可以得知)。那么情况可能是这样的,即第二次拷贝构造函数是由于函数返回对象时引起的。因为在函数play调用结束后形参b会被析构掉,所以需要将b复制到一个新的内存区域。调用b.play(2)时的内存布局可能是这样的(根据上面的地址绘制):



为了验证这个测试,可以让play函数不返回B的对象,代码如下:

#include <iostream>

using namespace std;

class Base{
public:
	//构造函数
	Base(){
		cout<<"default constructor invoke!"<<(int *)this<<endl;
	}
	Base(int d){
		this->data=d;
		cout<<"constructor invoke!"<<(int *)this<<endl;
	}
	//拷贝构造函数
	Base(const Base &b){
		cout<<"copy constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//赋值操作符
	void  operator=(const Base &b){
		cout<<"operator constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//7构函数
	~Base(){
		cout<<"deconstructor invoke!"<<(int *)this<<endl;
	}

	void play(Base  b){
		
	}
private:
	int data;

};

int main(){

	Base b(1);//构造函数调用

	Base b2=b;//拷贝构造函数调用

	Base b3;//默认构造函数调用
	b3=b;//赋值操作符调用

	b.play(2);//构造函数调用
	b.play(b);//调用一次拷贝构造函数

	return 0;
}
可以发现少了一次拷贝构造函数的调用:


最后再来看看函数参数为引用的时候的情况,这种情况下不会再创建形参,函数是直接对实参进行操作:

#include <iostream>

using namespace std;

class Base{
public:
	//构造函数
	Base(){
		cout<<"default constructor invoke!"<<(int *)this<<endl;
	}
	Base(int d){
		this->data=d;
		cout<<"constructor invoke!"<<(int *)this<<endl;
	}
	//拷贝构造函数
	Base(const Base &b){
		cout<<"copy constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//赋值操作符
	void  operator=(const Base &b){
		cout<<"operator constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//7构函数
	~Base(){
		cout<<"deconstructor invoke!"<<(int *)this<<endl;
	}

	void play(Base  b){
		
	}
	void refer(Base  b){
		cout<<"refer address:"<<(int *)&b<<endl;
	}
private:
	int data;

};

int main(){

	Base b(1);//构造函数调用

	Base b2=b;//拷贝构造函数调用

	Base b3;//默认构造函数调用
	b3=b;//赋值操作符调用

	b.play(b);//只会调用拷贝构造函数
	b.play(2);//构造函数调用

	cout<<"-----------------"<<endl;
	b.refer(b);
	cout<<"-----------------"<<endl;
	return 0;
}
执行结果为:



refer函数参数为引用返回对象时:

#include <iostream>

using namespace std;

class Base{
public:
	//构造函数
	Base(){
		cout<<"default constructor invoke!"<<(int *)this<<endl;
	}
	Base(int d){
		this->data=d;
		cout<<"constructor invoke!"<<(int *)this<<endl;
	}
	//拷贝构造函数
	Base(const Base &b){
		cout<<"copy constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//赋值操作符
	void  operator=(const Base &b){
		cout<<"operator constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//7构函数
	~Base(){
		cout<<"deconstructor invoke!"<<(int *)this<<endl;
	}

	void play(Base  b){
		
	}

	Base refer(Base & b){
		cout<<"refer address:"<<(int *)&b<<endl;
		return b;
	}
private:
	int data;

};

int main(){

	Base b(1);//构造函数调用

	Base b2=b;//拷贝构造函数调用

	Base b3;//默认构造函数调用
	b3=b;//赋值操作符调用

	b.play(b);//只会调用拷贝构造函数
	b.play(2);//构造函数调用

	cout<<"-----------------"<<endl;
	b.refer(b);
	cout<<"-----------------"<<endl;
	return 0;
}
依然调用了拷贝构造函数来复制返回值:



refer函数参数为引用并且返回引用时,才不会调用拷贝构造函数:

#include <iostream>

using namespace std;

class Base{
public:
	//构造函数
	Base(){
		cout<<"default constructor invoke!"<<(int *)this<<endl;
	}
	Base(int d){
		this->data=d;
		cout<<"constructor invoke!"<<(int *)this<<endl;
	}
	//拷贝构造函数
	Base(const Base &b){
		cout<<"copy constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//赋值操作符
	void  operator=(const Base &b){
		cout<<"operator constructor invoke!"<<(int *)this<<endl;
		this->data=b.data;
	}
	//7构函数
	~Base(){
		cout<<"deconstructor invoke!"<<(int *)this<<endl;
	}

	void play(Base  b){
		
	}

	Base& refer(Base & b){
		cout<<"refer address:"<<(int *)&b<<endl;
		return b;
	}
private:
	int data;

};

int main(){

	Base b(1);//构造函数调用

	Base b2=b;//拷贝构造函数调用

	Base b3;//默认构造函数调用
	b3=b;//赋值操作符调用

	b.play(b);//只会调用拷贝构造函数
	b.play(2);//构造函数调用

	cout<<"-----------------"<<endl;
	b.refer(b);
	cout<<"-----------------"<<endl;
	return 0;
}
输出:

注意:不能返回局部对象的引用,所以如果参数不为引用是不能返回一个引用的对象的。



到这里应该对构造函数,拷贝构造函数和赋值操作符在何时会调用应该有一个清晰的认识了。



  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值