C++的四种强制转换

C++中的数据类型,可以分为:整形、浮点型、字符型和布尔型。四种强制转换(主要用于不同类型之间的转换)包含有

  • static_cast:主要用于数据类型的强制转换,强制将一种数据类型转换为另外一种数据类型。
  • const_cast:主要用于去除不能被修改的常数特性,它并非去除原有变量的常量性,而是去除指向常量对象的指针或引用的常量性。==去除constvoliate==两个特性。
  • reinterpret_cast:主要用于处理无关类型的转换该运算符不会改变括号中运算对象的值,而是对该对象从位模式上进行重新解释。
  • dynamic_cast:主要用于“类继承层次间的指针或引用安全地向下转型”

static_cast

功能:主要用于数据类型的强制转换,强制将一种类型转换为另一种数据类型。例如,将浮点类型转换为整数类型。

使用方法:static<要转换的类型>(变量或表达式)

对于C语言中转换案例如下:

1
2
3
int a=10;
int b=4;
double ans=(double)a/(double)b;

而在C++中采用static_cast的方式如下所示

1
2
3
int a=10;
int b=4;
double ans=static<double>(a)/static<double>(b);

用法如下:

1)用于基本数据类型的转换。

2)用于类层次之间基类和派生类之间指针或者引用的转换,其中上行转换(派生类指针或引用转换为基类表示是安全的),下行转换(基类指针或引用转换为派生类指示表示由于没有动态类型检查所以是不安全的)

3)可以将空指针转换为目标类型的空指针。

4)可以将任何类型的表达式转换为void类型

const_cast

功能:强制去除常量属性,不能用于去掉变量的常量性,只能用于去除指针或引用的常量性,将常量指针或将常量引用转换为非常量类型再进行修改。

使用情景1:在有些时候我们需要修改变量值只能拿到一个变量的常量指针,但是没有办法拿到原始变量,因此可以将该变量的常量指针转变为普通变量指针来修改值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;

int main(void) {
int variable = 21;
const int* const_p = &variable;
int* modifier = const_cast<int*>(const_p);

*modifier = 7;
cout << "variable:" << variable << endl;// variable:7

return 0;
}

使用情景2:当需要调用一个参数不是const的函数(调用别人写的函数时),而我们要传进去的实际参数却是const的,但是我们知道该函数是不会对参数做修改的。因此我们就需要使用const_cast去除const限定,以便该函数能够接收这个实际参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;
void Printer (int* val,string seperator = "\n")
{
cout << val<< seperator;
}
int main(void)
{
const int consatant = 20;
//Printer(consatant);//Error: invalid conversion from 'int' to 'int*'
Printer(const_cast<int *>(&consatant));

return 0;
}

使用情景3:const对象调用自身非const函数时。

image-20220405172521111

解决方法:

image-20220405172707725

reinterpret_cast

功能:主要用于处理无关类型的转换,它会产生一个新的值,这个值会与原始参数(expression)具有完全相同的比特位。总结来说reinterptr_cast用在任意指针(或引用)类型之间的转换;以及指针于足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。**reinterpret_cast运算符并不会改变括号中运算对象的值,而是对该对象从位运算模式上进行重新解释。**

使用案例:

对于如下代码,num是一个32位的int类型,采用16进制表示该值,内存中的存储结构如下所示,接下来使用pnumpstr分别指向num的地址。而最终输出pstrpnum所指向的内容时,输出的内容不同,主要原因是编译器会根据指针类型去解释所指向的内容。

img
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
int num = 0x00636261;//用16进制表示32位int,0x61是字符'a'的ASCII码
int * pnum = &num;
char * pstr = reinterpret_cast<char *>(pnum);
cout<<"pnum指针的值: "<<pnum<<endl;
cout<<"pstr指针的值: "<<static_cast<void *>(pstr)<<endl;//直接输出pstr会输出其指向的字符串,这里的类型转换是为了保证输出pstr的值
cout<<"pnum指向的内容: "<<hex<<*pnum<<endl;
cout<<"pstr指向的内容: "<<pstr<<endl;
return 0;
}

使用场景:MSDN中给出其使用的场景用于辅助哈希函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>
// Returns a hash code based on an address
unsigned short Hash( void *p ) {
unsigned int val = reinterpret_cast<unsigned int>( p );
return ( unsigned short )( val ^ (val >> 16));
}

using namespace std;
int main() {
int a[20];
for ( int i = 0; i < 20; i++ )
cout << Hash( a + i ) << endl;
}

//如果跟我一样是64位的系统,可能需要将unsigned int改成 unsigned long才能运行。

在进行哈希处理时,整数相对于指针更好处理。

注意:reinterpret_cast不能像const_cast那样去除const修饰符。

1
2
3
4
5
6
7
8
9
10
11
int main() 
{
typedef void (*FunctionPointer)(int);
int value = 21;
const int* pointer = &value;

int * pointer_r = reinterpret_cast<int*> (pointer); // 这里编译会报错

FunctionPointer funcP = reinterpret_cast<FunctionPointer> (pointer);
}

上述通过reinterpret_castconst限制时会报错,但是将const int*通过reinterpret_cast赋值给函数指针可以编译通过,理解的角度可以是由于函数指针本身就应该是一个const类型的,在函数定义好之后所指内容不应该改变,只能通过传入参数来使用该函数。

dynamic_cast

功能:主要用于类继承层次间的指针或引用转换主要还是执行“安全的向下转型”,也即是基类对象的指针或引用转换为同一继承层次的其他指针或引用。也可以向上转型,但由于传统方式完成向上转型是安全的,而且采用dynamic_cast还会产生一定的开销

向下转型分为两种情况:

  • 基类指针所指对象是派生类类型的,这种转换是安全的
  • 基类指针所指对象是基类类型的,这种情况下dynamic_cast在运行时做检查,转换失败,返回结果为0。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#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");
}

运行结果如下:

img

以上介绍的是指针的向下转型,同样对于引用也可以进行向下转型

同样对于引用类型的向上转型也是安全的,并且也可以分为两种情况(引用父类对象和子类对象),与指针唯一不同的在于不存在空引用,所有转换引用的dynamic_cast检测失败时会抛出一个bad_cast异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main()
{
//第一种情况,转换成功
Derived b ;
Base &base1= b;
Derived &der1 = dynamic_cast<Derived&>(base1);
cout<<"第一种情况:";
der1.Show();
cout<<endl;

//第二种情况
Base a ;
Base &base = a ;
cout<<"第二种情况:";
try{
Derived & der = dynamic_cast<Derived&>(base);
}
catch(bad_cast)
{
cout<<"转化失败,抛出bad_cast异常"<<endl;
}
system("pause");
}

运行结果:

img

注意:在使用dynamic_cast转换时基类必须定义一个虚函数,否则不会通过编译。当类没有虚函数表时,dynamic_cast无法使用RTTI不能通过编译(有待验证)。

RTTI(Run Time Type Identification)通过运行时类型识别,程序能够使用基类指针或引用来检查这些指针或引用所指的对象的实际派生类型。