当前位置:嗨网首页>书籍在线阅读

16-类的自动转换和强制类型转换

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

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

下面介绍类的另一个主题——类型转换。本节讨论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章介绍过,这些转换将降低精度。例如,将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++如何自动进行转换,或通过强制类型转换来完成。为了说明这是如何进行的,我们将第3章中的磅转换为英石的程序改写成类的形式。首先,设计一种合适的类型。我们基本上是以两种方式(磅和英石)来表示重量的。对于在一个实体中包含一个概念的两种表示来说,类提供了一种非常好的方式。因此可以将重量的两种表示放在同一个类中,然后提供以这两种方式表达重量的类方法。程序清单11.16提供了这个类的头文件。

程序清单11.16 stonewt.h

// 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

正如第10章指出的,对于定义类特定的常量来说,如果它们是整数,enum提供了一种方便的途径。也可以采用下面这种方法:

static const int Lbs_per_stn = 14;

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

// stonewt.cpp -- Stonewt methods
#include <iostream>
using std::cout;
#include "stonewt.h"
// construct Stonewt object from double value
Stonewt::Stonewt(double lbs)
{
    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)
{
    stone = stn;
    pds_left = lbs;
    pounds = stn * Lbs_per_stn +lbs;
}
Stonewt::Stonewt() // default constructor, wt = 0
{
    stone = pounds = pds_left = 0;
}
Stonewt::~Stonewt() // destructor
{
}
// 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";
}

因为Stonewt对象表示一个重量,所以可以提供一些将整数或浮点值转换为Stonewt对象的方法。我们已经这样做了!在C++中,接受一个参数的构造函数为将类型与该参数相同的值转换为类提供了蓝图。因此,下面的构造函数用于将double类型的值转换为Stonewt类型:

Stonewt(double lbs); // template for double-to-Stonewt conversion

也就是说,可以编写这样的代码:

Stonewt myCat; // create a Stonewt object
myCat = 19.6;   // use Stonewt(double) to convert 19.6 to Stonewt

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

只有接受一个参数的构造函数才能作为转换函数。下面的构造函数有两个参数,因此不能用来转换类型:

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 lbs); // no implicit conversions allowed

这将关闭上述示例中介绍的隐式转换,但仍然允许显式转换,即显式强制类型转换:

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限定了这种构造函数,则它只能用于显示转换,否则也可以用于隐式转换。

编译器在什么时候将使用Stonewt(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

// 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";
    return 0;
}
void display(const Stonewt & st, int n)
{
    for (int i = 0; i < n; i++)
    {
        cout << "Wow! ";
        st.show_stn();
    }
}

下面是程序清单11.18所示程序的输出:

The celebrity weighed 19 stone, 9 pounds
The detective weighed 20 stone, 5.7 pounds
The President weighed 302 pounds
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.
Wow! 30 stone, 2 pounds
Wow! 30 stone, 2 pounds
No stone left unearned

程序说明

当构造函数只接受一个参数时,可以使用下面的格式来初始化类对象:

// 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)构造函数满足这种要求,因此编译器将int转换为double,然后使用Stonewt(double)将其转换为一个Stonewt对象。