1. 类的自动转换和强制类型转换
下面介绍类的另一个主题类型转换。
本节讨论C++如何处理用户定义类型的转换。
在讨论这个问题之前,我们先来复习ー下C++是如何处理内置类型转换的。
将一个标准类型变量的值赋给另一种标准类型的变量时,如果这两种类型兼容,则C++自动将这个值转换为接收变量的类型。
例如,下面的语句都将导致数值类型转换:
long count =8; // int value 8 converted to type long
double time = 11 // int value 11 converted to type double
int side =3.33; //double value 3.33 converted to type int 3
上述赋值语句都是可行的,因为在C++看来,各种数值类型都表示相同的东西——数字,同时C++包含用于进行转换的内置规则。然而,这些转换将降低精度。例如,将3.33赋给int变量时,转换后的值为3,丢失了0.33。
C++语言不自动转换不兼容的类型。例如,下面的语句是非法的,因为左边是指针类型,而右边是数字:
int *p = 10; //type clash
虽然计算机内部可能使用整数来表示地址,但从概念上说,整数和指针完全不同。例如,不能计算指针的平方。然而,在无法自动转换时,可以使用强制类型转换
int *p = (int *)10; //ok, p and(int * 10 both pointers
上述语句将10强制转换为int指针类型(即int*类型),将指针设置为地址10。这种赋值是否有意义是另一回事。
可以将类定义成与基本类型或另一个类相关,使得从一种类型转换为另一种类型是有意义的。在这种情况下,程序员可以指示C++如何自动进行转换,或通过强制类型转换来完成。
首先,设计一种合适的类型。我们基本上是以两种方式(磅和英石)来表示重量的。
对于在一个实体中包含一个概念的两种表示来说,类提供了一种非常好的方式。因此可以将重量的两种表示放在同一个类中,然后提供以这两种方式表达重量的类方法。程序清单11.16提供了这个类的头文件。
程序清单 11.16 stonewt.h
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
// stonewt.h -- definition for the Stonewt class
#ifndef STONEWT_H_
#define STONEWT_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); // constructor for double pounds
Stonewt(int stn, double lbs); // constructor for stone, lbs
Stonewt(); // default constructor
~Stonewt();
void show_lbs() const; // show weight in pounds format
void show_stn() const; // show weight in stone format
};
#endif
Stonewt类有3个构造函数,让您能够将 Stonewt对象初始化为一个浮点数(单位为磅)或两个浮点数(分别代表英石和磅)。也可以创建 Stonewt对象,而不进行初始化:
Stonewt blossem(132.5); //weight=132.5 pounds
stonewt buttercup(10, 2); // weight= 10 stone, 2 pounds
Stonewt bubbles; // weight =default value
这个类并非真的需要声明构造函数,因为自动生成的默认构造函数就很好。另一方面,提供显式的声明可为以后做好准备,以防必须定义构造函数,另外, Stonewt类还提供了两个显示函数。一个以磅为单位来显示重量,另一个以英石和磅为单位来显示重量。程序清单11.17列出了类方法的实现。每个构造函数都给这三个私有成员全部赋了值。因此创建Stonewt对象时,将自动设置这两种重量表示。
程序清单11.17 stonewt.cpp
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
// stonewt.cpp -- Stonewt methods
#include <iostream>
using std::cout;
using std::endl;
#include "stonewt.h"
// construct Stonewt object from double value
Stonewt::Stonewt(double lbs)
{
cout << "Stonewt(double lbs)-->lbs=" << lbs << endl;
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)
{
cout << "Stonewt(int stn, double lbs)" << endl;
stone = stn;
pds_left = lbs;
pounds = stn * Lbs_per_stn +lbs;
}
Stonewt::Stonewt() // default constructor, wt = 0
{
cout << "Stonewt()" << endl;
stone = pounds = pds_left = 0;
}
Stonewt::~Stonewt() // destructor
{
cout << "~Stonewt()-->this->pounds=" << pounds << endl;
}
// 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";
}
因为 Stone对象表示一个重量,所以可以提供一些将整数或浮点值转换为 Stonewt对象的方法。
我们已经这样做了!在C++中,接受一个参数的构造函数为将类型与该参数相同的值转换为类提供了蓝图。因此,下面的构造函数用于将 double类型的值转换为 Stonewt类型:
stonewt(double lbs); //template for double-to-stonewt conversion
也就是说,可以编写这样的代码
Stonewt mycat;// create a Stonewt obiect
mycat = 19.6; // use Stonewt(double)to convert 19.6 to stonewt
程序将使用构造函数 Stone( double)来创建一个临时的 Stonewt对象,并将19.6作为初始化值。随后,采用逐成员赋值方式将该临时对象的内容复制到 myCat中。
这一过程称为隐式转换,因为它是自动进行的,而不需要显式强制类型转换。
只有接受一个参数的构造函数オ能作为转换函数。
1. 隐式转换的一个例子
use_stonewt.cpp
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
// stone.cpp -- user-defined conversions
// compile with stonewt.cpp
#include <iostream>
using std::cout;
#include "stonewt.h"
void display(const Stonewt & st, int n);
int main()
{
Stonewt mycat;// create a Stonewt obiect
mycat = 19.6; // use Stonewt(double)to convert 19.6 to stonewt
cout << "temporary object is destructed\n";
//pause();
return 0;
}
直接使用19.6 赋值给 Stonewt对象,看看输出结果
book@book-desktop:~/meng-yue/c++/use_class/03$ ./use_stonewt
Stonewt()
Stonewt(double lbs)-->lbs=19.6
~Stonewt()-->this->pounds=19.6
temporary object is destructed
~Stonewt()-->this->pounds=19.6
book@book-desktop:~/meng-yue/c++/use_class/03$
先是实例化mycat的时候,调用默认的构造函数。
然后执行到
mycat = 19.6; // use Stonewt(double)to convert 19.6 to stonewt
会调用Stonewt(double lbs)
构造一个临时对象,赋值结束就会被析构掉
所以接下来先是临时对象的析构函数里面的打印,然后在打印temporary object is destructed
注意:如果是mycat = 19;
也是可以的,还是调用Stonewt(double lbs)
构造函数
运行结果
book@book-desktop:~/meng-yue/c++/use_class/03$ ./use_stonewt
Stonewt()
Stonewt(double lbs)-->lbs=19
~Stonewt()-->this->pounds=19
temporary object is destructed
~Stonewt()-->this->pounds=19
book@book-desktop:~/meng-yue/c++/use_class/03$
下面的构造函数有两个参数,因此不能用来转换类型:
stonewt(int stn, double lbs): //not a conversion function
然而,如果给第二个参数提供默认值,它便可用于转换int:
Stonewt(int stn, double lbs=0); //int-to-stonewt conversion
将构造函数用作自动类型转换函数似乎是一项不错的特性。然而,当程序员拥有更丰富的C++经验时,将发现这种自动特性并非总是合乎需要的,因为这会导致意外的类型转换。
因此,C+新增了关键字 explicit用于关闭这种自动特性。也就是说,可以这样声明构造函数
explicit Stonewt(double 1bs); // no implicit conversions allowed
例如,在stonewt.h当中将构造函数改成下面这样
再进行尝试下面的赋值,看看能通过吗?
Stonewt mycat;// create a Stonewt obiect
mycat = 19.6; // use Stonewt(double)to convert 19.6 to stonewt
编译结果报错了,没有匹配的函数!
book@book-desktop:~/meng-yue/c++/use_class/03$ g++ -o use_stonewt use_stonewt.cpp stonewt.cpp
use_stonewt.cpp: In function ‘int main()’:
use_stonewt.cpp:16: error: no match for ‘operator=’ in ‘mycat = 19’
stonewt.h:13: note: candidates are: Stonewt& Stonewt::operator=(const Stonewt&)
book@book-desktop:~/meng-yue/c++/use_class/03
使用explict 将关闭上述示例中介绍的隐式转换,但仍然允许显式转换,即显式强制类型转换
stonewt mycat;// create a Stonewt object
mycat =19.6;// not valid if Stonewt(double) is declared as explicit
mycat = Stonewt(19.6); //ok, an explicit conversion
mycat =(Stonewt)19.6; // ok, old form for explicit typecast
注意:只接受一个参数的构造函数定义了从参数类型到类类型的转换。如果使用关键字 explicit限定了这种构造函数,则它只能用于显示转换,否则也可以用于隐式转换。
编译器在什么时候将使用 Stone( double)函数呢?如果在声明中使用了关键字 explicit,则Stonewt( double)将只用于显式强制类型转换,否则还可以用于下面的隐式转换。
- 将 Stonewt对象初始化为 double值时。
- 将 double值赋给 Stonewt对象时。
- 将 double值传递给接受 Stonewt参数的函数时。
- 返回值被声明为 Stonewt的函数试图返回 double值时
- 在上述任意一种情况下,使用可转换为 double类型的内置类型时
下面详细介绍最后一点。函数原型化提供的参数匹配过程,允许使用 Stonewt( double)构造函数来转换其他数值类型。也就是说,下面两条语句都首先将int转换为 double,然后使用 Stonewt( double)构造函数
Stonewt Jumbo(7000); // uses Stonewt(double), converting int to double
Jumbo = 7300;// uses Stonewt(double), converting int to double
然而,当且仅当转换不存在二义性时,才会进行这种二步转换。也就是说,如果这个类还定义了构造函数 Stonewt(long),则编译器将拒绝这些语句,可能指出:int可被转换为long或 double,因此调用存在二义性。
程序清单11.18使用类的构造函数初始化了一些 Stonewt对象,并处理类型转换。请务必将程序清单11.18和程序清单11.17一起编译。
程序清单11.18 stone.cpp
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
// stone.cpp -- user-defined conversions
// compile with stonewt.cpp
#include <iostream>
using std::cout;
#include "stonewt.h"
void display(const Stonewt & st, int n);
int main()
{
Stonewt incognito = 275; // uses constructor to initialize
Stonewt wolfe(285.7); // same as 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; // uses constructor for conversion
taft = 325; // same as taft = Stonewt(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";
// std::cin.get();
return 0;
}
void display(const Stonewt & st, int n)
{
for (int i = 0; i < n; i++)
{
cout << "Wow! ";
st.show_stn();
}
}
运行结果:
book@book-desktop:~/meng-yue/c++/use_class/03$ ./stone
Stonewt(double lbs)-->lbs=275
Stonewt(double lbs)-->lbs=285.7
Stonewt(int stn, double lbs)
The celebrity weighed 19 stone, 9 pounds
The detective weighed 20 stone, 5.7 pounds
The President weighed 302 pounds
Stonewt(double lbs)-->lbs=276.8
~Stonewt()-->this->pounds=276.8
Stonewt(double lbs)-->lbs=325
~Stonewt()-->this->pounds=325
After dinner, the celebrity weighed 19 stone, 10.8 pounds
After dinner, the President weighed 325 pounds
Wow! 23 stone, 3 pounds
Wow! 23 stone, 3 pounds
The wrestler weighed even more.
Stonewt(double lbs)-->lbs=422
Wow! 30 stone, 2 pounds
Wow! 30 stone, 2 pounds
~Stonewt()-->this->pounds=422
No stone left unearned
~Stonewt()-->this->pounds=325
~Stonewt()-->this->pounds=285.7
~Stonewt()-->this->pounds=276.8
book@book-desktop:~/meng-yue/c++/use_class/03$
当构造函数只接受一个参数时,可以使用下面的格式来初始化类对象
//a syntax for initializing a class object when
// using a constructor with one argument
Stonewt incognito = 275;
这等价于前面介绍过过的另外两种格式
//standard syntax forms for initializing class objects
Stonewt incognito(275);
Stonewt incognito = Stonewt(275);
然而,后两种格式可用于接受多个参数的构造函数。
接下来,请注意程序清单11.18的下面两条赋值语句:
incognito = 276. 8;
taft = 325;
第一条赋值语句使用接受 double参数的构造函数,将276.8转换为一个 Stonewt 值,这将把 incognito的 pound成员设置为276.8。因为该语句使用了构造函数,所以还将设置 stone和 pds_ left,成员。同样,第二条赋值语句将一个int值转换为 double类型,然后使用 Stonewt( double)来设置全部3个成员。
最后,请注意下面的函数调用:
display(422, 2); / convert 422 to double, then to Stonewt
display(()的原型表明,第一个参数应是 Stonewt对象( Stonewt和 Stonewt&形参都与 Stonewt实参匹配)
遇到int参数时,编译器査找构造函数 Stonewt(int),以便将该int转换为 Stonewt类型。由于没有找到这样的构造函数,因此编译器寻找接受其他内置类型(int可以转换为这种类型)的构造函数。 Stone( double)造函数满足这种要求,因此编译器将转换为 double,然后使用 Stoney( double)将其转换为一个 Stonewt对象
2. 转换函数
程序清单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)指出了要转换成的类型,因此不需要指定返回类型。转换函数是类方法意味着:它需要通过类对象来调用,从而告知函数要转换的值。因此,函数不需要参数。
要添加将 stonewt对象转换为int类型和 double类型的函数,需要将下面的原型添加到类声明中:
operator int ();
operator double();
程序清单11.19列出了修改后的类声明。
程序清单11.19 stonewt1.h
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
// stonewt.h -- definition for the Stonewt class
#ifndef STONEWT_H_
#define STONEWT_H_
#include <string>
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); // constructor for double pounds
Stonewt(int stn, double lbs); // constructor for 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
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
// stonewt.cpp -- Stonewt methods
#include <iostream>
using std::cout;
using std::endl;
#include "stonewt.h"
// construct Stonewt object from double value
Stonewt::Stonewt(double lbs)
{
cout << "Stonewt(double lbs)-->lbs=" << lbs << endl;
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)
{
cout << "Stonewt(int stn, double lbs)" << endl;
stone = stn;
pds_left = lbs;
pounds = stn * Lbs_per_stn +lbs;
}
Stonewt::Stonewt() // default constructor, wt = 0
{
cout << "Stonewt()" << endl;
stone = pounds = pds_left = 0;
}
Stonewt::~Stonewt() // destructor
{
cout << "~Stonewt()-->this->pounds=" << pounds << endl;
}
// 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语句用显式强制类型转换。请务必将程序清单1.20与程序清单11.21一起编译。
程序清单11.21 stone1.cpp
/*
author:梦悦foundation
公众号:梦悦foundation
可以在公众号获得源码和详细的图文笔记
*/
// stone.cpp -- user-defined conversions
// compile with stonewt.cpp
#include <iostream>
using std::cout;
#include "stonewt1.h"
void display(const Stonewt & st, int n);
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";
// std::cin.get();
return 0;
}
下面是程序清单11.19~程序清单1121组成的程序的输出;它显示了将 Stonewt对象转换为 double类型和int类型的结果
book@book-desktop:~/meng-yue/c++/use_class/03$ ./stone1
Stonewt(int stn, double lbs)
Convert to double => Poppins: 128.8 pounds.
Convert to int => Poppins: 129 pounds.
~Stonewt()-->this->pounds=128.8
book@book-desktop:~/meng-yue/c++/use_class/03$
自动应用类型转换
程序清单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 20并用作数组索引。原则上说,最好使用显式转换,而避免隐式转换。在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++为类提供了下面的类型转换:
- 只有一个参数的类构造函数用于将类型与该参数相同的值转换为类类型。例如,将in值赋Stonewt对象时,接受in参数的 Stoney类构造函数将自动被调用。然而,在构造函数声明中用 explicit可防止隐式转换,而只允许显式转换.
- 被称为转换函数的特殊类成员运算符函数,用于将类对象转换为其他类型。
转换函数是类成员,没有返回类型、没有参数、名为 operator typename(),其中, typename是对象将被转换成的类型将类对象赋给 typename变量或将其强制转换为 typename类型时,该转换函数将自动被调用。
3. 转换函数和友元函数
下面为 Stone类重载加法运算符。可以使用成员函数或友元函数来重载加法。(出于简化的目的,我们假设没有定义 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;
}
别忘了,可以提供方法定义或友元函数定义,但不能都提供。上面任何一种格式都允许这样做:
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 = 1460;
Stonewt total;
total = pennyD + jennySt;
为了解其中的原因,将每一种加法都转换为相应的函数调用。首先:
total = jennySt + bennySt ;
被转换为
total = jennySt.operator+(bennySt); //member function
或:
total = operator+(jennySt, bennySt); //friend function
上述两种转换中,实参的类型都和形参匹配。另外,成员函数是通过 Stonewt对象调用的。
其次:
total = jennySt+ kennyD;
被转换为:
total = jennySt.operator+(kennyD); //member function
或
total = operator+(jennySt, kennyD); // friend function
同样,成员函数也是通过 Stonewt对象调用的。这一次,每个调用中都有一个参数( kennyD)是 double类型的,因此将调用 Stonewt( double)构造函数,将该参数转换为 Stonewt对象。另外,在这种情况下,如果定义了 operator double()成员函数,将造成混乱,因为该函数将提供另一种
解释方式。
编译器不是将 kennyD转换为 double并执行 Stonewt加法,而是将 jennySt转换为 double并执行double加法。
过多的转换函数将导致二义性。
最后:
total = pennyD + jennySt;
被转换为:
total = operator+(pennyD, jennySt); //friend function
其中,pennyD是 double类型,因此将调用构造函数 Stonewt( double),将它转换为 Stonewt对象,然而,不能调用成员函数将 jennySt和 pennyD相加。将加法语法转换为函数调用将类似于下面这样:
total = pennyD.operator+(jennySt); // not meaningful
这没有意义,因为只有类对象才可以调用成员函数。C++不会试图将 pennyD转换为 Stonewt对象。
这里的经验是,将加法定义为友元可以让程序更容易适应自动类型转换。原因在于,两个操作数都成为函数参数,因此与函数原型匹配。
实现加法时的选择
要将 double量和 Stonewt量相加,有两种选择。
第一种方法是(刚介绍过)将下面的函数定义为友元函数,让 Stonewt( double)构造函数将 double类型的参数转换为 Stonewt类型的参数:
operator+(const Stonewt &, const Stonewt &)
第二种方法是,将加法运算符重载为一个显式使用 double类型参数的函数:
Stonewt operator+(double x); //member function
friend Stonewt operator+(double x, Stonewt &s);
这样,下面的语句将与成员函数 operator+( double x)完全匹配:
total =jennySt + kennyD; // Stonewt + double
而下面的语句将与友元函数 operator+( double x, Stonewt&s)完全匹配:
total= pennyD + jennySt; //double +Stonewt
每一种方法都有其优点。第一种方法(依赖于隐式转換)使程序更简短,因为定义的函数较少。这也意味程序员需要完成的工作较少,出错的机会较小。这种方法的缺点是,每次需要转换时,都将调用转換
构造函数,这增加时间和内存开销。
第二种方法(增加一个显式地匹配类型的函数)则正好相反。它使程序较长,程序员需要完成的工作更多,但运行速度较快。
如果程序经常需要将 double值与 Stonewt对象相加,则重载加法更合适;如果程序只是偶尔使用这加法,则依赖于自动转换更简单,但为了更保险,可以使用显式转换。