第十一章-使用类(下)11.6

11.6 类的自动转换和强制类型转换

可以将类定义成与基本类型或另一个类相关,使得从一种类型转换为另一种类型是有意义的。这种情况下可以指示C++如何自动进行转换,或通过强制类型转换来完成。举例:

定义在头文件中
#ifndef STONEWT_H_
#define STONEWT_H_
class Stonewt
{
private:
	enum {Lbs_per_stn = 14};//对于定义特定的常量来说,如果是整数,enum提供了一种方便的途径
	//static const int Lbs_per_stn = 14; 也可以这样定义
	int stone;
	double pds_left;
	double pounds;
public:
	Stonewt(double lbs); //构造doulbe类型的pounds
	Stonewt(int stn, double lbs);
	Stonewt(); //默认构造
	~Stonewt();
	void show_lbs() const;
	void show_stn() const;
}

三个构造函数可以将Stonewt对象初始化位一个浮点数或两个浮点数,也可以创建对象而不初始化:

Stonewt blossem(132.5);
Stonewt buttercup(10, 2);
Stonewt bubbles;

接下来是类方法实现:

定义在源文件中
#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\n";
}
void Stonewt::show_lbs() const
{
	cout << pounds << " pounds\n";
}

Stonewt(double lbs)方法将整数或浮点值转换为Stonewt对象,因此可以编写这样的代码:

Stonewt myCat;
myCat = 19.6;

程序将使用构造函数Stonewt(double lbs)创建一个临时的Stonewt对象,并将19.6作为初始值。随后采用逐成员赋值的方式将该临时对象的内容复制到myCat中。这一过程称为隐式转换(因为是自动进行,不需要显式强制类型转换)
只有接受一个参数的构造函数才能作为转换函数
因此Stonewt(int stn, double lbs);构造函数因为有两个参数,所以不能同来转换类型
但是,如果给第二个参数提供默认值就可用于转换int:

Stonewt(int stn, double lbs = 0);

将构造函数用于自动类型转换函数似乎是一项不错的特性,但是C++经验越来越丰富后将发现这种自动特性并不是总是合乎需要,因为可能会导致以外的类型转换。因此C++新增了关键字explicit能关闭这种特性。
可以这样声明构造函数:explicit Stonewt(double lbs);
这将关闭上述示例中介绍的隐式转换,但仍允许显式转换,即显式强制转换:

Stonewt myCat;
myCat = 19.6;
myCat = Stone(19.6);
myCat = (Stonewt) 19.6;

编译器在什么时候将使用Stonewt(double)函数呢?如果在声明中使用了关键字explicit,则Stonewt(double)将只适用于显式强制转换,否则还可以用于下面的隐式转换:

  • 将Stonewt对象初始化为double值时
  • 将double值赋给Stonewt对象
  • 将double值传递给接受Stonewt参数的函数时
  • 返回值被声明为Stonewt的函数试图返回double值时
  • 上述情况下,使用可转换为double类型的内置类型时

详细介绍最后一点:函数原型提供的参数匹配过程,允许使用Stonewt(double)构造函数来转换其他数值类型。就是说下面两条语句都首先将int转换为double,然后用Stonewt(double)构造函数。

Stonewt Jumbo(7000); //使用Stonewt(double),将int变为double
Jumbo = 7300;        //使用Stonewt(double),将int变为double

仅当转换不存在二义性时,才会进行这种二步转换。也就是说如果这个类还定义了构造函数Stonewt(long),则编译器将拒绝这些语句。可能指出:int可被转换为long或double,因此调用存在二义性。

定义在源文件中
#include <iostream>
using std::cout;
#include "stonewt.h"
void display(const Stonewt &st, int n);
int main()
{
	Stonewt incognito = 275;
	Stonewt wolfe(285.7);
	Stonewt taft(21, 8);

	cout << "The celebrity weighed ";
	incognito.show_stn();
	cout << "The detective weighed ";
	wolfe.show_stn();
	cout << "The president weighed ";
	taft.show_lbs();
	incognito = 276.8; //使用接受double参数的构造函数,将276.8转换为一个Stonewt值,这将incognito的pound成员设置为276.8
	taft = 325;
	cout << "After dinner, the celebrity weighed ";
	incognito.show_stn();
	cout << "After dinner, the President weighed ";
	taft.show_lbs();
	display(taft, 2);
	cout << "The wrestler weighed even more.\n";
	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 incognito = 275;

等价于这两种:

Stonewt incognito(275);
Stonewt incognito = Stonewt(275);

注意后两种可用于接受多个参数的构造函数。

incognito = 276.8;
//使用接受double参数的构造函数,将276.8转换为一个Stonewt值
//这将incognito的pound成员设置为276.8。
//因为使用了构造函数,所以还设置了stone和pds_left成员
taft = 325;
//这条将int转换为double,然后使用Stonewt(double)设置全部三个成员

注意这条:

display(422, 2);

display()原型表明第一个参数应是Stonewt对象(Stonewt和Stonewt&形参斗鱼Stonewt实参匹配)。遇到int参数时,编译器查找构造函数Stonewt(int),以便将该int转换为Stonewt类型。由于没有找到这样的构造函数,因此编译器寻找接受其他内置类型(int可以转换为这种类型)的构造函数。Stonewt(double)构造函数满足这种要求,因此编译器将int转换为double,然后使用Stonewt(double)将其转换为一个Stonewt对象。

11.6.1 转换函数

既然之前将数字转换为了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;
//编译器发现右边是Stonewt类型,左边是double类型
//因此它将查看是否定义了与此匹配的转换函数

要转换typeName类型,需要使用这种形式的转换函数:

operator typeName();

注意以下几点:

  • 转换函数必须是类方法
  • 转换函数不能指定返回类型
  • 转换函数不能有参数
    比如转换为double类型的函数原型:
operator double();

因为是类方法,所以需要通过类对象来调用,从而告知函数要转换的值,因此函数不需要参数。

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;
}
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\n";
}
void Stonewt::show_lbs() const
{
	cout << pounds << " pounds\n";
}
//定义转换函数
Stonewt::operator int() const
{
	return int (pounds + 0.5);  //四舍五入
}
Stonewt::operator double() const
{
	return pounds;
}
int main()
{
	using std::cout;
	Stonewt poppins(9, 2.8);
	double p_wt = poppins;
	cout << "Convert to double => ";
	cout << "Poppins: " << p_wt << " pounds.\n";
	cout << "Convert to int => ";
	cout << "Poppins: " << int (poppins) << " pounds.\n";
	return 0;
}

