1 C类型转换本质1.1 自动类型转换隐式利用编译器内置的转换规则或者用户自定义的转换构造函数以及类型转换函数这些都可以认为是已知的转换规则。例如从 int 到 double、从派生类到基类、从type *到void *、从 double 到 Complex 等。注type *是一个具体类型的指针例如int *、double *、Student *等它们都可以直接赋值给void *指针。例如malloc() 分配内存后返回的就是一个void *指针我们必须进行强制类型转换后才能赋值给指针变量。1.2 强制类型转换显式隐式不能完成的类型转换工作就必须使用强制类型转换(new_type) expression1.3 类型转换的本质数据类型的本质这种「确定数据的解释方式」的工作就是由数据类型Data Type来完成的。例如int a;表明a 这份数据是整数不能理解为像素、声音、视频等。数据类型转换的本质数据类型转换就是对数据所占用的二进制位做出重新解释。隐式类型转换编译器可以根据已知的转换规则来决定是否需要修改数据的二进制位强制类型转换由于没有对应的转换规则所以能做的事情仅仅是重新解释数据的二进制位但无法对数据的二进制位做出修正。1.4 类型转换的安全性隐式类型转换必须使用已知的转换规则虽然灵活性受到了限制但是由于能够对数据进行恰当地调整所以更加安全几乎没有风险。强制类型转换能够在更大范围的数据类型之间进行转换例如不同类型指针引用之间的转换、从 const 到非 const 的转换、从 int 到指针的转换有些编译器也允许反过来等这虽然增加了灵活性但是由于不能恰当地调整数据所以也充满了风险程序员要小心使用。2 四种类型转换运算符2.1 C语言的强制类型转换与C的区别C风格的强制类型转换统一使用()而()在代码中随处可见所以也不利于使用检索工具定位强转的代码位置。C 对类型转换进行了分类并新增了四个关键字来予以支持它们分别是关键字 说明static_cast 用于良性转换一般不会导致意外发生风险很低。const_cast 用于 const 与非 const、volatile 与非 volatile 之间的转换。reinterpret_cast 高度危险的转换这种转换仅仅是对二进制位的重新解释不会借助已有的转换规则对数据进行调整但是可以实现最灵活的 C 类型转换。dynamic_cast 借助 RTTI用于类型安全的向下转型Downcasting。语法格式为 xxx_castnewType(data)3 static_caststatic_cast 是“静态转换”的意思也就是在编译期间转换转换失败的话会抛出一个编译错误。举个例子123456789101112131415161718192021222324252627282930#include iostream#include cstdlibusingnamespacestd;classComplex{public:Complex(doublereal 0.0,doubleimag 0.0): m_real(real), m_imag(imag){ }public:operatordouble()const{returnm_real; }//类型转换函数private:doublem_real;doublem_imag;};intmain(){//下面是正确的用法intm 100;Complex c(12.5, 23.8);longn static_castlong(m);//宽转换没有信息丢失charch static_castchar(m);//窄转换可能会丢失信息int*p1 static_castint*(malloc(10 *sizeof(int)) );//将void指针转换为具体类型指针void*p2 static_castvoid*(p1);//将具体类型指针转换为void指针doublerealstatic_castdouble(c);//调用类型转换函数//下面的用法是错误的float*p3 static_castfloat*(p1);//不能在两个具体类型的指针之间进行转换p3 static_castfloat*(0X2DF9);//不能将整数转换为指针类型return0;}4 reinterpret_castreinterpret_cast 用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时执行的是逐个比特复制的操作。1234567891011121314151617181920212223242526272829#include iostreamusingnamespacestd;classA{public:inti;intj;A(intn):i(n),j(n) { }};intmain(){A a(100);intr reinterpret_castint(a);//强行让 r 引用 ar 200;//把 a.i 变成了 200cout a.i , a.j endl;// 输出 200,100intn 300;A *pa reinterpret_castA* ( n);//强行让 pa 指向 npa-i 400;// n 变成 400pa-j 500;//此条语句不安全很可能导致程序崩溃cout n endl;// 输出 400longlongla 0x12345678abcdLL;pa reinterpret_castA*(la);//la太长只取低32位0x5678abcd拷贝给paunsignedintu reinterpret_castunsignedint(pa);//pa逐个比特拷贝到ucout hex u endl;//输出 5678abcdtypedefvoid(* PF1) (int);typedefint(* PF2) (int,char*);PF1 pf1; PF2 pf2;pf2 reinterpret_castPF2(pf1);//两个不同类型的函数指针之间可以互相转换}reinterpret_cast体现了 C 语言的设计思想用户可以做任何操作但要为自己的行为负责。5 const_castconst_cast 运算符仅用于进行去除 const 属性的转换它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。将 const 引用转换为同类型的非 const 引用将 const 指针转换为同类型的非 const 指针时可以使用 const_cast 运算符。例如123conststring s Inception;string p const_caststring (s);string* ps const_caststring* (s);// s 的类型是 const string*6 dynamic_cast用 reinterpret_cast 可以将多态基类包含虚函数的基类的指针强制转换为派生类的指针但是这种转换不检查安全性即不检查转换后的指针是否确实指向一个派生类对象。dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用而且能够检查转换的安全性。对于不安全的指针转换转换结果返回 NULL 指针。dynamic_cast 用于在类的继承层次之间进行类型转换它既允许向上转型Upcasting也允许向下转型Downcasting。向上转型是无条件的不会进行任何检测所以都能成功向下转型的前提必须是安全的要借助 RTTI 进行检测所有只有一部分能成功。dynamic_cast 与 static_cast 是相对的dynamic_cast 是“动态转换”的意思static_cast 是“静态转换”的意思。dynamic_cast 会在程序运行期间借助 RTTI 进行类型转换这就要求基类必须包含虚函数static_cast 在编译期间完成类型转换能够更加及时地发现错误。dynamic_cast 是通过“运行时类型检查”来保证安全性的。dynamic_cast 不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用——这种转换没法保证安全性只好用 reinterpret_cast 来完成。6.1 向上转型Upcasting向上转型时只要待转换的两个类型之间存在继承关系并且基类包含了虚函数这些信息在编译期间就能确定就一定能转换成功。因为向上转型始终是安全的所以 dynamic_cast 不会进行任何运行期间的检查这个时候的 dynamic_cast 和 static_cast 就没有什么区别了。「向上转型时不执行运行期检测」虽然提高了效率但也留下了安全隐患请看下面的代码123456789101112131415161718192021222324252627282930313233343536373839#include iostream#include iomanipusingnamespacestd;classBase{public:Base(inta 0): m_a(a){ }intget_a()const{returnm_a; }virtualvoidfunc()const{ }protected:intm_a;};classDerived:publicBase{public:Derived(inta 0,intb 0): Base(a), m_b(b){ }intget_b()const{returnm_b; }private:intm_b;};intmain(){//情况①Derived *pd1 newDerived(35, 78);Base *pb1 dynamic_castDerived*(pd1);coutpd1 pd1, pb1 pb1endl;coutpb1-get_a()endl;pb1-func();//情况②intn 100;Derived *pd2 reinterpret_castDerived*(n);Base *pb2 dynamic_castBase*(pd2);coutpd2 pd2, pb2 pb2endl;coutpb2-get_a()endl;//输出一个垃圾值pb2-func();//内存错误return0;}情况①是正确的没有任何问题。对于情况②pd 指向的是整型变量 n并没有指向一个 Derived 类的对象在使用 dynamic_cast 进行类型转换时也没有检查这一点而是将 pd 的值直接赋给了 pb这里并不需要调整偏移量最终导致 pb 也指向了 n。因为 pb 指向的不是一个对象所以get_a()得不到 m_a 的值实际上得到的是一个垃圾值pb2-func()也得不到 func() 函数的正确地址。pb2-func()得不到 func() 的正确地址的原因在于pb2 指向的是一个假的“对象”它没有虚函数表也没有虚函数表指针而 func() 是虚函数必须到虚函数表中才能找到它的地址。6.2 向下转型Downcasting向下转型是有风险的dynamic_cast 会借助 RTTI 信息进行检测确定安全的才能转换成功否则就转换失败。那么哪些向下转型是安全地呢哪些又是不安全的呢下面我们通过一个例子来演示12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#include iostreamusingnamespacestd;classA{public:virtualvoidfunc()const{ coutClass Aendl; }private:intm_a;};classB:publicA{public:virtualvoidfunc()const{ coutClass Bendl; }private:intm_b;};classC:publicB{public:virtualvoidfunc()const{ coutClass Cendl; }private:intm_c;};classD:publicC{public:virtualvoidfunc()const{ coutClass Dendl; }private:intm_d;};intmain(){A *pa newA();B *pb;C *pc;//情况①pb dynamic_castB*(pa);//向下转型失败if(pb NULL){coutDowncasting failed: A* to B*endl;}else{coutDowncasting successfully: A* to B*endl;pb - func();}pc dynamic_castC*(pa);//向下转型失败if(pc NULL){coutDowncasting failed: A* to C*endl;}else{coutDowncasting successfully: A* to C*endl;pc - func();}cout-------------------------endl;//情况②pa newD();//向上转型都是允许的pb dynamic_castB*(pa);//向下转型成功if(pb NULL){coutDowncasting failed: A* to B*endl;}else{coutDowncasting successfully: A* to B*endl;pb - func();}pc dynamic_castC*(pa);//向下转型成功if(pc NULL){coutDowncasting failed: A* to C*endl;}else{coutDowncasting successfully: A* to C*endl;pc - func();}return0;}当使用 dynamic_cast 对指针进行类型转换时程序会先找到该指针指向的对象再根据对象找到当前类指针指向的对象所属的类的类型信息并从此节点开始沿着继承链向上遍历如果找到了要转化的目标类型那么说明这种转换是安全的就能够转换成功如果没有找到要转换的目标类型那么说明这种转换存在较大的风险就不能转换。对于本例中的情况①pa 指向 A 类对象根据该对象找到的就是 A 的类型信息当程序从这个节点开始向上遍历时发现 A 的上方没有要转换的 B 类型或 C 类型实际上 A 的上方没有任何类型了所以就转换败了。对于情况②pa 指向 D 类对象根据该对象找到的就是 D 的类型信息程序从这个节点向上遍历的过程中发现了 C 类型和 B 类型所以就转换成功了。总起来说dynamic_cast 会在程序运行过程中遍历继承链如果途中遇到了要转换的目标类型那么就能够转换成功如果直到继承链的顶点最顶层的基类还没有遇到要转换的目标类型那么就转换失败。对于同一个指针例如 pa它指向的对象不同会导致遍历继承链的起点不一样途中能够匹配到的类型也不一样所以相同的类型转换产生了不同的结果。从表面上看起来 dynamic_cast 确实能够向下转型本例也很好地证明了这一点B 和 C 都是 A 的派生类我们成功地将 pa 从 A 类型指针转换成了 B 和 C 类型指针。但是从本质上讲dynamic_cast 还是只允许向上转型因为它只会向上遍历继承链。造成这种假象的根本原因在于派生类对象可以用任何一个基类的指针指向它这样做始终是安全的。本例中的情况②pa 指向的对象是 D 类型的pa、pb、pc 都是 D 的基类的指针所以它们都可以指向 D 类型的对象dynamic_cast 只是让不同的基类指针指向同一个派生类对象罢了。
C++强制类型转换的四种方式
1 C类型转换本质1.1 自动类型转换隐式利用编译器内置的转换规则或者用户自定义的转换构造函数以及类型转换函数这些都可以认为是已知的转换规则。例如从 int 到 double、从派生类到基类、从type *到void *、从 double 到 Complex 等。注type *是一个具体类型的指针例如int *、double *、Student *等它们都可以直接赋值给void *指针。例如malloc() 分配内存后返回的就是一个void *指针我们必须进行强制类型转换后才能赋值给指针变量。1.2 强制类型转换显式隐式不能完成的类型转换工作就必须使用强制类型转换(new_type) expression1.3 类型转换的本质数据类型的本质这种「确定数据的解释方式」的工作就是由数据类型Data Type来完成的。例如int a;表明a 这份数据是整数不能理解为像素、声音、视频等。数据类型转换的本质数据类型转换就是对数据所占用的二进制位做出重新解释。隐式类型转换编译器可以根据已知的转换规则来决定是否需要修改数据的二进制位强制类型转换由于没有对应的转换规则所以能做的事情仅仅是重新解释数据的二进制位但无法对数据的二进制位做出修正。1.4 类型转换的安全性隐式类型转换必须使用已知的转换规则虽然灵活性受到了限制但是由于能够对数据进行恰当地调整所以更加安全几乎没有风险。强制类型转换能够在更大范围的数据类型之间进行转换例如不同类型指针引用之间的转换、从 const 到非 const 的转换、从 int 到指针的转换有些编译器也允许反过来等这虽然增加了灵活性但是由于不能恰当地调整数据所以也充满了风险程序员要小心使用。2 四种类型转换运算符2.1 C语言的强制类型转换与C的区别C风格的强制类型转换统一使用()而()在代码中随处可见所以也不利于使用检索工具定位强转的代码位置。C 对类型转换进行了分类并新增了四个关键字来予以支持它们分别是关键字 说明static_cast 用于良性转换一般不会导致意外发生风险很低。const_cast 用于 const 与非 const、volatile 与非 volatile 之间的转换。reinterpret_cast 高度危险的转换这种转换仅仅是对二进制位的重新解释不会借助已有的转换规则对数据进行调整但是可以实现最灵活的 C 类型转换。dynamic_cast 借助 RTTI用于类型安全的向下转型Downcasting。语法格式为 xxx_castnewType(data)3 static_caststatic_cast 是“静态转换”的意思也就是在编译期间转换转换失败的话会抛出一个编译错误。举个例子123456789101112131415161718192021222324252627282930#include iostream#include cstdlibusingnamespacestd;classComplex{public:Complex(doublereal 0.0,doubleimag 0.0): m_real(real), m_imag(imag){ }public:operatordouble()const{returnm_real; }//类型转换函数private:doublem_real;doublem_imag;};intmain(){//下面是正确的用法intm 100;Complex c(12.5, 23.8);longn static_castlong(m);//宽转换没有信息丢失charch static_castchar(m);//窄转换可能会丢失信息int*p1 static_castint*(malloc(10 *sizeof(int)) );//将void指针转换为具体类型指针void*p2 static_castvoid*(p1);//将具体类型指针转换为void指针doublerealstatic_castdouble(c);//调用类型转换函数//下面的用法是错误的float*p3 static_castfloat*(p1);//不能在两个具体类型的指针之间进行转换p3 static_castfloat*(0X2DF9);//不能将整数转换为指针类型return0;}4 reinterpret_castreinterpret_cast 用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时执行的是逐个比特复制的操作。1234567891011121314151617181920212223242526272829#include iostreamusingnamespacestd;classA{public:inti;intj;A(intn):i(n),j(n) { }};intmain(){A a(100);intr reinterpret_castint(a);//强行让 r 引用 ar 200;//把 a.i 变成了 200cout a.i , a.j endl;// 输出 200,100intn 300;A *pa reinterpret_castA* ( n);//强行让 pa 指向 npa-i 400;// n 变成 400pa-j 500;//此条语句不安全很可能导致程序崩溃cout n endl;// 输出 400longlongla 0x12345678abcdLL;pa reinterpret_castA*(la);//la太长只取低32位0x5678abcd拷贝给paunsignedintu reinterpret_castunsignedint(pa);//pa逐个比特拷贝到ucout hex u endl;//输出 5678abcdtypedefvoid(* PF1) (int);typedefint(* PF2) (int,char*);PF1 pf1; PF2 pf2;pf2 reinterpret_castPF2(pf1);//两个不同类型的函数指针之间可以互相转换}reinterpret_cast体现了 C 语言的设计思想用户可以做任何操作但要为自己的行为负责。5 const_castconst_cast 运算符仅用于进行去除 const 属性的转换它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。将 const 引用转换为同类型的非 const 引用将 const 指针转换为同类型的非 const 指针时可以使用 const_cast 运算符。例如123conststring s Inception;string p const_caststring (s);string* ps const_caststring* (s);// s 的类型是 const string*6 dynamic_cast用 reinterpret_cast 可以将多态基类包含虚函数的基类的指针强制转换为派生类的指针但是这种转换不检查安全性即不检查转换后的指针是否确实指向一个派生类对象。dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用而且能够检查转换的安全性。对于不安全的指针转换转换结果返回 NULL 指针。dynamic_cast 用于在类的继承层次之间进行类型转换它既允许向上转型Upcasting也允许向下转型Downcasting。向上转型是无条件的不会进行任何检测所以都能成功向下转型的前提必须是安全的要借助 RTTI 进行检测所有只有一部分能成功。dynamic_cast 与 static_cast 是相对的dynamic_cast 是“动态转换”的意思static_cast 是“静态转换”的意思。dynamic_cast 会在程序运行期间借助 RTTI 进行类型转换这就要求基类必须包含虚函数static_cast 在编译期间完成类型转换能够更加及时地发现错误。dynamic_cast 是通过“运行时类型检查”来保证安全性的。dynamic_cast 不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用——这种转换没法保证安全性只好用 reinterpret_cast 来完成。6.1 向上转型Upcasting向上转型时只要待转换的两个类型之间存在继承关系并且基类包含了虚函数这些信息在编译期间就能确定就一定能转换成功。因为向上转型始终是安全的所以 dynamic_cast 不会进行任何运行期间的检查这个时候的 dynamic_cast 和 static_cast 就没有什么区别了。「向上转型时不执行运行期检测」虽然提高了效率但也留下了安全隐患请看下面的代码123456789101112131415161718192021222324252627282930313233343536373839#include iostream#include iomanipusingnamespacestd;classBase{public:Base(inta 0): m_a(a){ }intget_a()const{returnm_a; }virtualvoidfunc()const{ }protected:intm_a;};classDerived:publicBase{public:Derived(inta 0,intb 0): Base(a), m_b(b){ }intget_b()const{returnm_b; }private:intm_b;};intmain(){//情况①Derived *pd1 newDerived(35, 78);Base *pb1 dynamic_castDerived*(pd1);coutpd1 pd1, pb1 pb1endl;coutpb1-get_a()endl;pb1-func();//情况②intn 100;Derived *pd2 reinterpret_castDerived*(n);Base *pb2 dynamic_castBase*(pd2);coutpd2 pd2, pb2 pb2endl;coutpb2-get_a()endl;//输出一个垃圾值pb2-func();//内存错误return0;}情况①是正确的没有任何问题。对于情况②pd 指向的是整型变量 n并没有指向一个 Derived 类的对象在使用 dynamic_cast 进行类型转换时也没有检查这一点而是将 pd 的值直接赋给了 pb这里并不需要调整偏移量最终导致 pb 也指向了 n。因为 pb 指向的不是一个对象所以get_a()得不到 m_a 的值实际上得到的是一个垃圾值pb2-func()也得不到 func() 函数的正确地址。pb2-func()得不到 func() 的正确地址的原因在于pb2 指向的是一个假的“对象”它没有虚函数表也没有虚函数表指针而 func() 是虚函数必须到虚函数表中才能找到它的地址。6.2 向下转型Downcasting向下转型是有风险的dynamic_cast 会借助 RTTI 信息进行检测确定安全的才能转换成功否则就转换失败。那么哪些向下转型是安全地呢哪些又是不安全的呢下面我们通过一个例子来演示12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273#include iostreamusingnamespacestd;classA{public:virtualvoidfunc()const{ coutClass Aendl; }private:intm_a;};classB:publicA{public:virtualvoidfunc()const{ coutClass Bendl; }private:intm_b;};classC:publicB{public:virtualvoidfunc()const{ coutClass Cendl; }private:intm_c;};classD:publicC{public:virtualvoidfunc()const{ coutClass Dendl; }private:intm_d;};intmain(){A *pa newA();B *pb;C *pc;//情况①pb dynamic_castB*(pa);//向下转型失败if(pb NULL){coutDowncasting failed: A* to B*endl;}else{coutDowncasting successfully: A* to B*endl;pb - func();}pc dynamic_castC*(pa);//向下转型失败if(pc NULL){coutDowncasting failed: A* to C*endl;}else{coutDowncasting successfully: A* to C*endl;pc - func();}cout-------------------------endl;//情况②pa newD();//向上转型都是允许的pb dynamic_castB*(pa);//向下转型成功if(pb NULL){coutDowncasting failed: A* to B*endl;}else{coutDowncasting successfully: A* to B*endl;pb - func();}pc dynamic_castC*(pa);//向下转型成功if(pc NULL){coutDowncasting failed: A* to C*endl;}else{coutDowncasting successfully: A* to C*endl;pc - func();}return0;}当使用 dynamic_cast 对指针进行类型转换时程序会先找到该指针指向的对象再根据对象找到当前类指针指向的对象所属的类的类型信息并从此节点开始沿着继承链向上遍历如果找到了要转化的目标类型那么说明这种转换是安全的就能够转换成功如果没有找到要转换的目标类型那么说明这种转换存在较大的风险就不能转换。对于本例中的情况①pa 指向 A 类对象根据该对象找到的就是 A 的类型信息当程序从这个节点开始向上遍历时发现 A 的上方没有要转换的 B 类型或 C 类型实际上 A 的上方没有任何类型了所以就转换败了。对于情况②pa 指向 D 类对象根据该对象找到的就是 D 的类型信息程序从这个节点向上遍历的过程中发现了 C 类型和 B 类型所以就转换成功了。总起来说dynamic_cast 会在程序运行过程中遍历继承链如果途中遇到了要转换的目标类型那么就能够转换成功如果直到继承链的顶点最顶层的基类还没有遇到要转换的目标类型那么就转换失败。对于同一个指针例如 pa它指向的对象不同会导致遍历继承链的起点不一样途中能够匹配到的类型也不一样所以相同的类型转换产生了不同的结果。从表面上看起来 dynamic_cast 确实能够向下转型本例也很好地证明了这一点B 和 C 都是 A 的派生类我们成功地将 pa 从 A 类型指针转换成了 B 和 C 类型指针。但是从本质上讲dynamic_cast 还是只允许向上转型因为它只会向上遍历继承链。造成这种假象的根本原因在于派生类对象可以用任何一个基类的指针指向它这样做始终是安全的。本例中的情况②pa 指向的对象是 D 类型的pa、pb、pc 都是 D 的基类的指针所以它们都可以指向 D 类型的对象dynamic_cast 只是让不同的基类指针指向同一个派生类对象罢了。