C++的复制构造函数和赋值运算符函数究竟有什么用呢?下面通过示例来展示其用法,特别注意几处容易忽略的地方。
先定义两个数据结构:
// @type UWORD | 2 byte unsigned integer.
typedef unsigned short UWORD;
// @type SDWORD | 4 byte signed integer.
typedef signed long SDWORD;
//data definition.
struct Electron
{
SDWORD ID_len;
DWORD ID_status;
int ID;
SDWORD KeyCode_len;
DWORD KeyCode_status;
char KeyCode[64];
SDWORD DeviceNumber_len;
DWORD DeviceNumber_status;
char DeviceNumber[64];
SDWORD DeviceID_len;
DWORD DeviceID_status;
int DeviceID;
SDWORD DeviceType_len;
DWORD DeviceType_status;
int DeviceType;
SDWORD DeviceName_len;
DWORD DeviceName_status;
char DeviceName[64];
SDWORD DataState_len;
DWORD DataState_status;
int DataState;
SDWORD DataValue_len;
DWORD DataValue_status;
float DataValue;
SDWORD StateName_len;
DWORD StateName_status;
char StateName[64];
SDWORD RegionBegin_len;
DWORD RegionBegin_status;
float RegionBegin;
SDWORD RegionEnd_len;
DWORD RegionEnd_status;
float RegionEnd;
SDWORD UTCTime_len;
DWORD UTCTime_status;
char UTCTime[32];
};
class DeviceData
{
public:
DeviceData();
~DeviceData();
DeviceData(const DeviceData&data); //定义一个复制构函函数
void operator=(const DeviceData& data); //定义一个赋值运算符函数
public:
int ItemCount;
vector<Electron>* m_pItemList;//这里定义一个指针
};
下面为其实现:
DeviceData::DeviceData()
{
m_pItemList = new vector<Electron>();
}
DeviceData::DeviceData(const DeviceData& data)
{
this->m_pItemList = new vector<Electron>();
this->ItemCount = data.ItemCount;
if(data.m_pItemList->size()>0)
{
this->m_pItemList->assign(data.m_pItemList->begin(),data.m_pItemList->end());
}
}
DeviceData::~DeviceData()
{
if(m_pItemList != NULL)
{
m_pItemList->clear();
delete m_pItemList;
m_pItemList = NULL;
}
}
void DeviceData::operator=(const DeviceData& data)
{
//注意,和复制构造函数的区别是,本身对象已经存在,所以this->m_pItemList指针是已经存在的。
this->m_pItemList->clear();
this->ItemCount = data.ItemCount;
if(data.m_pItemList->size()>0)
{
this->m_pItemList->assign(data.m_pItemList->begin(),data.m_pItemList->end());
}
}
//下面是调用代码:
//1. 创建而没有赋值,调用构造函数。
DeviceData data; //这样调用构造函数
data.ItemCount = 100;
Electron elem;
elem.DataValue = 100;
char* pstr = "1111111111111";
memcpy(elem.DeviceNumber,pstr,strlen(pstr)+1);
data.m_pItemList->push_back(elem);
//2. 创建的同时用另一个已有的对象来赋值,调用复制构造函数。
DeviceData dest1(data);//这样调用复制构造函数
DeviceData dest2 = data;//这样调用复制构造函数
//3. 对象已创建,调用另一个对象来赋值,调用赋值运算符函数。
DeviceData dest3; //调用构造函数
dest3 = data; //赋值,调用赋值运算符函数。
注意以上代码,构造了4次,那么退出调用的代码块之后,将用调用4次析构函数。因为定义了指针,如果不显示的定义复制构造函数,那么在析构的时候,通过复构构造的多个实例的m_pItemList指针是同一个地址,必定会重复释放从而引起异常而崩溃。而如果m_pItemList不是指针,则不用显示定义自己的复制构造函数。同理,如果包含指针成员,则也必须定义自己的赋值运算符 函数。
下面展示一下函数调用时,传实体的引用与传实体值的区别:
传引用调用函数:
void CallDeviceData(const DeviceData& data)
{
if(data.ItemCount>0)
{
//do something.
}
}
调用:
DeviceData data;
data.ItemCount = 100;
CallDeviceData(data); //这是传引用,出函数后,不会调用析构函数。
//入函数体调用复制构造函数,出函数体调用析构函数。
void CallDeviceData(const DeviceData data)
{
if(data.ItemCount>0)
{
//do something.
}
}
调用:
DeviceData data;
data.ItemCount = 100;
CallDeviceData(data); // 这是传值调用,实际上,入函数时系统会调用复制构造函数复制一个副本,退出函数后,这个副本会调用析构函数。
注意了,这里退出调用块之后,析构函数会调用两次,一次是data的析构,一次调用函数时产生的副本的析构。
从以上可以看出,对于定义了指针的类,一定要定义自己的复制构造函数和赋值运算符函数。