赋值操作符重载-C++复习二

本文通过实例深入解析赋值操作符重载在C++中的重要性,特别是对于包含动态分配资源的类。文章详细说明了在没有正确实现赋值操作符重载的情况下,可能会导致的内存泄漏和异常问题,并提供了正确的实现方式。

赋值操作符重载

上篇文章着重讲述了复制构造函数的重要性,以及如果没有实现复制构造函数可能出现的致命错误,同时也指出赋值操作符重载在类中也和赋值构造函数一样重要.
但赋值操作符重载有着其独特的性质,本篇文章就专门介绍赋值操作符重载的一些性质,以及在实际开发中需要注意的地方.

引出问题

为了描述问题,我们简单设计一个类People, 包含三个成员m_pName, m_nAge, m_nGender,其中m_pName被声明为指向char类型的指针,用于保存姓名字符串.类头文件和cpp实现文件如下:
People.h

#ifndef PEOPLE_H
#define PEOPLE_H

class People
{
    public:
        People();
        People(const char *name, int age, int gender);
        //People(const People &pp);

        virtual ~People();

        People &operator=(const People &pp);

        void Print() const;
    protected:

    private:
        char *m_pName;
        int m_nAge;
        int m_nGender;
};

#endif // PEOPLE_H

People.cpp

#include "People.h"
#include <string.h>
#include <iostream>
using namespace std;

People::People()
{
    m_pName = new char[1];
    m_pName[0] = 0;
    m_nAge = 0;
    m_nGender = -1;
}

People::People(const char *name, int age, int gender)
{
    m_pName = new char[strlen(name)+1];
    strcpy(m_pName, name);
    m_nAge = age;
    m_nGender = gender;
}

//People::People(const People &pp)
//{
//    m_pName = new char[strlen(pp.m_pName)+1];
//    strcpy(m_pName, pp.m_pName);
//    m_nAge = pp.m_nAge;
//    m_nGender = pp.m_nGender;
//}

People::~People()
{
    delete []m_pName;
}

People &People::operator=(const People &pp)
{
    if(this == &pp)
    {
        return *this;
    }

    delete []m_pName;

    m_pName = new char[strlen(pp.m_pName)+1];
    strcpy(m_pName, pp.m_pName);
    m_nAge = pp.m_nAge;
    m_nGender = pp.m_nGender;

    return *this;
}

void People::Print() const
{

    std::cout<<"name:"<<m_pName<<endl;
    std::cout<<"age:"<<m_nAge<<endl;
    std::cout<<"gender:"<<m_nGender<<endl;
}

首先,我们吧复制构造函数注释掉,然后在main中调用People类的对象:
main.cpp

#include <iostream>

using namespace std;

#include "People.h"

int main()
{
    cout << "Hello world!" << endl;
    {
        People p("Emily", 28, 0);
        p.Print();

        People p1 = p;
        p1.Print();

        People p2;
        p2 = p1;
        p2.Print();
    }
    return 0;
}

运行main,结果输出如下图所示:
在这里插入图片描述从输出结果分析,程序对m_pName释放了两次,引发异常,程序崩溃.

分析

即使实现了赋值操作符重载,当使用形如"type_name obj_dst = obj_src;"这样的赋值语句对对象赋值时,程序仍然没有调用赋值操作符,而是调用了编译器默认提供的复制构造函数.
这是因为默认的复制构造函数形参只有一个,C++中对对象赋值时,将调用只有一个参数的构造函数,而此时这个构造函数正好与复制构造函数相匹配,所以调用了复制构造函数.但是我们在本例子中,把复制构造函数注释掉了,这样,程序就调用了编译器生成的复制构造函数,而默认的复制构造函数没有深拷贝m_pName成员,所以在析构函数被执行时,对象就试图delete不属于它创建的内存.

总结

必须实现复制构造函数,否则,只能使用先声明,后赋值的语法对对象赋值,如:
People p1(“Alex”, 30, 1);
PeoPle p2;
p2 = p1;
这时,重载的赋值操作符将被调用.

### C++赋值操作符重载的实现 在 C++ 中,为了确保类的对象能安全地执行赋值操作并处理资源管理问题,通常需要重载赋值运算符 `operator=`。当涉及到动态内存分配或其他资源时,简单的成员逐个复制可能导致浅拷贝问题,进而引发潜在的风险。 对于一个包含指针成员变量的类来说,在默认情况下编译器提供的赋值操作仅会复制指针本身而不是它指向的数据。这可能会造成两个对象共享同一块堆上分配的空间,从而导致双重释放等问题。因此,应当通过显式定义赋值运算符来实施深拷贝逻辑[^2]。 下面是一个完整的示例代码展示如何正确地重载赋值运算符: ```cpp #include <iostream> #include <cstring> class String { private: char* data; public: // 构造函数初始化data为空字符串 String(const char* str = "") { size_t length = strlen(str); data = new char[length + 1]; strcpy(data, str); } // 拷贝构造函数用于创建新对象时做深拷贝 String(const String& other) : String(other.data) {} // 赋值运算符重载 String& operator=(const String& rhs) { if (this != &rhs) { // 防止自我赋值 delete[] data; // 清理当前持有的资源 size_t length = strlen(rhs.data); data = new char[length + 1]; // 分配新的空间给data strcpy(data, rhs.data); // 复制数据到新分配的空间中 } return *this; // 返回本对象的引用 } ~String() { delete[] data; } void display() const { std::cout << data << '\n'; } }; int main() { String s1("hello"); String s2; s2.display(); // 输出空串 s2 = s1; // 使用自定义的赋值运算符 s2.display(); // 应该输出 "hello" return 0; } ``` 此程序展示了如何在一个名为 `String` 的类中实现赋值运算符重载以支持深拷贝语义。注意这里还包含了防止自我赋值检查以及清理已有资源的操作。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值