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指针,代码更清楚。

 

 

 

  

posted on 2014-09-28 11:54  三十/而立  阅读(275)  评论(0编辑  收藏  举报