17-转换函数
11.6.1 转换函数
程序清单11.18将数字转换为Stonewt对象。可以做相反的转换吗?也就是说,是否可以将Stonewt对象转换为double值,就像如下所示的那样?
Stonewt wolfe(285.7);
double host = wolfe; // ?? possible ??
可以这样做,但不是使用构造函数。构造函数只用于从某种类型到类类型的转换。要进行相反的转换,必须使用特殊的C++运算符函数——转换函数。
转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。例如,如果定义了从Stonewt到double的转换函数,就可以使用下面的转换:
Stonewt wolfe(285.7);
double host = double (wolfe); // syntax #1
double thinker = (double) wolfe; // syntax #2
也可以让编译器来决定如何做:
Stonewt wells(20, 3);
double star = wells; // implicit use of conversion function
编译器发现,右侧是Stonewt类型,而左侧是double类型,因此它将查看程序员是否定义了与此匹配的转换函数。(如果没有找到这样的定义,编译器将生成错误消息,指出无法将Stonewt赋给double。)
那么,如何创建转换函数呢?要转换为typeName类型,需要使用这种形式的转换函数:
operator typeName();
请注意以下几点:
- 转换函数必须是类方法;
- 转换函数不能指定返回类型;
- 转换函数不能有参数。
例如,转换为double类型的函数的原型如下:
operator double();
typeName(这里为double)指出了要转换成的类型,因此不需要指定返回类型。转换函数是类方法意味着:它需要通过类对象来调用,从而告知函数要转换的值。因此,函数不需要参数。
要添加将stone_wt对象转换为int类型和double类型的函数,需要将下面的原型添加到类声明中:
operator int();
operator double();
程序清单11.19列出了修改后的类声明。
程序清单11.19 stonewt1.h
// stonewt1.h -- revised definition for the Stonewt class
#ifndef STONEWT1_H_
#define STONEWT1_H_
class Stonewt
{
private:
enum {Lbs_per_stn = 14}; // pounds per stone
int stone; // whole stones
double pds_left; // fractional pounds
double pounds; // entire weight in pounds
public:
Stonewt(double lbs); // construct from double pounds
Stonewt(int stn, double lbs); // construct from stone, lbs
Stonewt(); // default constructor
~Stonewt();
void show_lbs() const; // show weight in pounds format
void show_stn() const; // show weight in stone format
// conversion functions
operator int() const;
operator double() const;
};
#endif
程序清单11.20是在程序清单11.18的基础上修改而成的,包括了这两个转换函数的定义。注意,虽然没有声明返回类型,这两个函数也将返回所需的值。另外,int转换将待转换的值四舍五入为最接近的整数,而不是去掉小数部分。例如,如果pounds为114.4,则pounds +0.5等于114.9,int(114.9)等于114。但是如果pounds为114.6,则pounds + 0.5是115.1,而int(115.1)为115。
程序清单11.20 stonewt1.cpp
// stonewt1.cpp -- Stonewt class methods + conversion functions
#include <iostream>
using std::cout;
#include "stonewt1.h"
// construct Stonewt object from double value
Stonewt::Stonewt(double lbs)
{
stone = int (lbs) / Lbs_per_stn; // integer division
pds_left = int (lbs) % Lbs_per_stn + lbs - int(lbs);
pounds = lbs;
}
// construct Stonewt object from stone, double values
Stonewt::Stonewt(int stn, double lbs)
{
stone = stn;
pds_left = lbs;
pounds = stn * Lbs_per_stn +lbs;
}
Stonewt::Stonewt() // default constructor, wt = 0
{
stone = pounds = pds_left = 0;
}
Stonewt::~Stonewt() // destructor
{
}
// show weight in stones
void Stonewt::show_stn() const
{
cout << stone << " stone, " << pds_left << " pounds\n";
}
// show weight in pounds
void Stonewt::show_lbs() const
{
cout << pounds << " pounds\n";
}
// conversion functions
Stonewt::operator int() const
{
return int (pounds + 0.5);
}
Stonewt::operator double()const
{
return pounds;
}
程序清单11.21对新的转换函数进行测试。该程序中的赋值语句使用隐式转换,而最后的cout语句使用显式强制类型转换。请务必将程序清单11.20与程序清单11.21一起编译。
程序清单11.21 stone1.cpp
// stone1.cpp -- user-defined conversion functions
// compile with stonewt1.cpp
#include <iostream>
#include "stonewt1.h"
int main()
{
using std::cout;
Stonewt poppins(9,2.8); // 9 stone, 2.8 pounds
double p_wt = poppins; // implicit conversion
cout << "Convert to double => ";
cout << "Poppins: " << p_wt << " pounds.\n";
cout << "Convert to int => ";
cout << "Poppins: " << int (poppins) << " pounds.\n";
return 0;
}
下面是程序清单11.19~程序清单11.21组成的程序的输出;它显示了将Stonewt对象转换为double类型和int类型的结果:
Convert to double => Poppins: 128.8 pounds.
Convert to int => Poppins: 129 pounds.
自动应用类型转换
程序清单11.21将int(poppins)和cout一起使用。假设省略了显式强制类型转换:
cout << "Poppins: " << poppins << " pounds.\n";
程序会像在下面的语句中那样使用隐式转换吗?
double p_wt = poppins;
答案是否定的。在p_wt示例中,上下文表明,poppins应被转换为double类型。但在cout示例中,并没有指出应转换为int类型还是double类型。在缺少信息时,编译器将指出,程序中使用了二义性转换。该语句没有指出要使用什么类型。
有趣的是,如果类只定义了double转换函数,则编译器将接受该语句。这是因为只有一种转换可能,因此不存在二义性。
赋值的情况与此类似。对于当前的类声明来说,编译器将认为下面的语句有二义性而拒绝它。
long gone = poppins; // ambiguous
在C++中,int和double值都可以被赋给long变量,所以编译器使用任意一个转换函数都是合法的。编译器不想承担选择转换函数的责任。然而,如果删除了这两个转换函数之一,编译器将接受这条语句。例如,假设省略了double定义,则编译器将使用int转换,将poppins转换为一个int类型的值。然后在将它赋给gone时,将int类型值转换为long类型。
当类定义了两种或更多的转换时,仍可以用显式强制类型转换来指出要使用哪个转换函数。可以使用下面任何一种强制类型转换表示法:
long gone = (double) poppins; // use double conversion
long gone = int (poppins); // use int conversion
第一条语句将poppins转换为一个double值,然后赋值操作将该double值转换为long类型。同样,第二条语句将poppins首先转换为int类型,随后转换为long。
和转换构造函数一样,转换函数也有其优缺点。提供执行自动、隐式转换的函数所存在的问题是:在用户不希望进行转换时,转换函数也可能进行转换。例如,假设您在睡眠不足时编写了下面的代码:
int ar[20];
...
Stonewt temp(14, 4);
...
int Temp = 1;
...
cout << ar[temp] << "!\n"; // used temp instead of Temp
通常,您以为编译器能够捕获诸如使用了对象而不是整数作为数组索引等错误,但Stonewt类定义了一个operator int(),因此Stonewt对象temp将被转换为int 200,并用作数组索引。原则上说,最好使用显式转换,而避免隐式转换。在C++98中,关键字explicit不能用于转换函数,但C++11消除了这种限制。因此,在C++11中,可将转换运算符声明为显式的:
class Stonewt
{
...
// conversion functions
explicit operator int() const;
explicit operator double() const;
};
有了这些声明后,需要强制转换时将调用这些运算符。
另一种方法是,用一个功能相同的非转换函数替换该转换函数即可,但仅在被显式地调用时,该函数才会执行。也就是说,可以将:
Stonewt::operator int() { return int (pounds + 0.5); }
替换为:
int Stonewt::Stone_to_Int() { return int (pounds + 0.5); }
这样,下面的语句将是非法的:
int plb = poppins;
但如果确实需要这种转换,可以这样做:
int plb = poppins.Stone_to_Int();
警告: 应谨慎地使用隐式转换函数。通常,最好选择仅在被显式地调用时才会执行的函数。
总之,C++为类提供了下面的类型转换。
- 只有一个参数的类构造函数用于将类型与该参数相同的值转换为类类型。例如,将int值赋给Stonewt对象时,接受int参数的Stonewt类构造函数将自动被调用。然而,在构造函数声明中使用explicit可防止隐式转换,而只允许显式转换。
- 被称为转换函数的特殊类成员运算符函数,用于将类对象转换为其他类型。转换函数是类成员,没有返回类型、没有参数、名为operator typeName(),其中,typeName是对象将被转换成的类型。将类对象赋给typeName变量或将其强制转换为typeName类型时,该转换函数将自动被调用。