实例展示C++复制构造函数和赋值运算符函数的使用

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的析构,一次调用函数时产生的副本的析构。

从以上可以看出,对于定义了指针的类,一定要定义自己的复制构造函数和赋值运算符函数。

 

 

 

 

 


 

### C++ 构造函数赋值运算符的区别 #### 一、概念差异 构造函数是一种特殊的成员函数,在创建对象时被自动调用,其目的是初始化新创建的对象。而赋值运算符则是在两个已有对象之间进行数据传递时使用的操作符重载方法。 - **拷贝构造函数**:当需要通过已有的对象来初始化一个新的对象时,会调用拷贝构造函数[^2]。 - **赋值运算符**:如果目标对象已经存在,则使用赋值运算符将其内容替换为另一个对象的内容[^3]。 --- #### 二、调用场景对比 | 场景 | 调用方式 | |------|----------| | 创建并初始化一个新对象 | 拷贝构造函数 | | 将现有对象的数据赋给另一个已存在的对象 | 赋值运算符 | 具体来说: 1. 当以下情况发生时,会触发拷贝构造函数: - 函数参数按值传递时; - 返回局部对象时; - 显式地用一个对象去初始化另一个对象时[^4]。 2. 对于赋值运算符而言,它会在如下条件下被调用: - 已经声明好的对象重新获得其他对象的值时;例如 `objA = objB` 这样的语句中。 --- #### 三、实现细节比较 ##### (1) 拷贝构造函数 通常定义形式如下所示: ```cpp ClassName(const ClassName& other); ``` 需要注意的是,为了避免潜在的风险(比如指针指向同一内存区域),可能还需要考虑深拷贝逻辑而不是默认的浅拷贝。 示例代码展示如何正确编写带资源管理功能的类及其拷贝构造函数: ```cpp class MyClass { private: int* data; public: explicit MyClass(int value) : data(new int(value)) {} ~MyClass() { delete data; } // Copy Constructor with deep copy logic. MyClass(const MyClass& other) : data(new int(*(other.data))) {} }; ``` 上述例子展示了手动分配动态存储空间的情况,并且确保了每次复制都能独立拥有自己的副本。 ##### (2) 赋值运算符 一般形式为: ```cpp ClassName& operator=(const ClassName& rhs); ``` 同样重要的一点是防止自我赋值带来的问题以及执行必要的清理工作后再做实际的赋值动作。 下面给出完整的带有安全机制版本的例子: ```cpp #include <iostream> using namespace std; class String { private: char *str; public: String(): str(nullptr){} String(const char*s){ if(s != nullptr){ this->str=new char[strlen(s)+1]; strcpy(this->str,s); } } ~String(){ cout << "~String()" << endl; delete []this->str; } // Assignment Operator Overloading String& operator= (const String &rhs){ if(&rhs == this)return *this;// Self assignment check delete[] this->str; // Release current resource first. if(rhs.str!=nullptr){ this->str=new char[strlen(rhs.str)+1]; strcpy(this->str,rhs.str); }else{ this->str=nullptr; } return *this; } }; int main(){ String s1("hello"); String s2="world"; s2=s1; } ``` 此程序片段不仅实现了字符串类型的简单封装还包含了对赋值过程中的异常处理措施。 --- ### 总结 综上所述,虽然两者看起来相似但实际上用途完全不同——前者负责构建新的实例后者则是更新现有的实体状态[^1]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值