C++ 四种类型转换

将类型名作为强制类型转换运算符的做法是C语言的老式做法,C++语言为保持兼容而予以保留。

C++ 引入了四种功能不同的强制类型转换运算符以进行强制类型转换:static_cast、reinterpret_cast、const_cast 和 dynamic_cast。

强制类型转换是有一定风险的,有的转换并不一定安全,如把整型数值转换成指针,把基类指针转换成派生类指针,把一种函数指针转换成另一种函数指针,把常量指针转换成非常量指针等。C++ 引入新的强制类型转换机制,主要是为了克服C语言强制类型转换的以下三个缺点

 

  • 没有从形式上体现转换功能和风险的不同。

例如,将 int 强制转换成 double 是没有风险的,而将常量指针转换成非常量指针,将基类指针转换成派生类指针都是高风险的,而且后两者带来的风险不同(即可能引发不同种类的错误),C语言的强制类型转换形式对这些不同并不加以区分

  • 将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。
  • 难以在程序中寻找到底什么地方进行了强制类型转换。

强制类型转换是引发程序运行时错误的一个原因,因此在程序出错时,可能就会想到是不是有哪些强制类型转换出了问题。如果采用C语言的老式做法,要在程序中找出所有进行了强制类型转换的地方,显然是很麻烦的,因为这些转换没有统一的格式。而用 C++ 的方式,则只需要查找_cast字符串就可以了。甚至可以根据错误的类型,有针对性地专门查找某一种强制类型转换。例如,怀疑一个错误可能是由于使用了 reinterpret_cast 导致的,就可以只查找reinterpret_cast字符串。

C++ 强制类型转换运算符的用法如下:

1 强制类型转换运算符 <要转换到的类型> (待转换的表达式)
2 例如:
3 double d = static_cast <double> (3*5);  //将 3*5 的值转换成实数

下面分别介绍四种强制类型转换运算符。

1) static_cast

在C++语言中static_cast用于数据类型的强制转换,强制将一种数据类型转换为另一种数据类型。例如将整型数据转换为浮点型数据。
[例1]C语言所采用的类型转换方式:

int a = 10;
int b = 3;
double result = (double)a / (double)b;

例1中将整型变量a和b转换为双精度浮点型,然后相除。在C++语言中,我们可以采用static_cast关键字来进行强制类型转换,如下所示。
[例2]static_cast关键字的使用:

int a = 10;
int b = 3;
double result = static_cast<double>(a) / static_cast<double>(b);

在本例中同样是将整型变量a转换为双精度浮点型。采用static_cast进行强制数据类型转换时,将想要转换成的数据类型放到尖括号中,将待转换的变量或表达式放在元括号中,其格式可以概括为如下形式:    

用法:static_cast <类型说明符> (变量或表达式)

它主要有如下几种用法:
    (1)用于类层次结构中基类和派生类之间指针或引用的转换
      进行上行转换(把派生类的指针或引用转换成基类表示)是安全的
      进行下行转换(把基类的指针或引用转换为派生类表示),由于没有动态类型检查,所以是不安全的
    (2)用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证
    (3)把空指针转换成目标类型的空指针
    (4)把任何类型的表达式转换为void类型
    注意:static_cast不能转换掉expression的const、volitale或者__unaligned属性。

static_cast:可以实现C++中内置基本数据类型之间的相互转换。

 如果涉及到类的话,static_cast只能在有相互联系的类型中进行相互转换,不一定包含虚函数。

static_cast 用于进行比较“自然”和低风险的转换,如整型和浮点型、字符型之间的互相转换。另外,如果对象所属的类重载了强制类型转换运算符 T(如 T 是 int、int* 或其他类型名),则 static_cast 也能用来进行对象到 T 类型的转换。static_cast 不能用于在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,当然也不能用于不同类型的引用之间的转换。因为这些属于风险比较高的转换。static_cast 用法示例如下:

 1 #include <iostream>
 2 using namespace std;
 3 class A
 4 {
 5 public:
 6     operator int() { return 1; }
 7     operator char*() { return NULL; }
 8 };
 9 int main()
