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

类型转换

将一个标准类型变量的值赋值给另一种标准类型的变量时,如果这两种类型兼容,则C++自动将这个值转换为接收变量的类型。

long count = 8; //int类型转换为long类型
double time = 11; //int类型转换为double类型
int side = 3.33; //double类型转换为int类型

在C++各种数值类型都表示相同的东西,一个数字,同时C++包含用于进行转换的内置规则。

C++语言不自动转换不兼容的类型:

int *p = 10; //错误

左边是指针类型,右边是数字。

在无法自动转换时,可以使用强制类型转换:

int *p = (int *) 10; //可行,p和(int *)10都是指针

将10强制转换为int指针类型,将指针设置为地址10。

磅转换英石

磅转换为英石,用一种类型转换为另一种类型的方式。

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

对于定义类特定的常量来说,如果它们是整数,enum提供一种方便的途径。

static const int Lbs_per_stn = 14;

这个类并非真的需要声明构造函数,,因为自动生成的more构造函数就很好。提供显示声明可防止必须定义构造函数。

stonewt.cpp

#include "stonewt.h"
#include <iostream>
using namespace std;

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";
}

在C++中,接受一个参数的构造函数为将类型与该参数相同的值转换为类提供了蓝图。下面的构造函数用于将double类型的值转换为Stonewt类型。

Stonewt(double lbs); //double转Stonewt

也就是可以这样编写

Stonewt myCat;
myCat = 19.6;

程序将使用构造函数Stonewt(double)来创建一个临时的Stonewt对象,并将19.6作为初始化值。随后,采用逐成员赋值方式将该临时对象的内容复制到myCat中。这过程称为隐式转换,因为它是自动进行的,而不需要显式强制类型转换。

只有接受一个参数的构造函数才能作为转换函数。

构造函数有两个参数,不能用来转换类型:

Stonewt(int stn, double lbs); //不能实现转换功能

然而,如果给第二个参数提供默认值,它便可用于转换int。

Stonewt(int stn, double lbs = 0); //int转换为Stonewt

构造函数用作自动类型转换函数可能会导致意外的类型转换。explicit可以关闭这种自动特性。

explicit Stonewt(double lbs); //不允许隐式转换 

这将关闭上述示例中介绍的隐式转换,只能显式强制类型转换:

Stonewt myCat; //创建一个Stonewt对象
myCat = 19.6; //错误的,必须为显式
myCat = Stonewt(19.6); //正确,显式转换
myCat = (Stonewt)19.6; //正确,旧版本显示转换

注意:只接受一个参数的构造函数定义了从参数类型到类类型的转换。如果使用关键字explicit限定了这种构造函数,则它只能用于显示转换。

编译器在什么时候将使用Stonewt(double)函数
● 将Stonewt对象初始化为double值时。
● 将double值赋给Stonewt对象时。
● 将double值传递给接受Stonewt参数的函数时。
● 返回值被声明为Stonewt的函数试图返回double值时。
● 在上述任意一种情况下,使用可转换为double类型的内置类型时。

函数原型化提供的参数匹配过程,允许使用Stonewt(double)构造函数来转换其他数值类型。

Stonewt Jumbo(7000); //使用Stonewt(double),int转换为double
Jumbo = 7300; 使用Stonewt(double),int转换为double

将int转换为double,然后使用Stonewt(double)构造函数。

main.cpp

#include <iostream>
#include "stonewt.h"
using namespace std;

void display(const Stonewt &st, int n);

int main()
{
	Stonewt incognito = 275; //构造函数初始化
	Stonewt wolfe(285.7); //Stonewt wolfe = 285.7
	Stonewt taft(21, 8);

	cout << "The celebrity weighted ";
	incognito.show_stn();
	cout << "The detective weighted ";
	wolfe.show_stn();
	cout << "The President weighted ";
	taft.show_lbs();
	incognito = 276.8; //利用构造函数转换
	taft = 325; //taft = Stonewt(325)
	cout << "After dinner, the celecbrity 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;
taft = 325;

第一条赋值语句使用接受double参数的构造函数,将276.8转换为一个Stonewt值,这将把incognito的pound成员设置为276.8。因为该语句使用了构造函数,所以还将设置stone和pds_left成员。
第二句将一个int值转换为double类型,然后使用Stonewt(double)来设置全部3个成员。

最后。

display(422, 2);

display(),第一个参数应是Stonewt对象。遇到int参数时,编译器查找构造函数Stonewt(int),以便将该int转换为Stonewt类型。由于没有找到这样的构造函数,因此编译器寻找接受其他内置类型(int可以转换为这种类型)的构造函数。Stone(double)构造函数满足这种要求,因此编译器将int转换为double,然后使用Stonewt(double)将其转换为一个Stonewt对象。
在这里插入图片描述

转换函数

转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用他们。

如果定义了从Stonewt到double的转换函数,就可以使用下面的转换。

Stonewt wolfe(285.7);
double host = double(wolfe);
double thinker = (double)wolfe;

也可以让编译器来决定如何做:

Stonewt wells(20, 3);
double star = wells;

编译器发现,右侧是Stonewt类型,而左侧是double类型,因此它将查看程序员是否定义了与此匹配的转换函数(如果没有找到这样的定义,编译器将生成错误消息,指出无法将Stonewt赋给double)。

创建转换函数:

operator typeName();

● 转换为typeName类型。
● 转换函数必须是类方法。
● 转换函数不能指定返回类型。
● 转换函数不能有参数。

转换为double类型的函数的原型:

operator double();

typeName指出了要转换成的类型,因此不需要指定返回类型。

转换函数是类方法意味着:它需要通过类对象来调用,从而告知函数要转换的值。因此,函数不需要参数。

要添加将stone_wt对象转换为int类型和double类型的函数,需要将下面的原型添加到类声明中:

operator int();
operator double();

stonewt1.h

#ifndef STONEWT1_H_
#define STONEWT1_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

int转换将待转换的值四舍五入为最接近的整数,而不是去掉小数部分。

stonewt1.cpp

#include <iostream>
#include "stonewt1.h"
using namespace std;

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;
}

