精华内容
下载资源
问答
  • 赋值运算符重载函数继承了吗?

    千次阅读 2013-08-24 14:36:56
    C++语法规定赋值运算符重载函数不能被继承。关于这一点我的理解是赋值运算符和构造、析构函数一样,都是为类量身定做的,继承没有意义,所以编译器屏蔽了这个继承。  最近在网上看到一篇文章...

           C++语法规定赋值运算符重载函数不能被继承。关于这一点我的理解是赋值运算符和构造、析构函数一样,都是为类量身定做的,继承没有意义,所以编译器屏蔽了这个继承。

        最近在网上看到一篇文章(http://blog.csdn.net/pbmichael/article/details/1608250),认为“‘赋值运算符重载函数’不是不能被派生类继承,而是被派生类的默认‘赋值运算符重载函数’给覆盖了。这就是C++赋值运算符重载函数不能被派生类继承的真实原因!”这个观点是不对的,真实的情况如开头所述,赋值运算符不是被覆盖,而是根本没有继承。

        为论证该文章中的观点是错误的,我首先要说明不是所有类都自动生成赋值运算符的。当类中含有常数据成员或者引用时,赋值运算符不自动生成,请看下面程序1。由于类B1含有常数据成员和引用,所以编译器并没有自动给其生成赋值运算符,对语句"v=w"进行了报错

    //程序1 

    #include <iostream>
    using namespace std;

    class B1 
    {
    public:
            const int m;
            int &p;
            B1(int i=0, int j=0);
    };
    B1::B1(int i, int j):m(i),p(j)
    {cout<<"B1 constructed;"<<endl;}

    void main()
    {        
            B1 v,w;
            v=w;
    //报错error C2582: 'B1' : 'operator =' function is unavailable
    }

    下面用程序1中没有自动生成赋值运算符的类B1去继承一个重载了赋值运算符的类A1,见程序2。对语句"v=2"同样报错,说明类B1中并没有赋值运算符的定义,即没有从A1中继承赋值运算符。

    //程序2
    #include <iostream>
    using namespace std;

    class A1
    {
    public:
            int operator=(int a)
            {
              return 1;
            }   
    };

    class B1 : public A1
    {
    public:
            const int m;
            int &p;
            B1(int i=0, int j=0);
    };
    B1::B1(int i, int j):m(i),p(j)
    {cout<<"B1 constructed;"<<endl;}

    void main()
    {        
        A1 a;    
        B1 v,w;
        cout << (a = 2) << endl; 
    // 正确,输出1

        cout << (v = 2) << endl;

    //报错:error C2582: 'B1' : 'operator =' function is unavailable
    }

        从上面的例子我们可以验证我们的结论:C++中赋值运算符重载函数没有被继承,而不是由于派生类生成的新赋值运算符覆盖了基类的赋值运算符。

    展开全文
  • 目录 背景: 正文: 1、举例 2、参数解析: ...5、提供默认赋值运算符重载函数的时机 ...6、构造函数还是赋值运算符重载函数 ...7、显式提供赋值运算符重载函数的时机 ...9、赋值运算符重载函数...10、赋值运算符重载函数...

                                               目录

     

    背景:

     正文:

    1、举例

    2、参数解析:

    3、返回值

    4、调用时机

    5、提供默认赋值运算符重载函数的时机

     6、构造函数还是赋值运算符重载函数

    7、显式提供赋值运算符重载函数的时机

    8、浅拷贝和深拷贝

    9、赋值运算符重载函数只能是类的非静态的成员函数

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

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


    背景:

           做项目的时候,要往类的其中一个属性(int num)赋值,死活赋不进去,后来发现框架写了赋值运算符重载函数,没有num的赋值,在这个函数内写上就可以解决。当然还有另一种方法,如果赋值运算符重载函数里面没有其他内容时,可以注释,让编译器选择默认的赋值运算符重载函数。

     正文:

    1、举例

    #include<iostream>
    #include<string>
    using namespace std;
    
    class MyStr
    {
    private:
        char *name;
        int id;
    public:
        MyStr() {}
        MyStr(int _id, char *_name)   //constructor
        {
            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;
            if (name != NULL)
                delete[] name;
            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;
    }

    结果:

      

    2、参数解析:

    一般地,赋值运算符重载函数的参数是函数所在类的const类型的引用(如上面例1),加const是因为

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

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

    用引用是因为:

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

    注意

    上面的规定都不是强制的,可以不加const,也可以没有引用,甚至参数可以不是函数所在的对象,正如后面例2中的那样。

    3、返回值

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

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

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

    注意

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

    4、调用时机

    当为一个类对象赋值(注意:可以用本类对象为其赋值(如上面例1),也可以用其它类型(如内置类型)的值为其赋值,关于这一点,见后面的例2)时,会由该对象调用该类的赋值运算符重载函数。

    如上边代码中

    str2 = str1;

    一句,用str1为str2赋值,会由str2调用MyStr类的赋值运算符重载函数。

    需要注意的是,

    MyStr str2;

    str2 = str1;

    MyStr str3 = str2;

    在调用函数上是有区别的。正如我们在上面结果中看到的那样。

          前者MyStr str2;一句是str2的声明加定义,调用无参构造函数,所以str2 = str1;一句是在str2已经存在的情况下,用str1来为str2赋值,调用的是拷贝赋值运算符重载函数;而后者,是用str2来初始化str3,调用的是拷贝构造函数。

    5、提供默认赋值运算符重载函数的时机

           当程序没有显式地提供一个以本类或本类的引用为参数的赋值运算符重载函数时,编译器会自动生成这样一个赋值运算符重载函数。注意我们的限定条件,不是说只要程序中有了显式的赋值运算符重载函数,编译器就一定不再提供默认的版本,而是说只有程序显式提供了以本类或本类的引用为参数的赋值运算符重载函数时,编译器才不会提供默认的版本。可见,所谓默认,就是“以本类或本类的引用为参数”的意思。

    #include<iostream>
    #include<string>
    using namespace std;
    
    class Data
    {
    private:
        int data;
    public:
        Data() {};
        Data(int _data)
            :data(_data)
        {
            cout << "constructor" << endl;
        }
        Data& operator=(const int _data)
        {
            cout << "operator=(int _data)" << endl;
            data = _data;
            return *this;
        }
    };
    int main()
    {
        Data data1(1);
        Data data2,data3;
        cout << "=====================" << endl;
        data2 = 1;
        cout << "=====================" << endl;
        data3 = data2;
        return 0;
    }

    结果:

     6、构造函数还是赋值运算符重载函数

    如果我们将上面例子中的赋值运算符重载函数注释掉,main函数中的代码依然可以编译通过。只不过结论变成了

    可见,当用一个非类A的值(如上面的int型值)为类A的对象赋值时

    如果匹配的构造函数和赋值运算符重载函数同时存在(如例2),会调用赋值运算符重载函数。

    如果只有匹配的构造函数存在,就会调用这个构造函数。

    7、显式提供赋值运算符重载函数的时机

    用非类A类型的值为类A的对象赋值时(当然,从5中可以看出,这种情况下我们可以不提供相应的赋值运算符重载函数而只提供相应的构造函数来完成任务)。

    当用类A类型的值为类A的对象赋值且类A的成员变量中含有指针时,为避免浅拷贝(关于浅拷贝和深拷贝,下面会讲到),必须显式提供赋值运算符重载函数(如例1)。

    8、浅拷贝和深拷贝

          拷贝构造函数和赋值运算符重载函数都会涉及到这个问题。

          所谓浅拷贝,就是说编译器提供的默认的拷贝构造函数和赋值运算符重载函数,仅仅是将对象a中各个数据成员的值拷贝给对象b中对应的数据成员(这里假设a、b为同一个类的两个对象,且用a拷贝出b或用a来给b赋值),而不做其它任何事。

          假设我们将例1中显式提供的拷贝构造函数注释掉,然后同样执行MyStr str3 = str2;语句,此时调用默认的拷贝构造函数,它只是将str2的id值和nane值拷贝到str3,这样,str2和str3中的name值是相同的,即它们指向内存中的同一区域(在例1中,是字符串”hhxx”)。如下图

                                                         

    这样,会有两个致命的错误

    ①当我们通过str2修改它的name时,str3的name也会被修改!

    ②当执行str2和str3的析构函数时,会导致同一内存区域释放两次,程序崩溃!

          这是万万不可行的,所以我们必须通过显式提供拷贝构造函数以避免这样的问题。就像我们在例1中做的那样,先判断被拷贝者的name是否为空,若否,delete[] name(后面会解释为什么要这么做),然后,为name重新申请空间,再将拷贝者name中的数据拷贝到被拷贝者的name中。执行后,如图

                                                             

    这样,str2.name和str3.name各自独立,避免了上面两个致命错误。

    我们是以拷贝构造函数为例说明的,赋值运算符重载函数也是同样的道理。

    9、赋值运算符重载函数只能是类的非静态的成员函数

           C++规定,赋值运算符重载函数只能是类的非静态的成员函数,不能是静态成员函数,也不能是友元函数。关于原因,有人说,赋值运算符重载函数往往要返回*this,而无论是静态成员函数还是友元函数都没有this指针。这乍看起来很有道理,但仔细一想,我们完全可以写出这样的代码

    
    
    static friend MyStr& operator=(const MyStr str1,const MyStr str2)
    {
        ……
        return str1;
    }
    
    

          可见,这种说法并不能揭露C++这么规定的原因。

          其实,之所以不是静态成员函数,是因为静态成员函数只能操作类的静态成员,不能操作非静态成员。如果我们将赋值运算符重载函数定义为静态成员函数,那么,该函数将无法操作类的非静态成员,这显然是不可行的。

          在前面的讲述中我们说过,当程序没有显式地提供一个以本类或本类的引用为参数的赋值运算符重载函数时,编译器会自动提供一个。现在,假设C++允许将赋值运算符重载函数定义为友元函数并且我们也确实这么做了,而且以类的引用为参数。与此同时,我们在类内却没有显式提供一个以本类或本类的引用为参数的赋值运算符重载函数。由于友元函数并不属于这个类,所以,此时编译器一看,类内并没有一个以本类或本类的引用为参数的赋值运算符重载函数,所以会自动提供一个。此时,我们再执行类似于str2=str1这样的代码,那么,编译器是该执行它提供的默认版本呢,还是执行我们定义的友元函数版本呢?

           为了避免这样的二义性,C++强制规定,赋值运算符重载函数只能定义为类的成员函数,这样,编译器就能够判定是否要提供默认版本了,也不会再出现二义性。

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

    #include<iostream>
    #include<string>
    using namespace std;
    
    class A
    {
    public:
        int X;
        A() {}
        A& operator =(const int x)
        {
            X = x;
            return *this;
        }    
    };
    class B :public A
    {
    public:
        B(void) :A() {}
    };
    
    int main()
    {
        A a;
        B b;
        a = 45;
        //b = 67;
        (A)b = 67;
        return 0;
    }

     

    注释掉的一句无法编译通过。报错提示:没有与这些操作数匹配的”=”运算符。对于b = 67;一句,首先,没有可供调用的构造函数(前面说过,在没有匹配的赋值运算符重载函数时,类似于该句的代码可以调用匹配的构造函数),此时,代码不能编译通过,说明父类的operator =函数并没有被子类继承。

         为什么赋值运算符重载函数不能被继承呢?

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

         所以,C++规定,赋值运算符重载函数不能被继承。

        上面代码中, (A)b = 67; 一句可以编译通过,原因是我们将B类对象b强制转换成了A类对象。

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

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

         为什么要避免自赋值呢?

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

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

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

    感谢博主同勉共进,转自:https://www.cnblogs.com/zpcdbky/p/5027481.html

     

    展开全文
  • 目录 Ⅰ.举例 Ⅱ.... Ⅲ....Ⅳ....Ⅴ.提供默认赋值运算符重载函数的时机 ...Ⅵ.构造函数还是赋值运算符重载... 赋值运算符重载函数不能被继承 Ⅺ.赋值运算符重载函数要避免自赋值 转自:https://www.cnblogs.com/zpcdbky/...

    目录

    Ⅰ.举例

    Ⅱ.参数

    Ⅲ.返回值

    Ⅳ.调用时机

    Ⅴ.提供默认赋值运算符重载函数的时机

    Ⅵ.构造函数还是赋值运算符重载函数

    Ⅶ.显式提供赋值运算符重载函数的时机

    Ⅷ.浅拷贝和深拷贝

    Ⅸ.赋值运算符重载函数只能是类的非静态的成员函数

    Ⅹ. 赋值运算符重载函数不能被继承

    Ⅺ.赋值运算符重载函数要避免自赋值


    转自:https://www.cnblogs.com/zpcdbky/p/5027481.html


    .举例

    例1

    #include<iostream>
    #include<string>
    using namespace std;
    
    class MyStr
    {
    private:
        char *name;
        int id;
    public:
        MyStr() {}
        MyStr(int _id, char *_name)   //constructor
        {
            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;
            if (name != NULL)
                delete name;
            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;
    }

    结果:


    .参数

    一般地,赋值运算符重载函数的参数是函数所在类的const类型的引用(如上面例1),加const是因为:

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

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

    用引用是因为:

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

    注意

    上面的规定都不是强制的,可以不加const,也可以没有引用,甚至参数可以不是函数所在的对象,正如后面例2中的那样。


    .返回值

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

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

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

    注意

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


    .调用时机

          当为一个类对象赋值(注意:可以用本类对象为其赋值(如上面例1),也可以用其它类型(如内置类型)的值为其赋值,关于这一点,见后面的例2)时,会由该对象调用该类的赋值运算符重载函数。

    如上边代码中

    str2 = str1;

    一句,用str1为str2赋值,会由str2调用MyStr类的赋值运算符重载函数。

    需要注意的是,

    MyStr str2;

    str2 = str1;

    MyStr str3 = str2;

    在调用函数上是有区别的。正如我们在上面结果中看到的那样。

          前者MyStr str2;一句是str2的声明加定义,调用无参构造函数,所以str2 = str1;一句是在str2已经存在的情况下,用str1来为str2赋值,调用的是拷贝赋值运算符重载函数;而后者,是用str2来初始化str3,调用的是拷贝构造函数。


    .提供默认赋值运算符重载函数的时机

          当程序没有显式地提供一个以本类或本类的引用为参数的赋值运算符重载函数时,编译器会自动生成这样一个赋值运算符重载函数。注意我们的限定条件,不是说只要程序中有了显式的赋值运算符重载函数,编译器就一定不再提供默认的版本,而是说只有程序显式提供了以本类或本类的引用为参数的赋值运算符重载函数时,编译器才不会提供默认的版本。可见,所谓默认,就是“以本类或本类的引用为参数”的意思。

    见下面的例2

    #include<iostream>
    #include<string>
    using namespace std;
    
    class Data
    {
    private:
        int data;
    public:
        Data() {};
        Data(int _data)
            :data(_data)
        {
            cout << "constructor" << endl;
        }
        Data& operator=(const int _data)
        {
            cout << "operator=(int _data)" << endl;
            data = _data;
            return *this;
        }
    };
    
    int main()
    {
        Data data1(1);
        Data data2,data3;
        cout << "=====================" << endl;
        data2 = 1;
        cout << "=====================" << endl;
        data3 = data2;
        return 0;
    }

    结果:

         上面的例子中,我们提供了一个带int型参数的赋值运算符重载函数,data2 = 1;一句调用了该函数,如果编译器不再提供默认的赋值运算符重载函数,那么,data3 = data2;一句将不会编译通过,但我们看到事实并非如此。所以,这个例子有力地证明了我们的结论。


    .构造函数还是赋值运算符重载函数

         如果我们将上面例子中的赋值运算符重载函数注释掉,main函数中的代码依然可以编译通过。只不过结论变成了

    可见,当用一个非类A的值(如上面的int型值)为类A的对象赋值时

    ①如果匹配的构造函数和赋值运算符重载函数同时存在(如例2),会调用赋值运算符重载函数。

    ②如果只有匹配的构造函数存在,就会调用这个构造函数。


    .显式提供赋值运算符重载函数的时机

    ①用非类A类型的值为类A的对象赋值时(当然,从Ⅵ中可以看出,这种情况下我们可以不提供相应的赋值运算符重载函数而只提供相应的构造函数来完成任务)。

    ②当用类A类型的值为类A的对象赋值且类A的成员变量中含有指针时,为避免浅拷贝(关于浅拷贝和深拷贝,下面会讲到),必须显式提供赋值运算符重载函数(如例1)。


    .浅拷贝和深拷贝

          拷贝构造函数和赋值运算符重载函数都会涉及到这个问题。

          所谓浅拷贝,就是说编译器提供的默认的拷贝构造函数和赋值运算符重载函数,仅仅是将对象a中各个数据成员的值拷贝给对象b中对应的数据成员(这里假设a、b为同一个类的两个对象,且用a拷贝出b或用a来给b赋值),而不做其它任何事。

          假设我们将例1中显式提供的拷贝构造函数注释掉,然后同样执行MyStr str3 = str2;语句,此时调用默认的拷贝构造函数,它只是将str2的id值和nane值拷贝到str3,这样,str2和str3中的name值是相同的,即它们指向内存中的同一区域(在例1中,是字符串”hhxx”)。如下图

                                                      

         这样,会有两个致命的错误

    ①当我们通过str2修改它的name时,str3的name也会被修改!

    ②当执行str2和str3的析构函数时,会导致同一内存区域释放两次,程序崩溃!

          这是万万不可行的,所以我们必须通过显式提供拷贝构造函数以避免这样的问题。就像我们在例1中做的那样,先判断被拷贝者的name是否为空,若否,dalete name(后面会解释为什么要这么做),然后,为name重新申请空间,再将拷贝者name中的数据拷贝到被拷贝者的name中。执行后,如图

                                                       

          这样,str2.name和str3.name各自独立,避免了上面两个致命错误。

          我们是以拷贝构造函数为例说明的,赋值运算符重载函数也是同样的道理。


    .赋值运算符重载函数只能是类的非静态的成员函数

           C++规定,赋值运算符重载函数只能是类的非静态的成员函数,不能是静态成员函数,也不能是友元函数。关于原因,有人说,赋值运算符重载函数往往要返回*this,而无论是静态成员函数还是友元函数都没有this指针。这乍看起来很有道理,但仔细一想,我们完全可以写出这样的代码

    static friend MyStr& operator=(const MyStr str1,const MyStr str2)
    {
        ……
        return str1;
    }

          可见,这种说法并不能揭露C++这么规定的原因。

          其实,之所以不是静态成员函数,是因为静态成员函数只能操作类的静态成员,不能操作非静态成员。如果我们将赋值运算符重载函数定义为静态成员函数,那么,该函数将无法操作类的非静态成员,这显然是不可行的。

          在前面的讲述中我们说过,当程序没有显式地提供一个以本类或本类的引用为参数的赋值运算符重载函数时,编译器会自动提供一个。现在,假设C++允许将赋值运算符重载函数定义为友元函数并且我们也确实这么做了,而且以类的引用为参数。与此同时,我们在类内却没有显式提供一个以本类或本类的引用为参数的赋值运算符重载函数。由于友元函数并不属于这个类,所以,此时编译器一看,类内并没有一个以本类或本类的引用为参数的赋值运算符重载函数,所以会自动提供一个。此时,我们再执行类似于str2=str1这样的代码,那么,编译器是该执行它提供的默认版本呢,还是执行我们定义的友元函数版本呢?

           为了避免这样的二义性,C++强制规定,赋值运算符重载函数只能定义为类的成员函数,这样,编译器就能够判定是否要提供默认版本了,也不会再出现二义性。


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

    见下面的例3

    #include<iostream>
    #include<string>
    using namespace std;
    
    class A
    {
    public:
        int X;
        A() {}
        A& operator =(const int x)
        {
            X = x;
            return *this;
        }    
    };
    class B :public A
    {
    public:
        B(void) :A() {}
    };
    
    int main()
    {
        A a;
        B b;
        a = 45;
        //b = 67;
        (A)b = 67;
        return 0;
    }

          注释掉的一句无法编译通过。报错提示:没有与这些操作数匹配的”=”运算符。对于b = 67;一句,首先,没有可供调用的构造函数(前面说过,在没有匹配的赋值运算符重载函数时,类似于该句的代码可以调用匹配的构造函数),此时,代码不能编译通过,说明父类的operator =函数并没有被子类继承。

         为什么赋值运算符重载函数不能被继承呢?

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

         所以,C++规定,赋值运算符重载函数不能被继承。

        上面代码中, (A)b = 67; 一句可以编译通过,原因是我们将B类对象b强制转换成了A类对象。


    Ⅺ.赋值运算符重载函数要避免自赋值

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

         为什么要避免自赋值呢?

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

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

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

    展开全文
  • 标题的问题同样可以是:为什么赋值...有一篇文章说得比较好,连接在此:为什么C++赋值运算符重载函数不能被继承?,推测过程如下:class A1 { public: int operator=(int a) { return 8; } int operator+(...

    标题的问题同样可以是:为什么赋值运算符operator=()不能使用虚函数。

    有一篇文章说得比较好,连接在此:为什么C++赋值运算符重载函数不能被继承?,推测过程如下:

    class A1
    {
    public:
            int operator=(int a)
            {
                    return 8;
            }
    
            int operator+(int a)
            {
                    return 9;
            }
    };
    
    class B1 : public A1
    {
    public:
            int operator-(int a)
            {
                    return 7;
            }
    };
    
    int main()
    {        
            B1 v;
            cout << (v + 2) << endl; // OK, print 9
            cout << (v - 2) << endl; // OK, print 7
            cout << (v = 2) << endl; // Error, see below
    
            return 0;
    }

    VC编译器的错误提示:

    error C2679: binary '=' : no operator defined which takes a right-hand operand of type 'const int' (or there is no acceptable conversion)

    明显是找不到定义,但很明显这份代码的意图就是让派生类对象使用基类继承下来的赋值运算符来返回一个整数。但是却找不到形如const int operator=(const int )的函数定义。

    这份代码比较有帮助就是在于,一般的=都是把自身的类型转化为自身的引用。但是这个=确实以int为参数,类类型的引用为返回的操作符。因此能看出问题,所以罗列线索(线索转自链接文章原文):

    1,每一个类对象实例在创建的时候,如果用户没有定义“赋值运算符重载函数”,那么,编译器会自动生成一个隐含和默认的“赋值运算符重载函数”。所以,B1的实际上的声明应该类似于下面这种情况:
    class A1
    {
    public:
            int operator=(int a)
            {
                    return 8;
            }


            int operator+(int a)
            {
                    return 9;
            }
    };
    class B1 : public A1
    {
    public:
            B1& operator =(const B1& robj); // 注意这一行是编译器添加的
            int operator-(int a)
            {
                    return 7;
            }
    };

    2,C++标准规定:如果派生类中声明的成员与基类的成员同名,那么,基类的成员会被覆盖,哪怕基类的成员与派生类的成员的数据类型和参数个数都完全不同。显然,B1中的赋值运算符函数名operator =和基类A1中的operator =同名,所以,A1中的赋值运算符函数int operator=(int a);被B1中的隐含的赋值运算符函数B1& operator =(const B1& robj);所覆盖。 A1中的int operator=(int a);函数无法被B1对象访问。

    3,程序中语句v = 2实际上相当于v.operator =(2);,但是A1中的int operator=(int a);已经被覆盖,无法访问。而B1中默认的B1& operator =(const B1& robj);函数又与参数2的整数类型不相符,无法调用。

    4,为了确认B1中默认的B1& operator =(const B1& robj);函数的存在性,可以用以下代码验证:
    B1 b;
    B1 v;
    v = b; // OK, 相当于调用v.operator =(b);

    5,所以,“赋值运算符重载函数”不是不能被派生类继承,而是被派生类的默认“赋值运算符重载函数”给覆盖了。

    这就是C++赋值运算符重载函数不能被派生类继承的真实原因!

    这里面呢,我发现了一点我觉得有问题的地方,就在于第2条“如果派生类中声明的成员与基类的成员同名,那么,基类的成员会被覆盖”,只有虚函数才有这种覆盖的特性,如果是非虚函数的话,还是以对象的引用或者指针的类型决定了operator=()使用基类还是派生类的。因此,在文章的例子中出现的问题并不在于A1的方法是否被覆盖,而是由于“B1中默认的B1& operator =(const B1& robj);函数又与参数2的整数类型不相符,无法调用”。这就回答了原标题的问题:“为什么赋值运算符不能被继承”,如果本例的计算流程变一下,类型匹配之后也许会发现是可以被继承的。

            这之后,再回到自己提的问题“为什么赋值运算符operator=()不能使用虚函数”。

    假设基类中的operator=()为虚函数,同样举个例子:

    DerivedClass a;//派生类某对象a

    DerivedClass b;//派生类某对象b

    BaseClass &c = a;//基类某指针、或者引用都行c

    BaseClass &d = b;//基类某指针、或者引用都行d

    a = b;//调用A.operator=(const DerivedClass & B)

    如果B中忘记自己定义的赋值操作符了,那么编译器会给我们生成默认的操作符,在a的拷贝过程中,将会采用此默认的operator=(),即浅拷贝赋值的方法逐成员调用=(注:派生类成员),这对非动态管理的派生类没有问题,但是对于有字符串或new操作的派生类来说却是不行的。但是呢,如果由用户自己重新定义了虚赋值操作符,那么通过正确的改写是可以正确地实现赋值。

    反观如果不用虚函数,使用基类的赋值操作符来进行逐成员赋值(注:基类成员改变、派生类成员不变)同样会产生问题。所以结论和之前的一样,不是不行,而是需要结合具体的情况去正确地实现。


    展开全文
  • 为什么C++赋值运算符重载函数不能被继承? (zz:http://www.cqumzh.cn/topic_show.php?tid=134570) 这个问题曾经困扰过我一阵子。请先看一下下面的源代码: class A1 { public:  int perator=(int a) ...
  •  关于C++的赋值运算符重载函数(operator=),网络以及各种教材上都有很多介绍,但可惜的是,内容大多雷同且不全面。面对这一局面,在下在整合各种资源及融入个人理解的基础上,整理出一篇较为全面/详尽的文章,以飨...
  • C++赋值运算符重载函数(operator=)

    千次阅读 2018-01-23 20:28:34
     关于C++的赋值运算符重载函数(operator=),网络以及各种教材上都有很多介绍,但可惜的是,内容大多雷同且不全面。面对这一局面,在下在整合各种资源及融入个人理解的基础上,整理出一篇较为全面/详尽的文章,以飨...
  • 一文说尽C++赋值运算符重载函数(operator=) 在前面:  关于C++的赋值运算符重载函数(operator=),网络以及各种教材上都有很多介绍,但可惜的是,内容大多雷同且不全面。面对这一局面,在下在整合各种...
  •  为什么C++赋值运算符重载函数不能被继承? 本文转帖自呵呵的博客,http://hi.baidu.com/chplj/blog/item/c3f69d820031bcbf6c8119f6.html<br />为什么C++赋值运算符重载函数不能被继承?   ...
  • 关于C++的赋值运算符重载函数(operator=),网络以及各种教材上都有很多介绍,但可惜的是,内容大多雷同且不全面。面对这一局面,在下在整合各种资源及融入个人理解的基础上,整理出一篇较为全面/详尽的文章,以飨...
  • Time Time::operator++() //重载++前置运算符 { return Time(year,month,++day); //注意不要成day+1,因为这样不会改 //变day的值,Time的值不会发生任何的变化 } Time Time::operator --(int) //重载--后置...
  • 在C++继承中,编译器默认生成的赋值运算符重载函数的实现中,将会进行如下操作: 1. 对当前类中数据进行逐个赋值 2. 调用父类的赋值运算符重载函数 说明:如果在某个类中手动实现了赋值运算符重载函数,则编译器...
  • 文章目录const修饰成员函数友元全局函数做友元类做友元成员函数做友元运算符重载加号运算符重载左移运算符重载递增运算符重载赋值运算符重载关系运算符重载函数调用运算符重载继承基本语法继承方式继承中的对象模型...
  • C++ 允许在同一作用域中的某个函数和运算符指定多个定义分别称为函数重载和运算符重载。 重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明,但是它们的参数列表和定义 (实现) 不相同。 ...
  • 运算符重载 对于面向对象的程序设计来说,运算符重载可以完成两个对象之间的复杂操作...为了重载运算符,首先要定义运算符重载函数,它通常是类的非静态成员函数或者友元函数,运算符的操作数通常也应为对象。 定...
  • 主要包括类的声明、对象定义、构造函数和析构函数运算符重载继承和派生、多态性实现等。 课程需要有C语言程序设计的基础(可以利用本人开出的《C语言与程序设计》系列课学习)。学习者能够通过...
  • c++多态分为静态多态和动态多态。资料上显示静态多态通过模块和函数重载来实现的,动态...当运算符重载为类的成员函数时,函数的参数个数比原来的少一,因为第一个参数默认为this指针隐式传递。 Time operator+(Tim

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 57,775
精华内容 23,110
关键字:

继承写运算符重载函数