• 重载赋值运算符


    1、为什么要重载赋值运算符?
    预定义的赋值运算符"="要求左右两边的操作数类型是匹配的或者至少是兼容的。有时希望赋值运算符两边的操作数即使类型不兼容也能成立。此时可以通过重载赋值运算符来完成这种需求。

    2、重载赋值运算符的特点

    • C++规定,只能将赋值运算符重载为成员函数类型。

    3、举例

    class String {
    private:
    	char * str;
    public:
    	String ():str(new char[1]) { str[0] = 0;}//无参构造函数
    	const char * c_str() { return str; };//普通成员函数
    	String & operator = (const char * s);//重载赋值运算符的符号成员函数
    	String( ) { delete [] str; } //析构成员函数
    };
    
    //重载“=”以使得 obj = “hello”能够成立
    String & String::operator = (const char * s)
    {   
    	delete [] str;
    	str = new char[strlen(s)+1];
    	strcpy( str, s);
    	return * this;
    }
    
    int main()
    {
    	String s;
    	s = "Good Luck," ; //等价于 s.operator=("Good Luck,");  左边是String类类型,右边是string类型。赋值符号两边虽然类型不匹配,但是由于重载了赋值运算符,它能够调用符号函数,使得编译能够通过。
    	cout << s.c_str() << endl;
    	// String s2 = "hello!";  这条语句要是不注释掉就会出错。因为这是初始化语句,而不是赋值语句。重载赋值运算符只在赋值语句中起作用。
    	s = "Shenzhou 8!"; //等价于 s.operator=("Shenzhou 8!");  这是属于赋值语句。
    	cout << s.c_str() << endl;
    	return 0;
    }
    /*
    输出:
    Good Luck,
    Shenzhou 8!
    */
    

    4、该例中存在的问题

    上述代码中执行:

    String s2 = "hello!";
    

    这条语句会发生错误,因为这里的等号并不是赋值的意思,而是初始化的意思。那么如何解决这种定义时初始化的问题呢?

    解决方案:
    可以定义一个类型转换构造函数:

    	String(char* s){
            str = new char[strlen(s)+1];
            strcpy( str, s);
        }
    

    这样,在这行这条语句时,运行的将会是类型转换构造函数。

    5、为什么将赋值运算符函数的返回值写成引用类型?

    首先考虑可选的类型:

    • void
    • String
    • String

    我们写符号函数应该尽量符合符号原有的用法习惯。
    (1)写成void的带来的问题:

    a = b = c;
    

    如果赋值运算符的返回类型为空,那么执行该语句就会出现错误。首先,运算符重载并不改变运算符的优先级和结合性,由于赋值运算符的右结合性,因此先执行b=c,此时能够正确执行,但是执行的结果为void类型,将一个空类型赋值给String类型的a对象,显然会导致错误。但是按照我们原有的对赋值运算符的用法,它是可以被用来连等的。解决这个问题的方法是,将返回值类型设置为String,或者String&。

    (2)写成String带来的问题:

    (a=b)=c;
    

    解释一:
    此时由于括号“()”改变了结合顺序,所以先执行a=b,结果是将b的值赋值给a,这里没有问题。但是,因为函数的返回值类型只有为引用时返回得到的才是左值,为其他类型时返回得到是右值。右值是不可以位于赋值运算符的左侧的,因此该语句会导致结果异常。

    解释二:
    此时由于括号“()”改变了结合顺序,所以先执行a=b,结果是将b的值赋值给a,这里没有问题。该函数表达式调用赋值符号函数,返回一个String类型的临时变量。然后再次调用赋值符号函数,将c的值赋值给该临时变量。因此,输出a的话得到的结果仍是b的值。

    这两条解释的区别在于,赋值符号函数返回的结果是左值,还是非左值。可以在编译器上跑一下是哪种结果。此时可以把=看成是一个函数,这个函数返回值是左值,那么赋值符号返回的就是左值。这个函数返回值是右值,那么赋值符号返回的就是右值。在《C++ primer》一书中提到,函数返回值只有为引用类型时,返回结果才是左值,而这里显然不是左值。所以按照这样分析,应该是解释一成立。

    实际上,无论这两条解释的哪一条成立,导致的结果是:我们不想看到的效果。

    6、实验验证上述解释

    • 将赋值符号函数返回值改为void类型,在Codeblock上面跑此代码。确实如同上面预想的一样。在执行连等的时候会发生意外。
    • 将赋值符号函数返回值改为String类型,在Codeblock上面跑此代码。和上述解释中任何一个都不符合,实验的结果显示,将赋值符号函数返回值更改为String类型后,甚至连一个等号的赋值都会发生意外。代码与结果如下(代码较上述代码有所优化):
    #include<iostream>
    #include<cstring>
    using namespace std;
    class String {
    private:
    	char * str;
    public:
    	String ():str(NULL) { }//无参构造函数
    	const char * c_str() const{ return str; };//普通成员函数
    	String  operator = (const char * s);//重载赋值运算符的符号成员函数
    	~String( ) {
    	if(str)
            delete [] str;
        } //析构成员函数
    };
    
    //重载“=”以使得 obj = “hello”能够成立
    String String::operator = (const char * s)
    {
        cout <<"operator = " << endl;
        if(str)
            delete [] str;
        if(s){
        cout <<"if s " << endl;
            str = new char[strlen(s)+1];
            strcpy( str, s);
        }else{
            str=NULL;
        }
        cout << (*this).c_str() << endl;
    	return *this;
    }
    
    int main()
    {
    	String s,s1,s2;
    	s = "Good Luck,") ; 
        cout <<"s1"<< s1.c_str() << endl;
        s1 = "shenzhou8";
        cout <<"s1:"<< s1.c_str() << endl;
        s2=s1=s;
        cout <<"s2:"<< s2.c_str() << endl;
    	return 0;
    }
    

    在这里插入图片描述

  • 相关阅读:
    1.RabbitMQ简介
    有这样一个需求 element +vue 实现显示的table 的表头添加一个添加图标, 并绑定一个点击事件,我查了好多资料, 终于找到table 表头的一个事件 :render-header 可以实现。
    原生的html js 做的文件上上传
    elment + vue 文件上传
    FastJson对于JSON格式字符串、JSON对象及JavaBean之间的相互转换
    mysql 数据库迁移 sql server (沃尔玛)
    quartz 浅谈 Scheduler
    quartz CronTrigger的cron表达式 详解:
    Linux中的一些常用命令
    类图中常用的六种关系
  • 原文地址:https://www.cnblogs.com/lasnitch/p/12764280.html
Copyright © 2020-2023  润新知