【C++】运算符重载 | 赋值运算符重载

Ⅰ. 运算符重载

引入

❓什么叫运算符重载?

就是:运用函数,将现有的运算符重新定义,使其能满足各种自定义类型的运算。

回想一下,我们以前运算的对象是不是都是int、char这种内置类型?

那我们自定义的“preson”类型,想要进行加减运算,该怎么办呢?

这就需要运算符重载。

先瞅一眼它大致长啥样:

bool operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

概念

运算符重载是具有特殊函数名的函数

也具有其返回值类型、函数名及参数列表。

函数名:关键字operator后面接需要重载的运算符符号。

格式:返回值类型 operator 操作符(参数列表)

1.常用的操作符有:+、-、*、/、++、--、=(赋值)、==(判断相等)、>、<、>=、<=等

2.有几个操作数,就有几个形参。

不过,当重载成员函数时,有一个形参是隐形的,即this指针。

说明:

1.不能通过连接其他符号来创建新的操作符。

如:operator@

2.重载操作符必须有一个类类型的参数。

如果参数里没有类类型,那运算符重载还有啥意义。

3.用于内置类型的运算符,其含义不能改变。

如:内置的 整型 +,不能改变其含义

4.作为类成员函数重载时,其形参看起来比操作数少1

因为成员函数的第一个参数为隐藏的this

(见下面的例子)

5.(笔试选择题常考)这5个运算符不能重载:

.* 点星运算符

: : 域运算符

sizeof

? : 条件运算符

. 点运算符

6.运算符重载写好了以后,直接用就行。编译器会自动调用函数。

Date& operator+=(int day){

}

d1+=100;    //直接用。调用会自动完成🤣

举例

class Date
{
public:
    Date(int year = 2000, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }                                 //需要注意的是,左操作数是this,指向调用函数的对象
    bool operator==(const Date& d)    //bool operator==(Date*this,const Date& d)
    {
        return _year == d._year &&    //其实是this->_year==d._year
            _month == d._month &&
            _day == d._day;
    }
​
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(2023, 8, 12);
    Date d2(2023, 8, 12);
    cout << (d2 == d1) << endl;
}

结果为:

Ⅱ. 赋值运算符重载

概念

赋值运算符重载作为类的6大成员函数之一,

负责将一个对象赋值给另一个对象。

如果我们不写,那编译器会自动生成

先看看它大概长啥样:

 Date& operator=(const Date& d)  
    {                               
        _year = d._year;
        _month = d._month;
        _day = d._day;
        return *this;
    }

再看看一般怎么调用:

int main(){
    Date d1(2000,1,1);
    Date d2(2023,8,22);
    d1=d2;     
}

就是“d1=d2;”这样平平无奇的调用。。。

因为编译器看到会自动开栈帧调用的啦!不用我们操心。👻

格式

T& operator =(const T& 参数)

说明:

1.参数类型为const T&。传引用可以提高传参效率。

2.返回类型为T&。

❓你可能会疑惑:这里只要完成赋值动作的话,返回类型为void不就可以了吗?

为什么要有返回值呢?

有返回值其实是为了支持连续赋值。

如”d1=d2=d3;“ 要想连续赋值,

那d2=d3在调用完函数以后要有一个返回值,这个返回值作为右操作数,参与到d1=…中去。

如果返回void,那d1=空,无法完成连续赋值。

所以,要想连续赋值,就得有返回值。

在返回时,传值返回是可以,但我们尽量使用引用返回

因为能减少传参过程中的拷贝,效率更高。

🔬  不信我们来实验下,

通过对比 传值返回与传引用 调用拷贝构造函数 的次数,

来看 传引用究竟有没有减少拷贝!

实验组1:传值

class Date
{
public:
    Date(int year = 2000, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    Date(const Date& d)    //我们自己写一个拷贝构造函数
    {                      //这样,它每次被调用,都会打印出来
        cout << "我被调用了!" << endl;
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    Date operator=(const Date& d)   //传值是可以的,但是没有引用好
    {                                //实验结果将为我们证明这一点
        _year = d._year;
        _month = d._month;
        _day = d._day;
        return *this;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d1(2023, 8, 12);
    Date d2(2023, 8, 12);
    Date d3(2000, 1, 1);
    Date d4(2020, 1, 1);
    d1 = d2 =d3 = d4;
}

结果为:(这已经是被优化后的结果)

实验组2:传引用

...
    Date& operator=(const Date& d)   //传引用
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
        return *this;
    }
    ...

结果为:

实验证明,传引用比传值调用拷贝构造函数的次数少,效率更高。

所以,我们能传引用的地方,就尽量传引用。

3.检测是否自己给自己赋值。

如果是”d1=d1;“那这样的赋值完全没意义。

为了更高效,我们用if语句来避免自己给自己赋值的情况。

Date& operator=(const Date& d)
    {
        if (this != &d)    //加上判断
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
            return *this;
        }
    }

注:我们要用this去判断,不要用对象!

因为对象仅能判断值是否相等,而this能从地址判断它俩是否是同一个。

4.返回*this。

为什么可以返回*this?

我们知道,函数的局部变量是不能返回的,

因为局部变量在出了作用域就销毁了。

而这里不同,*this是 作用域在函数外面的 对象。

出作用域,对象并不会因此销毁。所以*this有效。

只能重载成成员函数

赋值运算符只能重载成成员函数,不能重载成全局函数。😥

因为如果不在类中实现,那编译器会生成一个默认的。

此时你在类外实现的全局运算符重载,就和默认的那个冲突了。

因此,赋值运算符必须定义成成员函数。

赋值or拷贝构造?

来看这个例子:这两种写法,分别是赋值还是拷贝构造?

  

其实都是拷贝构造!

赋值操作的是一个已存在的变量👌,而拷贝构造是定义一个新的变量。

默认赋值运算符重载

当你没有显示实现时,编译器会自动生成一个默认的赋值运算符重载,

以值的方式逐字节拷贝。

注:内置类型成员是直接赋值的,

而自定义类型成员变量需要调用 对应类的 赋值运算符重载 来完成赋值。

我们演示一下:

class Date {
public:
    Date(int year = 2000, int month = 1, int day = 1)
    {
        this->_year = year;
        this->_month = month;
        this->_day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};
​
int main(void)
{
    Date d1(2010,1,1);
    Date d2(2023,8,12);
                  //我们并未实现,
    d1 = d2;   //但这里会自动调用 默认赋值运算符重载
    return 0;
}

结果:

❓既然默认生成的已经可以完成值的拷贝了,那还需要我们自己去实现吗?

如果是像日期类这种,是不需要的,值拷贝已经足够。

但如果涉及资源管理,

如动态内存分配、指针、打开的文件等,就得深拷贝,

这时就必须要自己去实现了。

(这里的原因和拷贝构造函数那儿是贯通的。)

原因再说明一下:

如果有指针,而默认的赋值运算符重载只能浅拷贝,

并不会再开一块指针指向的空间。

这就导致了两个指针指向同一块空间,彼此相互影响。

  • 21
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值