C++强制类型转换运算符
C++强制类型转换运算符(static_cast、reinterpret_cast、const_cast和dynamic_cast)
https://en.cppreference.com/w/cpp/language/reinterpret_cast ; https://zh.cppreference.com/w/cpp/language/reinterpret_cast
C++ 引入新的强制类型转换机制,主要是为了克服C语言强制类型转换的以下三个缺点。
没有从形式上体现转换功能和风险的不同。
例如,将 int 强制转换成 double 是没有风险的,而将常量指针转换成非常量指针,将基类指针转换成派生类指针都是高风险的,而且后两者带来的风险不同(即可能引发不同种类的错误),C语言的强制类型转换形式对这些不同并不加以区分。
将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。
难以在程序中寻找到底什么地方进行了强制类型转换。
强制类型转换是引发程序运行时错误的一个原因,因此在程序出错时,可能就会想到是不是有哪些强制类型转换出了问题。
如果采用C语言的老式做法,要在程序中找出所有进行了强制类型转换的地方,显然是很麻烦的,因为这些转换没有统一的格式。
而用 C++ 的方式,则只需要查找
_cast
字符串就可以了。甚至可以根据错误的类型,有针对性地专门查找某一种强制类型转换。例如,怀疑一个错误可能是由于使用了 reinterpret_cast 导致的,就可以只查找reinterpret_cast
字符串。
C++ 强制类型转换运算符的用法如下:
1 | 强制类型转换运算符 <要转换到的类型> (待转换的表达式) |
例如:
1 | double d = static_cast <double> (3*5); //将 3*5 的值转换成实数 |
static_cast 静态转换
static_cast 用于进行比较“自然”和低风险的转换,如整型和浮点型、字符型之间的互相转换。另外,如果对象所属的类重载了强制类型转换运算符 T(如 T 是 int、int* 或其他类型名),则 static_cast 也能用来进行对象到 T 类型的转换。
static_cast 不能用于在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,当然也不能用于不同类型的引用之间的转换。因为这些属于风险比较高的转换。
基本等价于隐式转换的一种类型转换运算符,可使用于需要明确隐式转换的地方。
可以用于低风险的转换。
- 整型和浮点型
- 字符与整型
- 转换运算符
- 空指针转换为任何目标类型的指针
不可以用与风险较高的转换
- 不同类型的指针之间互相转换
- 整型和指针之间的互相转换
- 不同类型的引用之间的转换
举例1:
1 | // int类型、float类型,都是用4个字节编码、表示的 |
父类子类转换:
- 父类转子类(不安全)(理解成子类内容比较多,父类转换可能会越界)隐式转换通过不了,static_cast静态转换可以通过,但是不安全,没有提供运行时的检测;
- 子类转父类(安全)(因为子类包含父类)隐式转换可以通过。
举例2:
1 |
|
reinterpret_cast
reinterpret_cast 用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。
这种转换提供了很强的灵活性,但转换的安全性只能由程序员的细心来保证了。例如,程序员执意要把一个 int* 指针、函数指针或其他类型的指针转换成 string* 类型的指针也是可以的,至于以后用转换后的指针调用 string 类的成员函数引发错误,程序员也只能自行承担查找错误的烦琐工作:(C++ 标准不允许将函数指针转换成对象指针,但有些编译器,如 Visual Studio 2010,则支持这种转换)。
比如:reinterpret_cast<void*>(decoder);
、 reinterpret_cast<const int16_t*>(data)
- 用于进行各种不同类型的转换
- 不同类型指针之间
- 不同类型引用之间
- 指针和能容纳的整数类型之间的转换
- 编译期处理,执行的是逐字节复制的操作
- 类似于显式强转,后果自负
各种类型的指针转换
举例1:
1 | // 显示强转 |
举例2:
1 |
|
第 19 行的代码不安全,因为在编译器看来,pa->j 的存放位置就是 n 后面的 4 个字节。 本条语句会向这 4 个字节中写入 500。但这 4 个字节不知道是用来存放什么的,贸然向其中写入可能会导致程序错误甚至崩溃。
上面程序中的各种转换都没有实际意义,只是为了演示 reinteipret_cast 的用法而已。在编写黑客程序、病毒或反病毒程序时,也许会用到这样怪异的转换。
reinterpret_cast体现了 C++ 语言的设计思想:用户可以做任何操作,但要为自己的行为负责。
const_cast
const_cast 运算符仅用于进行去除 const 属性的转换,它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。
const_cast中的类型必须是 指针、引用、指向对象类型成员的指针(this指针)
将 const 引用转换为同类型的非 const 引用,将 const 指针转换为同类型的非 const 指针时可以使用 const_cast 运算符。例如:
1 | const string s = "Inception"; |
常量对象或是基本数据类型不允许转换为非常量对象,只能通过指针和引用来修改,可以通过const_cast转换成同类型的非const引用或指针:
1 | const std::string s = "abc"; |
常成员函数(不能修改成员变量的值)中去除this指针的const属性:
1 | class CTest{ |
dynamic_cast
用 reinterpret_cast 可以将多态基类(包含虚函数的基类)的指针强制转换为派生类的指针,但是这种转换不检查安全性,即不检查转换后的指针是否确实指向一个派生类对象。dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回 NULL 指针。
dynamic_cast 是通过“运行时类型检查”来保证安全性的。dynamic_cast 不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用——这种转换没法保证安全性,只好用 reinterpret_cast 来完成。
用于具有虚函数的基类与派生类之间的的转换。
基类必须具备虚函数
原因:dynamic_cast是运行时类型检查,需要运行时类型信息(RTTI),而这个信息是存储与类的虚函数表关系紧密,只有一个类定义了虚函数,才会有虚函数表。
运行时检查,转型不成功则返回一个空指针
非必要不要使用dynamic_cast,有额外的函数开销
常见的转换方式:
- 基类指针或引用转派生类指针(必须使用dynamic_cast)
- 派生类指针或引用转基类指针(可以使用dynamic_cast,但是更推荐使用static_cast)
举例1:
1 |
|
具有多态类型的向下转换时使用,其余情况可以不用。
举例2:
1 |
|
程序的输出结果是:
unsafe dynamic_cast1
第 20 行,通过判断 pd 的值是否为 NULL,就能知道第 19 行进行的转换是否是安全的。第 23 行同理。
如果上面的程序中出现了下面的语句:
1 | Derived & r = dynamic_cast <Derived &> (b); |
那该如何判断该转换是否安全呢?不存在空引用,因此不能通过返回值来判断转换是否安全。C++ 的解决办法是:dynamic_cast 在进行引用的强制转换时,如果发现转换不安全,就会拋出一个异常,通过处理异常,就能发现不安全的转换。
在visual stdio 菜单栏 项目 -> 属性 -> 左边菜单栏 C/C++ 语言 -> 启用运行时类型信息 改为 “是”