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

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

重载操作符与转换

--转换与类类型【上】


引言:

在前面我们提到过:可以用一个实参调用的非explicit构造函数定义一个隐式转换。当提供了实参类型的对象需要一个类类型的对象时,编译器将使用该转换。于是:这种构造函数定义了到类类型的转换。

除了定义到类类型的转换之外,还可以定义从类类型到其他类型的转换。即:我们可以定义转换操作符,给定类类型的对象,操作符将产生其他类型的对象。和其他转换一样,编译器将自动应用这个转换。

一、转换为什么有用?

定义一个SmallInt的类,该类实现安全小整数,这个类将使我们能够定义对象以保存与 8位 unsignedchar 同样范围的值,即:0到255。这个类可以捕获下溢和上溢错误,因此使用起来比内置unsignedchar 更安全

我们希望这个类定义unsignedchar 支持的所有操作。具体而言,我们想定义5个算术操作符(+、-、*、/、%)及其对应的复合赋值操作符,4个关系操作符(<、<=、>、>=),以及相等操作符(==、!=)。显然,需要定义16个操作符%>_<%。

1、支持混合类型表达式

而且,我们希望可以在混合模式表达式中使用这些操作符。例如,应该可以将两个SmallInt对象相加,也可以将任意算术类型加到SmallInt。通过为每个操作符定义三个实例来达到目标:

int operator+(int,const SmallInt &);
int operator+(const SmallInt &,int);
SmallInt operator+(const SmallInt &,const SmallInt &);

但是,这个设计仅仅接近内置整数运算的行为,它不能适用于处理浮点类型的混合模式,也不能适当支持longunsignedintunsignedlong的加运算

2、转换减少所需操作符的数目

C++提供了一种机制:一个类可以定义自己的转换,应用于其类类型对象。对SmallInt而言,可以定义一个从SmallInt到 int类型的转换。如果定义了该转换,则无须再定义任何算术、关系或相等操作符(不然要定义48个!。给定到int的转换,SmallInt对象可以用在任何可用int值的地方。

如果存在一个到int的转换,则以下代码:

    SmallInt si(3);
    /**可以存在这样的转换:
    *1. 将si转换成为int值
    *2. 将所得 int 结果转换为 double 值并与双精度字面值常量 3.14159 相加,
    *   得到 double 值
    */
    si + 3.1415926;

二、转换操作符

转换操作符是一种特殊的类成员函数((⊙o⊙)真的很特殊!):它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字operator之后紧跟着转换的目标类型:

class SmallInt
{
public:
    SmallInt(int i = 0):val(i)
    {
        if (i < 0 || i > 255)
        {
            throw std::out_of_range("Bad SmallInt initializer");
        }
    }

    operator int() const
    {
        return val;
    }

private:
    std::size_t val;
};

转换函数采用如下通用形式:

    operator type();

这里,type表示内置类型名、类类型名或由类型别名所定义的名字。对任何可作为函数返回类型的类型(除了void之外)都可以定义转换函数。一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的。

【注意】

转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。

虽然转换函数不能指定返回类型,但是每个转换函数必须显式返回一个指定类型的值。例如,operatorint 返回一个int值;如果定义operatorSales_item,它将返回一个Sales_item对象,诸如此类。

【最佳实践】

转换函数一般不应该改变被转换的对象。因此,转换操作符通常定义为const成员!

1、使用类类型转换

只要存在转换,编译器将在可以使用内置转换的地方自动调用它

1)在表达式中:

    SmallInt si;
    double dval;
    si >= dval; //si转换为int,然后它们转换成为double

2)在条件中:

    if (si) //si转换为int,然后它们转换成为bool
    {
        //...
    }

3)将实参传给函数或从函数返回值:

    int calc(int);
    SmallInt si;
    calc(si);   //si转换为int,然后调用函数calc

4)作为重载操作符的操作数:

    cout << si << endl;   //si转换为int,然后调用opreator<<

5)在显式类型转换中:

    int ival;
    SmallInt si = 3.14;
    //显式将si转换成为int
    ival = static_cast
  
   (si) + 3;

  

2、类类型转换和标准转换

使用转换函数时,被转换的类型不必与所需要的类型完全匹配。必要时可以在类类型转换之跟上标准转换以获得想要的类型。

    SmallInt si;
    double dval;
    si >= dval; //si转换成为int然后标准转换成为double

3、只能应用一个类类型转换

类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码出错!

class Intergral
{
public:
    Intergral(int i):val(i){}
    operator SmallInt () const
    {
        return val % 256;
    }

private:
    std::size_t val;
};

可以在需要SmallInt的地方使用Intergral,但不能在需要int的地方使用Intergral:

    int calc(int);
    Intergral intVal;
    SmallInt si(intVal);    //OK:intVal转换成为SmallInt
    int i = calc(si);       //OK:si转换成为int
    int j = call(intVal);   //Error

在最后的 calc调用中:没有从Integral到 int的直接转换。int需要两次类类型转换:首先从Integral到 SmallInt,然后从SmallInt到int。但是,语言只允许一次类类型转换,所以该调用出错

4、标准转换可放在类类型转换之前

使用构造函数执行隐式转换的时候,构造函数的形参类型不必与所提供的类型完全匹配。

    void calc(SmallInt);
    short sobj;
    /*
    *调用 SmallInt类中定义的构造函数(SmallInt(int)),
    *将 sobj 转换为 SmallInt 类型
    */
    calc(sobj);

如果需要,在调用构造函数执行类类型转换之前,可将一个标准转换序列应用于实参。为了调用函数calc(),应用标准转换将dobj从 do