C++11 四种 cast 转换
(type cast 学习总结)
(复习,很多文档中这边介绍介绍,那边唠唠的,于是自己去看C++说明文档,学习笔记如下。有些场景本文并没有去实践)
C++ 是一种强类型的语言。 许多转换,特别是那些暗示对值有不同解释的转换,需要显式转换,在 C++ 中称为类型转换。 有两种主要语法:函数式和类似 c 的语法:
double x = 10.3;
int y;
y = int (x); // 函数式
y = (int) x; // 类似 c 的语法
这些类型转换的通用形式的功能足以满足基本数据类型的大多数需求。
这些运算符也可以不加选择地应用于类和指向类的指针,但是这可能导致代码在运行时错误(编译可能没有问题)。 例如,下面的代码编译没有错误:
#include<iostream>
using namespace std;
class Dummy{
double i,j;
};
class Addition{
int x,y;
public:
Addition(int a, int b){x=a; y=b;}
int result() {return x+y;}
};
int main(){
Dummy d;
Addition *a_ptr;
/* 该程序声明了一个指向 Addition 的指针,
但随后它使用显式类型转换为它分配了对另一个
不相关类型的对象的引用:*/
a_ptr = (Addition*)&d;
cout << a_ptr->result(); // -1717986920
return 0;
}
不受限制的显式类型转换 允许将任何指针转换为任何其他指针类型,而与它们指向的类型无关。 对成员 result 的后续调用将产生运行时错误或其他一些意外结果。
为了控制类之间的这些类型的转换,我们有四个特定的转换运算符:dynamic_cast、reinterpret_cast、static_cast 和 const_cast。 格式:xx_cast< T >( 要转换的表达式 )
dynamic_cast <new_type> (expression)
reinterpret_cast <new_type> (expression)
static_cast <new_type> (expression)
const_cast <new_type> (expression)
【补充:dereference】
dereference 翻译为:解除引用、解引用、反引用、逆向引用、提领(侯捷译)。
释义:获取指针地址或引用地址上的值
维基百科:对指针变量进行操作,并返回与指针地址处的值等效的左值。
(计科方面的一些专有名词翻译着实让人无奈哎)
1、const_cast
将 const 变量转为非 const。
这种类型的转换操作指针指向的对象的常量性,要么设置要么删除。 例如,将 const 指针传递给需要非 const 参数的函数:
#include <iostream>
using namespace std;
void print (char * str){
cout << str << '\n';
}
int main () {
const char * c = "sample text";
print ( const_cast<char *> (c) );
// print(c); // const char *实参与char *形参不兼容
return 0;
}
// 输出结果:
// sample text
上面的示例保证可以工作,因为函数 print 不会写入指向的对象。 但请注意,删除指向对象的常量以实际写入它会导致未定义的行为。
2、static_cast
1️⃣ static_cast 可以执行指向相关类的指针之间的转换。
- 可以向上转换(从指向派生的指针到指向基的指针);
- 可以执行向下转换(从指向基的指针到指向派生的指针)——不执行任何检查以保证安全!需要程序员自己保证!
因此:static_cast 不会产生 dynamic_cast 类型安全检查的开销。
class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast<Derived*>(a);
// 尽管 b 会指向类的不完整对象,并且如果解引用(dereference)可能会导致运行时错误。
2️⃣ static_cast 还能够执行隐式允许的所有转换、及其相反的转换(不仅是那些具有指向类的指针的转换)。它可以:
- 从 void* 转换为任何指针类型。在这种情况下,它保证如果 void* 值是通过从相同的指针类型转换获得的,则生成的指针值是相同的。
- 将整数、浮点值和枚举类型转换为枚举类型。
- 非 const 转 const。
3️⃣ 此外,static_cast 还可以执行以下操作:
- 显式调用单参数构造函数或转换运算符。
- 转换为右值引用。
- 将枚举类值转换为整数或浮点值。
- 将任何类型转换为 void,评估并丢弃该值。
3、dynamic_cast
1️⃣ dynamic_cast 只能与类的指针和引用一起使用(或与void * 一起使用),其目的是确保类型转换的结果指向目标指针类型的有效完整对象。
- 向上转换:派生类指针转换为基类指针,与隐式转换所允许的方式相同。
- 向下转换多态类(具有虚拟成员的类):基类指针转换为派生类指针,当且仅当指向的对象是目标类型的有效完整对象。
#include <iostream>
#include <exception>
using namespace std;
class Base { virtual void dummy() {} };
class Derived: public Base { int a; };
int main () {
try {
Base * pB1 = new Derived; // 基类指针pB1指向派生类对象
Base * pB2 = new Base; // 基类指针pB2指向基类对象
Derived * pD; // 派生类指针pD
pD = dynamic_cast<Derived*>(pB1); // 基类指针pB1强转为派生类指针
if (pD==0) cout << "Null pointer on first type-cast.\n";
pD = dynamic_cast<Derived*>(pB2); // 基类指针pB2强转为派生类指针
if (pD==0) cout << "Null pointer on second type-cast.\n";
}
catch (exception& e) {
cout << "Exception: " << e.what();
}
return 0;
}
// 输出结果:
// Null pointer on second type-cast.
【代码解释】
即使 pB1
和 pB2
都是 Base*
类型的指针,pB1
实际上指向一个 Derived 类型的对象,而 pB2
指向一个 Base 类型的对象。 因此,当使用 dynamic_cast 执行它们各自的类型转换时,pB1
指向类 Derived 的完整对象,而 pB2
指向类 Base 的对象,它是类 Derived 的不完整对象。
(父类开始有子类时,父类的成员变化,儿子的相应的成员也会随之变化。儿子继承了父亲的一切,但是儿子又会有只属于自己的性质,当儿子的指针指向父亲时,原来属于儿子的内容,父亲实际上并没有,指针转换时就存在不完整转换了,这就是不完整对象的由来)
【返回值】
- 转换成功时返回相应正确的类型。
- 转换失败时:
- 由于不是所需类的完整对象而无法转换指针时(如上例中的第二次转换),它将返回一个**空指针(NULL)**以指示失败。
- 由于引用类型并且无法转换,则会抛出 bad_cast 类型的异常。
2️⃣ dynamic_cast 还可以对指针执行其他隐式转换:
- 在指针类型之间(甚至在不相关的类之间)转换空指针;
- 将任何类型的任何指针转换为
void*
指针。
【补充:void *】
void *
是一种指针类型,常用在函数参数、函数返回值中需要兼容不同指针类型的地方。
- 可以将别的类型的指针 无需强制类型转换的赋值给
void *
类型。 - 可以将
void *
强制类型转换成任何别的指针类型,至于强转的类型是否合理,就需要程序员自己控制了。
dynamic_cast 运用 RTTI 技术在运行时判断变量类型和要转换的类型是否相同 来判断是否能够进行向下转换;RTTI 技术(Runtime Type Information,运行时类型信息),提供了运行时确定对象类型的方法。在 C++ 层面主要体现在 dynamic_cast 和 typeid ,在 vs 编译器中虚函数表的 -1 位置存放了指向 type_info 的指针,对于存在虚函数的类型,dynamic_cast 和typeid 都会去查询 type_info 。
4、reinterpret_cast
reinterpret:重新解释
可以做任何类型的转换 ,既不检查指向的内容,也不检查指针类型本身。容易出问题。
运算结果是从一个指针到另一个指针的 值的简单二进制副本。
- 还可以将指针转换为整数类型或从整数类型转换。 此整数值表示指针的格式是特定于平台的。 唯一的保证是将指针转换为足够大的整数类型以完全包含它(例如 intptr_t),保证能够转换回有效指针。
可以由 reinterpret_cast 但不能由 static_cast 执行的转换是基于重新解释类型的二进制表示的低级操作,在大多数情况下,这会导致代码特定于系统,因此不可移植。 例如:
class A { /* ... */ };
class B { /* ... */ };
A * a = new A;
B * b = reinterpret_cast<B*>(a);
// 这段代码可以编译,尽管它没有多大意义。
// 因为现在 b 指向一个完全不相关且可能不兼容的类的对象。
// 解除引用(dereference) b 是不安全的。
typeid
typeid 允许检查表达式的类型:typeid(expression)
此运算符返回对在标准标头 typeinfo 中定义的 type_info 类型的常量对象的引用。
- 可以使用运算符 == 和 != 将 typeid 返回的值与 typeid 返回的另一个值进行比较;
- 可以使用其 name() 成员来获取表示数据类型或类名的以空字符结尾的字符序列。
#include <iostream>
#include <typeinfo>
using namespace std;
int main () {
int * a; int b;
a=0; b=0;
if (typeid(a) != typeid(b))
{
cout << "a and b are of different types:\n";
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
}
return 0;
}
// 输出结果:
// a and b are of different types:
// a is: int *
// b is: int
当 typeid 应用于类时,typeid 使用 RTTI 来跟踪动态对象的类型。 当 typeid 应用于类型为多态类的表达式时,结果是派生最多的完整对象的类型:
#include <iostream>
#include <typeinfo>
#include <exception>
using namespace std;
class Base { virtual void f(){} };
class Derived : public Base {};
int main () {
try {
Base* a = new Base;
Base* b = new Derived;
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
cout << "*a is: " << typeid(*a).name() << '\n';
cout << "*b is: " << typeid(*b).name() << '\n';
}
catch (exception& e) {
cout << "Exception: " << e.what() << '\n';
}
return 0;
}
// 输出结果:
// a is: class Base *
// b is: class Base *
// *a is: class Base
// *b is: class Derived
注意:type_info 的成员名返回的字符串取决于你的编译器和库的具体实现。 它不一定是具有典型类型名称的简单字符串,就像在用于生成此输出的编译器中一样。
请注意 typeid 为指针考虑的类型是指针类型本身(a 和 b 都属于类 Base *)。 然而,当 typeid 应用于对象(如 *a 和 *b)时,typeid 会产生它们的动态类型(即它们最衍生的完整对象的类型)。
如果 typeid 计算的类型是一个以解引用运算符 (*) 开头的指针,并且该指针具有空值,则 typeid 将引发 bad_typeid 异常。