类的构造函数、拷贝构造函数、赋值函数、析构函数,分别在何时调用。

  在编写一个类时,有时候我们需要为类编写构造函数、拷贝构造函数、赋值函数、析构函数。那么这些函数在何时被调用呢?这篇文章将通过实验进行验证。
(编译器Visual Studio 2012)
  一、首先来看下面的代码:
#ifndef _CLASS_CONSTRUCTOR_H_
#define _CLASS_CONSTRUCTOR_H_
#include<iostream>
using namespace std;

class Class_Constructor
{
public:
	Class_Constructor() { cout<<"无参数的构造函数,member的初始值为:"<<member<<endl; }

	Class_Constructor(int i) : member(i) 
	{
		cout<<"有参数的构造函数,member的初始值为:"<<member<<endl; 
	}
	
	Class_Constructor(const Class_Constructor& obj) 
	{ 
		member = obj.member;
		cout<<"拷贝构造函数,member的初始值为:"<<member<<endl;
	}
	
	Class_Constructor& operator=(const Class_Constructor& obj)
	{
		member = obj.member;
		cout<<"赋值函数,member的值为:"<<member<<endl;
		return *this;
	}
	
	~Class_Constructor() { cout<<"析构函数"<<endl; }

    int member;
};


#endif
  在上面的代码中,我们定义了一个名为Class_Constructor的类,并为该类定义了默认构造函数、带参数的构造函数、拷贝构造函数、赋值函数、析构函数。然后我们使用一下这个类,可以看出这些函数都是在什么时候被调用的。代码如下:
#include"Class_Constructor.h"

int main()
{
	//用默认构造函数进行初始化
	Class_Constructor obj1;

	//用有一个参数的构造函数进行初始化
	Class_Constructor obj2(99);

	//用拷贝构造函数进行初始化
	Class_Constructor obj3 = obj2;
	Class_Constructor obj4(obj1);

	//调用赋值函数
	obj1 = obj2;

	return 0;
}
  在上面的代码中,我们用默认构造函数初始化obj1,用带参数的构造函数初始化obj2,用拷贝构造函数初始化obj3、obj4,最后用赋值函数为obj1重新赋值。运行得到的结果如下结果:

  通过上面例子不难看出,obj1是由默认构造函数进行的初始化,由于我们并未给obj1.member赋值,它的值是未定义的;obj2由带参数的构造函数进行初始化;obj4由拷贝构造函数进行初始化;obj3的初始化看起来很像赋值运算,但他实际并没有调用赋值函数,而是由拷贝构造函数进行初始化;再次对obj1赋值时,才调用了赋值函数;最后在程序退出之前,析构函数被调用。

  二、进一步深入的例子
  我们现在将Class_Constructor类修改如下:
#ifndef _CLASS_CONSTRUCTOR_H_
#define _CLASS_CONSTRUCTOR_H_
#include<iostream>
using namespace std;

static int index = 0;

class Class_Constructor
{
public:
	Class_Constructor() 
	{
		index++;
		m_index = index;
		cout<<m_index<<"——>无参数的构造函数,member的初始值为:"<<member<<endl; 
	}

	Class_Constructor(int i) : member(i) 
	{
		index++;
		m_index = index;
		member = 2;
		cout<<m_index<<"——>有参数的构造函数,member的初始值为:"<<member<<endl; 
	}
	
	Class_Constructor(const Class_Constructor& obj) 
	{
		index++;
		m_index = index;
		member = obj.member;
		cout<<m_index<<"——>拷贝构造函数,member的初始值为:"<<member<<endl;
	}
	
	Class_Constructor operator=(const Class_Constructor obj)
	{
		member = obj.member;
		cout<<m_index<<"——>赋值函数,member的值为:"<<member<<endl;
		return *this;
	}
	
	~Class_Constructor() { cout<<m_index<<"——>析构函数"<<endl; }

    int member;
	int m_index;
};


#endif
  在上面的代码中,我们做了三个改动:
  1、新加了一个名为index的全局变量,用来表示被创建对象的序号,并保存在每个对象的m_index字段。
  2、在带有一个参数的构造函数中,增加了一条赋值语句,对member重新赋值。
  3、将operator=函数的入参和返回值都改为非引用的形式。
  使用修改后的Class_Constructor类的代码如下:
#include"Class_Constructor.h"

int main()
{
	//用默认构造函数进行初始化
	Class_Constructor obj1;

	//用有一个参数的构造函数进行初始化
	Class_Constructor obj2(99);

	//用拷贝构造函数进行初始化
	Class_Constructor obj3 = obj2;
	Class_Constructor obj4(obj1);

	cout<<"---------------"<<endl;
	//调用赋值函数
	obj1 = obj2;

	return 0;
}
  执行结果如下图:

  通过上面代码,可以得到如下结论:
  1、初始化列表的执行顺序,先于函数体内部的程序。
  2、如果函数的入参对象或返回对象,不是对象的引用形式,程序得到的将是入参对象或返回对象的副本。

  三、总结
  1、初始化列表的执行顺序,先于函数体内部的程序。
  2、赋值形式的初始化,其实是调用了拷贝构造函数。可见赋值和初始化是两个完全不同的过程。
  3、如果函数的入参对象或返回对象,不是对象的引用形式,程序得到的将是入参对象或返回对象的副本。
  4、我们在编写拷贝构造函数、赋值函数时,函数的入参应尽量用const的引用形式。这样做既可以表明函数的意图,又可以减少对象的拷贝。
  5、赋值函数的返回类型也应尽量用引用形式。这样做可以让自定义的赋值运算符=的表现力更像内置运算符=,同时可以减少对象的拷贝。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值