zoukankan      html  css  js  c++  java
  • 第36课 经典问题(上)----重载赋值操作符

    什么时候需要重载赋值操作符?
    编译器是否提供默认的赋值操作符?


    编译器为每个类默认重载了赋值操作符
    默认的赋值操作符仅完成浅拷贝
    当需要进行深度拷贝时必须重载赋值操作符
    赋值操作符与拷贝构造函数有相同的存在意义

    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    private:
        int *m_pointer;
    public:
        Test()
        {
            m_pointer = NULL;
        }
        Test(int i)
        {
            m_pointer = new int(i);
        }
        void print()
        {
            cout << "m_pointer=" << hex << m_pointer << endl;
        }
        ~Test()
        {
            delete m_pointer;
        }
    
    };
    
    int main()
    {
        Test t1 = 1;
        Test t2;
    
        t2 = t1;
    
        t1.print();
        t2.print();
    
        return 0;
    
    }

    编译时可以通过,运行时程序发生崩溃。

    原因:t2 = t1;

    程序在崩溃之前,指向了相同的堆空间,打印完之后,main函数即将结束,将对象t1和t2销毁,将触发析构函数的调用。两次删除同一个堆空间,程序肯定要崩溃,此时必须进行深拷贝。

    只要一个类中它有成员指向了外部的资源,此时必须进行深拷贝,于是必须要重载赋值操作符,在有必要的情况下自定义拷贝构造函数。赋值操作符和拷贝构造函数具有同等重要的意义

    #include <iostream>
    
    using namespace std;
    
    class Test
    {
    private:
        int *m_pointer;
    public:
        Test()
        {
            m_pointer = NULL;
        }
        Test(int i)
        {
            m_pointer = new int(i);
        }
        Test(const Test& obj)  //定义
        {
            m_pointer = new int(*obj.m_pointer);
        }
    
        Test& operator = (const Test& obj)  //赋值操作符的重载,1.返回值必须是一个引用,就是为了连续赋值。2.参数必须是一个const的引用类型。
                                       //3.赋值操作不是自赋值,就是自己赋给自己。4.必须将当前的对象返回。
        {
            if(this != &obj)
            {
                delete m_pointer;  //先将当前对象中的m_pointer指针所指的内存空间删除。下面会重新生成。
                m_pointer = new int(*obj.m_pointer);
            }
    
            return *this;
        }
    
        void print()
        {
            cout << "m_pointer=" << hex << m_pointer << endl;
        }
        ~Test()
        {
            delete m_pointer;
        }
    
    };
    
    int main()
    {
        Test t1 = 1;
        Test t2;
    
        t2 = t1;  //编译器会去看,在当前的Test类中有没有重载赋值操作符。如果发现重载赋值操作符,就使用所定义的实现了。
    
        t1.print();
        t2.print();
    
        return 0;
    
    }

    (1)要熟练记住,重载赋值操作符的那4点注意事项。只要记住了这4点,以后工作中如果遇到重载赋值操作符,基本上就不会出现bug。

    (2)t2 = t1,这个地方它会不会调用拷贝构造函数,即深拷贝。答案是不会的,为什么?

    因为这个地方是赋值,不是初始化。赋值的时候是不会触发拷贝构造函数的调用。这点在前面已经说过。如果是下面的这种形式:

    Test t2 (t1);等价于Test t2 = t1(这是初始化),此时会触发拷贝构造函数的调用。要注意:构造函数只有在定义对象或初始化对象的时候调用,这与赋值操作符是完全不同的,不要混淆。

    (3)假设在程序中,没有重载赋值操作符,t2 = t1;只会调用默认的拷贝构造函数,实现浅拷贝的工作。结果就是两个对象的指针指向了同一片内存空间,程序运行时崩溃。

    (4)在程序中,如果将t2 = t2,会出现什么情况。

    编译可以通过,运行也没有问题,但是没有任何的意义。没有意义,为什么这种情况还存在,就是为了兼容C语言。

    在C语言中,这样是合法的:

    int i = 0;

    i = i

    C++为了兼容C语言,不得已也必须支持这种写法。因此我们在重载赋值操作符的时候,必须处理自赋值的情况。如何处理呢?此处是通过地址判断的。

    数组类的优化

    #ifndef _INTARRAY_H_
    #define _INTARRAY_H_
    
    class IntArray
    {
    private:
        int m_length;
        int* m_pointer;
    
        IntArray(int len);
        IntArray(const IntArray& obj);//拷贝构造函数定义为私有的,它在外部是无法调用的。实际上,使用了二阶构造之后,拷贝构造函数就不起作用了。
                                      //不允许拷贝构造,但是允许赋值,因此赋值操作符的重载还是有必要的。
        bool construct();
    public:
        static IntArray* NewInstance(int length);
        int length();
        bool get(int index, int& value);
        bool set(int index ,int value);
        int& operator [](int index);
        IntArray& operator = (const IntArray& obj);
        IntArray& self();
        ~IntArray();
    };
    
    #endif
    #include "IntArray.h"
    
    IntArray::IntArray(int len)
    {
        m_length = len;
    }
    
    bool IntArray::construct()
    {
        bool ret = true;
    
        m_pointer = new int[m_length];
    
        if( m_pointer )
        {
            for(int i=0; i<m_length; i++)
            {
                m_pointer[i] = 0;
            }
        }
        else
        {
            ret = false;
        }
    
        return ret;
    }
    
    IntArray* IntArray::NewInstance(int length)
    {
        IntArray* ret = new IntArray(length);
    
        if( !(ret && ret->construct()) )
        {
            delete ret;
            ret = 0;
        }
    
        return ret;
    }
    
    int IntArray::length()
    {
        return m_length;
    }
    
    bool IntArray::get(int index, int& value)
    {
        bool ret = (0 <= index) && (index < length());
    
        if( ret )
        {
            value = m_pointer[index];
        }
    
        return ret;
    }
    
    bool IntArray::set(int index, int value)
    {
        bool ret = (0 <= index) && (index < length());
    
        if( ret )
        {
            m_pointer[index] = value;
        }
    
        return ret;
    }
    int& IntArray::operator [](int index)
    {
        return m_pointer[index];
    }
    IntArray& IntArray::operator =(const IntArray& obj)
    {
        if(this != &obj)
        {
           int* pointer = new int[obj.m_length];
    
           for(int i=0; i<obj.m_length; i++)
           {
                pointer[i] = obj.m_pointer[i];
           }
           m_length = obj.m_length;
           delete[] m_pointer;
           m_pointer = pointer;
        }
    
        return *this;
    }
    IntArray& IntArray::self()
    {
        return *this; //返回this指针指代的当前对象即可。
    }
    IntArray::~IntArray()
    {
        delete[]m_pointer;
    }
    #include <iostream>
    #include <string>
    #include "IntArray.h"
    
    using namespace std;
    
    int main()
    {
        IntArray* a = IntArray::NewInstance(5);
        IntArray* b = IntArray::NewInstance(10);
    
        if( a && b )
        {
            IntArray& array = a->self();
            IntArray& brray = b->self();
    
            cout << "array.length() = " << array.length() << endl;
            cout << "brray.length() = " << brray.length() << endl;
    
            array = brray;
    
            cout << "array.length() = " << array.length() << endl;
            cout << "brray.length() = " << brray.length() << endl;
        }
    
        delete a;
        delete b;
    
        return 0;
    }

    一般性原则:重载赋值操作符,必然需要实现深拷贝。反过来,如果要实现深拷贝,就必须提供赋值操作符的自定义实现以及自定义拷贝构造函数。

    编译器默认提供的函数

     补充:

    1. new int[]是创建一个int型数组,数组大小是在[]中指定
    int *p = new int[3];//申请一个动态整形数组,数组的长度为[]中的值
    2. new int()是创建一个int型数,并且用()括号中的数据进行初始化,例如:
    int *p = new int(10); //p指向一个值为10的int数。

  • 相关阅读:
    FlatBuffers要点
    tarjan+缩点+强连通定理
    编程之美2.16 最长递增子序列
    Android Studio之多个Activity的滑动切换(二)
    Effective java读书札记第一条之 考虑用静态工厂方法取代构造器
    【PM】关于系统数据库和服务现场升级的一些看法
    用户及权限基础 2---- 权限
    Android双向滑动菜单完全解析,教你如何一分钟实现双向滑动特效
    【转贴】gdb中的信号(signal)相关调试技巧
    基于新浪sae使用php生成图片发布图文微博
  • 原文地址:https://www.cnblogs.com/-glb/p/11924831.html
Copyright © 2011-2022 走看看