【C++】类的拷贝赋值函数

转载【转】C++的赋值构造函数(赋值运算符重载)

注:文章的内容未严格验证,如有疑问或者错误请留言。


当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值构造函数。当没有重载赋值构造函数(赋值运算符)时,通过默认赋值构造函数来进行赋值操作。

A a;
A b;
b = a;

注意:这里a,b对象是已经存在的,是用a对象来赋值给b的。

赋值运算符的重载声明如下:

A& operator = (const A& other)

通常大家会对拷贝构造函数和赋值构造函数混淆,这里仔细比较两者的区别:

1)拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值构造函数时对于一个已经被初始化的对象来进行赋值操作。

class  A;
A a;
A b=a;   //调用拷贝构造函数(b不存在)
A c(a) ;   //调用拷贝构造函数

/****/

class  A;
A a;
A b;
b = a ;   //调用赋值函数(b存在)

2)实现不一样,拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个新对象。赋值构造函数是把一个新的对象赋值给一个原有的对象。

举例:

#include<iostream>
#include<string>
using namespace std;

class MyStr
{
private:
    char *name;
    int id;
public:
    MyStr():id(0),name(NULL) {}
    MyStr(int _id, char *_name)   //构造函数
    {
        cout << "constructor" << endl;
        id = _id;
        name = new char[strlen(_name) + 1];
        strcpy_s(name, strlen(_name) + 1, _name);
    }
    MyStr(const MyStr& str)    //拷贝构造函数
    {
        cout << "copy constructor" << endl;
        id = str.id;
        name = new char[strlen(str.name) + 1];
        strcpy_s(name, strlen(str.name) + 1, str.name);
    }
    MyStr& operator =(const MyStr& str)//赋值运算符
    {
        cout << "operator =" << endl;
        if (this != &str)
        {
            if (name != NULL)
                delete[] name;
            this->id = str.id;
            int len = strlen(str.name);
            name = new char[len + 1];
            strcpy_s(name, strlen(str.name) + 1, str.name);
        }
        return *this;
    }
    ~MyStr()
    {
        delete[] name;
    }
};

int main()
{
    MyStr str1(1, "hhxx");
    cout << "====================" << endl;
    MyStr str2;
    str2 = str1;
    cout << "====================" << endl;
    MyStr str3 = str2;
    return 0;
}

结果:

说明:

1、参数

一般地,赋值运算符重载函数的参数是函数所在类的const类型的引用,加const是因为:

(1)我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。

(2)加上const,对于const的和非const的实参,函数都能接受;如果不加,就只能接受非const的实参。

用引用是因为:

这样可以避免在函数调用时对实参的一次拷贝,提高了效率。

注意:上面的规定都不是强制的,可以不加const,也可以没有引用。

2、返回值

一般地,返回值是被赋值者的引用,即*this(如上面例1),原因是:

(1)这样在函数返回时避免一次拷贝,提高了效率。

(2)更重要的,这样可以实现连续赋值,即类似a=b=c这样。如果不是返回引用而是返回值类型,那么,执行a=b时,调用赋值运算符重载函数,在函数返回时,由于返回的是值类型,所以要对return后边的“东西”进行一次拷贝,得到一个未命名的副本(有些资料上称之为“匿名对象”),然后将这个副本返回,而这个副本是右值,所以,执行a=b后,得到的是一个右值,再执行=c就会出错。

注意:这也不是强制的,我们甚至可以将函数返回值声明为void,然后什么也不返回,只不过这样就不能够连续赋值了

3、赋值运算符重载函数不能被继承

因为相较于基类,派生类往往要添加一些自己的数据成员和成员函数,如果允许派生类继承积累的赋值运算符重载函数,那么,在派生类不提供自己的赋值运算符重载函数时,就只能调用基类的,但基类版本只能处理基类的数据成员,在这种情况下,派生类自己的数据成员怎么办?所以,C++规定,赋值运算符重载函数不能被继承。

4、赋值运算符重载函数要避免自赋值

对于赋值运算符重载函数,我们要避免自赋值(即自己给自己赋值)的发生,一般地,我们通过比较赋值者与被赋值者的地址是否相同来判断两者是否是同一对象(如例中的if(this != &str)一句)。避免自赋值的意义是:

(1)提高效率,显然,自己给自己赋值完全是毫无意义的无用功,特别地,对于基类数据成员间的赋值,还会调用基类的赋值运算符重载函数,开销是很大的。如果我们一旦判定是自赋值,就立即return *this,会避免对其它函数的调用。

(2)如果类的数据成员中含有指针,自赋值有时会导致灾难性的后果。对于指针间的赋值(注意这里指的是指针所指内容间的赋值,这里假设用_p给p赋值),先要将p所指向的空间delete掉(为什么要这么做呢?因为指针p所指的空间通常是new来的,如果在为p重新分配空间前没有将p原来的空间delete掉,会造成内存泄露),然后再为p重新分配空间,将_p所指的内容拷贝到p所指的空间。如果是自赋值,那么p和_p是同一指针,在赋值操作前对p的delete操作,将导致p所指的数据同时被销毁。那么重新赋值时,拿什么来赋?

所以,对于赋值运算符重载函数,一定要先检查是否是自赋值,如果是,直接return *this。

转载自:

C++中构造函数,拷贝构造函数和赋值函数的区别和实现

一文说尽C++赋值运算符重载函数(operator=)

### C++拷贝赋值函数的使用方法及其实现 #### 定义与作用 拷贝赋值函数(Copy Assignment Operator),也称为赋值操作符重载函数 `operator=`,用于处理已有对象之间的赋值操作。当一个对象被赋予另一个同型对象的内容时,会调用此成员函数来完成属性的数据复制过程[^2]。 对于自定义而言,默认情况下编译器提供了一个简单的位对位拷贝版本;然而,在某些场景下——比如管理动态内存或其他资源时,则需自行定义以确保正确的行为并防止潜在错误的发生[^4]。 #### 实现方式 通常来说,拷贝赋值函数会在内部声明如下形式: ```cpp class MyClass { public: // ...其他成员... MyClass& operator=(const MyClass& rhs); }; ``` 这里返回型为当前的一个引用 (`MyClass&`) 是为了支持链式赋值表达式的书写习惯,即允许像 a=b=c 这样的连续赋值语句正常工作[^3]。 实际编写时需要注意自我赋值的情况以及如何释放旧有的资源再分配新的副本等问题。下面给出一段较为完整的例子说明其具体做法: ```cpp #include <iostream> using namespace std; class String { private: char* data; public: explicit String(const char* str = "") :data(new char[strlen(str)+1]) { strcpy(data, str); } ~String() { delete[] data; } /// @brief 拷贝赋值运算符 String& operator=(const String& rhs) { if(this != &rhs){ // 防止自我赋值带来的问题 // 清理现有资源 delete [] this->data; // 复制新内容 size_t length = strlen(rhs.data); this->data = new char[length + 1]; strncpy(this->data,rhs.data,length+1); } return *this; } void display() const{ cout << "Data: " << data << endl; } }; int main(){ String s1("hello"); String s2; s2.display(); s2 = s1; s2.display(); return 0; } ``` 在这个案例里展示了怎样安全有效地执行字符串型的深拷贝逻辑,同时也考虑到了特殊情况下的边界条件处理[^5]。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值