一、前言
在C语言的基本数据类型中,可分为四种:整型,浮点型,字符型,布尔型。同时呢,C语言中也有:隐式类型转换 和 显示类型转换。
- 隐式类型的转换:相关类型的转换,即意义相似的类型,例如: int转float
- 强制类型的转换:不同类型的转换,比如: 指针和整型.
那么,在C++中引入了四个关键字:
- static_cast
- const_cast
- reinterpret_cast
- dynamic_cast
新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换。相比C语言表面上看起来功能强大什么都能转,但是转化不够明确,不能进行错误检查,容易出错;C++语言更清晰的表明它们要干什么。程序员只要扫一眼这样的代码,就能立即知道一个强制转换的目的。那么接下来就对这四个关键字进行一个分析。
二、static_cast(相关类型)
在C++语言中static_cast用于数据类型的强制转换,强制将一种数据类型转换为另一种数据类型。例如将整型数据转换为浮点型数据。
int a = 10;
int b = 3;
double result = static_cast<double>(a) / static_cast<double>(b);
格式用法:static_cast <类型说明符> (变量或表达式)
static_cast主要有几种以下用法:
1)、用于类层次结构中基类和派生类之间指针或引用的转换。另外,static_cast只能在有相互联系的类型中进行相互转换,不一定包含虚函数。
- 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的。
- 进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的。(前三种转换均是编译期完成,无法动态类型检查)
2)、用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证。
【注意】static_cast不能转换掉表达式的const、volitale或者__unaligned属性。
三、const_cast(去除常量性)
const_cast是用于强制去掉常量const不能被修改的常数特性,但需要特别注意的是:
const_cast不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。
这么讲有点抽象,咱还是直接上例子吧:
/*----- 错误例子 -----*/
const int a = 10;
const int * p = &a;
*p = 20; // p指针具有常量性,不可修改
int b = const_cast<int>(a);
//const_cast强制转换对象必须为指针或引用,而a为一个变量
/*----- 正确例子 -----*/
const int a = 10;
const int * p = &a;
int *q;
q = const_cast<int *>(p);
*q = 20; //fine
cout << a << *p << *q <<endl;
cout << &a << p << q <<endl;
/*----- 输出 -----*/
10 20 20
002CFAF4 002CFAF4 002CFAF4
通过例子,我们可以看到,指针p和指针q都是指向a变量的,指向地址相同,而且经过调试发现002CFAF4地址内的值确实由10被修改成了20,这是怎么一回事呢?为什么a的值打印出来还是10呢?
其实,这种操作属于一种“未定义行为”,也即是说操作结果C++并没有明确地定义,结果是怎样的完全由编译器的心情决定。对于未定义的行为,我们只能避免之。
上面对于const_cast的讨论,其实不太能明白const_cast的具体用处。其实呢,我们使用const_cast的目的并不是为了修改项目已经定义了const的变量值,而是用非常量性的指针或引用来接收前面定义的const变量。当然,在现实项目中,要尽量避免使用const_cast。
四、reinterpret_cast(不相关类型)
在C++语言中,reinterpret_cast主要有三种强制转换用途:
-
改变指针或引用的类型
-
将指针或引用转换为一个足够长度的整形
-
将整型转换为指针或引用类型。
int *a = new int;
double *d = reinterpret_cast<double *>(a);
reinterpret_cast可以进行不相关类型的转换,比如上面的int转double,若是上面例子使用static_cast就不行,
static_cast只能进行类似类型的转换,比如:基本数据类型、基类和派生类之间的转换。
五、dynamic_cast(动态转换)
主要用于进行父类的指针/引用向子类的指针/引用的转换,这涉及到C++里面的多态机制。因此,这边有几点需要注意:
- 其他三种转换都是编译时完成的,dynamic_cast是运行时处理的,运行时要进行类型检查。(不难理解,因为是动态绑定)
- 不能用于内置的基本数据类型的强制转换.(可用static_cast)
- dynamic_cast转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL。(有安全检测,更好)
- 使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过。【这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表, 只有定义了虚函数的类才有虚函数表。】
在类层次间转换:
- 1)上行转换:和 static_cast 的效果是一样的。
- 2)下行转换:向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。
话不多说,上代码:
#include<iostream>
using namespace std;
class base{
public :
void m(){cout<<"m"<<endl;}
};
class derived : public base{
public:
void f(){cout<<"f"<<endl;}
};
int main()
{
derived * p;
p = static_cast<derived *>(new base);
p->m();
p->f();
return 0;
}
【注意】上面的例子可以看出,我们将父类指针强转为派生类指针p,并指向父类函数m( ),到这里都没有问题。但是,我们调用了派生类的f()函数,此时我们的p为父类指针,父类中并没有定义f()函数,因此会导致编译成功,运行时期错误。这种错误归根于static_cast并没有提供检测类型安全的功能,而dynamic_cast则提供了安全类型检测。
若将上面换为dynamic_cast,则会报错。
为了避免语法问题,我们满足下行转换的虚函数要求,将m写为virtual虚函数,则编译通过,终端异常。
#include<iostream>
#include<cstring>
using namespace std;
class A
{
public:
virtual void f(){
cout<<"hello"<<endl;
};
};
class B : public A
{
public:
void f(){
cout<<"hello2"<<endl;
};
};
class C
{
void pp(){
return;
}
};
int fun() { return 1; }
/*-----测试代码-----*/
int main()
{
A* a1 = new B;//a1是A类型的指针指向一个B类型的对象
A* a2 = new A;//a2是A类型的指针指向一个A类型的对象
B* b;
C* c;
b=dynamic_cast<B*>(a1);//结果为not null,向下转换成功,a1之
//前指向的就是B类型的对象,所以可以转换成B类型的指针。
if(b==NULL){
cout<<"null"<<endl;
}
else{
cout<<"not null"<<endl;
}
b=dynamic_cast<B*>(a2);//结果为null,向下转换失败,a2转换前指向的是A对象
if(b==NULL){
cout<<"null"<<endl;
}
else{
cout<<"not null"<<endl;
}
c=dynamic_cast<C*>(a);//结果为null,向下转换失败
if(c==NULL){
cout<<"null"<<endl;
}
else{
cout<<"not null"<<endl;
}
delete(a);
return 0;
}