C++运算符“=”重载引用的使用详解

在讲解这个概念之前我先明确一下这边文章中的几个基本概念;当时写文章时因为这几个概念混淆了,导致大部分内容重写。

复制构造函数及调用复制构造函数的三种情况:

class A{
	int x;
public:
	A(int a):x(a) { }//构造函数
	A(const A& a1) {	}//复制构造函数
};
A Func(A a2) { }//1、类的对象作为形参,调用复制构造函数
A Func1() {A a3(4);return a3;}//2、类的对象作为函数的返回值,调用复制构造函数
int main(){
	A a(1),a1(a);//3、用类的对象a初始化类的另一个对象a1,调用类的复制构造函数
}

浅拷贝(浅复制):将对象a内存中的数据按照二进制位(Bit)复制到对象b所在的内存(a和b指向同一内存空间)

深拷贝(深复制):将对象a的所有成员变量拷贝给新对象b,并为对象b分配一块新的内存,并将原有对象所持有的内存也拷贝过来(a和b指向不同内存空间)

运算符”=“如果没有被重载,默认执行的是浅拷贝;如果要让运算符”=“执行深拷贝,需要自己重载运算符。

重载“=”运算符

标准格式

class A{
public:
    A & operator = (line & b);
    //返回值时引用,与类的复制(拷贝)构造函数有关
};

返回值为空类型

class A{
public:
    void operator = (line & b);
    //返回值为void类型
    //当使用两个或两个以上“=”时,有时会类型不符
};

对象间的连续赋值

A a, b, c;
a=b=c;
//a=b=c等价于a=(b=c);

为了实现连等,要将返回值定义为该类的类型。

当执行到return *this;时,会调用赋值构造函数;若为定义复制构造函数,就会调用默认复制构造函数。但默认复制构造函数是浅拷贝,当类中有指针时就会出错。若不用引用做返回时,就必须自定义深拷贝构造函数。


接下来这个例子必须要重载运算符,且重载运算符时必须要使用引用。


#include
using namespace std;
class Myclass {
    char* str;
public:
    Myclass(const char* str1 = "default string") {
        //构造函数,在对象生成时使用,用来初始化对象
        str = new char[strlen(str1) + 1];//给成员变量str用new分配一片空间
        strcpy(str, str1);//将参数值复制到成员对象str中
        cout << "constructor called" << endl;
    }
    Myclass(const Myclass& a) {
        cout << "copy construct" << endl;
    }
    ~Myclass() {//析构函数,在对象消亡时使用
        cout << "destrustor called" << endl;
    }
    void showChar() {//成员函数,用来显示该对象
        cout << str << endl;
    }
    Myclass operator=(const Myclass& ele) {//重载“=”运算符
        delete[] str;//str原来的内存空间大小可能不合适,需要释放掉重新分配
        str = new char[strlen(ele.str) + 1];//给str重新分配空间
        strcpy(str, ele.str);
        return *this;//返回调用该函数的对象本身
    }
};
int main() {
    Myclass class1("string1"), class2;
    class2 = class1;//由于“=”被重载了,故等价于:class2.operator=(class1)
    // 若未重载“=”运算符;遇到对象用对象给对象赋值时,会调用复制构造函数
    class1.showChar(); class2.showChar();
    return 0;
}
/*
输出结果:
constructor called
constructor called
string1
string1
destrustor called
destrustor called
*/

若不重载“=”运算符       

由于未重载“=”运算符,故执行class2 = class1;时,“=”运算符执行的是浅拷贝的功能,class1和class2的str都指向同一内存空间。

当调用析构函数时,先调用class2的析构函数,把str所指向的内存给清除;然后调用class1的析构函数,此时就会对同一片内存delete两次(class2的析构函数已经清除过一次该内存),出现存泄漏的情况。

若重载“=”的函数的参数不使用引用

若参数不用引用,则有Myclass &operator=(const Myclass ele) { }

输出结果:

constructor called
constructor called
destrustor called
string1
string1
destrustor called
destrustor called

调用Myclass &operator=(const Myclass ele) { }是调用复制构造函数的一种情况:类的对象作为形参

实参传值给形参会创建一个临时变量,调用复制构造函数将实参复制给形参(此处进行的也是浅拷贝),实参形参的str都指向同一块内存。

当赋值函数执行完时,会清除函数的临时对象所占的内存;此时执行class1.showchar()访问的是刚被delete掉的内存,会发现内存泄漏。

备注:一片内存空间被delete后,其上存放的内容并不被立刻抹去,只是这块内存可以被再分配给其他。以下这段程序说明内存被delete掉后指针a1仍然可以指向原来的内存空间,此时的指针a1被称为野指针。

int main()
{
    int *a1 = new int(3);  cout << *a1 << endl;//输出:01464E40,3
    delete a1;  cout << *a1 << endl;//输出:01464E40,-572662307
    //此结果和有些文章不同,建议自己动手运行一下
    //有些文章说再delete语句后加a1=NULL或a1=nullptr可将a1的值清除掉,的确是这样,但是程序会报错并中止运行
    return 0;
}

重复delete一片空间危害:当new出来的内存空间a被delete掉后可以被再次分配,这片内存空间可以被再分配;此时b要申请一片内存空间,系统恰好将刚释放掉的a的空间分配给了他;如果后面的程序使用b的内容之前,由于重复delete这片内存空间,因此后面的程序运行出错。(虽然代码短了不会出现这种情况,但架不住代码几万行代码运行时会出现这种问题)

对已经被delete掉的指针我们不能对其进行任何操作,不过有争议的是delete之后是否要将指针置空(即ptr=NULL)。

参考文章https://cloud.tencent.com/developer/article/1879316

若重载“=”的函数的参数不使用常引用而使用普通引用

若参数使用普通引用,则有Myclass &operator=(Myclass& ele) { }

由于形参是引用,所以被调用的函数中执行时用的就是实参本身;由于引用不是const引用,所以一旦形参中的操作涉及到更改形参只,那么实参值也会被更改。

因此要使用const引用,禁止被调用的函数时,使得实参的值被改变。

若重载“=”的函数返回值不是引用

若返回值不是引用,则有

Myclass operator=(const Myclass& ele) 
{    ………………………………
     return *this;//返回引用该函数的对象本身(对象本身类型为Myclass)
}
/*
返回值非引用的输出:
constructor called
constructor called
copy construct
destrustor called
string1
string1
destrustor called
destrustor called
……………………………………………………
返回值为引用的输出
constructor called
constructor called
string1
string1
destrustor called
destrustor called
*/

返回值为return *this;其作用是返回引用该函数的对象本身,即返回类的对象。

返回值为类的对象是调用复制构造函数的右一种情况:若返回值的类型不为引用,则返回对象时会创建一个临时对象,再调用复制构造函数,将返回的对象复制给临时对象,二者指向同一内存空间;函数结束后,临时对象被清除,其内存空间也被delete掉了;故calss2=class1的str指向一块随机内存,在调用class2.showchar()时会随机输出。

返回值为引用时,从输出结果可以看出并不生成临时对象,也就不存在上述问题;函数返回值就是调用该函数的对象本身的别名;同时使用引用返回的参数可以作为左值。

总结:

主要是为了防止浅拷贝(指向同一片内存空间,delete时会出问题)、调用复制构造函数和析构函数(多出来的操作都是对时间的消耗,令效率打折)


如有错误请在评论区指出,谢谢。

你可能感兴趣的:(c++)