10 {
11     A a;
12     int n;
13     char* p = "New Dragon Inn";
14     n = static_cast <int> (3.14);  // n 的值变为 3
15     n = static_cast <int> (a);     // 调用 a.operator int,n 的值变为 1
16     p = static_cast <char*> (a);   // 调用 a.operator char*,p 的值变为 NULL
17     n = static_cast <int> (p);     // 编译错误,static_cast不能将指针转换成整型
18     p = static_cast <char*> (n);   // 编译错误,static_cast 不能将整型转换成指针
19     return 0;
20 }

2) const_cast

在C语言中,const限定符通常被用来限定变量,用于表示该变量的值不能被修改。

而const_cast则正是用于强制去掉这种不能被修改的常数特性,但需要特别注意的是const_cast不是用于去除变量的常量性而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用

用法:const_cast<type_id> (expression)
    该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
    常量指针被转化成非常量指针,并且仍然指向原来的对象;
    常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

[例3]一个错误的例子:

const int a = 10;
const int * p = &a;
*p = 20;                  //compile error
int b = const_cast<int>(a);  //compile error

在本例中出现了两个编译错误,第一个编译错误是*p因为具有常量性,其值是不能被修改的;另一处错误是const_cast强制转换对象必须为指针或引用,而例3中为一个变量,这是不允许的!

 [例4]const_cast关键字的使用

#include<iostream>
using namespace std;
 
int main()
{
    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;
    return 0;
}

在本例中,我们将变量a声明为常量变量,同时声明了一个const指针指向该变量(此时如果声明一个普通指针指向该常量变量的话是不允许的,Visual Studio 2010编译器会报错)。

之后我们定义了一个普通的指针*q。将p指针通过const_cast去掉其常量性,并赋给q指针。之后我再修改q指针所指地址的值时,这是不会有问题的。

最后将结果打印出来,运行结果如下:
10 20 20
002CFAF4 002CFAF4 002CFAF4

【解释上述为何打印结果比较奇怪】
“因为编译器对常量优化了,在输出时直接用值替换了,你不妨在const前面加个volatile试试
让编译器不要优化这个变量再看看结果”。
即可以使用反汇编查看寄存器的变化。
vs2010 在debug模式下:调试->窗口->反汇编

查看运行结果,问题来了,指针p和指针q都是指向a变量的,指向地址相同,而且经过调试发现002CFAF4地址内的值确实由10被修改成了20,这是怎么一回事呢?为什么a的值打印出来还是10呢?
其实这是一件好事,我们要庆幸a变量最终的值没有变成20!变量a一开始就被声明为一个常量变量,不管后面的程序怎么处理,它就是一个常量,就是不会变化的。试想一下如果这个变量a最终变成了20会有什么后果呢?对于这些简短的程序而言,如果最后a变成了20,我们会一眼看出是q指针修改了,但是一旦一个项目工程非常庞大的时候,在程序某个地方出现了一个q这样的指针,它可以修改常量a,这是一件很可怕的事情的,可以说是一个程序的漏洞,毕竟将变量a声明为常量就是不希望修改它,如果后面能修改,这就太恐怖了。
在例4中我们称“*q=20”语句为未定义行为语句,所谓的未定义行为是指在标准的C++规范中并没有明确规定这种语句的具体行为,该语句的具体行为由编译器来自行决定如何处理。对于这种未定义行为的语句我们应该尽量予以避免!
从例4中我们可以看出我们是不想修改变量a的值的,既然如此,定义一个const_cast关键字强制去掉指针的常量性到底有什么用呢?我们接着来看下面的例子。

 【举例说明const_cast使用的真正场景

 1 #include<iostream>
 2 using namespace std;
 3 int main(int argc, char const *argv[]) 
 4 {
 5   const int a = 12;
 6   const int *ap = &a;
 7   int* tmp = const_cast<int*>(ap);
 8   *tmp = 11;
 9   cout<<a;
10   return 0;
11 }

const_cast的目的并不是为了让你去修改一个本身被定义为const的值,因为这样做的后果是无法预期的。const_cast的目的是修改一些指针/引用的权限,如果我们原本无法通过这些指针/引用修改某块内存的值,现在你可以了

3) dynamic_cast

