您的位置:首页 > 编程语言 > C语言/C++

C++ 类型转换

2016-03-14 21:21 357 查看
类型转换主要分为两种: 隐式类型转换 和 显示类型转换.

一、隐式类型转换

1)算术转换: 在混合类型的算术表达式中, 低精度类型将隐式转换为高精度类型。

int ival = 3;
int iv = 2;
double dval = 3.14159;

ival + dval;//ival被提升为double类型,然后进行加法运算
ival/iv + dval ; // ival/iv 执行完的结果才提升为double -> 1.0 + 3.14159


2)赋值表达式:目标类型是被赋值对象的类型

int *pi = 0; // 0被转化为int *类型
ival = dval; // double->int


例外:void指针赋值给其他指定类型指针时,不存在标准转换,编译出错

3)形参和实参类型不一致:目标转换类型为形参的类型

extern double sqrt(double);
cout << "The square root of 2 is " << sqrt(2) << endl;
//2被提升为double类型:2.0


4)函数返回值(返回表达式与返回类型不一致):目标转换类型为函数的返回类型

double difference(int ival1, int ival2)
{
return ival1 - ival2;
//返回值被提升为double类型
}


对于隐式类型转换,要求类型转换的两种类型是能够转换的。

比如对于数值型的内置类型,double转向int,将会发生截断,即导致精度缺失。

但是指针类型之间是无法进行隐式类型转换的,此时必须通过强制类型转换。

但是任何类型的指针都可以隐式的转换为void *类型,但是void *的类型需要显式的类型转换为其他指针类型.

int * pa;
void * pb = pa;             //隐式转换 : 任何类型的指针都可以隐式的转换为void * 类型

int * pc = (int*) pb;       //void *的类型需要显式的类型转换为其他指针类型.

double * pd = (double*)pc;  //指针类型之间是无法进行隐式类型转换的,此时必须通过强制类型转换。


二、显式类型转换

也称为强制类型转换,主要有两种风格:

C 风格: ( type-id )

C++风格: static_cast、dynamic_cast、reinterpret_cast、和const_cast..

最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_cast, dynamic_cast。标准C++中有四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。

2.1 static_cast

语法:

static_cast < type-id > ( expression )


说明:该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。无法保证安全性。

它主要有如下几种用法:

用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把派生类的指针或引用 转换成基类指针或引用)是安全的;进行下行转换时,由于没有动态类型检查,所以是不安全的。 因为,如果基类指针指向的对象并不是派生类对象,那么转换之后得到的派生类指针,很可能调用基类没有的方法,而造成不安全。

用于基本数据类型之间的转换,如把 int 转换成 char,把 int 转换成 enum。这种转换的安全性也要开发人员来保证。

把void指针转换成目标类型的指针(不安全!!). 因为存在int 转换为void ,在转换为double *情况.

把任何类型的表达式转换成void类型。

对于用户自定义类型,如果类型无关,则会编译出错。如果存在继承关系,则可由在基类和派生类之间进行任何转换,在编译期间不会出错,但是并不代表安全。

注意:static_cast不能转换掉expression的const、volitale、或者__unaligned 属性。

2.2 dynamic_cast

语法:

dynamic_cast < type-id > ( expression )


说明:

该运算符把expression转换成type-id类型的对象。

type-id 必须是类的指针、引用 或者void *;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。

expression 所代表的类必须包含虚函数。

dynamic_cast 是根据虚函数类的 type_info 信息进行转换。

dynamic_cast 转换的原理 是以 expression 所指处的 type_info信息根据,判断 type-id 与expression 之间是否存在父子关系。 (PS: type_info 是C++对象模型中的知识,具体参考 C++ 对象模型 )

#include <iostream>
using namespace std;

class A {
public:
~A() {
cout << "~A()";
}

int a;
};

class B{
public:
virtual ~B() {
cout << "~B()";
}
};

class C: public A, public B {
public:
~C() {
cout << "~C()";
}
};

int main() {
C * c = new C;
cout << c << " " << sizeof(*c) <<endl; //0x12b6010 16 , 多重继承,将包含2张虚函数表

B * b1 = dynamic_cast<B *>(c);
cout << b1 << " " << sizeof(*b1) <<endl; //0x12b6010 8 转换成功

A * a2 = dynamic_cast<A *>(b1);
cout << a2 << " " << sizeof(*a2) <<endl; //0x12b6018 4 转换成功

return 0;
}


如上代码 :

B \* b1 = dynamic_cast< B \* >(c) ;
会根据c指针 ( 其实就是vptr虚表指针变量的地址 ) 找到类C的虚表, 进而找到类C的type_info信息,因为从类C的 type_info 信息可以发现 B 和 C之间存在父子关系,可以转换成功。

A * a2 = dynamic_cast< A* >(b1);
根据C++对象模型,类C的内存布局大致如下图,其中C类因为没有自己的成员变量,也没有新增的Virtual 函数,所以C的内存布局如下图右. 也就是C对象中包含两个基类对象部分,而B部分则刚好和C整体的地址重合,所以
A * a2 = dynamic_cast<A*>(b1);
时。同样可以通过指针b1找到类C的type_info信息。进而成功转换指针。



如果上述代码更改如下:

在进行
B * b1 = dynamic_cast<B*>(a2);
转换时, 由于此时a2指针已经与 指针c不一样了,所以此时通过a2指针寻找到的将是类A 的type_info信息,而类A的type_info并没有记载到A与B之间的关系,所以将转换失败。

而此时,即使
C * c1 = dynamic_cast<C*>(a2);
也是无法转换成功的,因为只有在派生类中记载了与父类的父子关系,基类并不知道谁继承了自己。

A * a2 = dynamic_cast<A *>(c);
cout << a2 << " " << sizeof(*a2) <<endl;

B * b1 = dynamic_cast<B *>(a2); //编译错误
cout << b1 << " " << sizeof(*b1) <<endl;

C * c1 = dynamic_cast<C *>(a2); //编译错误
cout << c1 << " " << sizeof(*c1) <<endl;


dynamic_cast 的其他具体用法参考 C++ RTTI机制

2.3 reinpreter_cast

语法:

reinpreter_cast<type-id> (expression)


说明:

type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。

该运算符的用法比较多。用的较少,暂不作讨论。

2.4 const_cast

语法:

const_cast<type_id> (expression)


说明:该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。

常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

voiatile 和 const 类试。举如下一例:

class B
{
public:
int m_iNum;
}

void foo(){

const B b1;

b1.m_iNum = 100; //comile error

B b2 = const_cast<B>(b1);

b2. m_iNum = 200; //fine
}


上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。

三、小结

static_cast 是在编译时进行类型转换,所以发现如果两者之间无法转换,没有转换的可能性(没有”父子”关系),则将会编译错误。不能保证安全性。

dynamic_cast 根据在编译时产生的 type_info 信息在运行时进行类型转换,只应用于多态。可通过转换结果,来保证安全性。如果待转换的类型非多态,将会编译出错。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: