赋值和算术运算符的重载(1):赋值运算符 =

        一直纠结 = 和 + 运算符重载的问题,现在清楚一些了,分享给各路程序猿。
        并且希望你也能体会到编程的乐趣。C++是多么丰富多彩、严谨奇妙的世界呀!

运算符重载是个啥 
 

        如果有个字符串类叫string,你用它表示"Hello world"这样的东西,就像这样:
 

string s1("Hello World");
 

        它可以有赋值操作:
 

string s2;
s2 = s1;                // Now s2 == s1 == "Hello World"
 

        它也可以有相加的操作:
 

string s3("Hello "), s4("World"), s5;
s5 = s3 + s4;        // s5 == "Hello World"
 

        这个类不是C++的自带 (built-in) 基本类型(虽然有std::string,但这个也不算是built-in type,只是C++标准库的一个类),C++不会自动为你实现赋值和相加的功能,你需要自己告诉程序在碰到 = 和 + 的时候该做什么,你要赋予这两个运算符生命的意义,并且让它们生命更加完整!!
如何告诉它该做什么?通过运算符重载。在执行 = 和 + 操作的时候,程序是在调用函数,运算符重载就是你自己定义这个运算符执行时调用的函数。
 

class string
{
......
public:        
    string& operater = (const string& s);                  // 赋值运算符重载函数的声明
    const string operater + (const string& s);           // 加运算符重载函数的声明,重载为成员函数,只带一个参数
    ......
};
 

