C++ Primer 学习笔记_64_重载操作符与转换 --转换与类类型 (一)

2014-11-24 12:41:39 · 作者: · 浏览: 0

重载操作符与转换

--转换与类类型【下】

四、重载确定和类的实参

在需要转换函数的实参时,编译器自动应用类的转换操作符或构造函数。因此,应该在函数确定期间考虑类转换操作符。函数重载确定由三步组成:

1)确定候选函数集合:这些是与被调用函数同名的函数。

2)选择可行的函数:这些是形参数目和类型与函数调用中的实参相匹配的候选函数。选择可行函数时,如果有转换操作,编译器还要确定需要哪个转换操作来匹配每个形参

3)选择最佳匹配的函数。为了确定最佳匹配,对将实参转换为对应形参所需的类型转换进行分类。对于类类型的实参和形参,可能的转换的集合包括类类型转换

1、转换操作符之后的标准转换

如果重载集中两个函数可以用同一转换函数匹配,则使用在转换之后或之前的标准转换序列的等级来确定哪个函数具有最佳匹配。

否则,如果可以使用不同转换操作,则认为这两个转换是一样好的匹配,不管可能需要或不需要的标准转换的等级如何。

注意:只有两个转换序列使用同一转换操作时,才用类类型转换之后的标准转换序列作为选择标准。

2、多个转换和重载确定

class SmallInt
{
public:
    operator int() const
    {
        return val;
    }

    operator double() const
    {
        return val;
    }
    //...

private:
    std::size_t val;
};

    void compute(int);
    void compute(double);
    void compute(long double);

    SmallInt si;
    compute(si);    //Error:ambiguous

在这个例子中,可以使用operatorint 转换si并调用接受int参数的compute版本,或者,可以使用operatordouble 转换si并调用compute(double)。

编译器将不会试图区别两个不同的类类型转换。具体而言,即使一个调用需要在类类型转换之后跟一个标准转换,而另一个是完全匹配,编译器仍会将该调用标记为错误。即:

    void compute(int);
    //void compute(double);
    void compute(long double);

    SmallInt si;
    compute(si);    //Error:ambiguous,此处仍然错误

3、显式强制转换消除二义性

    void compute(int);
    void compute(double);
    void compute(long double);

    SmallInt si;
    compute(static_cast
  
   (si));  //OK
    compute(static_cast
   
    (si)); //OK 
   
  

4、标准转换和构造函数

class SmallInt
{
public:
    SmallInt(int = 0);
};
class Integral
{
public:
    Integral(int = 0);
};

    void manip(const SmallInt &);
    void manip(const Integral &);
    /*
    *:它既可以将 Integral 转换为 int 并调用 manip 的第一个版本,
    *也可以表示 SmallInt 转换为 int 并调用 manip 的第二个版本。
    */
    manip(10);  //Error

即使其中一个类定义了实参需要标准转换的构造函数,这个函数调用也可能具有二义性。例如,如果SmallInt定义了一个构造函数,接受short而不是 int参数,函数调用manip(10)将在使用构造函数之前需要一个从int到 short的标准转换。在函数调用的重载版本中进行选择时,一个调用需要标准转换而另一个不需要,这一事实不是实质性,编译器不会更喜欢直接构造函数,调用仍具有二义性。如:

class SmallInt
{
public:
    //将int该为short
    SmallInt(short = 0);
};
class Integral
{
public:
    Integral(int = 0);
};

    void manip(const SmallInt &);
    void manip(const Integral &);

    //此处调用仍具有二义性
    manip(10);  //Error

5、显式构造函数调用消除二义性

    manip(SmallInt(10));    //OK
    manip(Integral(10));    //OK

【警告!】

在调用重载函数时,需要使用构造函数或强制类型转换实参,这是设计拙劣的表现!

//P463 习题14.44
class LongDouble
{
public:
    operator double () const
    {
        cout << "double!" << endl;
        return val;
    }
    operator float () const
    {
        cout << "float!" << endl;
        return val;
    }

private:
    double val;
};

int main()
{
    LongDouble ldObj;
    int ex1 = ldObj;    //Error
    float ex2 = ldObj;  //OK : float
}

//习题14.45
class LongDouble
{
public:
    LongDouble(double);

private:
    double val;
};

void calc(int temp)
{
    cout << "int" << endl;
}
void calc(LongDouble temp)
{
    cout << "LongDouble" << endl;
}

int main()
{
    double dval;
    //将会调用void calc(int):因为标准转换要优于类类型转换
    calc(dval);
}

五、重载、转换和操作符

重载操作符就是重载函数。使用与确定重载函数调用一样的过程来确定将哪个操作符(内置的还是类类型的)应用于给定表达式。

    ClassX sc;
    int iobj = sc + 3;

有四种可能性:

1)有一个重载的加操作符与ClassX和 int相匹配。

2)存在转换,将sc和/或int值转换为定义了+的类型。如果是这样,该表达式将先使用转换,接着应用适当的加操作符。

3)因为既定义了转换操作符又定义了+的重载版本,该表达式具有二义性。

4)因为既没有转换又没有重载的+可以使用,该表达式非法。

1、重载确定和操作符

成员函数和非成员函数都是可能的,这一事实改变了选择候选函数集的方式!

操作符的重载确定遵循常见的三步过程(老生常谈了:-D):

1)选择候选函数

2)选择可行函数,包括识别每个实参的潜在转换序列

3)选择最佳匹配的函数

2、操作符的候选函数

一般而言,候选函数集由所有与被使用的函数同名的函数构成,被使用的函数可以从函数调用处看到。对于操作符用在表达式中的情况,候选函数包括操作符的内置版本以及该操作符的普通非成员版本。另外,如果左操作符具有类类型,而且该类定义了该操作符的重载版本,则候选集将包含操作符的重载版本。

一般而言,函数调用的候选集只包括成员函数或非成员函数,不会两者都包括。而确定操作符的使用时,操作符的非成员和成员版本可能都是候选者

确定指定函数的调用时,与操作符的使用相反,调用本身确定所考虑的名字的作用域。如果是通过类类型的对象(或通过这种对象的引用或指针)的调用,则只需考虑该类的成员函数。具有同一名字的成员函数和非成员函数不会相互重载。使用重载操作符是时,调用本身不会告诉我们与使用的操作符函数作用域相关的任何事情,因此,成员和非成员版本都必须考虑。

【警告:转换和操作符】

正确设计类