c++学习笔记——类的自动转化和强制类型转化

知识储备——c++内置类型的转化

(1)将一个标准类型变量的值赋给另一种标准类型的变量时,这两种类型兼容,则c++将自动将这个值转换为接受变量的类型;例如:

long count = 3;
double time = 11;
int side = 3.33;//会降低精度

(2) c++不会自动转换不兼容的类型,例如,左边是指针类型,右边是数字;

int * p = 10;

虽然计算机可能使用整数来表达地址,但是从概念上来看,整数和地址是完全不同的。

(3)对于不能自动转换时,可以强制类型转换,例如:

int * p = (int *) 10;

上述语句将10强制转换成int指针类型(即int * 类型),将指针设置设为10。

类的自动转换:

可以将类定义成与基本类型和另一个类相关,使得从一种类型转换成另一种类型。

示例:设计一个可以用英石和英镑两种方式来表达重量的类(1英石=14英镑)

首先头文件:

// stonewt.h -- definition for the Stonewt class
#ifndef STONEWT_H_
#define STONEWT_H_
class Stonewt
{
private:
    enum {Lbs_per_stn = 14};      // pounds per stone 1英石=14英镑
    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个构造函数,可以将Stinewt对象初始化成一个浮点数(单位为磅)或者两个浮点数(英石和磅)或者只创建Stonewt对象但不初始化。

还提供了两个显示函数,一个以磅为单位来显示重量,一个以英石和磅为单位来显示重量。

实现细节如下:

// 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 (double lbs);提供了将double类型转换成Stonewt类型。就是接受一个double类型的英镑值,将其转换成几英石几英镑。

可以这样编写代码:

Stonewt = myCat;    //使用Stonewt()这个默认构造函数创建一个myCat对象
myCat = 19.6;        //使用Stonewt将其转换成Stonewt对象

 上面第二行代码等号右边使用构造函数Stonewt(double)来创建一个临时的Stonewt对象,并将19.6作为其初始值。随后,采用逐成员赋值的方式将临时对象的内容复制到myCat中。这种过程被称为隐式转换,因为它是自动进行的,不需要强制类型转换。

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

Stonewt(int stn, double lbs);    //这不是一个转换函数

但是如果给第二个参数设置默认值,它就可以用于转换int;

Stonewt(int stn, double lbs = 0);

将构造函数作为自动类型转换函数有时候不错,但是有时会造成意外,可以使用关键字explicit来关闭这种自动特性。可以像下面这样声明构造函数:

explicit Stonewt(double lbs);

向上面这样声明就可以关闭上述示例中介绍的隐式转换,但是仍然允许显式转换,即显式强制转换:

onewt = myCat;            //创建了一个Stonewt对象
myCat = 19.6;             //如果函数被声明为explicit,就不允许被转换
mycat = Stonewt(19.6);    //可以,显式转换
mycat = (Stonewt) 19.6;    //可以,显式类型转换的旧形式

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

总结:使用Stonewt(double)的场合

使用了explicit关键字:

1、如果在函数声明中使用了explicit.关键字,则Stonewt(double)将只用于显式强制转换。

没有使用关键字explicit:

1、显式强制类型转换

2、隐式强制类型转化:

(1)将Stonewt对象初始化为double值时(先创建一个Stonewt对象,但不初始化,再将double值赋给Stonewt); 

(2)将double值赋给Stonewt对象时;

(3)将double值传递给接受Stonewt参数的函数时;

(4)返回值被声明为Stonewt的函数试图返回double值时。

最后再回顾和详细介绍一下:

Stone Jumbo(7000);
Jumbo = 7300;

第一行就是显式转换,7000是一个int值,先将7000这个int值转换成double值,然后在使用函数Stonewt(double)将double转换成Stonewt类类型;

第二行就是隐式转换,首先使用Stonewt(double)创建一个Stonewt类的临时对象,并将7000作为其初始值,初始的过程就是现将7000这个int型转换成double型,然后再使用函数Stonewt(double)函数将其转化成Stonewt类类型。随后采用逐成员赋值的方式将临时对象的内容复制到Jumbo中。

注意:当且仅当不存在二义性时,才能进行这种二步转换。也就是说,例如,如如果这个类还定义了函数Stonewt(long),则编译器就拒绝编译上面的语句,原因是int可以转换成long或者double,对所以调用存在二义性。

对上面定义的类进行使用测验:

// 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();
    }
}

输出结果如下:

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

程序解读:

需要注意的是语句display(422, 2);

display()函数的原型是void display(const Stonewt & st, int n),其中第一个参数应该是Stonewt对象,遇到int参数时,编译器首先将查找构造函数Stone(int),以便于将int转换成Stonewt类型。但是由于该程序中没有这样的构造函数,所以编译器将查找其他内置类型(int可以转换这种类型)的构造函数,这里的Stonewt(double)满足该要求,编译器将int转换成double,然后使用Stonewt(double)将其转换成一个Stonewt对象。

转换函数

示例:将Stonewt对象转换成数字

可以将类类型转换成数字,但是不是使用构造函数,构造函数只能将其他类型转换成类类型,要进行相反的转换,就是必须使用特殊的c++运算符——转换函数。

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

