c++中赋值操作符的重载

  直接抛问题,两个同类型的对象可以相互赋值?

class cls
{
public:
    int a;
    char c;

    cls() : a(10), c('g')
    {}

    cls(int a, char c) : a(a), c(c)
    {}

};

int main(void)
{
    cls c1(6, 's');

    cls c2;

    c2 = c1;

    printf("c2.a = %d, c2.c = %c\n", c2.a, c2.c);   

    return 0;
}

  编译运行:
这里写图片描述

  显然c++支持两个同类型的对象可以相互赋值的。

  ”c2 = c1;”注意要跟”cls c2 = c1;”区分开,前者是赋值语句,后者是初始化。对于后者,对象c2会调用类的拷贝构造函数,实现将c1到c2的拷贝。关于拷贝构造函数的使用,在前面http://blog.youkuaiyun.com/qq_29344757/article/details/76037255一文中有详细介绍。
  两个同类型的对象可以直接使用”=”(赋值操作符)进行赋值,其实这是类已经实现对赋值操作符的重载。但是在上面的代码中,我们并没有定义赋值操作符重载函数,可见,c++类会默认为我们定义。

  综合前面所学习的可知,一个空的c++类:

class cls
{
};

  编译器会为我们默认定义构造函数、拷贝构造函数、析构函数以及赋值操作符重载函数,也就变成:

class cls
{
public:
    cls();
    ~cls();
    cls(const cls& c);
    cls& operator= ();
};

  那么问题出现,默认的赋值操作符重载函数内部实现的深度拷贝还是浅拷贝?

class cls
{
public:
    int a;
    int *p;

    cls()
    {
        p = new int(0);
        a = 0;
    }

    cls(int a, int b)
    {
        p = new int(b);
        this->a = a;
    }

    ~cls()
    {
        delete p;
    }
};

int main(void)
{
    cls c1(6, 7);
    cls c2;

    c2 = c1;    //调用默认的赋值运算符重载函数

    printf("c2.a = %d, *c2.p = %d\n", c2.a, *c2.p);

    return 0;
}

  编译运行:
这里写图片描述
  堆栈出错,提示重复释放内存。
  显然,跟默认拷贝构造一个样,默认的赋值运算符重载函数也是浅拷贝,所以指针变量c2.p和c1.p是一样的。我们需要自定义赋值运算符重载函数(顺便把拷贝构造函数也自定义):

class cls
{
public:
    int a;
    int *p;

    cls()
    {
        p = new int(0);
        a = 0;
    }

    cls(int a, int b)
    {
        p = new int(b);
        this->a = a;
    }

    //自定义拷贝构造函数
    cls(const cls& c)
    {
        p = new int(*c.p);
    }

    //自定义赋值运算符重载函数
    cls& operator= (const cls& c)
    {
        if (this == &c)
            return *this;

        delete p;
        p = NULL;

        p = new int(*c.p);

        return *this;
    }

    ~cls()
    {
        delete p;
    }
};

int main(void)
{
    cls c1(6, 7);
    cls c2;

    c2 = c1;        //调用了cls& operator= (const cls& c)

    printf("c1.p = %p, c2.p = %p\n", c1.p, c2.p);

    printf("c2.a = %d, *c2.p = %d\n", c2.a, *c2.p);

    return 0;
}

编译运行正常:
这里写图片描述

下来仔细看看自定义的操作符重载函数:

cls& operator= (const cls& c)
{
    if (this == &c)
        return *this;

    delete p;
    p = NULL;

    p = new int(*c.p);

    return *this;
}

(1) 参数const cls& c:加上const原因在于我们不希望此函数对用来进行赋值的c做任何修改,其次有加上const的形参,能接受const和非const的实参,反之只能接收非const的实参
(2) 返回值cls&:返回值是返回被赋值着的引用,即*this,这样可以实现连续赋值,即类似于:

x = y = z;

若不是返回引用”cls& “而是直接是”cls”,那返回的是(*this)的副本,再用这个副本做左值,那么就出错了。
(3)避免自赋值:c/c++的语法并不反对类似”a = a”这样的自赋值语法,所以要在操作符重载加以判断避免自赋值操作,一来为了提高效率,二来避免出错。假设如上代码去掉if判断:

cls& operator= (const cls& c)
{
    delete p;
    p = NULL;

    p = new int(*c.p);

    return *this;
}

而*this跟参数c是同一个对象,那么在执行”delete p;”后也就意味着c.p也被delete了,那执行到”p = new int(*c.p);”就出错了,因为被delete后的p已经是一个野指针,对一个野指针解引用就会Segmentation fault。
(4)为什么要先”delete p;”再执行new
因为原先的p是通过类的构造函数new的,要再new一个空间并初始化为(*c.p)就需要先将原来p给delete,不然将造成内存泄漏。其实在这里可以复用p原型的堆空间,那么代码将改成:

cls& operator= (const cls& c)
{
    if (this == &c)
        return *this;

    *p = *c.p;      //这也是深度拷贝

    return *this;
}

这样改成反而看着简单。
(6)赋值运算符的重载函数只能是类的成员函数,不能是是类的静态函数(因为静态成员函数只能操作类的静态成员),也不能是(友元)全局函数,否则在编译阶段就出错了!假设可以为全局函数,c++类已经默认提供了赋值重载函数了,那么在赋值运算符重载函数(全局函数)和赋值运算符重载函数(类的成员函数)同时存在的情况下,当进行相同类型间的赋值时,编译器就不知道要调用哪一个函数了。再者,假设可以用全局函数重载赋值操作符:

int operator= (int a, cls& c)
{
    //...
}

int main(void)
{
    cls c(5);

    6 = c;      //哥们,这就有点过分了

    return 0;
}

(7) 调用的时机
对比拷贝构造函数和赋值运算符重载函数的代码,可见除了避免自赋值判断之外,赋值运算符重载函数还比拷贝构造函数多了一句delete。一开始我很纳闷,想不出为何,其实那是我忽略了初始化和赋值这两个小玩意。初始化调用的是拷贝构造函数,”p = new int(*c.p);”语句是对象首次动态分配空间中边分配边为该空间初始化的,但是在赋值时调用的是赋值运算符重载函数,”p = newint(*c.p);”是在第二次分配空间的时候变分配边为该空间初始化的,所以需要把上次的new到的空间delete。

### 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` 的类中实现赋值运算符重载以支持深拷贝语义。注意这里还包含了防止自我赋值检查以及清理已有资源的操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值