从C语言到C++_37(特殊类设计和C++类型转换)单例模式(中):https://developer.aliyun.com/article/1522501
2.4 dynamic_cast
dynamic_cast是C++特有的,因为dynamic_cast设计到继承和多态的内容。
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转换:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转换:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
首先要知道:父类对象无论如何都是不允许转换成子类对象的
class A // 父类 { public: virtual void f() // 父类必须含有虚函数 {} int _a = 1; }; class B : public A // 子类 { public: int _b = 2; }; int main() { A aa; // 父类对象无论如何都是不允许转换成子类对象的 B bb = dynamic_cast<B>(aa); B bb = (B)aa; B bb; return 0; }
但是父类的指针/引用是允许转换成子类指针/引用的,但C语言这样的场景就会报错:
void Func(A* pa) { B* bptr = (B*)pa; cout << bptr << endl; bptr->_a = 6; bptr->_b = 7; } int main() { A aa; B bb; Func(&aa); return 0; }
class A是父类,class B是子类,父类中有成员变量int _a,子类中有成员变量_b。
在main函数中,传父类指针&aa给函数,在函数中将A* pa父类指针接收该值,然后将其强转为子类指针B*,使用子类指针访问子类成员,bptr->_b = 7发生运行时错误。
形参A* pa是父类指针,接收的也是父类指针,所以强转成子类指针后访问子类成员_b会发生越界。如果传的是子类指针就不会报错,因为即使形参是父类指针,强转成子类以后并不会越界。
C++使用dynamic_cast将父类指针强转为子类指针:
void Func(A* pa) { B* bptr = dynamic_cast<B*>(pa); cout << bptr << endl; bptr->_a = 6; bptr->_b = 7; } int main() { A aa; B bb; Func(&aa); return 0; }
传父类指针,然后强转为子类指针后,打印出来的结构是nullptr,表示该次转换不能进行。
传子类指针:
void Func(A* pa) { B* bptr = dynamic_cast<B*>(pa); cout << bptr << endl; bptr->_a = 6; bptr->_b = 7; } int main() { A aa; B bb; // Func(&aa); Func(&bb); return 0; }
传子类指针,形参的父类指针接收后再强转为子类,打印出来的结构是强转后的地址,表示该次强转可以成功。
注意:
- dynamic_cast只能用于父类含有虚函数的类。
- dynamic_cast会先检查是否能转换成功,能成功则转换并返回正确的地址,不能则返回nullptr。
- dynamic_cast是安全的,直接使用C语言的转换方式是不安全的(因为有越界风险)。
再看一段代码:
class A1 { public: virtual void f(){} public: int _a1 = 0; }; class A2 { public: virtual void f(){} public: int _a2 = 0; }; class B : public A1, public A2 { public: int _b = 1; }; int main() { B bb; A1* ptr1 = &bb; A2* ptr2 = &bb; cout << ptr1 << endl; cout << ptr2 << endl << endl; B* pb1 = (B*)ptr1; B* pb2 = (B*)ptr2; cout << pb1 << endl; cout << pb2 << endl << endl; B* pb3 = dynamic_cast<B*>(ptr1); B* pb4 = dynamic_cast<B*>(ptr2); cout << pb3 << endl; cout << pb4 << endl << endl; return 0; }
结果可能和想的不一样,此时C++中dynamic_cast和C语言的强转就差了父类必须有虚函数:
class A1 { public: //virtual void f(){} public: int _a1 = 0; }; class A2 { public: //virtual void f(){} public: int _a2 = 0; }; class B : public A1, public A2 { public: int _b = 1; }; int main() { B bb; A1* ptr1 = &bb; A2* ptr2 = &bb; cout << ptr1 << endl; cout << ptr2 << endl << endl; B* pb1 = (B*)ptr1; B* pb2 = (B*)ptr2; cout << pb1 << endl; cout << pb2 << endl << endl; B* pb3 = dynamic_cast<B*>(ptr1); B* pb4 = dynamic_cast<B*>(ptr2); cout << pb3 << endl; cout << pb4 << endl << endl; return 0; }
屏蔽掉下面代码:
C++中的类型转换,尤其是前两种static_cast和reinterpret_cast是建议用法,可以采用也可以不采用。const_cast是一种新用法,但是存在风险,dynamic_cast是一种安全的类型转换。
3. RTTI(了解)和类型转换常见面试题
RTTI:Run - time Type identification的简称,即:运行时类型识别。
C++通过以下方式来支持RTTI:
1. typeid运算符
2. dynamic_cast运算符
3. decltype
常见面试题
1. C++中的4中类型转化分别是:_________、_________、_________、_________。
2. 说说4中类型转化的应用场景。
① static_cast,命名上理解是静态类型转换
使用场景:
用于类层次结构中基类和派生类之间指针或引用的转换
注意: 上行转换(派生类 -> 基类)是安全的,下行转换(基类 -> 派生类)由于没有动态类型检查,所以是不安全的。
用于基本数据类型之间的转换,如把int转换为char,这种带来安全性问题由程序员来保证。
。使用特点:
主要执行非多态的转换操作,用于代替C中通常的转换操作。
隐式转换都建议使用static_cast进行标明和替换。
② const_cast,字面上理解就是去const属性
使用场景:
常量指针转换为非常量指针,并且仍然指向原来的对象。
常量引用被转换为非常量引用,并且仍然指向原来的对象。
使用特点:
cosnt_cast是四种类型转换符中唯一可以对常量进行操作的转换符。
去除常量性是一个危险的动作,尽量避免使用。
③ reinterpreter_cast,仅仅重新解释类型,但没有进行二进制的转换
使用场景:
不到万不得已,不用使用这个转换符,高危操作。
使用特点:
reinterpret_cast可以将整型转换为指针,也可以把指针转换为数组。
reinterpret_cast可以在指针和引用里进行肆无忌惮的转换。
④ dynamic_cast,命名上理解是动态类型转换
使用场景:
只有在派生类之间转换时才使用dynamic_cast,type-id必须是类指针,类引用或者void
使用特点:
基类必须要有虚函数。
对于下行转换,dynamic_cast是安全的(当类型不一致时,转换过来的是空指针),而static_cast是不安全的。(当类型不一致时,转换过来的是错误意义的指针,可能造成踩内存,非法访问等各种问题)
总结:
去const属性用const_cast。
基本类型转换用static_cast。
不同类型的指针类型转换用reinterpreter_cast。
多态类之间的类型转换用daynamic_cast。
本篇完。
特殊类的设计中,要掌握好一点,就是只能通过一个接口来获取类,其他的方式不允许,让成员函数或私有或禁掉就可以。特别是单例模式,经常要用到。
对于类型转换,除了dynamic_cast是在多态转换中必须使用外,其他三种方式建议使用,可以增加代码的规范性。