转换函数的定义方式,要转换成typeName类型,那么函数形式如下:

operator typeName();

例如,要转换成double类型的原型如下:

operator double();

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

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

 需要注意一下几点:

(1)转换函数必须是类方法

(2)转换函数不能指定返回类型(因为在函数原型中typeName就已经告知函数要转换的值,所以不需要指定函数的返回类型)

(3)转换函数不能有参数(转换函数是通过类对象来调用的,从而告知函数要转换的值,所以,函数不需要参数)

修改后的头文件:

// stonewt1.h -- revised definition for the Stonewt class
#ifndef STONEWT1_H_
#define STONEWT1_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);          // construct from double pounds
    Stonewt(int stn, double lbs); // construct from 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

 头文件中包含了两个转换函数的定义

相应的实现细节:

// stonewt1.cpp -- Stonewt class methods + conversion functions
#include <iostream>
using std::cout;
#include "stonewt1.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";
}

// conversion functions
Stonewt::operator int() const
{

    return int (pounds + 0.5);

}

Stonewt::operator double()const
{
    return pounds; 
}

测试上面的类定义:

// stone1.cpp -- user-defined conversion functions
// compile with stonewt1.cpp
#include <iostream>
#include "stonewt1.h"

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

输出是:

Convert to double => Poppins: 128.8 pounds.
Convert to int => Poppins: 129 pounds.

总结:

作用:向上面的语句  cout << "Poppins: " << int (poppins) << " pounds.\n";中有显式强制类型转换。假设省略了强制类型转化,语句变成cout << "Poppins" << popins << " pounds.\n";此时程序是不会进行隐式转换。因为在类声明中有两个转换函数double和int,不明确指出时,程序会发生二义性转换。

语句double p_wt = poppings;是可以进行转换的,因为double明确指出了p_wt是double类型的变量,所以popings会被转换double类型,不存在二义性错误。

如果类声明中只有double这一种转换函数,则编译器就会接受该语句,不会存在二义性。语句long gone = poppings;也会发生二义性错误,因为double和int型变量都可以赋值给long变量。

综上,当类定义了两种或者更多的转换时,任然可以使用显式强制类型转换来指出要使用那个转换函数,有两种强制类型转换的格式:

long gone = (double) poppings;
long gone = int (popings);

但是,转换函数有其优缺点,提供自动、隐式转换函数有问题:当用户不希望进行转换时,转换函数也可能进行转换。为了消除这种影响,有两种方法:

(1)使用explicit关键字:声明转换函数时使用这个关键字就只能强制类型转换;

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

例如可以将Stonewt::operator int () { return int (pounds + 0.5 ) };

替换成:int Stonewt :: Stone_to_Int() { return int (pounds + 0.5) };

这样的话使用int plb = popings;//是非法的

可以使用int plb = popping.Stone_to_Int();

所以应该谨慎使用隐式转换函数,最好是选择只有在显式地调用时才会执行的函数。

转换函数和友元函数

第一种情况实例

第一个参数是Stonewt对象,第二个参数是double类型,这里假设我们定义了operator double()转换函数,这种情况就是可以使用成员函数或者友元函数来重载加法运算符;

例如:

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

其中的加法运算:total =  jennySt + kennyD;可以通过成员函数的运算符重载,

函数原型是Stonewt operator+(double x);

转换成下列这种情况:

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

 成员函数通过Stonewt()对象来调用,再使用构造函数Stonewt(double)将参数转换成Stonewt对象,最后相加。

也可以通过友元函数的运算符重载,函数原型是:

friend Stonewt operator + (double x, Stonewt & s);

转换成下列的这种情况:

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

注意:上面使用友元函数重载+运算符,如果在这种情况下,定义了operator double()成员函数,会造成混乱,因为函数将提供另一种解释方式,编译器不是将kennyD转换成double并执行Stonewt加法,而是将jennySt转换成double并执行double加法,所以过多的转换函数会造成二义性错误。

第二种情况实例

第一个参数是double类型,而第二个参数是Stonewt类对象。这种情况下只能使用友元函数。

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

这种情况是只能使用友元函数,因为语句total = pennyD + jennySt;等号右边的第一个参数是pennyD不是类对象,它不能调用重载运算符的成员函数,c++也不会试图将pennyD转换成Stonewt对象并执行加法算法,只能对成员函数参数进行转换,而不是调用成员函数的对象

该语句可以转换成下列这样:

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

 这里的经验是,将加法定义为友元可以让程序更容易适应自动类型转换,因为两个操作数都成为函数参数,因此与函数原型匹配。

上面的两个实例中提供的函数原型都是将加法运算符重载为一个显式使用double类型参数的函数

第三种情况实例

函数原型是friend Stonewt operator + (const Stonewt &, const Stonewt &);

如果友元函数原型是上面的这种情况的话,第一个参数和第二个参数都可以是double类型的参数,参数传递过来后,隐式地将double类型的参数转化成Stonewt类型。

总结:如果程旭需要经常将double值和Stonewt对象相加,则重载运算符更合适;如果程序只是偶尔使用这种加法,则依赖自动转换更简单,为了保险起见,可以使用显式转换。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值