// 运算符重载函数的定义
string& string::operater = (const string& s)
{
    // Do whatever u need
    ......

 

const string operater + (const string& s)
{
    // Do whatever u need
    ......

        这两个函数不是C++给你的,是要你自己写的(这里我免费送你了),函数的参数和返回类型可不能乱写,这就是麻烦的地方。下面我告诉你为啥要像上面一样写。
 

1. 赋值运算符


1.1 重载函数的参数类型

 
string& operater = (const string& s);    //  函数参数是个常引用,返回类型是普通引用

        函数的参数是个 reference-to-const, 常对象的引用 ,简称 常引用
 

        1.1.1  什么是引用,什么是常引用 ?

 
        引用就是对象的别名(外号),它不像指针,指针是个变量,是要占内存的,而引用不是一个变量,它只是个代号,用于表示一个对象,不占内存。
        小明同学可能有个外号叫朱大常,不管是“朱大常”还是“小明”,都代表那个人。所以你踹朱大常一脚,和踹小明是一样一样的。

这对于这里的对象也是一样的(特此注明对象是object而不是 boyfriend or girlfriend),你对对象的别名代表的东西的操作就是操作对象本身。
        而一个const修饰的引用,表明你拿到这个别名,你对它所代表的对象进行的操作,是不能改变这个对象的。
        也就是对于"朱大常"所代表的这个小明,你不能踹,不能摸,只能看(对对象来说就是,对象是只读的)。
 

        1.1.2  为什么赋值运算符的参数应该是引用,而不是普通引用呢?
 

 
        
因为在执行 s1 = s2 时,运算符重载函数的参数代表的是 s2,在把 s2 拷贝给 s1 时,s2的值是不会被改变的,用 const 作为函数参数是在告诉调用者,这个函数保证不修改s2的值:

 
string& operater = (const string& s);      //  函数参数是个常引用,表明该函数不会修改形参 s 的值
 

        另一方面,如果你用的是普通引用,就像这样:
 

string& operater = (string& s);     //  函数参数是个普通引用(不要这么做)
 

        那么常对象将不能被用来为其他对象赋值(这显然不是我们想要的):
 

const string s1("Hello Kitty");    //  s1是个常对象
string s2;
s2 = s1;        // 如果运算符重载函数的参数是普通引用,则 s1 不能作为重载函数的参数,编译不会通过。反之则可以
 

        为什么?普通引用作为函数参数意味着你可以在重载函数中修改s1,而在s1的声明中,你曾经作出承诺s1是不能被改变的。
        说好的不欺负小明,可偏偏在后面欺负“朱大常”,这是编译器不能忍的。

        而对于函数参数是常引用的情况,重载函数也是可以接受普通对象的:

string& operater = (const string& s);     
//  函数参数是个常引用

string s1("Hello Kitty"), s2;
s2 = s1;                                            //  s1 是普通对象,也是可以作为重载函数的实参的

 

        1.1.3 为什么参数是个常引用,而不是普通对象,或者常对象呢?


        
如果参数是对象或常对象:

string& operater = (string s);             //  函数参数是个普通对象(不要这么做)
string& operater = (const string s);    //  函数参数是个常对象(不要这么做)
 

        那么在 s1 = s2 的时候,程序会构造一个 s2 的副本,并将副本赋值给重载函数的形参。
        等等,将副本赋值给重载函数的形参?!程序会调用赋值操作的重载函数,调用赋值重载函数的时候,要将副本赋值给形参,赋值的时候要调用赋值重载函数…………
        这显然不科学。如果函数参数是常引用的话,相当于直接把对象加了只读保护传给赋值重载函数,没有构造副本,没有赋值操作,直接通向了幸福美好的生活。
        事实上,如果一个函数不打算修改传进来的对象,用常引用作为参数是让调用者比较有安全感(const表明对象不会被修改),并且是比较高效的,特别是对于比较庞大的对象(因为不会有构造函数的调用)

1.2 重载函数的返回类型 

 

        1.2.1 赋值运算符的返回类型为什么应该是普通引用,而不是常引用、对象、常对象?

 
        事实上,如果仅仅是满足以下这种情况,返回常引用、对象、常对象都不会出错的:
 

s1 = s2 ;
 

        或者连续赋值:
 

s1 = s2 = s3;
 

        这样的情况,事实上赋值操作是从右往左跑的,就像这样: s1 = (s2 = s3); 
        如果运算符重载函数返回的是常引用,括号里的赋值操作执行的最后会返回对象s2的常引用,并且这个引用代表的对象不能被修改,执行对s1的赋值操作时,之前返回的常引用会被作为参数传入赋值重载函数,这个重载函数的参数是常引用(就像之前说的那样),保证不修改传入的对象,因此没有什么问题。
        返回普通对象,则括号内执行完后会返回s2对象的一个副本,将副本作为参数传给后续的对s1的赋值操作调用的重载函数,那么函数的参数就是这个副本的常引用,也不会有问题。
        返回常对象也是一样,不会有问题。返回对象或常对象和返回引用的区别就在于,返回对象或常对象会有建立副本这个操作,会调用构造函数,效率上会比返回引用差,特别是对象比较庞大的时候。
        既然都可以,是不是可以随意写?最好还是用普通引用为啥?因为如果有偏执狂写出这样的代码:
 

( s1 = s2 ) = s3;
( s1 = s2 ) += s3;
 

        这种情况对于C++固有类型(built-in types)是合法的:
 

int i, j = 1, k = 2;
( i = j ) = k;        // i == 2, j == 1, k == 2
 

        程序会首先用 j 对 i 赋值,再用 k 对 i 赋值。
        这么写的程序员很清楚他想做什么,而不会是出于疏忽。
        这种情况下,如果运算符返回的是常引用或是普通对象或是常对象,将无法满足需求
 

(s1 = s2) = s3; 
 

        若赋值运算符的重载函数返回常引用,s1 = s2 执行后返回的是s1对象的常引用,这个引用代表的对象是不能被修改的,然而后面却试图用 s3 对 s1 进行赋值,试图玩弄C++的感情,编译不会通过的。
        若返回普通对象常对象, s1 = s2 执行后返回的是s1的一个副本,它没有名字,没有归宿,用完之后就会被销毁,对它进行赋值,显然不会是你想要的。
        因此为了保证你写的赋值运算符和C++基本数据类型的赋值操作的行为一致性,最好还是让运算符返回普通引用。
        一致性是非非非非非非常常常常常常重要的,特别是对有强迫症的孩纸。

1.3 总结一下


        赋值运算符的函数参数请使用 reference-to-const 也就是常引用,返回类型请使用普通引用

string& operater = (const string& s);

        这同样适用于其他与赋值相关的运算符,比如 +=、-=、*=、/=、&=、|=、^=、%= ...... 注意:不是与“=”相关的运算符!!而是与“赋值”相关的运算符!!!

        常引用作为参数同样适用于不需要改变传入的对象的函数(对参数只需要读权限的函数),可以避免调用者的误解,也可以提高效率。

2. 算术运算符

 
        + 运算符放在后一篇讲吧。 





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值