在c++中进行类型转换只需要在变量前加上变量类型,并且转换是双向的。
例如:
int i = 0;
double d = 1.9;
int i1 = (int) d;
double d1 = (double) i;
这种类型转换方式只适用于基本数据类型,对复杂的自定义类型不适用。
因此,C++中提供了四种类型转换符:static_cast、dynamic_cast、const_cast、reinterpret_cast。
1、dynamic_cast
dynamic_cast用于类继承层次间的指针或引用转换。主要还是用于执行“安全的向下转型(safe downcasting)”,也即是基类对象的指针或引用转换为同一继承层次的其他指针或引用。至于“向上转型”(即派生类指针或引用类型转换为其基类类型),本身就是安全的,尽管可以使用dynamic_cast进行转换,但这是没必要的, 普通的转换已经可以达到目的,毕竟使用dynamic_cast是需要开销的。
不同于其它的强制类型转换,dynamic_cast在运行时会进行类型检查,如果绑定到引用或指针的对象不是目标类型的对象,则dynamic_cast失败:
class Base(){virtual void dummy(){} };
class Derived:public Base{};
Base *b1 = new Base;
Base *b2 = new Derived;
Derived *b2d1 = dynamic_cast<Derived*>(b1); //转换失败,返回NULL
Derived *b2d2 = dynamic_cast<Derived*>(b2); //转换成功
Derived &b2d3 = dynamic_cast<Derived&>(*b1); //转换失败,抛出异常
Derived &b2d4 = dynamic_cast<Derived&>(*b2); //转换成功
对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;
对引用进行dynamic_cast,失败抛出一个异常,成功返回正常cast后的对象引用。
dynamic_cast是四个强制类型转换操作符中最特殊的一个,它支持运行时识别指针或引用。
首先,dynamic_cast依赖于RTTI信息,其次,在转换时,dynamic_cast会检查转换的source对象是否真的可以转换成target类型,
这种检查不是语法上的,而是真实情况的检查。
dynamic_cast与继承层次的指针
对于“向下转型”有两种情况。
一种是基类指针所指对象是派生类类型的,这种转换是安全的;
另一种是基类指针所指对象为基类类型,在这种情况下dynamic_cast在运行时做检查;
#include "stdafx.h"
#include<iostream>
using namespace std;
class Base
{
public:
Base(){};
virtual void Show(){cout<<"This is Base calss";}
};
class Derived:public Base
{
public:
Derived(){};
void Show(){cout<<"This is Derived class";}
};
int main()
{
//这是第一种情况
Base* base = new Derived;
if(Derived *der= dynamic_cast<Derived*>(base))
{
cout<<"第一种情况转换成功"<<endl;
der->Show();
cout<<endl;
}
//这是第二种情况
Base * base1 = new Base;
if(Derived *der1 = dynamic_cast<Derived*>(base1))
{
cout<<"第二种情况转换成功"<<endl;
der1->Show();
}
else
{
cout<<"第二种情况转换失败"<<endl;
}
delete(base);
delete(base1);
system("pause");
}
运行结果:
dynamic_cast和引用类型
在前面的例子中,使用了dynamic_cast将基类指针转换为派生类指针,也可以使用dynamic_cast将基类引用转换为派生类引用。
同样的,引用的向上转换总是安全的:
Derived c;
Derived & der2= c;
Base & base2= dynamic_cast<Base&>(der2);//向上转换,安全
base2.Show();
所以,在引用上,dynamic_cast依旧是常用于“安全的向下转型”。与指针一样,引用的向下转型也可以分为两种情况,与指针不同的是,并不存在空引用,所以引用的dynamic_cast检测失败时会抛出一个bad_cast异常:
int main()
{
//第一种情况,转换成功
Base &base1 = new Derived;
Derived &der1 = dynamic_cast<Derived&>(base1);
cout<<"第一种情况:";
der1.Show();
cout<<endl;
//第二种情况
Base &base = new Base ;
cout<<"第二种情况:";
try{
Derived & der = dynamic_cast<Derived&>(base);
}
catch(bad_cast)
{
cout<<"转化失败,抛出bad_cast异常"<<endl;
}
system("pause");
}
运行结果:
使用dynamic_cast转换的Base类至少带有一个虚函数
当一个类中拥有至少一个虚函数的时候,编译器会为该类构建出一个虚函数表(virtual method table),虚函数表记录了虚函数的地址。如果该类派生了其他子类,且子类定义并实现了基类的虚函数,那么虚函数表会将该函数指向新的地址。虚表是C++多态实现的一个重要手段,也是dynamic_cast操作符转换能够进行的前提条件。当类没有虚函数表的时候(也即一个虚函数都没有定义),dynamic_cast无法使用RTTI,不能通过编译(个人猜想...有待验证)。
当然,虚函数表的建立对效率是有一定影响的,构建虚函数表、由表查询函数 都需要时间和空间上的消耗。所以,除了必须声明virtual(对于一个多态基类而言),不要轻易使用virtual函数。对于虚函数的进一步了解,可以查看《Effective C++》
2、const_cast
const_cast转换过程中增加或删除const属性。
class Test{};
const Test *t1 = new Test;
Test *t2 = const_cast<Test*>(t1); //转换成功
除了添加或删除const特性,用const_cast符来执行其它类型转换,都会引起
编译错误。
3、static_cast
static_cast可以完全替代c的类型转换,而且在对对象指针之间的类型转换时,可以将父类指针转换成子类指针,也可以将子类指针转换成父类指针,但是如果两个类不相关则无法相互转换。 需注意的是,如果父类指针指向一个父类对象,此时将父类指针转换成子类指针虽然可以通过static_cast实现,但是这种转换很不安全;如果父类指针本身就指向子类指针则不存在安全问题。
class Base(){};
class Derived:public Base{};
Base *b1 = new Base;
Base *b2 = new Derived;
Derived *b2d1 = static_cast<Derived*>(b1); //转换成功不安全
Derived *b2d2 = static_cast<Derived*>(b2); //转换成功安全
int i = 0;
double d = 1.9;
int d2i = static_cast<int>d;
double i2d = static_cast<double>i;
编译器隐式执行的任何类型转换都可以由static_cast显式完成:
当需要将一个较大的算术类型赋值给较小的类型时,使用强制类型转换非常有效。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不关心潜在的精度损失。对于从一个较大的算术类型到一个较小类型的赋值,编译器通常会产生警告。当显示提供强制类型转换时,警告信息会被关闭。
编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:
double d = 97.0;
// cast specified to indicate that the conversion is intentional
char ch = static_cast<char>(d);
如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的。例如,下面的程序使用 static_cast 找回存放在 void* 指针中的值:
void* p = &d; // ok: address of any data object can be stored in a void*
// ok: converts void* back to the original pointer type
double *dp = static_cast<double*>(p);
可通过 static_cast 将存放在 void* 中的指针值强制转换为原来的指针类型,此时我们应确保保持指针值。也就是说,强制转换的结果应与原来的地址值相等。
4、reinterpret_cast
reinterpret_cast可以将一种类型的指针直接转换成另一种类型的指针,不论两个类型之间是否有继承关系。而且reinterpret_cast可以将一个指针转换为一个整数,也可以把一个整数转换成一个指针。reinterpret_cast还经常用咋不同函数指针之间的转换。
class A{};
class B{};
A *a = new A;
B *a2b = reinterpret_cast<B*>(a); //转换成功
static_cast 运算符完成*相关类型*之间的转换. 而 reinterpret_cast 处理*互不相关的类型*之间的转换.
"互不相关的类型"指的是两种完全不同的类型,如从整型到指针类型,或者从一个指针到另一个毫不相干的指针.
所以 reinterpret_cast 常常被用作不同类型指针间的相互转换,因为所有类型的指针的长度都是一致的(32位系统上都是4字节),按比特位拷贝后不会损失数据.
通常为操作数的位模式提供较低层次的重新解释,reinterpret_cast 本质上依赖于机器。为了安全地使用 reinterpret_cast,要求程序员完全理解所涉及的数据类型,以及编译器实现强制类型转换的细节。
例如,对于下面的强制转换:
int *ip;
char *pc = reinterpret_cast<char*>(ip);
变量 pc 所指向的真实对象其实是 int 型,而并非字符数组。任何假设 pc 是普通字符指针的应用,都有可能带来有趣的运行时错误。例如,下面语句用 pc 来初始化一个 string 对象,它可能会引起运行时的怪异行为。
string str(pc);
问题源于类型已经改变时编译器没有提供任何警告或错误提示。当我们用 int 型地址初始化 pc 时,由于显式地声明了这样的转换是正确的,因此编译器不提供任何错误或警告信息。后面对 pc 的使用都假设它存放的是 char* 型对象的地址,编译器确实无法知道 pc 实际上是指向 int 型对象的指针。
因此用 pc 初始化 str 是完全正确的——虽然实际上是无意义的或是错误的。查找这类问题的原因相当困难,特别是如果 ip 到 pc 的强制转换和使用 pc 初始化 string 对象这两个应用发生在不同文件中的时候。
强烈建议:应该避免使用强制类型转换,强制类型转换关闭或挂起了正常的类型检查。
内容资料来源于:
https://blog.csdn.net/pyy18829518070/article/details/80077655
https://www.cnblogs.com/xiangtingshen/p/10851851.html
https://www.cnblogs.com/yshl-dragon/archive/2013/03/22/2975376.html