用 reinterpret_cast 可以将多态基类(包含虚函数的基类)的指针强制转换为派生类的指针,但是这种转换不检查安全性,即不检查转换后的指针是否确实指向一个派生类对象dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回 NULL 指针
dynamic_cast 是通过“运行时类型检查”来保证安全性的(另外的3种强制类型转化都是在编译阶段进行的)。dynamic_cast 不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用——这种转换没法保证安全性,只好用 reinterpret_cast 来完成。
指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针; 
引用进行dynamic_cast,失败抛出一个异常bad_cast,成功返回正常cast后的对象引用。 
对于“向上转换”(即派生类指针或引用类型转换为其基类类型),无论是指针还是引用向上转换都是安全地。
 
对于“向下转型”有两种情况:
一种是基类指针所指对象是派生类类型的,这种转换是安全的;
另一种是基类指针所指对象为基类类型,在这种情况下dynamic_cast在运行时做检查,转换失败,返回结果为0;
在引用上,dynamic_cast依旧是常用于“安全的向下转型”。与指针一样,引用的向下转型也可以分为两种情况,与指针不同的是,并不存在空引用,所以引用的dynamic_cast检测失败时会抛出一个bad_cast异常。
 
【这里涉及到RTTI以及typeid的一些问题】补充如下:https://www.jb51.net/article/55968.htm
====================================================

前言

RTTI是”Runtime Type Information”的缩写,意思是运行时类型信息,它提供了运行时确定对象类型的方法。RTTI并不是什么新的东西,很早就有了这个技术,但是,在实际应用中使用的比较少而已。而我这里就是对RTTI进行总结,今天我没有用到,并不代表这个东西没用。学无止境,先从typeid函数开始讲起。

typeid函数

typeid的主要作用就是让用户知道当前的变量是什么类型的,比如以下代码:

代码如下:
 1 #include <iostream>
 2 #include <typeinfo>
 3 using namespace std;
 4  
 5 int main()
 6 {
 7      short s = 2;
 8      unsigned ui = 10;
 9      int i = 10;
10      char ch = 'a';
11      wchar_t wch = L'b';
12      float f = 1.0f;
13      double d = 2;
14  
15      cout<<typeid(s).name()<<endl; // short
16      cout<<typeid(ui).name()<<endl; // unsigned int
17      cout<<typeid(i).name()<<endl; // int
18      cout<<typeid(ch).name()<<endl; // char
19      cout<<typeid(wch).name()<<endl; // wchar_t
20      cout<<typeid(f).name()<<endl; // float
21      cout<<typeid(d).name()<<endl; // double
22  
23      return 0;
24 }
对于C++支持的内建类型,typeid能完全支持,我们通过调用typeid函数,我们就能知道变量的信息。对于我们自定义的结构体,类呢?

代码如下:

 1 #include <iostream>
 2 #include <typeinfo>
 3 using namespace std;
 4  
 5 class A
 6 {
 7 public:
 8      void Print() { cout<<"This is class A."<<endl; }
 9 };
10  
11 class B : public A
12 {
13 public:
14      void Print() { cout<<"This is class B."<<endl; }
15 };
16  
17 struct C
18 {
19      void Print() { cout<<"This is struct C."<<endl; }
20 };
21  
22 int main()
23 {
24      A *pA1 = new A();
25      A a2;
26  
27      cout<<typeid(pA1).name()<<endl; // class A *
28      cout<<typeid(a2).name()<<endl; // class A
29  
30      B *pB1 = new B();
31      cout<<typeid(pB1).name()<<endl; // class B *
32  
33      C *pC1 = new C();
34      C c2;
35  
36      cout<<typeid(pC1).name()<<endl; // struct C *
37      cout<<typeid(c2).name()<<endl; // struct C
38  
39      return 0;
40 } 

是的,对于我们自定义的结构体和类,tpyeid都能支持。在上面的代码中,在调用完typeid之后,都会接着调用name()函数,可以看出typeid函数返回的是一个结构体或者类,然后,再调用这个返回的结构体或类的name成员函数;其实,typeid是一个返回类型为type_info类型的函数。那么,我们就有必要对这个type_info类进行总结一下,毕竟它实际上存放着类型信息。

type_info类

去掉那些该死的宏,在Visual Studio 2012中查看type_info类的定义如下:

代码如下:
 

