类的自动和强制转换
C++中, 接收一个参数的构造函数为将类型与该参数相同的值转换为类提供了方法, 例如:
// 有一个参数的构造函数
Stonewt(double lbs);
// 就可以编写下面这种代码
Stonewt myCat;
// 使用Stonewt(double lbs)这个构造函数将19.6转换为Stonewt
myCat = 19.6;
程序将使用构造函数Stonewt(double lbs)来创建一个临时的Stonewt对象, 并将19.6作为初始化值, 随后采用逐成员赋值的方式将该临时对象的内容复制到myCat中. 这一过程称为隐式转换.
只有接受一个参数的构造函数才能作为转换函数, 但是如果多个参数的构造函数除了第一个其余都有默认值的话, 也可以用于隐式转换例如:
// 可以将int转换为Stonewt
Stonewt(int stn, double lbs = 0);
同时C++提供了新的关键字explicit, 可以关闭这种自动转换的特性, 也就是说:
// 这种写法表示不能将double自动转换成Stonewt类,
// 但是仍然允许显示转换
explicit Stonewt(double lbs);
Stonewt myCat;
// 非法的
myCat = 19.6;
// 合法的
myCat = Stonewt(19.6);
myCat = (Stonewt) 19.6;
编译器在没有在声明中使用关键字explicit的时候, 可以使用Stonewt(double)函数进行如下的隐式转换:
1.将Stonewt对象初始化为double值时
2.将double值赋给Stonewt对象时
3.将double值传递给接收Stonewt参数的函数时
4.返回值被声明为Stonewt的函数试图返回double值时
5.在上述任意一种情况下, 使用可转换为double类型的内置类型时. 也就是说, 函数原型化提供的参数匹配过重, 允许使用Stonewt(double)构造函数来转换其他数值类型. 例如:
// 先将7000转换为double, 再使用Stonewt(double)构造函数
Stonewt Jumbo(7000);
// 先将7000转换为double, 再使用Stonewt(double)构造函数创建临时变量, 再赋值给Jumbo
Jumbo = 7300;
然而, 当且仅当转换不存在二义性时, 才会进行这种两步转换, 也就是说如果这个类还定义了构造函数Stonewt(long), 则编译器将拒绝执行这种转换, 因为int可能被转换为long也可能被转换为double
接下来看一个完整的例子:
第一个文件:
// 第一个文件
// stonewt.h
#ifndef STONEWT_H_
#define STONEWT_H_
class Stonewt
{
private:
enum {Lbs_per_stn = 14};
int stone;
double pds_left;
double pounds;
public:
Stonewt(double lbs);
Stonewt(int stn, double lbs);
Stonewt();
// 析构函数
~Stonewt();
void show_lbs() const;
void show_stn() const;
};
#endif
第二个文件
// 第二个问题
// stonewt.cpp
#include <iostream>
using std::cout;
#include "stonewt.h"
Stonewt::Stonewt(double lbs)
{
stone = int (lbs) / Lbs_per_stn;
pds_left = int (lbs) % Lbs_per_stn + lbs - int(lbs);
pounds = lbs;
}
Stonewt::Stonewt(int stn, double lbs)
{
stone = stn;
pds_left = lbs;
pounds = stn * Lbs_per_stn + lbs;
}
Stonewt::Stonewt()
{
stone = pounds = pds_left = 0;
}
Stonewt::~Stonewt()
{
}
void Stonewt::show_stn() const
{
cout << stone << " stone, " << pds_left << " pounds " << std::endl;
}
void Stonewt::show_lbs() const
{
cout << pounds << " pounds\n";
}
第三个文件:
// stone.cpp
// compile with stonewt.cpp
#include <iostream>
#include "stonewt.h"
using std::cout;
using std::endl;
void display(const Stonewt & st, int n);
int main()
{
// 使用的是带默认参数的构造函数进行的类的隐式类型转换
Stonewt incognito = 275;
// 进行的是隐式类型转换
Stonewt wolfe(285.7);
// 使用两个参数的构造函数进行初始化
Stonewt taft(21, 9);
cout << "The celebrity weighed ";
incognito.show_stn();
cout << "The detective weighed ";
wolfe.show_stn();
cout << "The President weighed ";
taft.show_lbs();
// 使用的是隐式类型转换
incognito = 276.8;
// 和taft = Stonewt(335); 是一样的
// 会先把335转换成一个double然后在用
// Stonewt(double) 进行初始化
taft = 335;
cout << "After dinner, the celebrity weighed ";
taft.show_lbs();
display(taft, 2);
cout << "The wrestler weighed even more." << endl;
// 由于Stonewt没有一个int型的参数的构造函数
// 把422转换成double然后使用Stonewt(double)
// 因此相当于display(Stonewt(422), 2);
display(422, 2);
cout << "No stone left unearned \n";
return 0;
}
void display(const Stonewt & st, int n)
{
for(int i = 0; i < n; i++)
{
cout << "Wow!";
st.show_stn();
}
}
程序运行结果为:
转换函数
上面的程序将数字转换为了Stonewt对象, 类似的, 我们还可以将Stonewt对象转换为double值类似下面这样的写法:
Stonewt wolfe(285.7);
double host = wolfe;
要想实现上面这种操作, 需要使用C++运算符函数--转换函数
转换函数是用于定义的强制类型转换, 可以像使用强制类型转换那样使用它们, 例如, 如果定义了从Stonewt到double的转换函数, 就可以使用下面的转换:
Stonewt wolfe(285.7);
double host = double(wolfe);
double thinker = (double) wolfe;
也可以让编译器来决定如何做:
Stonewt wells(20, 3);
// 前提是实现了转换函数
double star = wells;
注意如果我们没有定义匹配的转换函数, 则编译器将会报错.
如何创建转换函数呢?
operator typeName();
注意一下几点:
1.转换函数必须是类方法
2.转换函数不能指定返回类型
3.转换函数不能有参数.
例如: 转换为double类型的函数的原型如下:
operator double();
typeName(这里是double)指出了要转换成的类型, 因此不需要指定返回类型. 转换函数是类方法意味着: 它需要通过类对象来调用, 从而告知函数要转换的值. 因此函数不需要参数.
要添加将stonewt对象转换为int类型和double类型的函数, 需要将下面的原型添加到类声明中:
operator int();
operator double();
在之前的例子中, 加入转换函数后:
// 第一个文件
// stonewt1.h
#ifndef STONEWT_H_
#define STONEWT_H_
class Stonewt
{
private:
enum {Lbs_per_stn = 14};
int stone;
double pds_left;
double pounds;
public:
Stonewt(double lbs);
Stonewt(int stn, double lbs);
Stonewt();
// 析构函数
~Stonewt();
void show_lbs() const;
void show_stn() const;
// 转换函数
operator int() const;
operator double() const;
};
#endif
第二个文件:
// 第二个问题
// stonewt1.cpp
#include <iostream>
using std::cout;
#include "stonewt1.h"
Stonewt::Stonewt(double lbs)
{
stone = int (lbs) / Lbs_per_stn;
pds_left = int (lbs) % Lbs_per_stn + lbs - int(lbs);
pounds = lbs;
}
Stonewt::Stonewt(int stn, double lbs)
{
stone = stn;
pds_left = lbs;
pounds = stn * Lbs_per_stn + lbs;
}
Stonewt::Stonewt()
{
stone = pounds = pds_left = 0;
}
Stonewt::~Stonewt()
{
}
void Stonewt::show_stn() const
{
cout << stone << " stone, " << pds_left << " pounds " << std::endl;
}
void Stonewt::show_lbs() const
{
cout << pounds << " pounds\n";
}
// 转换函数
// 必须是类方法
Stonewt::operator int()const
{
return int (pounds + 0.5);
}
Stonewt::operator double()const
{
return pounds;
}
第三个文件:
// stone1.cpp
// compile with stonewt1.cpp
#include <iostream>
#include "stonewt1.h"
using std::cout;
using std::endl;
void display(const Stonewt & st, int n);
int main()
{
Stonewt taft(9, 2.8);
double p_wt = taft;
cout << "Convert to double => ";
cout << "taft:" << p_wt << " pounds" << endl;
cout << "Convert to int =>";
cout << "taft : " << int (taft) << " pounds" << endl;
return 0;
}
程序运行结果为:
自动应用类型转换
假如:上面的例子中没有使用显示强制类型转换:
cout << "taft : " << taft << " pounds" << endl;
就不会再使用隐式转换了, 因为cout上下文中没有指出应转换为int还是double类型, 在缺少信息时, 就会产生二义性, 也就会报错.
但是如果我们只定义了double转换函数或者只定义了int函数那么就可以了.
赋值的情况也是一样, 如果有二义性则会报错:
// int和double值都可以赋值给long,因此有二义性, 除非删除一个
long gone = taft;
当定义了两种或更多的转换时, 仍然可以使用显示强制类型转换来支出要使用哪个转换函数:
long gone = (double) taft;
long gone = int (taft);
和类的隐式转换一样, C++11中可以使用explicit来限制只能显示调用转换函数, 例如:
class Stonewt
{
...
explicit operator int() const;
explicit operator double() const;
...
};
有了这个声明后, 需要使用强制转换才能使用转换函数
C++为类提供了下面的类型转换:
1.只有一个参数的类构造函数用于将类与给参数相同的值转换为类类型. 例如, 将int值赋值给Stonewt对象时, 接收int参数的Stonewt类构造函数将自动被调用. 然后, 在构造函数声明中使用explicit可防止隐式转换, 只允许显示转换.
2.转换函数, 用于将类对象转换为其他类型. 转换函数是类成员函数, 没有返回值, 没有参数列表, 名为operator typeName(), 其中, typeName是对象将被转换成的类型. 将类对象赋值给typeName变量或将其强制转换为typeName类型时, 该转换函数将自动被调用.