首先说一下C++如何处理内置类型转换的:
分为三种情况:
(1)如果要进行的转换之间是兼容的,C++自动将值转换为接收变量的类型:
如:
intcount=8.8;
doubletime=3;
longday=8;
在C++看来这些都是合法的,因为他们表示的本质是一样的:数字,而且C++包含了用于进行转换的内置规则。然而可能会降低精度。
(2):C++不自动转换不兼容的类型:
如int*pr=10;
这种语句是非法的,因为左边是指针,但是右边却是数字。
(3)强制类型转换:
如:int*pr=(int*)10;
这条语句通过类型的强制转换的结果是这条语句可以正常进行。表示:定义一个地址为10的指针。(这种赋值是没有什么意义的);
C++如何处理用户自定义的类型:
1.将别的转换为类对象:
将类定义成与基本类型相关或者是与另一个类相关,使得从一个类到另一个类的转换时有意义的。在这种情况下可以指示C++如何进行自动类型转换,或者通过强制类型转换来完成:
下面我们通过一个例子来详细介绍:
需求:计量单位磅转换为英石:
stone.h文件:
#ifndefSTONEWT_H_
#defineSTONEWT_H_
classCStonewt{
private:
enum{Lbs_per_stn=14};
intm_stone;
doublem_pds_left;
doublem_pounds;
public:
CStonewt();
CStonewt(doublelbs);
CStonewt(intstn,doublelbs);
~CStone();
voidshow_lbs()const;
voidshow_stn()const;
}
#endifSTONEWT_H_
stone.cpp文件:
#includeiostream
#include"stonewt.h";
CStonewt::CStonewt(){
m_stone=m_pounds=m_pds_left=0;
}
CStonewt::CStonewt(doublelbs){
m_stone=int(lbs)/Lbs_per_stn;
m_pds_left=int(lbs)%Lbs_per_stn+lbs-int(lbs);
m_pounds=lbs;
}
CStonewt::CStonewt(intstn,doublelbs){
m_stone=stn;
m_pds_left=lbs;
m_pounds=stn*Lbs_per_stn+lbs;
}
CStonewt::~CStonewt(){}
voidCStonewt::show_lbs()const{
std::coutm_pounds"pounds\n";
}
voidCStonewt::show_stn()const{
std::coutm_stone"stone,"m_pds_left"pounds\n";
}
main程序:
#includeiostream
#include"stonewt.h";
usingstd::cout;
voiddisplay(constCStonewtst,intn);
intmain(){
CStonewtincognito=;
CStonewtwolfe(.7);
CStonewttaft(21.8);
cout"theCelebrityweight";
incognito.show_stn();
cout"Thedetectiveweighted";
taft.show_lbs();
incognito=.8;
taft=;
cout"Afterdinner;thecelebrityweighted";
incognito.show_stn();
cout"Afterdinner,thePressidentweighted";
taft.show_lbs();
display(taft,2);
cout"Thewrestlerweighedevenmore.\n";
display(,2);
cout"Nostoneleftunearned.\n";
system("pause");
return0;
}
voiddisplay(constCStonewtst,intn)
{
inti;
for(i=0;in;i++)
{
cout"WOW!";
st.show_stn();
}
}
CStonewt类有三个构造函数,可以允许将CStonewt对象初始化为一个浮点数或者两个浮点数,也可以不进行初始化。
隐式的自动类型转换:
下面我们先来看一下这个构造函数:
CStonewt(doublelbs);
这个构造函数允许将double类型的值转化为Stonewt类型
eg:CStonewtmyCat;
myCat=19.6;
原理:程序使用CStonewt(doublelbs),来创建一个临时的CStonewt对象,然后将19.6赋值给它,随后,采用逐成员赋值的方式将该对象的内容赋值到myCat中,这一过程成为隐式转换,因为他是自动进行的。而不需要显示强制类型转换。
只有接受单个参数的构造函数才能作为转换函数,例如:CStonewt(intstn,doublelbs);不能作为转换类型。但是如果给第二哥参数提供默认值,它遍可以转换int:
CStonewt(intstn,doublelbs=0);
显式类型强制转换:
将构造函数用作自动类型转换函数似乎是一种非常好的特性,但是这种自动类型转换并不是总是合乎需要的,因为这可能导致意外的类型转换:
解决方法:C++新增了关键字explicit用于关闭这种自动特性,也就是说可以这样声明构造函数:
explicitCStonewt(doublelbs);
这将关闭隐式类型转换,但仍然允许显示类型转换,即显式类型转换:
CStonewtmyCat;
myCat=19.6(这种方式是错的)
myCat=CStonewt(19.6)(允许的)
myCat=(CStonewt)19.6;(允许的)
那么问题来了,编译器都在什么时候调用CStonewt(doublelbs)函数呢?(如果声明为explicit,则只能显式类型转换,不支持以下几点):
1.将CStonewt对象初始化为double值时;
2.将double值传递给接受CStonewt参数的函数时;
3.返回值被声明为CStonewt得到函数试图返回double值时;
4.将double值赋给CStonewt对象时;
5.在上述任一一种情况下,使用可转换为double类型的内置类型时;
最重要的一点:
函数原型化提供的参数匹配过程中,允许使用CStonewt(double)构造函数来转换其他数值类型。
eg:CstonewtJumb();
Jumb=;
这两条语句都是先将int转化为double,然后使用CStonewt(double)构造函数。然而这是有前提性的:
即不存在二义性。如果存在CStonewt(long),
则编译器将拒绝执行这些语句,因为int可以转为long或者double,一次调用会出现二义性..、
将类类型转换为别的类型:
构造函数只用于从某种类型到类类型的转换,要进行相应的反转,必须使用特殊的C++运算符函数--转换函数
转换函数是自定义的强制类型转换,可以像使用强制类型转换那样使用它们。
CStonewtwolfe=.7;
doublehost=double(wolfe);
doublethinker=(double)wolfe;
如何创建转换函数:
operatortypeName();
注意点:
转换函数必须是类方法
转换函数不能指定返回类型
转换函数不能有参数
接着上个程序接着说:
如果现在我想让CStonewt类型转化为double类型,要怎么做呢?
1.在CStonewt的.h文件中声明类方法:
operatordouble()const;
operatorint()const;
然后在cpp文件中完成定义:
CStonewt::operatorint()const
{
returnpounds;
}
CStonewt::operatordouble()const
{
returnint(pounds+0.5);
}
此时,我们就可以在main函数中将CStonewt赋值给int、double了;
CStonewtpopins(9,2.8);
doublep_wt=popins;
cout(int)popinnsendl;
我们首先来分析一下以上几个语句:
首先定义popins对象,将其赋值给一个double变量。
输出时要注意将其强行转换一下,如果我们直接输出popins,那么编译器不知道该将其转换为int还是double后输出。(因为我们没有重载CStonewt的输出)
还有一种情况需要注意:
Longtemp=popins;这条语句是不正确的,因为存在二义性,因为int、double都可以转换为long类型。如果删除一个int或者double的转换函数,则这条语句将可以执行。。
缺点:
跟构造函数一样,转换函数提供执行自动、隐式转换的函数所存在的问题是:
在用户不希望转换的时候转换函数也可能进行转换。
总结:
1.要谨慎的使用隐式转换函数,通常最好的选择是仅在被显示的调用时才会执行。
2.只有一个参数的类构造函数将用于类型与该参数相同的值转换为类类型。
例如:将int类型转换为CStonewt对象是,接受int参数的CStonewt类构造函数将自动被调用,然而在构造函数声明中使用explicit可防止隐式转换,而只允许显式转换。
3.被称为转换函数的特殊类成员运算符函数,用于将类对象转换为其他类型。
转换函数是类成员,没有返回类型,没有参数名为operatortypeName();
其中typeName是对象将被转换为的类型,将类对象赋给typeName变量或者将其强制转换为typeName
类型时,该转换函数将会被自动调用。
4.尤其要注意过多的转换函数将导致出现二义性的几率变大,要谨慎防止二义性的出现。C++隐式类类型转换
《C++Primer》中提到:
“可以用单个形参来调用的构造函数定义了从形参类型到该类类型的一个隐式转换。”
这里应该注意的是,“可以用单个形参进行调用”并不是指构造函数只能有一个形参,而是它可以有多个形参,但那些形参都是有默认实参的。
那么,什么是“隐式转换”呢?上面这句话也说了,是从构造函数形参类型到该类类型的一个编译器的自动转换。
下面通过代码来看一看:
#includestring
#includeiostream
usingnamespacestd;
classBOOK//定义了一个书类
{
private:
string_bookISBN;//书的ISBN号
float_price;//书的价格
public:
//定义了一个成员函数,这个函数即是那个“期待一个实参为类类型的函数”
//这个函数用于比较两本书的ISBN号是否相同
boolisSameISBN(constBOOKother){
returnother._bookISBN==_bookISBN;
}
//类的构造函数,即那个“能够用一个参数进行调用的构造函数”(虽然它有两个形参,但其中一个有默认实参,只用一个参数也能进行调用)
BOOK(stringISBN,floatprice=0.0f):_bookISBN(ISBN),_price(price){}
};
intmain()
{
BOOKA("A-A-A");
BOOKB("B-B-B");
coutA.isSameISBN(B)endl;//正经地进行比较,无需发生转换
coutA.isSameISBN(string("A-A-A"))endl;//此处即发生一个隐式转换:string类型--BOOK类型,借助BOOK的构造函数进行转换,以满足isSameISBN函数的参数期待。
coutA.isSameISBN(BOOK("A-A-A"))endl;//显式创建临时对象,也即是编译器干的事情。
system("pause");
}
代码中可以看到,isSameISBN函数是期待一个BOOK类类型形参的,但我们却传递了一个string类型的给它,这不是它想要的啊!还好,BOOK类中有个构造函数,它使用一个string类型实参进行调用,编译器调用了这个构造函数,隐式地将stirng类型转换为BOOK类型(构造了一个BOOK临时对象),再传递给isSameISBN函数。
隐式类类型转换还是会带来风险的,正如上面标记,隐式转换得到类的临时变量,完成操作后就消失了,我们构造了一个完成测试后被丢弃的对象。
我们可以通过explicit声明来抑制这种转换:
explicitBOOK(stringISBN,floatprice=0.0f):_bookISBN(ISBN),_price(price){}
explicit关键字只能用于类内部的构造函数声明上.这样一来,BOOK类构造函数就不能用于隐式地创造对象了,编译上面的代码会出现这样的提示:
现在用户只能进行显示类型转换,显式地创建临时对象。
总结一下:
1.可以使用一个实参进行调用,不是指构造函数只能有一个形参。
2.隐式类类型转换容易引起错误,除非你有明确理由使用隐式类类型转换,否则,将可以用一个实参进行调用的构造函数都声明为explicit。
3.explicit只能用于类内部构造函数的声明。它虽然能避免隐式类型转换带来的问题,但需要用户能够显式创建临时对象(对用户提出了要求)。