zoukankan      html  css  js  c++  java
  • C++ Primer 学习笔记_60_重载操作符与转换 --赋值、下标、成员訪问操作符

    重载操作符与转换

    --赋值、下标、成员訪问操作符



    一、赋值操作符

        类赋值操作符接受类类型形參,通常该形參是对类类型的const引用,但也能够是类类型或对类类型的非const引用。假设未定义这个操作符,则编译器将合成它。类赋值操作符必须是类的成员,以便编译器能够知道是否须要合成一个。并且还能够为一个类定义很多附加的赋值操作符,这些赋值操作符会由于右操作数的不同而构成重载!如string类型:

        string car("Volks");
        car = "Studebaker"; //接受C风格字符串实參
    
        string model;
        model = 'T';        //接受字符实參
        model = car;        //接受string实參
    


       为了支持这些操作符,string类应包括例如以下的成员:

    class string
    {
    public:
        string &operator=(const string &);
        string &operator=(const char *);
        string &operator=(char);
    
        //...
    };

       赋值操作符能够重载,不管形參为何种类型,赋值操作符都必须定义为成员函数!这一点与复合赋值操作符不同。


    赋值必须返回*this的引用

       为了与内置类型达成一致,赋值返回一个引用,同一时候也能够不用创建和撤销结果的暂时副本。返回值一般是左操作数的引用:

    Sales_item &Sales_item::operator+=(const Sales_item &rhs)
    {
        units_sold += rhs.units_sold;
        revenue += rhs.revenue;
    
        return *this;
    }
    

    【最佳实践】

        一般而言:赋值操作符复合赋值操作符应返回左操作数的引用

    //P442 习题14.14
    Sales_item &Sales_item::operator=(const string &str)
    {
        isbn = str;
    
        return *this;
    }
    


    //习题14.15/16
    class CheckoutRecord
    {
    public:
        typedef unsigned Date;
        //...
        CheckoutRecord &operator=(const CheckoutRecord &rhs);
        //新加入�一本书
        CheckoutRecord &operator=(const string &);
        //新加入�一位等待读者
        CheckoutRecord &operator=(const pair<string,string> &);
    
    private:
        double book_id;
        string title;
        Date date_borrowed;
        Date date_due;
        pair<string,string> borrower;
        vector< pair<string,string> * > wait_list;
    };
    
    CheckoutRecord &CheckoutRecord::operator=(const CheckoutRecord &rhs)
    {
        book_id = rhs.book_id;
        title = rhs.title;
        date_borrowed = rhs.date_borrowed;
        date_due = rhs.date_due;
        borrower = rhs.borrower;
    
        //为vector赋值
        wait_list.clear();
        for (vector< pair<string,string> * >::const_iterator iter = rhs.wait_list.begin();
                iter != rhs.wait_list.end(); ++iter)
        {
            pair<string,string> *ppair = new pair<string,string>;
            *ppair = **iter;    //复制的是,vector中的对象,而不是指针!
    
            wait_list.push_back(ppair);
        }
    
        return *this;
    }
    
    CheckoutRecord &CheckoutRecord::operator=(const string &Title)
    {
        title = Title;
    
        return *this;
    }
    
    CheckoutRecord &CheckoutRecord::operator=(const pair<string,string> &newReader)
    {
        pair<string,string> *ppair = new pair<string,string>;
        *ppair = newReader;
    
        wait_list.push_back(ppair);
    
        return *this;
    }
    

    二、下标操作符

       能够从容器中检索单个元素的容器类通常会定义下标操作符,即operator[]。如vectorstring

       下标操作符必须定义为类成员函数


    1、提供读写訪问

         定义下标操作符比較复杂的地方在于,它在用作赋值的左右操作符数时都应该能表现正常。下标操作符出如今左边,必须生成左值,能够指定引用作为返回类型而得到左值。仅仅要下标操作符返回引用,就可用作赋值的随意一方。

    【最佳实践】

        类定义下标操作符时,一般须要定义两个版本号:一个为非const成员并返回引用,还有一个为const成员并返回引用!


    2、原型下标操作

    class Foo
    {
    public:
        int &operator[] (const size_t);
        const int &operator[] (const size_t) const;
    
    private:
        vector<int> data;
    };
    
    int &Foo::operator[](const size_t index)
    {
        return data[index];	//注意:没有范围检測...
    }
    
    //除了函数原型不同之外,没有其它不论什么区别!
    const int &Foo::operator[] (const size_t index) const
    {
        return data[index];
    }
    

    //P443 习题14.17/18/19
    class CheckoutRecord
    {
    public:
        typedef unsigned Date;
        typedef vector< pair<string,string> *>::size_type sizeType;
        //...
    
        pair<string,string> &operator[] (const size_t);
        const pair<string,string> &operator[] (const size_t) const;
    
        const pair<string,string> &get_a_waiter(sizeType) const;
    
    private:
        double book_id;
        string title;
        Date date_borrowed;
        Date date_due;
        pair<string,string> borrower;
        vector< pair<string,string> * > wait_list;
    };
    
    pair<string,string> &CheckoutRecord::operator[](const size_t index)
    {
        return *wait_list.at(index);
    }
    
    const pair<string,string> &CheckoutRecord::operator[](const size_t index) const
    {
        return *wait_list.at(index);
    }
    
    /**尽管下标操作符使用简单,可是他也有他的缺点
    *如:下标操作符适合于表示“获取容器中特定元素”的语义
    *而获取一个等待者则语义不明
    *这样就不如使用一个普通的成员函数表意明白!
    */
    //事实上就是将函数原型做了略微的修改,其它方面都不做更改
    const pair<string,string> &CheckoutRecord::get_a_waiter(sizeType index) const
    {
        return *wait_list.at(index);
    }
    

    三、成员訪问操作符

        为了支持指针型类,比如迭代器,C++语言同意重载解引用操作符(*)和箭头操作符(->)

        箭头操作符必须定义为类成员函数。解引用操作符不要求定义为成员,可是将他作为成员一般也是正确的!


    1、构建更安全的指针

        解引用操作符和箭头操作符经常使用在实现智能指针的类中。作为样例,假定想要定义一个类类型表示指向第十二章Screen类型对象的指针,将该类命名为ScreenPtr

        不用为 ScreenPtr类定义默认构造函数。因此,我们知道一个ScreenPtr对象将总 是指向一个Screen对象,不会有未绑定的ScreenPtr。应用程序能够使用ScreenPtr对象而无须首先測试它是否指向一个Screen对象。

        像HasPtr类一样,ScreenPtr类将对其指针进行使用计数。我们将定义一个伙伴类保存指针及其相关使用计数:

    class ScrPtr
    {
        friend class ScreenPtr;
        Screen *sp;
        size_t use;
        ScrPtr(Screen *p):sp(p),use(1){}
        ~ScrPtr()
        {
            delete sp;
        }
    
    };
    

        ScrPtr保存指针及其相关使用计数。将ScreenPtr设为友元,以便ScreenPtr能够訪问使用计数

    class ScreenPtr
    {
    public:
        ScreenPtr(Screen *p):ptr(new ScrPtr(p)){}
        ScreenPtr(const ScreenPtr &orig):ptr(orig.ptr)
        {
            ++ ptr->use;
        }
    
        ScreenPtr &operator=(const ScreenPtr &);
    
        ~ScreenPtr()
        {
            if ( -- ptr->use == 0 )
                delete ptr;
        }
    
    private:
        ScrPtr *ptr;
    };
    

         由于没有默认构造函数,所以ScreenPtr类型的每一个对象都必须提供一个初始化函数,初始化函数必须是还有一个ScreenPtr对象或者运行动态分配的Screen指针。

        ScreenPtr p1;               //Error
        ScreenPtr p2(new Screen);   //OK
    

    2、支持指针操作

    指针支持的基本操作有解引用操作和箭头操作,我们的类能够这样定义这些操作:

    class ScreenPtr
    {
    public:
        Screen &operator*()
        {
            return * ptr->sp;
        }
        Screen *operator->()
        {
            return ptr -> sp;
        }
    
        const Screen &operator*() const
        {
            return * ptr -> sp;
        }
        const Screen *operator->() const
        {
            return ptr -> sp;
        }
    //AS Before...
    
    private:
        ScrPtr *ptr;
    };
    

    3、重载解引用操作符

        我们的解引用操作符有const和非 const版本号。它们的区别在于返回类型:const成员返回const引用以防止用户改变基础对象


    4、重载箭头操作符

       箭头操作符与众不同。它可能表现得像二元操作符一样:接受一个对象和一个成员名。对对象解引用以获取成员。不管外表怎样,箭头操作符不接受显式形參

       这里没有第二个形參,由于->右操作数不是表达式,相反,是相应着类成员的一个标识符。没有明显可行的途径将一个标识符作为形參传递给函数,相反,由编译器处理获取成员的工作

    当这样编写时:

        point -> action();
    


    由于优先级规则,它实际等价于这样编写:

        (point -> action) ();
    

    换句话说,我们想要调用的是对point->action求值的结果。编译器这样对该代码进行求值【以下的一段话不太好理解,注意】:

        1)假设point是一个指针,指向具有名为action的成员的类对象,则编译器将代码编译为调用该对象的 action成员。

        2)否则,假设action是定义了operator->操作符的类的一个对象,point-> action point.operator->()->action同样。即,运行pointoperator-> (),然后使用该结果反复这三步

        3)否则,代码出错。


    5、使用重载箭头

    能够这样使用ScreenPtr对象訪问Screen对象的成员:

        Screen myScreen;
        ScreenPtr p(&myScreen);
        p -> display();
    

        由于 p是一个ScreenPtr对象,p->display的含义与对(p.operator->())->display求值同样。p.operator->()求值将调用ScreenPtr类的operator->,它返回指向Screen对象的指针,该指针用于获取并运行ScreenPtr所指对象的display成员。


    6、对重载箭头的返回值的约束(最后两段不太好理解(ˇ_ˇ)~)

        重载箭头操作符必须返回指向类类型的指针,或者返回定义了自己的箭头操作符的类类型对象

        假设返回类型是指针,内置箭头操作符可用于该指针,编译器对该指针解引用并从结果对象获取指定成员。

        假设返回类型是类类型的其它对象(或是这样的对象的引用),则将递归应用该操作符。编译器检查返回对象所属类型是否具有成员箭头,假设有,就应用那个操作符;否则,编译器产生一个错误。这个过程继续下去,直到返回一个指向带有指定成员的的对象的指针,或者返回某些其它值,在后一种情况下,代码出错。

    //P446 习题14.20
    ScreenPtr &ScreenPtr::operator=(const ScreenPtr &rhs)
    {
        ++ rhs.ptr -> use;
    
        if (-- ptr->use == 0 )
        {
            delete ptr;
        }
        ptr = rhs.ptr;
    
        return *this;
    }
    

    //习题14.21
    class NoName
    {
    public:
        NoName(Screen *p):ptr(new ScreenPtr(p)){}
    
        ScreenPtr operator-> ()
        {
            return *ptr;
        }
        const ScreenPtr operator-> () const
        {
            return *ptr;
        }
    
    private:
        ScreenPtr *ptr;
    };
    

    //习题14.22
    /*
    *==操作符须要在类中声明
    *    friend bool operator==(const ScreenPtr &lhs,
                               const ScreenPtr &rhs);
    */
    inline
    bool operator==(const ScreenPtr &lhs,const ScreenPtr &rhs)
    {
        return lhs.ptr == rhs.ptr;
    }
    inline
    bool operator!=(const ScreenPtr &lhs,const ScreenPtr &rhs)
    {
        return !(lhs == rhs);
    }


  • 相关阅读:
    Retrofit 2使用要点梳理:小白进阶回忆录
    浅析Android动画(三),自定义Interpolator与TypeEvaluator
    浅析Android动画(二),属性动画高级实例探究
    浅析Android动画(一),View动画高级实例探究
    Retrofit 2使用要点梳理:浅析POST文件/表单上传
    AS历史代码记录,找回本地未提交代码
    注解基础篇:自定义Java Annotation
    注解提高篇:自定义注解处理器(APT)
    AndroidStudio快捷键大全
    VisualStudio2015常用快捷键
  • 原文地址:https://www.cnblogs.com/hrhguanli/p/3945605.html
Copyright © 2011-2022 走看看