《C++ Primer》学习笔记—— 重载操作符与转换

    技术2022-05-14  1

     

    一,      重载操作符的定义

    1,不可重载的操作符:

    ::

    .*

    ? :

    2,不能通过连接其他合法符号来创建任何新的操作符。

    3,不能改变或添加内置类型的操作符。

    4,操作符的优先级、结合性、操作数数目是不能改变的。

    5,除了函数调用操作符operator()外,重载操作符时使用默认实参是非法的。

    6,重载&&||,或逗号操作符不是一种好的方法。

    这些操作符具有短路求值特性等

    7,多数重载操作符可以定义为成员函数也可以定义为非成员函数:

    成员函数比非成员函数少一个显示的形参;

    一般有+操作符就有+=,其余类似同;

    一般将算术和关系操作符定义为非成员函数,赋值操作符定义为成员函数;

    成员函数this指向左操作数;

    通常需要将非成员函数设为友元。

     

     

    二,      重载操作符的设计

    1,不要重载有内置意义的操作符:

    逗号、取地址、逻辑与、逻辑或等操作符具有有用的内置意义,一旦定义了自己的版本,就不能再使用这些内置含义;

    赋值操作符:赋值之后左右操作数的值应该相等,并且操作符应返回左操作数的引用,重载的赋值运算应该在赋值的内置含义基础上进行定制,而不是完全绕开。

    2,当一个重载操作符的含义不明显,以及操作很少用,给操作符去一个名字更好。

    3,当要用作关联容器的键类型时需要定 < 操作符,无论是什么容器,都应该定义 == < 操作符,因为很多算法需要;

    如果定义了 == 操作符,也应定义 != 操作符;

    如如果定义了 < 操作符,通常也应定义 >, <, >=, <=

    但是如果定义 < 操作符将导致与 == 操作符定义上的矛盾,则不应定义。

    4,成员还是非成员:

    赋值(),下标(),调用(),成员访问箭头()必须是成员,否则报错;

    复合赋值操作符通常应为成员,不过不这样也不会报错;

    自增,自减,解应用通常应该为类成员;

    堆成的操作符,如算术操作符、想等操作符、关系操作符、关系操作符和位操作符最好定义为普通函数。

     

    三,      输入和输出操作符

    1,支持I/O操作的类提供的I/O接口应该与iostream的接口相同。

    2,输出操作符<<的重载:

    a,ostream&为第一形参,对类类型的const对象的引用为第二形参(从而可以使用一个定义来输出const类型和非const类型),返回对ostream形参的引用;

    b,ostream& operator << (ostream& os,  const ClassType& object){

    … …

    os << … …

    return  os;

    }

    c, 输出操作符通常所做的格式化应该尽量少,尤其不应该输出换行符!把控制权交给用户!

    d,I/O操作符必须为非成员函数:否则左操作数只能是该类类型的对象。

    3,输入操作符 >> 的重载:

    a,第一个形参是一个引用,指向它要读的流;返回的是对同一个流的引用;第二个形参是对要读入的对象的非const引用;

    b,输入操作符必须处理错误和文件结束的可能性!

    c, 如(书中的例子):

    istream& operator >> (istream& in, Sales_item& s){

             double price;

             in >> s.isbn >> s.units_sold >> price;

             //检查输入流是否正确

             if (in)

                    s.revenue = s.units_sold * price;

             else

                    s = Sale_item();          //出错处理

             return in;

    }

    d,任何读操作都可能因为提供的值不正确而失败;任何读入都可能碰到输入流中的文件结束或其他一些错误;

    e,一旦读入失败了,则应该确保对象处于可用和一致的状态,即成员都已经定义,且定义的内容是一致的,不存在有的成员赋了正确的值而别的则未赋值或赋了错误值的情况;

    f,  输入操作符时,一定要确定错误恢复措施;

    g,指出错误:

    ==================================================

    此处的具体细节未细看。

    ==================================================

     

    四,      算术操作符和关系操作符

    1,通常应该是非成员函数。

    2,加减法操作符:

    a,为了与内置操作符保持一致,返回一个对象,该对象是局部变量,初始化为右值,因此不能返回引用,否则报错;

    b,ClassType operator+ (const ClassType& lhs,  const ClassType& rhs){… …}

    c, 定义了算术操作符通常也定义复合赋值操作符,此时用复合赋值操作符更有效,因为不必创建和撤销一个临时量。

    3,相等操作符:

    a==操作符的含义是两个对象包含同样的数据;

    b,如果定义了==,通常也要定义!=,而且要通过调用==的来实现!这样修改起来比较方便,不易出错;

    4,关系操作符:

    a,如果有想等操作符,通常也要有关系操作符;

    b,如果关系操作符与想等操作符的定义相矛盾,则不定义,一定要注意!

     

    五,      赋值操作符

    1,必须是成员函数,以便告诉编译器是否需要合成。

    2,通常形参是对类类型的const引用,也可以是类类型,以及对类类型的非const引用。

    3,赋值操作符可以多次重载,因操作数类型的不同而不同。

    4,当类具有资源管理的时候,必须要自己定义赋值操作符。

    5,如果有成员子对象,那么要显示定义成员子对象的赋值操作符重载。

    6,赋值操作符不能继承??????

    7,复制必须返回对*this的引用:

    是为了与内置类型一致。返回一个引用,就不需要创建和撤销结果的临时副本;

    一般赋值操作符合复合赋值操作符的返回值都应该是是左操作数的引用。

    8,例子:

    A& A::operator = (const A& aa){

            if(&a == this)return *this;               //避免自身赋值浪费

            x = a.x; y = a.y;

            delete p[];

            p = new char(strlen(a.p) + 1);

    strcpy(p, a.p);

    return *this;

    }

    六,      下标操作符

    1,可以从容器中检索单个元素的容器类一般会定义下标操作符;

    2,下标操作符必须是成员函数;

    3,下标操作符在用作赋值的左右操作数时都应该能表现正常:可以指定引用作为返回类型。

    4,一般要定义两个版本:一个为非const成员并返回引用;另一个为const成员并返回const引用。

    5,例子:

    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];

    }

    七,      成员访问操作符

    1,箭头操作符必须定义为类成员函数,解引用操作符都可以。

    2,构建更安全的指针:

    解引用操作符和箭头操作符常用在实现智能指针的类中;

    //private class for use by ScreenPtr only

    class ScrPtr {

            friend class ScreenPtr;

            Screen * sp;

            size_t use;

            ScrPtr(Screen *p): sp(p), use(1) {}

            ~ScreenPtr() { delete sp; }

    };

    class ScreenPtr {

    public:

            ScreenPtr( Screen *p ): ptr(new ScrPtr(p)) {}

            ScreenPtr(const ScreenPtr &orig): ptr(orig.ptr) { ++ptr->use; }

            ~ScreenPtr(){ if (--ptr->use == 0) delete ptr; }

    private:

            ScrPtr *ptr;

    };

    注:没有默认构造函数,所有的ScreenPtr必须绑定一个Screen对象,所以必须为每个对象提供一个初始化函数,初始化函数必须是另一个ScreenPtr对象或者指向动态分配的Screen的指针。

    3,支持指针操作(解引用操作和箭头操作)

    class ScreenPtr{

            //constructor and copy control members as before

            Screen & operator*() { return *ptr->sp; }

            const Screen & operator* () const { return *ptr->sp; }

            Screen & operator->() { return ptr->sp; }

            const Screen * operator-> () const { return ptr->sp; }

     

    };

    A,重载解引用操作符:

    返回指向Screen的引用;

    和下标操作符一样,需要const和非const版本。

           B,重载箭头操作符:

    二元操作符:接受一个对象和一个成员名,但此处没有第二个形参,因为->的有操作数不是表达式,而是类成员的一个标识符;

    没有明显可行的途径将标识符作为形参传递,由编译器处理这个问题;

    对于 pointer -> action() 这样的语句:

    如果pointer是一个指针,指向具有名为action的类成员的类对象,则编译为调用该对象的action成员;

    否则,如果action是定义了operator->操作符的类的一个对象,则pinter->actionpoint.operator->()->action相同,即先执行pointeroperator->(),再重复这三步;

    否则,代码出错。

    4,使用重载箭头:

    与正常使用方法相同,要理解3

    5,对重载箭头返回值的约束:

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

     

    八,      自增操作符和自减操作符

    1,通常类似迭代器这样的类需要实现++- - 操作符重载,这样的类提供类似于指针的行为来访问序列中的元素;

    a)        因为这些操作符改变操作对象的状态,所以更倾向于将他们作为成员;

    b)        一般来说前缀式和后缀式都定义,因为用户习惯二者都存在。

    2,前自增/自减操作符:

    a)        为了与内置类型一致,返回被增量或减量对象的引用。

    3,后自增/自减操作符:

    a)        在形参里接受一个int型形参,用于区别,该值不使用,无需命名;

    b)        为了与内置一致,应返回旧值,并且应该是作为值返回,而非返回引用;

    c)        在实现后增减时,可以调用前缀式操作符来实现,这样统一而方便修改。

    4,显示调用:

    a)        object.operator++();

    b)        ogject.operator++(0);              后缀式显示调用必须给一个参数0

    5,例子:类似指向int数组的迭代器

    class CheckedPtr{

    public:

            CheckedPtr(int *b, int *e) : beg(b), end(e), curr(b) {}

            CheckedPtr& operator++ ();

            CheckedPtr& operator - - ();

            CheckedPtr& operator++ ();

            CheckedPtr& operator - - ();

    private:

            int* beg;

            int* end;

            int* curr;

    };

    //实现

    CheckedPtr& CheckedPtr:: operator++(){

            if (curr == end)

                   throw out_of_range (“increment past the end of CheckedPtr”);

            ++curr;

            return *this;

    }

    CheckedPtr& CheckedPtr:: operator - -(){

            if (curr == beg)

                   throw out_of_range(“dcrement past the beginning of CheckedPtr”);

                  --curr;

                  return *this;              

    }

    CheckedPtr  CheckedPtr:: operator ++ (int){

            CheckedPtr ret(*this);

            ++*this;

            return ret;

    }

    CheckedPtr  CheckedPtr:: operator - - (int){

            CheckedPtr ret(*this);

            --*this;

            return ret;

    }

    注:在后缀式的实现中调用了前缀操作符来完成诸如指针检查和增减的操作,更为统一而方便修改。

    九,      调用操作符和函数对象

    1,一般为表示操作的类重载调用操作符;

    a)        一个简单的例子:返回输入值的绝对值

    struct absInt{

            int operator() (int val){ return val < 0? –val : val; }

    };

    b)        函数调用操作符必须声明为成员函数;

    c)        一个类可以定义多个版本,用形参的数量和类型加以区别;

    d)        定义了调用操作符的类,其对象常称为函数对象

    2,            将对象用于标准库算法

    a)        一个例子:判断输入的字符长度是否大于指定值

    class GT_cls {

           public:

                  GT_cls (size_t val = 0): bound(val) { }

                  bool  operator() (const string &s)

                         { return s.size() >= bound; }

           private:

                  std::string::size_type bound;

    };

    b)        使用GT_cls函数对象:

    cout << count_if(words.begin(), words.end, GT_cls(6));

    边界值可以自己临时定义,这是用函数所实现不了的,函数会将边界值固化在自身内部;

    for(size_t i = 0; i != 11; ++i){

    cout << count_if( words.begin(), words.end(),GT_cls(i));

    }

    实现同样的功能,用函数完成可能需要写10个不同的函数。

     

    3,            标准库定义的函数对象:

     

    包含在functional头文件中的标准库对象

     

    类型

    函数对象

    所应用的操作符

    算术函数对象类型

    plus<Type>

    +

     

    minus<Type>

    -

     

    divides<Type>

    /

     

    multiplies<Type>

    *

     

    modulus<Type>

    %

     

    negate<Type>

    -

    关系函数对象类型

    equal_to<Type>

    ==

     

    not_equal_to<Type>

    !=

     

    greater<Type>

     

    less<Type>

     

    greater_equal<Type>

    >=

     

    less_equal<Type>

    <=

    逻辑函数对象类型

    logical_and<Type>

    &&

     

    logical_or<Type>

    |

     

    logical_not<Type>

    !

         

          

    a)      每个类表示一个给定操作符的模板类型;

    b)      negate<Type> logical_not<Type>是意愿函数对象,其余都是二元;

    c)      每个函数对象类都是一个类模板,需要为该模板提供一个类型,例子如下:

    puls<int> intAdd;

    int sum = intAdd(10, 20);

    d)      在算法中使用标准库函数对象:

    sort(svec.begin(), svec.end(), greater<string>());

     

    4,            函数对象的函数适配器

    a)        绑定器(binder):有两个,bind1stbind2nd,分别将给定值绑定到二元函数的第一个和第二个实参;

    count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));

    10绑定到二元函数对象的第二个实参;

    b)        求反器(negator):有两个,not1not2,分别将一元函数对象和二元函数对象的真值求反;

    count_if(vec.begin(), vec.end(), not1(bind2nd(less_equal<int>(),10)));

    将二元操作符less_equal转为一元操作符后,对一元函数对象求反;

          

    十,      转换与类类型

    未看

     


    最新回复(0)