自动类型应用转换
p418扩展了解

11.6.2 转换函数和友元函数

下面为Stonewt类重载加法运算符。讨论Time类时用成员函数或友元函数重载过加法。(假设没有定义operator double()转换函数)可以使用下面的成员函数实现加法:

Stonewt Stonewt::operator+(const Stonewt &st) const
{
	double pds = pounds + st.pounds;
	Stonewt sum(pds);
	return sum;
}

也可以将加法作为友元函数来实现,如下:

Stonewt operator+(const Stonewt &st1, const Stonewt &st2)
{
	double pds st1.pounds + st2.pounds;
	Stonewt sum(pds);
	return sum
}

别忘了可以提供方法定义或友元函数定义,但不能都提供。上面任意一种都可以这样做:

1.
Stonewt jennySt(9, 12);
Stonewt bennySt(12, 8);
Stonewt total;
total = jennySt + bennySt;

如果提供了Stonewt(double)构造函数,也可以这样做:

2.
Stonewt jennySt(9, 12);
double kennyD = 176.0;
Stonewt total;
total = jennySt + kennyD;

但只有友元函数才允许这样做:

3.
Stonewt jennySt(9, 12);
double pennyD = 146.0;
Stonewt total;
total = pennyD + jennySt;

为了解其中原因,将每一种加法都转换为响应的函数调用。1中:

1.
total = jennySt + bennySt被转换为:
total = jennySt.operator+(bennySt);
或
total = operator+(jennySt, bennySt);

2中:

total = jennySt + kennyD被转换为:
total = jennySt.operator+(kennyD);
或
total = operator+(jennySt, kennyD);
因为参数中kennyD是double类型,所以会调用Stonewt(double)构造函数,将该参数转换为Stonewt对象

上述两种成员函数都是通过Stonewt对象调用。
注意:这种情况下如果定义operator double()成员函数会造成混乱,因为该函数将提供另一种解释方法:编译器不是将kennyD转换为double并执行Stonewt加法,而是将jennySt转换为double并执行double加法。过多的转换函数将导致二义性。
最后,3中:

total = pennyD + jennySt被转换成:
total = operator+(pennyD, jennySt);

因为两个参数都是double,因此会调用构造函数Stonewt(double),将它们转换为Stonewt对象。但是不能调用成员函数将jennySt和pennyD相加,比如:
total = pennyD.operator+(jennySt);这没有意义,因为只有类对象才可以调用成员函数。C++不会试图将pennyD转换为Stonewt对象。将对成员函数参数进行转换,而不是调用成员函数的对象。
经验之谈:将加法定义为友元可以让程序更容易适应自动类型转换。因为两个操作数都称为函数参数,因此与函数原型匹配。

实现加法时的选择

要将double量和Stonewt量相加,有两种选择。第一种是将下面的函数定义为友元函数,让Stonewt(double)构造函数将double类型的参数转换为Stonewt类型的参数:

operator+(const Stonewt &, const Stonewt &)
//依赖于隐式转换所以程序更简短,因为定义的函数较少
//缺点是每次转换时都将调用转换构造函数,增加时间和内存开销

第二种方法是,将加法运算符重载为一个显式使用double类型参数的函数:

Stonewt operator+(double x);
friend Stonewt operator+(double x, Stonewt &s);
//优点是速度快
//缺点是程序较长

这样,下面的语句将与成员函数operator+(double x)完全匹配:

total = jennySt + kennyD;

而下面的语句将与友元函数operator+(double x, Stonewt &s)完全匹配:

total = pennyD + jennySt;
发布了101 篇原创文章 · 获赞 1 · 访问量 1960

猜你喜欢

转载自blog.csdn.net/weixin_43318827/article/details/105299928