十六、类型转换
注这个是博主复习使用的专题仅适用于自己以及学习过C知识点的同学目录前言一、C语言支持的类型转换二、C支持的类型转换三、C中4种显示强制类型转换3.1. 类型安全3.2. C中4个显示强制类型转换运算符static_castreinterpret_castconst_castdynamic_cast四、RTTI (Runtime Type Identification)前言注这个是博主复习使用的专题仅适用于自己以及学习过C知识点的同学。一、C语言支持的类型转换C语言和C都是强类型语言如果赋值运算符左右两侧类型不同或者形参与实参类型不匹配或者返回值类型与接受返回值列席不一致时等场景就需要发生类型转化。C语言中有两种形式的类型转换隐式类型转换 和 显式强制类型转换。隐式类型转换编译器在编译阶段自动进行能转就转不能转就编译失败。显式强制类型转换需要用户自行去显示在变量前用括号指定要转换的类型。并不是任意类型都支持转换两个类型支持转换需要有一定的关联性也就说转换后要有一定的意义两个毫不关联的类型是不支持转换的。#includeiostream using namespace std; int main() { // 隐式类型转换 int i 1; double d i; printf(%d\n, i); printf(%f\n\n, d); // 显式类型转换 int* p i; int address (int)p; cout p endl; cout address endl; return 0; }二、C支持的类型转换C 兼容 C所以C支持的隐式类型转换和显式强制类型转换 C 都支持。class A { public: //explicit A (int a) A (int a) :_a1(a),_a2(a) {} A(int a1, int a2) :_a1(a1), _a2(a2) {} private: int _a1 1; int _a2 1; }; int main() { //A aa1 1; A aa1 (A)1; A aa2 { 1,1 }; return 0; }上面代码中 A aa1 1 是 隐式类型转换 A aa1 (A)1 是 显示类型转换都会调用 A(int a) 这个构造函数当然如果 使用explicitA aa1 1 是不能使用的。C 还支持内置类型都自定义类型之间的转换内置类型转成自定义类型需要构造函数的支持自定义类型转成内置类型需要一个 operator 类型 () 的函数去支持。例如我们 将 A 对象转成 int 类型就需要构造operator int ()代码如下class A { public: explicit A (int a) //A (int a) :_a1(a),_a2(a) {} A(int a1, int a2) :_a1(a1), _a2(a2) {} operator int() { return _a1_a2; } private: int _a1 1; int _a2 1; }; int main() { A aa1 (A)1; int a aa1; cout a endl;// 2 return 0; }同理如果不想让 aa1 隐式类型转换成 int也可以使用 explicit。class A { public: explicit A (int a) //A (int a) :_a1(a),_a2(a) {} A(int a1, int a2) :_a1(a1), _a2(a2) {} //operator int() explicit operator int() { return _a1_a2; } private: int _a1 1; int _a2 1; }; int main() { A aa1 (A)1; int a (int)aa1; cout a endl;// 2 return 0; }C 还支持自定义类型到自定义类型之间的转换需要对应类型的构造函数支持即可。比如A类型对象想转成B类型则支持一个形参为A类型的B构造函数即可支持。class A { public: //explicit A(int a) A (int a) :_a1(a), _a2(a) {} A(int a1, int a2) :_a1(a1), _a2(a2) {} //operator int() operator int() const { return _a1 _a2; } private: int _a1 1; int _a2 1; }; class B { public: B(int b) :_b1(b) {} // 支持A类型对象转换为B类型对象 B(const A aa) :_b1(aa) {} int GetB1() { return _b1; } private: int _b1; }; int main() { A aa1 (A)1; B b (B)aa1; cout b.GetB1() endl; return 0; }B b (B)aa1 的调用逻辑如下三、C中4种显示强制类型转换3.1. 类型安全类型安全是指编程语言在 编译和运行时 提供的保护机制避免非法的类型转换和操作导致出现一个内存访问错误等从而减少程序运行时的错误。C 语言不是类型安全的语言C 语言允许隐式类型转换一些特殊情况下就会导致越界访问的内存错误其次不合理的使用强制类型转换也会导致问题比如一个 int* 指针强转成 double* 访问就会出现越界。C兼容C语言支持隐式类型转换和强制类型转换C也不是类型安全的语言C提出4个显示的明明强制类型转换 static_cast / reinterpret_cast / const_cast / dynamic_cast 就是为了让类型相对而更安全。3.2. C中4个显示强制类型转换运算符static_caststatic_cast用于编译时已知的、逻辑上合理的类型转换。主要包括基本数据类型之间的转换任意类型转换为 void*或从 void* 转换回原类型。基类的指针/引用 转换为派生类的指针/引用 可以但不推荐。static_cast不做运行时类型检查int main() { // 基本数据类型之间的转换 int i 1; //double d i; 等价 double d static_castdouble(i); int ref static_castint(d); cout d ref endl; // 将 void* 转换回原类型 int x 42; void* vp x; int* ip static_castint*(vp); cout *ip *ip endl; // 输出 42 return 0; }reinterpret_castreinterpret_cast⽤于两个类型意义不相近的转换reinterpret是重新解释的意思通常为运算对象的位模式提供较低层次上的重新解释也就是说转换后对原有内存的访问解释已经完全改变了非常的大胆。所以我们要谨慎使用清楚知道这样转换是没有内存访问安全问题的int main() { double d 12.34; int a static_castint(d); cout a endl; int* p1 reinterpret_castint*(a); cout p1 endl; return 0; }const_castconst_cast用于删除变量的const属性转换后就可以对const变量的值进行修改。比如int main() { // 对应强制类型转换中有风险的去掉const属性 // 所以要注意加volatile volatile const int b 0; int* p2 const_castint*(b); *p2 1; cout b endl; cout *p2 endl; return 0; }如果不想让编译器将const变量优化到寄存器当中可以用volatile关键字对const变量进行修饰这时当要读取这个const变量时编译器就会从内存中进行读取即保持了该变量在内存中的可见性。dynamic_castdynamic_cast用于将父类的指针或引用转换成子类的指针(或引用)。如果基类的指针指向基类对象则转换失败返回nullptr如果基类引用指向基类对象则转换失败抛出bad_cast异常。其次dynamic_cast要求基类必须是多态类型也就是基类中必须有虚函数。因为dynamic_cast是运行时通过虚表中存储的type_info判断基类指针指向的是基类对象还是派生类对象。class A { public: virtual void f() {} int _a 1; }; class B : public A { public: int _b 2; }; void func(A* pa) { B* pb1 (B*)pa; //不安全 B* pb2 dynamic_castB*(pa); //安全 cout pb1: pb1 endl; cout pb2: pb2 endl; } void fun1(A* pa) { // 指向父类转换时有风险的后续访问存在越界访问的⻛险 // 指向子类转换时安全 B* pb1 (B*)pa; cout pb1: pb1 endl; cout pb1-_a endl; cout pb1-_b endl; pb1-_a; pb1-_b; cout pb1-_a endl; cout pb1-_b endl; } void fun2(A* pa) { // dynamic_cast会先检查是否能转换成功(指向子类对象)能成功则转换 // (指向父类对象)转换失败则返回nullptr B* pb1 dynamic_castB*(pa); if (pb1) { cout pb1: pb1 endl; cout pb1-_a endl; cout pb1-_b endl; pb1-_a; pb1-_b; cout pb1-_a endl; cout pb1-_b endl; } else { cout 转换失败 endl; } } void fun3(A pa) { // 转换失败则抛出bad_cast异常 try { B pb1 dynamic_castB(pa); cout 转换成功 endl; } catch (const exception e) { cout e.what() endl; } } int main() { A a; B b; func(a); func(b); /*fun1(a); fun1(b);*/ fun2(a); fun2(b); fun3(a); fun3(b); return 0; }上述代码中如果传入func函数的是子类对象的地址那么在转换后pb1和pb2都会有对应的地址但如果传入func函数的是父类对象的地址那么转换后pb1会有对应的地址而pb2则是一个空指针。func1(A*pa) 演示 C 风格转换后直接访问派生类成员的风险。func2(A*pa)使用 dynamic_cast进行安全的向下转换并在转换失败时提供备选路径。func1(Apa)演示对引用使用 dynamic_cast转换失败时通过异常处理。四、RTTI (Runtime Type Identification)RTTI的英文全称是Runtime Type Identification中文称为运行时类型识别它指的是程序在运行的时候才确定需要用到的对象是什么类型的。用于在运行时而不是编译时获取有关对象的信息。RTTI主要由两个运算符实现typeid和dynamic_casttypeid主要用于返回表达式的类型dynamic_cast前面已经讲过了主要用于将基类的指针或者引用安全的转换成派生类的指针或者引用。typeid(e)中e可以是任意表达式或类型的名字typeid(e)的返回值是typeinfo或typeinfo派生类对象的引用typeinfo可以只支持比较等于和不等于name成员函数可以返回C风格字符串表示对象类型名字typeinfo的精确定义随着编译器的不同而略有差异也就以为着同⼀个e表达式不同编译器下typeid(e).name()返回的名字可能是不⼀样的。typeid(e)时当运算对象不属于类类型或者是一个不包含任何虚函数的类时typeid返回的是运算对象的静态类型当运算对象是定义了至少一个虚函数的类的左值时typeid的返回结果直到运行时才会求得。#includeiostream #includestring #includevector #includelist using namespace std; int main() { int a[10]; int* ptr nullptr; cout typeid(10).name() endl; cout typeid(a).name() endl; cout typeid(ptr).name() endl; cout typeid(string).name() endl; cout typeid(string::iterator).name() endl; cout typeid(vectorint).name() endl; cout typeid(vectorint::iterator).name() endl; return 0; }完