转换函数虽然没有声明返回类型,也要返回所需的值。

main.cpp

#include <iostream>
#include "stonewt1.h"
using namespace std;

int main()
{
	Stonewt poppins(9, 2.8); //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;
}

显示了将Stonewt对象转换为double类型和int类型的结果。
在这里插入图片描述

自动应用类型转换

假设省略了显式强制类型转换:

cout << "Poppins: " << poppins << " pounds.\n";

程序就会像下面那样进行隐式转换吗?

double p_wt = poppins;

不会。poppins应被转换为double类型,但cout并没有指出应转换为int或者double类型,在缺少信息时,就会二义性转换。如果类只定义了double转换函数,即只有一种转换可能,不存在二义性。

下面这种情况也会存在二义性。

long gone = poppins; //二义性

int和double值都可以被赋值给long变量,所以编译器使用任意一个转换函数都是合法的。

当类定义了两种或者更多的转换时,用显式强制类型转换来指出要使用哪个转换函数。

long gone = (double)poppins; //double类型转换
long gone = int(poppins); //int类型转换

将poppins转换为一个double值,然后赋值操作将该double值转换为long类型。

在用户不希望进行转换时,转换函数可能进行转换。

int ar[20];
...
Stonewt temp(14, 4);
...
int Temp = 1;
...
cout << ar[temp] << "!\n"; //用temp代替了Temp

Stonewt类定义了一个operator int(),因此Stonewt对象temp将被转换为int200,并用作数组索引。

在C++98中,关键字explicit不能用于转换函数,但C++11消除了这种限制,可将转换运算符声明为显式。

class Stonewt
{
...
    explicit operator int() const;
    explicit operator double() const;
};

有了这些声明后,需要强制转换时将调用这些运算符。

用一个功能相同的非转换函数替换该转换函数即可,但仅在被显式地调用时,该函数才会执行。

Stonewt::operator int() { return int(pounds + 0.5); }

替换为:

int plb = poppins;

这样,下面的语句将是非法的:

int plb = poppins; //非法

但如果确实需要这种转换:

int plb = poppins.Stone_to_Int();

警告:最好选择仅在被显式地调用时才会执行的函数。

C++为类提供了下面的类型转换:
● 只有一个参数的类构造函数用于将类型与该参数相同的值转换为类类型。例如,将int值赋给Stonewt对象时,接受int参数的Stonewt类构造函数将自动被调用。然而,在构造函数声明中使用explict可防止隐式转换,而只允许显示转换。
● 被称为转换函数的特殊类成员运算符函数,用于将类对象转换为其他类型。转换函数是类成员,没有返回类型、没有参数、名为operator typeName(),其中,typeName是对象将被转换成的类型。将类对象赋给typeName变量或将其强制转换为typeName类型时,该转换函数将自动调用。

转换函数和友元函数

Stonewt类重载加法运算符。
成员函数来实现加法:

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;
}

别忘了要提供方法定义或友元函数定义,但不能都提供。

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

如果提供了Stonewt(double)构造函数:

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

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

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

将每一种加法都转换为相应的函数调用。

首先:

total = jennySt + bennySt;

被转换为:

total = jennySt.operator+(bennySt); //成员函数

或:

total = operator+(jennySt, bennySt); //友元函数

成员函数是通过Stonewt对象调用的。

其次:

total = jennySt + kennyD;

被转换为:

total = jennySt.operator+(kennyD); //成员函数

或:

total = operator+(jennySt, kennyD); //友元函数

同样,成员函数也是通过Stonewt对象调用的。每个调用中都有一个参数(kennyD)是double类型的,因此将调用Stonewt(double)构造函数,将该参数转换为Stonewt对象。

如果定义了operator double()成员函数,将造成混乱,因为该函数将提供另一种解释方式。编译器不是将kennyD转换为double并执行Stonewt加法,而是将jennySt转换为double并执行double加法。过多的转换函数会导致二义性。

最后:

total = pennyD + jennySt;

被转换为:

total = operator+(pennyD, jennySt); //友元函数

其中,两个参数都是double类型,因此将调用构造函数Stonewt(double),将它们转换为Stonewt对象。

然而,不能调用成员函数将jennySt和peenyD相加。将加法语法转换为函数调用将类似如下:

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;

第一种方式(依赖于隐式转换)使程序更简短,因为定义的函数较少。需要完成的工作较少,出错的机会较小。但每次需要转换时,都将调用转换构造函数,这将增加时间和内存开销。

第二种方式(增加一个显式地匹配类型的函数)则正好相反。但它程序较长,需要完成的工作更多,运行速度较快。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值