转换构造函数
C++ 基本数据类型之间是可以经过一些类型转换的。将一个基本数据类型变量的值赋给另一个基本数据类型变量时,如果这两种类型兼容,则 C++ 自动将这个值转换为接收变量的类型;如果不兼容,C++ 不会自动转换,此时可以使用强制类型转换,不过要注意这种转换是否有意义。
C++ 的目标之一就是使用类对象可以像使用内置类型变量一样,对于类型转换这一部分,C++ 也提供了一种类型转换的机制。通过这个机制,可以将类定义成与基本数据类型 or 另一个类相关,使得从一种类型转换成另一种类型是有意义的。在这种情况下,程序员可以指示 C++ 如何自动进行转换,或通过强制类型转换来完成。
实现类的类型转换的机制是通过一种特殊的构造函数来完成的 —— 只接受一个参数的构造函数。一个构造函数接收一个不同于其类类型的形参,可以视为将其形参转换成类的一个对象。像这样的构造函数称为转换构造函数。
#ifndef STONE_H_
#define STONE_H_
class Stone {
private:
static const int LBS_PER_STN = 14; // 1 英石 = 14 磅
int stone; // 英石
double pds_left; // 剩余的磅数
double pounds; // 磅
public:
Stone();
Stone(double p);
Stone(int s, double pl);
~Stone();
// 以磅的形式展示数据
void show_stn() const;
// 以英石和磅的形式展示数据
void show_lbs() const;
};
#endif // STONE_H_
#include "stone.h"
#include <iostream>
Stone::Stone() {
stone = pds_left = pounds = 0;
}
Stone::Stone(double p) {
pounds = p;
stone = (int) p / LBS_PER_STN;
pds_left = p - stone * LBS_PER_STN;
}
Stone::Stone(int s, double pl) {
stone = s;
pds_left = pl;
pounds = s * LBS_PER_STN + pl;
}
Stone::~Stone() {
std::cout << "Destructor.\n";
}
void Stone::show_stn() const {
std::cout << stone << " stone, " << pds_left << " pounds\n";
}
void Stone::show_lbs() const {
std::cout << pounds << "pounds.\n";
}
Stone(double p);
这个构造函数用于将 double 类型的值转换为 Stone 类型,也就是说可以这样编写代码:
Stone s1;
s1 = 27.3;
程序将使用构造函数Stone(double p);
来创建一个临时的 Stone 对象,并将 27.3 作为初始化的值,然后采用逐成员赋值的方式将该临时对象的内容赋值到 s1 中。这一个过程被称为隐式转换,或者自动转换,因为它是自动进行的,不需要显式强制类型转换。
这种只接受一个参数的构造函数被称为转换函数。
下面的构造函数接受两个参数,因此不是转换函数,不能用来转换类型:
Stone(int s, double pl);
然而,如果给第二参数提供默认值,它便可用于转换 int。
将构造函数用于自动类型转换函数似乎是一项不错的特性。然而,当程序员拥有更丰富的 C++ 经验时,将发现这种自动特性并非总是合乎需要的,因为这会导致意外的类型转换。因此,C++ 新增了关键字 explicit,用于关闭这种特性。和 friend 关键字一样,explicit 只需要在类声明中的函数原型处使用,不能出现在类外的成员函数实现处。
explicit adj 明确的
class Stone {
...;
explicit Stone(double p);
};
Stone::Stone(double p) {
pounds = p;
stone = (int) p / LBS_PER_STN;
pds_left = p - stone * LBS_PER_STN;
}
当构造函数Stone(double p);
被 explicit 修饰之后,将关闭隐式类型转换,但仍然允许显式转换。
不只是基本数据类型,只要定义相应的转换构造函数,可以将用户自定义的其他类型转换为 Stone 对象。
转换函数
前面的程序展示了如何将其他类型转换为 Stone 对象,那么可以做相反的转换吗?也就是说,是否可以将 Stone 对象转换为 double 值?就像下面做的那样:
Stone s1 = Stone(25.7);
double d = s1; // 可以吗?
可以这样做,但是不能使用转换构造函数。转换构造函数只能用于从某种类型(包括基本数据类型以及其他类类型)到类类型的转换。要想将类类型转换为其他类型,必须使用特殊的 C++ 运算符函数 —— 转换函数。
如何创建转换函数?要将类类型转换为 typeName 类型,需要使用这种形式的转换函数:
operator typeName();
需要注意几点:
- 转换函数和构造函数一样,没有返回类型;
- 转换函数必须是类方法;
- 转换函数没有参数。
例如,转换为 double 类型的函数的原型如下:
operator double();
typeName(上例中是 double) 指出了要转换成的类型,因此不需要指定返回类型。转换函数是类方法意味着:它需要通过类对象来调用,从而告知函数要转换的值。因此,函数不需要参数,因为参数是隐式传递的。
添加转换函数之后的 Stone 类声明:
#ifndef STONE_H_
#define STONE_H_
#include <iostream>
class Stone {
private:
static const int LBS_PER_STN = 14; // 1 英石 = 14 磅
int stone; // 英石
double pds_left; // 剩余的磅数
double pounds; // 磅
public:
Stone();
explicit Stone(double p);
Stone(int s, double pl);
~Stone();
// 以磅的形式展示数据
void show_stn() const;
// 以英石和磅的形式展示数据
void show_lbs() const;
operator double() const;
};
Stone::Stone(double p) {
pounds = p;
stone = (int) p / LBS_PER_STN;
pds_left = p - stone * LBS_PER_STN;
}
Stone::operator double() const {
return pounds;
}
#endif // STONE_H_
// 对转换函数的测试:
int main(int argc, char ** args) {
Stone s1 = Stone(23.9);
double pound = s1;
std::cout << "Convert to double => " << pound << std::endl;
return 0;
}
// 输出结果:
// Convert to double => 23.9
和转换构造函数一样,转换函数也有其优缺点。提供执行自动、隐式转换的函数所存在的问题是:在用户不希望进行转换时,转换函数也可能进行转换;当定义了多个转换函数时,使用隐式转换可能有二义性的问题。
原则上说,最好使用显式转换,而避免隐式转换。学习过转换构造函数之后,应该可以想到 explicit 关键字,但需要注意的是在 C++98 中,关键字 explicit 不能用于转换函数,不过 C++11 消除了这种限制。因此,在 C++11 中,可以将转换运算符生命为显式的:
class Stone {
...
explicit operator double() const;
};
有了这些声明之后,转换类型时需要强制类型转换。
另一种方法是,用一个功能相同的非转换函数来替换转换函数,这样仅显式调用该函数才会转换类型。
Stone::operator double();
// 替换为:
double Stone::StoneToDouble();
使用时:
Stone s1 = Stone(23.5);
double d1 = s1; // 报错
double d2 = s1.StoneToDouble(); // 正确
小结
总之,C++ 为类提供了下面的类型转换:
- 只有一个参数的类构造函数用于将该构造函数参数类型转换为该类的类型。在构造函数的声明中使用 explicit 关键字将关闭隐式转换,只允许使用显式转换。
- 被称为转换函数的特殊的类成员运算符函数,用于将类对象转换成其他类型。转换函数是类成员函数,没有返回值、没有参数、名为 operator typeName(),其中 typeName 是需要转换成的类型。将类对象赋给 typeName 类型的变量or将其强制转换为 typeName 类型时,该转换函数将被调用。C++11 之后,可以用 explicit 关闭隐式转换。