- 转换与类类型
可用一个实参调用的非 explicit 构造函数定义一个隐式转换。当提供了实参类型的对象而需要一个类类型的对象时,编译器将使用该转换。这种构造函数定义了到类类型的转换。除了定义到类类型的转换之外,我们还可以定义从类类型的转换。即,我们可以定义转换操作符,给定类类型的对象,该操作符将产生其他类型的对象。像其他转换一样,编译器将自动应用这个转换。在介绍如何定义这种转换之前,将说明它们为什么可能有用。
假定想要定义一个名为 SmallInt 的类,该类实现安全小整数,这个类将使我们能够定义对象以保存与 8 位 unsigned char 同样范围的值,即,0 到 255。这个类可以捕获下溢和上溢错误,因此使用起来比内置 unsigned char 更安全。我们希望这个类定义 unsigned char 支持的所有操作。具体而言,我们想定义 5 个算术操作符(+、-、*、/、%)及其对应的复合赋值操作符,4 个关系操作符(<、<=、>、>=),以及相等操作符(==、!=)。显然,需要定义 16 个操作符。而且,我们希望可以在混合模式表达式中使用这些操作符。例如,应该可以将两个 SmallInt 对象相加,也可以将任意算术类型加到 SmallInt。通过为每个操作符定义三个实例来达到目标:
int operator+(int, const SmallInt&); int operator+(const SmallInt&, int); SmallInt operator+(const SmallInt&, const SmallInt&);
因为存在从任意算术类型到 int 的转换,这三个函数可以涵盖支持 SmallInt 对象的混合模式使用的要求。但是,这个设计仅仅接近内置整数运算的行为,它不能适当处理浮点类型混合模式操作,也不能适当支持 long 、unsigned int 或 unsigned long 的加运算。问题在于这个设计将所有算术类型(甚至包括那些比 int 大的)转换为 int 并进行 int 加运算。即使忽略浮点或大整型操作数的问题,如果要实现这个设计,也必须定义 48 个操作符!幸好,C++ 提供了一种机制,利用这种机制,一个类可以定义自己的转换,应用于其类类型对象。对 SmallInt 而言,可以定义一个从 SmallInt 到 int 类型的转换。如果定义了该转换,则无须再定义任何算术、关系或相等操作符。给定到 int 的转换,SmallInt 对象可以用在任何可用 int 值的地方。//弊端:必须对加减乘除等所有操作符进行重载;必须对与整型、浮点型、double型等所有类型的操作都进行重载。
如果存在一个到 int 的转换,则以下代码:
SmallInt si(3); si + 3.14159; // convert si to int, then convert to double
可这样确定:
将 si 转换为 int 值。将所得 int 结果转换为 double 值并与双精度字面值常量 3.14159 相加,得到 double 值。
- 转换操作符
转换操作符是一种特殊的类成员函数。它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 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 之外)都可以定义转换函数。一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的。转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空。
虽然转换函数不能指定返回类型,但是每个转换函数必须显式返回一个指定类型的值。例如,operator int 返回一个 int 值;如果定义 operator Sales_item,它将返回一个 Sales_item 对象,诸如此类。转换函数一般不应该改变被转换的对象。因此,转换操作符通常应定义为 const 成员。
- 使用类类型转换
只要存在转换,编译器将在可以使用内置转换的地方自动调用它:
在表达式中:
SmallInt si; double dval; si >= dval // si converted to int and then convert to double
在条件中:
if (si) // si converted to int and then convert to bool
将实参传给函数或从函数返回值:
int calc(int); SmallInt si; int i = calc(si); // convert si to int and call calc
作为重载操作符的操作数:
// convert si to int then call opeator<< on the int value cout << si << endl;
在显式类型转换中:
int ival; SmallInt si = 3.541; // instruct compiler to cast si to int ival = static_cast<int>(si) + 3;
使用转换函数时,被转换的类型不必与所需要的类型完全匹配。必要时可在类类型转换之后跟上标准转换以获得想要的类型。例如,在一个 SmallInt 对象与一个 double 值的比较中:
SmallInt si; double dval; si >= dval // si converted to int and then convert to double
- 只能应用一个类类型转换
类类型转换之后不能再跟另一个类类型转换。如果需要多个类类型转换,则代码将出错。例如,假定有另一个类 Integral,它可以转换为 SmallInt 但不能转换为 int:
// class to hold unsigned integral values class Integral { public: Integral(int i = 0): val(i) { } operator SmallInt() const { return val % 256; }//不能再转换为int了 private: std::size_t val; };
可以在需要 SmallInt 的地方使用 Integral,但不能在需要 int 的地方使用 Integeral:
int calc(int); Integral intVal; SmallInt si(intVal); // ok: convert intVal to SmallInt and copy to si int i = calc(si); // ok: convert si to int and call calc int j = calc(intVal); // error: no conversion to int from Integral