zoukankan      html  css  js  c++  java
  • Coding之路——重新学习C++(5):重载运算符的法则

    1.运算符重载的基本法则

      (1)禁止重载的运算符。“::”(作用域解析符),“.”(成员选择符),“.*”(通过到成员指针做选择),“?:”(三元运算符)。主要原因是它们都以名字(而不是值)作为第二个参数,重载后会造成运算符的二义性。

      (2)二元运算符可以是非静态成员函数,也可以定义为两个参数的非成员函数。

      (3)对于 operator=、operator[]、operator()、operator->只能作为非静态的成员函数,这就能保证第一个运算对象是左值。

      (4)对于赋值(=)、取地址(&)、和逗号(,)在应用类时有了预先的定义,所以它们的重载操作需要定义为private。

    class X{
    private:
        void operator=(const X&);
        void operator&();
        void operator,(const X&);
        //...
    };

      (5)一个运算符重载函数必须是一个成员函数,或者至少有一个用户自定义类型参数。特别的,不能定义只对指针进行操作的运算符函数,保证了C++是可扩充的。

      (6)如果某个运算符重载函数想接受某个内部类型作为第一个参数,那么它自然就不可能是成员函数。

      (7)枚举也是自定义类型,也可以进行运算符重载。 

      (8)在命名空间里定义的重载运算符将基于其运算对象的类型查找,就像基于参数的类型去查找函数一样。

      (9)现在考虑二元运算符@,如果x的类型为X而y的类型是Y,x@y将按如下方式解析:

        ——若X是类,查寻作为X的成员函数或者X的某个基类的成员函数的operator@;

        ——在围绕x@y的环境中查寻operator@的声明;

        ——若X在命名空间N里定义,在N里查寻operator@的声明;

        ——若Y在命名空间M里定义,在M里查寻operator@的声明;

        有可能一下找到了operator@的多个声明,找其中的最佳匹配。运算符查询机制并不认为成员函数比非成员函数更应该优先选取,而当一个类的成员调用命名函数时,函数查找时更偏向于同一个类及其基类的成员函数。

    2.由一个复数类型引出的运算符重载法则

      (1)为了尽量少的让函数直接操纵类,尽量在类中重载那些类本身就需要修改其第一个参数值的运算符,如“+=”。而像“+”这样基于参数的简单产生新值的运算符,可以在类之外重载:

    class Complex{
        double re, im;
    public:
        Complex& operator+=(const Complex& rhs);
        //...
    };
    
    Complex operator+(Complex lhs, Complex rhs){
        Complex temp = lhs;
        return temp += rhs;
    }

      (2)如果类之中有一个参数的构造函数,就可以刻画了参数类型到构造的类型的转换。如果对自定义类型声明了构造函数,就不能用初始化列表作为变量的初始式。

    //Complex.h
    class Complex{
        double re, im;
    public:
        Complex(double r):re(r), im(0) {}
        //...
    };
    
    //main.cpp
    Complex c = 3.0;    //正确
    Complex c_array = {3};    //错误,已经定义了构造函数

      (3)对于拷贝构造函数和赋值构造运算符,我们一般用const引用参数。其他运算符重载函数都可以使用值参数或者const引用参数。

    Complex::Complex(Complex c):re(c.re), im(c.im){}
    //错误,拷贝构造函数定义了复制参数,所以会引起无限循环调用

      (4)若想把运算符的左边运算对象定义为左值,只需将运算符重载为成员函数。

      (5)转换运算符。可以把一个新类型转换为内部类型或者已有类型。(Complex::operator double() const;)不过,不能把自定义类型转换为字符串常量。

      (6)大型对象。在大型对象中使用const引用能避免过度的复制。但是当一个运算符可能在某个表达式中使用多次,结构不能是static局部变量。这种结果通常需要自由空间分配,复制结果值常常比在自由空间内分配内存并释放一个对象更廉价。或者使用缓冲区来避免复制:

    //使用缓冲区避免复制
    const static int max_matrix_temp = 7;
    
    Matrix& get_matrix_temp(){
        static int nbuf = 0;
        static Matrix buf[max_matrix_temp ];
        if(nbuf == max_matrix_temp ) nbuf = 0;
        return buf[nbuf++];
    }
    
    Matrix& operator+(const Matrix& lhs, const Matrix& rhs){
        Matrix res = get_matrix_temp();
        //...
        return res;
    }

      (7)隐式转换只能进行一次,不能一个变量经过多次隐式转换编程其他类型。那些非法的赋值都会在编译期被捕捉。

      (8)函数operator[]()和operator()()必须是成员函数。

      (9)因为operator -> ()的转换不依赖被指向对象的成员m,我们把它看做后缀运算符。最常用的功能是“灵巧指针”。

    class Ptr_to_Y{
        Y *p;
    public:
        Y* operator->(){return p;}
        Y& operator*(){return *p;}
        Y& operator[](int i){return P[i];}
    };

    3.友元。

      (1)友元可以使普通函数,可以是类或者类的成员函数。像成员函数一样,友元声明不会给外围作用域引进一个名字。

    class Matrix{
        friend class Xfrom;
        friend Matrix invert(const Matrix&);
        //...
    };
    
    Xfrom xf;    //错误:作用域内无Xfrom
    Matrix (*p)(const Matrix&) = &invert;    //错误:作用域内无invert

      (2)一个友元类或者必须在外围作用域先声明,或者在将它作为友元的那个类的直接外围的非类作用域里定义。在外围的最内层命名空间作用域之外不考虑:

    class X {/*...*/};               //Y的友元
    
    namespace N{
        class Y{
            friend class X;
            friend class Z;
            friend class AE;
        };
        class Z{/*...*/};            //Y的友元
    }
    
    class AE{/*...*/};         //不是Y的友元

      (3)一个友元或者在外围作用域显示声明,或者以类或者派生类作为一个参数,否则无法调用友元。

    //假定作用域内无f()
    class X{
        friend void f();        //没用
        friend void h(const X&);            //可以通过参数找到
    };

      (4)重载运算符什么时候定义成友元?

      ——如果是需要改变类对象状态的操作应该定义为一个成员函数。

      ——如果某个运算的所有运算对象都希望能隐式转换,那么就应该定义成非成员函数,这是freind函数的主要来源。

      ——如果难分伯仲,尽量采用成员函数,因为可以隐式的利用this指针,代码更清楚。

      

  • 相关阅读:
    设计模式---策略模式
    maven+eclipse创建web项目
    【Mybatis】多对多实例
    【Mybatis】一对多实例
    【Mybatis】一对一实例
    S5PV210的开发与学习:2.6 UBOOT学习笔记(uboot源码分析3-uboot如何启动内核)
    S5PV210的开发与学习:2.5 UBOOT学习笔记(uboot源码分析2-启动第二阶段)
    S5PV210的开发与学习:2.4 UBOOT学习笔记(uboot源码分析1-启动第一阶段)
    uboot 移植文件差异比较报告
    S5PV210的开发与学习:2.3 UBOOT学习笔记(uboot配置和编译过程详解)
  • 原文地址:https://www.cnblogs.com/xskCoder/p/3998017.html
Copyright © 2011-2022 走看看