在type_info类中,复制构造函数和赋值运算符都是私有的,同时也没有默认的构造函数;所以,我们没有办法创建type_info类的变量,例如type_info A;这样是错误的。那么typeid函数是如何返回一个type_info类的对象的引用的呢?我在这里不进行讨论,思路就是类的友元函数。

typeid函数的使用

typeid使用起来是非常简单的,常用的方式有以下两种:

1.使用type_info类中的name()函数返回对象的类型名称

就像上面的代码中使用的那样;但是,这里有一点需要注意,比如有以下代码: 

代码如下:
 1 #include <iostream>
 2 #include <typeinfo>
 3 using namespace std;
 4  
 5 class A
 6 {
 7 public:
 8      void Print() { cout<<"This is class A."<<endl; }
 9 };
10  
11 class B : public A
12 {
13 public:
14      void Print() { cout<<"This is class B."<<endl; }
15 };
16  
17 int main()
18 {
19      A *pA = new B();
20      cout<<typeid(pA).name()<<endl; // class A *
21      cout<<typeid(*pA).name()<<endl; // class A
22      return 0;
23 }

我使用了两次typeid,但是两次的参数是不一样的;输出结果也是不一样的;当我指定为pA时,由于pA是一个A类型的指针,所以输出就为class A *;当我指定*pA时,它表示的是pA所指向的对象的类型,所以输出的是class A;所以需要区分typeid(*pA)和typeid(pA)的区别,它们两个不是同一个东西;但是,这里又有问题了,明明pA实际指向的是B,为什么得到的却是class A呢?我们在看下一段代码:

代码如下:
 1 #include <iostream>
 2 #include <typeinfo>
 3 using namespace std;
 4  
 5 class A
 6 {
 7 public:
 8      virtual void Print() { cout<<"This is class A."<<endl; }
 9 };
10  
11 class B : public A
12 {
13 public:
14      void Print() { cout<<"This is class B."<<endl; }
15 };
16  
17 int main()
18 {
19      A *pA = new B();
20      cout<<typeid(pA).name()<<endl; // class A *
21      cout<<typeid(*pA).name()<<endl; // class B
22      return 0;
23 }

好了,我将Print函数变成了虚函数,输出结果就不一样了,这说明什么?这就是RTTI在捣鬼了,当类中不存在虚函数时,typeid是编译时期的事情,也就是静态类型,就如上面的cout<<typeid(*pA).name()<<endl;输出class A一样;当类中存在虚函数时,typeid是运行时期的事情,也就是动态类型,就如上面的cout<<typeid(*pA).name()<<endl;输出class B一样,关于这一点,我们在实际编程中,经常会出错,一定要谨记。

2.使用type_info类中重载的==和!=比较两个对象的类型是否相等

这个会经常用到,通常用于比较两个带有虚函数的类的对象是否相等,例如以下代码: 

代码如下:
 1 #include <iostream>
 2 #include <typeinfo>
 3 using namespace std;
 4  
 5 class A
 6 {
 7 public:
 8      virtual void Print() { cout<<"This is class A."<<endl; }
 9 };
10  
11 class B : public A
12 {
13 public:
14      void Print() { cout<<"This is class B."<<endl; }
15 };
16  
17 class C : public A
18 {
19 public:
20      void Print() { cout<<"This is class C."<<endl; }
21 };
22  
23 void Handle(A *a)
24 {
25      if (typeid(*a) == typeid(A))
26      {
27           cout<<"I am a A truly."<<endl;
28      }
29      else if (typeid(*a) == typeid(B))
30      {
31           cout<<"I am a B truly."<<endl;
32      }
33      else if (typeid(*a) == typeid(C))
34      {
35           cout<<"I am a C truly."<<endl;
36      }
37      else
38      {
39           cout<<"I am alone."<<endl;
40      }
41 }
42  
43 int main()
44 {
45      A *pA = new B();
46      Handle(pA);
47      delete pA;
48      pA = new C();
49      Handle(pA);
50      return 0;
51 }

这是一种用法,呆会我再总结如何使用dynamic_cast来实现同样的功能。

dynamic_cast的内幕

在这篇《static_cast、dynamic_cast、const_cast和reinterpret_cast总结》的文章中,也介绍了dynamic_cast的使用,对于dynamic_cast到底是如何实现的,并没有进行说明,而这里就要对于dynamic_cast的内幕一探究竟。首先来看一段代码:

代码如下:

 1 #include <iostream>
 2 #include <typeinfo>
 3 using namespace std;
 4  
 5 class A
 6 {
 7 public:
 8      virtual void Print() { cout<<"This is class A."<<endl; }
 9 };
10  
11 class B
12 {
13 public:
14      virtual void Print() { cout<<"This is class B."<<endl; }
15 };
16  
17 class C : public A, public B
18 {
19 public:
20      void Print() { cout<<"This is class C."<<endl; }
21 };
22  
23 int main()
24 {
25      A *pA = new C;
26      //C *pC = pA; // Wrong
27      C *pC = dynamic_cast<C *>(pA);
28      if (pC != NULL)
29      {
30           pC->Print();
31      }
32      delete pA;
33 }

在上面代码中,如果我们直接将pA赋值给pC,这样编译器就会提示错误,而当我们加上了dynamic_cast之后,一切就ok了。那么dynamic_cast在后面干了什么呢?

dynamic_cast主要用于在多态的时候,它允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转换类型,把基类指针(引用)转换为派生类指针(引用)。我在《COM编程——接口的背后》这篇博文中总结的那样,当类中存在虚函数时,编译器就会在类的成员变量中添加一个指向虚函数表的vptr指针,每一个class所关联的type_info object也经由virtual table被指出来,通常这个type_info object放在表格的第一个slot。当我们进行dynamic_cast时,编译器会帮我们进行语法检查。如果指针的静态类型和目标类型相同,那么就什么事情都不做;否则,首先对指针进行调整,使得它指向vftable,并将其和调整之后的指针、调整的偏移量、静态类型以及目标类型传递给内部函数。其中最后一个参数指明转换的是指针还是引用。两者唯一的区别是,如果转换失败,前者返回NULL,后者抛出bad_cast异常。对于在typeid函数的使用中所示例的程序,我使用dynamic_cast进行更改,代码如下:

代码如下:

 1 #include <iostream>
 2 #include <typeinfo>
 3 using namespace std;
 4  
 5 class A
 6 {
 7 public:
 8      virtual void Print() { cout<<"This is class A."<<endl; }
 9 };
10  
11 class B : public A
12 {
13 public:
14      void Print() { cout<<"This is class B."<<endl; }
15 };
16  
17 class C : public A
18 {
19 public:
20      void Print() { cout<<"This is class C."<<endl; }
21 };
22  
23 void Handle(A *a)
24 {
25      if (dynamic_cast<B*>(a))
26      {
27           cout<<"I am a B truly."<<endl;
28      }
29      else if (dynamic_cast<C*>(a))
30      {
31           cout<<"I am a C truly."<<endl;
32      }
33      else
34      {
35           cout<<"I am alone."<<endl;
36      }
37 }
38  
39 int main()
40 {
41      A *pA = new B();
42      Handle(pA);
43      delete pA;
44      pA = new C();
45      Handle(pA);
46      return 0;
47 }

这个是使用dynamic_cast进行改写的版本。实际项目中,这种方法会使用的更多点。

4) reinterpret_cast

在C++语言中,reinterpret_cast主要有三种强制转换用途:改变指针或引用的类型、将指针或引用转换为一个足够长度的整形、将整型转换为指针或引用类型

用法:reinterpret_cast<type_id> (expression)
    type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。
    它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
    在使用reinterpret_cast强制转换过程仅仅只是比特位的拷贝,因此在使用过程中需要特别谨慎!

例7:

int *a = new int;
double *d = reinterpret_cast<double *>(a);
在例7中,将整型指针通过reinterpret_cast强制转换成了双精度浮点型指针。
reinterpret_cast可以将指针或引用转换为一个足够长度的整形,此中的足够长度具体长度需要多少则取决于操作系统,如果是32位的操作系统,就需要4个字节及以上的整型,如果是64位的操作系统则需要8个字节及以上的整型。 
 
 
总 结

  去const属性用const_cast。

  基本类型转换用static_cast。

  多态类之间的类型转换用daynamic_cast。

  不同类型的指针类型转换用reinterpreter_cast。

原文地址:https://www.cnblogs.com/xuelisheng/p/9327834.html