1.实现隐式类类型转换
short a=2000;
int b;
b=a;
short是两字节,int是四字节,由short型转成int型是宽化转换(bit位数增多),编译器没有warning,如下图所示。宽化转换(如char到int,int到long long,int到float,float到double,int到double等)构成隐式转换,编译器允许直接转换。
但若
double a=2000;
short b;
b=a;
是从8字节的double型转成2字节的short型变量,是窄化转换,编译器就会有warning了,如下所示,提醒程序员可能丢失数据。不过需要注意的是,有些隐式转换,编译器可能并不给出warning,比如int到short,但数据溢出却依然会发生。
隐式转换一
使用单参数的构造函数或N个参数中有N-1个是默认参数的构造函数,如:
class A
{
public:
A(stirng s);
A(string s,int a = 0);
};
class String
{
public:
String ( const char* p ); // 用C风格的字符串p作为初始化值
//…
}
String s1 = “hello”; //OK 隐式转换,等价于String s1 = String(”hello”),将char型变成了string类
隐式转换二
使用operator what_you_want_to_convert_type() const
class A
{
public:
operator char*() const
{
return data;//当从其他类型转换到char*时自动调用
}
private:
char* data;
};
避免隐式类型转换的方法:在单参数的构造函数或N个参数中有N-1个是默认参数的构造函数声明之前加上explicit。
C风格显式转换(C style explicit conversion)
要去掉上述waring很简单,熟悉C语言的程序员知道,有两种简单的写法(C风格转换与函数风格转换):
double a=2000.3;
short b;
b = (short) a; // c-like cast notation
b = short (a); // functional notation
// class type-casting
#include
using namespace std;
class CDummy {
float i,j;
CDummy():i(100),j(10){}
};
class CAddition:public CDummy
{
int *x,y;
public:
CAddition (int a, int b) { x=&a; y=b; }
int result() { return *x+y;}
};
int main () {
CDummy d;
CAddition * padd;
padd = (CAddition*) &d;
cout << padd->result();
return 0;
}
编译器不报任何错,但运行结果出错,
交流群728483370,一起学习加油~每天晚上群里面也会有大佬直播企业项目开发学习,更有各种项目案例供你自己学习哦!
此时父类的指针&d被C风格转换方式强制转成了子类的指针了,后面调用了子类的方法result,需要访问*x,但指针指向的对象本质还是父类的,所以x相当于父类中的i,y相当于父类中的j,*x相当于*i,但i是float型变量(初始化为100),不是地址,所以出错,如果程序员正是鲁莽地对这个地址指向的内存进行写入操作,那将可能会破坏系统程序,导致操作系统崩溃!
这里有一个重要概念,CAddition*是子类的指针,它的变量padd可以调用子类的方法,但是它指向的是父类的对象,也就是说padd指向的内存空间里存放的是父类的成员变量。深入地说,数据在内存中是没有“类型”一说的,比如0x3F可能是字符型,也可能是整型的一部分,还可能是地址的一部分。我们定义的变量类型,其实就是定义了数据应该“被看成什么”的方式。
因此padd类指针实质是定义了取值的方式,如padd->x就是一并取出内存空间里的0号单元至3号单元的值(共4个字节),将其拼成32位并当作指针,padd->y则取出内存空间里的4号单元至7号单元(共4个字节),将其拼成32位并当作int型变量。但实际上padd指向的是父类的对象,也就是前4个字节是float型变量,后4个字节也是float型变量。
从这里可以看出,程序员的这种转换使编译器“理解”出错,把牛当成马了。
从上可见,用C风格的转换其实是不安全的,编译器无法看到转换的不安全。
上行转换(up-casting)与下行转换(down-casting)
看到这个,读者可能会问,哪些转换不安全?根据前面所举的例子,可以看到,不安全来源于两个方面:其一是类型的窄化转化,会导致数据位数的丢失;其二是在类继承链中,将父类对象的地址(指针)强制转化成子类的地址(指针),这就是所谓的下行转换。“下”表示沿着继承链向下走(向子类的方向走)。
类似地,上行转换的“上”表示沿继承链向上走(向父类的方向走)。
我们给出结论,上行转换一般是安全的,下行转换很可能是不安全的。
为什么呢?因为子类中包含父类,所以上行转换(只能调用父类的方法,引用父类的成员变量)一般是安全的。但父类中却没有子类的任何信息,而下行转换会调用到子类的方法、引用子类的成员变量,这些父类都没有,所以很容易“指鹿为马”或者干脆指向不存在的内存空间。
值得一说的是,不安全的转换不一定会导致程序出错,比如一些窄化转换在很多场合都会被频繁地使用,前提是程序员足够小心以防止数据溢出;下行转换关键看其“本质”是什么,比如一个父类指针指向子类,再将这个父类指针转成子类指针,这种下行转换就不会有问题。
针对类指针的问题,C++特别设计了更加细致的转换方法,分别有:
static_cast
dynamic_cast
reinterpret_cast
const_cast
可以提升转换的安全性。
static_cast (expression) 静态转换
静态转换是最接近于C风格转换,很多时候都需要程序员自身去判断转换是否安全。比如:
double d=3.14159265;
int i = static_cast(d);
但static_cast已经有安全性的考虑了,比如对于不相关类指针之间的转换。参见下面的例子:
// class type-casting
#include
using namespace std;
class CDummy {
float i,j;
};
class CAddition {
int x,y;
public:
CAddition (int a, int b) { x=a; y=b; }
int result() { return x+y;}
};
int main () {
CDummy d;
CAddition * padd;
padd = (CAddition*) &d;
cout << padd->result();
return 0;
}
这个例子与之前举的例子很像,只是CAddition与CDummy类没有任何关系了,但main()中C风格的转换仍是允许的padd = (CAddition*) &d,这样的转换没有安全性可言。
如果在main()中使用static_cast,像这样:
int main () {
CDummy d;
CAddition * padd;
padd = static_cast
cout << padd->result();
return 0;
}
编译器就能看到这种不相关类指针转换的不安全,报出错误。注意这时不是以warning形式给出的,而直接是不可通过编译的error。从提示信息里可以看到,编译器说如果需要这种强制转换,要使用reinterpret_cast(稍候会说)或者C风格的两种转换。
总结一下:static_cast最接近于C风格转换了,但在无关类的类指针之间转换上,有安全性的提升。
dynamic_cast (expression) 动态转换
动态转换确保类指针的转换是合适完整的,它有两个重要的约束条件,
要求new_type为指针或引用
下行转换时要求基类是多态的(基类中包含至少一个虚函数)。
看一下下面的例子:
#include
using namespace std;
class CBase { };
class CDerived: public CBase { };
int main()
{
CBase b; CBase* pb;
CDerived d; CDerived* pd;
pb = dynamic_cast
pd = dynamic_cast
}
在最后一行代码有问题,编译器给的错误提示,基类CBase不是vritual多态类型。
把类的定义改成:
class CBase { virtual void dummy() {} };
class CDerived: public CBase {};
再编译,结果编译成功。
我们看到一个奇怪的现象,将父类经过dynamic_cast转成子类的指针竟然是空指针!这正是dynamic_cast提升安全性的功能,dynamic_cast可以识别出不安全的下行转换,但并不抛出异常,而是将转换的结果设置成null(空指针)。
再举一个例子:
#include
#include
using namespace std;
class CBase { virtual void dummy() {} };
class CDerived: public CBase { int a; };
int main () {
try {
CBase * pba = new CDerived;
CBase * pbb = new CBase;
CDerived * pd;
pd = dynamic_cast
if (pd==0) cout << "Null pointer on first type-cast" << endl;
pd = dynamic_cast
if (pd==0) cout << "Null pointer on second type-cast" << endl;
} catch (exception& e) {cout << "Exception: " << e.what();}
return 0;
}
输出结果是:Null pointer on second type-cast
两个dynamic_cast都是下行转换,第一个转换是安全的,因为指向对象的本质是子类,转换的结果使子类指针指向子类,天经地义;第二个转换是不安全的,因为指向对象的本质是父类,“指鹿为马”或指向不存在的空间很可能发生!
reinterpret_cast (expression) 重解释转换
这个转换是最“不安全”的,两个没有任何关系的类指针之间转换都可以用这个转换实现
class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast(a);//correct!
reinterpret_cast可以把整型数转换成地址(指针),这种转换在系统底层的操作,有极强的平台依赖性,移植性不好。
它同样要求new_type是指针或引用,下面的例子是通不过编译的:
double a=2000.3;
short b;
b = reinterpret_cast (a); //compile error!
const_cast (expression) 常量向非常量转换
// const_cast
#include
using namespace std;
void print (char * str)
{
cout << str << endl;
}
int main () {
const char * c = "sample text";
char *cc = const_cast
Print(cc);
return 0;
}
交流群728483370,一起学习加油~每天晚上群里面也会有大佬直播企业项目开发学习,更有各种项目案例供你自己学习哦!
从char *cc = const_cast