一、数据类型转换语法
C语言风格的类型转换格式
C++新式转型
- const_cast、dynamic_cast、reinterpret_cast、static_cast
- 详细介绍参阅:https://blog.csdn.net/qq_41453285/article/details/89187875
二、建议使用C++新式转型
- 旧式转型虽然虽然合法,但是程序设计时应该尽量使用新式转型。原因如下:
- ①新式转型在代码中很容易被识别出来
- ②新式转型使转型动作的目标比较有限。例如你打算将常量性移除,那么只能只用const_cast
旧式转型的使用
- 唯一使用旧式转型的场景就是,调用一个explicit构造函数将一个对象传递给一个函数时
- 例如:
class Widget { public: explicit Widget(int size); ... }; void doSomeWork(const Widget& w); doSomeWork(Widget(15)); //函数风格 doSomeWidget(static_cast<Widget>(15)); //c++风格
三、对转型的一种误解
- 很多人认为转型其实什么都没做,只是告诉编译器把某种类型视为另一种类型而已,其实这是错误的观念
- 任何一种类型转换(不论是显式还是隐式,或是编译器自身完成的隐式转换),编译器在编译出运行期间执行的代码时都要做相应的改变
演示案例
- 下面代码将int类型转换为double类型,x的存储结构肯定会改变
int x, y; double d = static_cast<double>(x) / y;
演示案例
- 上面将派生类转换为基类,但有时上述两个指针值并不相同。这时候会有个偏移量在运行期被施行于Derived*指针身上,用以取得正确的Base*指针值
class Base {}; class Derived :public Base {}; int main() { Derived d; Base* pb = &d; //派生类转换为基类 return 0; }
四、关于转型的一个错误演示案例
- 例如许多应用框架都要求在派生类的virtual函数中第一个动作就是先调用基类的对应函数。例如下面我们有一个Windows类以及它的派生SpecialWindow,其中onResize()是虚函数
class Window {
public:
virtual void onResize() {}
};
class SpecialWindows :public Window {
public:
virtual void onResize() {
//尝试调用基类的虚函数,语法虽然没错,但是逻辑是错误的(程序会产生不明确行为)
static_cast<Window>(*this).onResize();
}
};
- 为什么上面的类型转换是错误的:
- 我们在函数中将*this转换为基类类型,然后尝试调用onResize()虚函数,但是该转型动作产生的实际上是一个“this对象之基类成分”的一个副本,然后在这个副本上调用onResize函数
- 所以当在副本上面调用onResize()函数对于this对象根本没有任何的影响,于是客户端程序以为调用了onResize()函数使对象本身改变了,但是实际上没有改变,只是改变了一个临时对象而已
- 替代方案:就是在虚函数中显式的调用基类的函数。例如将上面派生类的虚函数更改为下面的形式就是对的了
class SpecialWindows :public Window {
public:
virtual void onResize() {
Window::onResize(); //调用Window::onResize作用于*this身上
}
};
五、关于dynamic_cast的一些概述
- dynamic_cast主要用于:想要使用派生类的方法,但是此时只有一个基类的指针/引用,此时可以使用该类型转换将基类的指针转换为派生类指针
- 为什么不建议使用该转型:
- 因为该转换会使代码执行的非常慢
- 有的编译器对于该转型的实现原理是“基于class名称的字符串比较”,也就是说如果在继承体系很多的对象身上应用这个类型转换,那么会有很多的strcmp的调用来比较class名称
- 下面是一个dynamic_cast的演示案例,只有派生类有一个闪烁效果的blink()函数,此时我们想用基类指针访问这个函数
class Window {
public:
//...
};
class SpecialWindows :public Window {
public:
void blink();
};
int main()
{
std::vector<std::tr1::shared_ptr<Window> > VPW;
//遍历容器中的每个Window对象并且调用blink函数
for (auto iter = VPW.begin(); iter != VPW.end(); ++iter) {
//使用dynamic_cast转型,get()函数是获取shared_ptr中的Window对象指针的意思
if (SpecialWindows* psw = dynamic_cast<SpecialWindows*>(iter->get())) {
psw->blink();
}
}
return 0;
}
替代dynamic_cast方法①
- 对于上面的演示案例我们给出了下面的一种替代方法:直接使用容器存储派生类对象,不存储基类对象
- 缺点:但是这种方法可能需要为每一种派生类都创建一个容器来存储
int main() { std::vector<std::tr1::shared_ptr<SpecialWindows> > VPW; for (auto iter = VPW.begin(); iter != VPW.end(); ++iter) { (*iter)->blink(); } return 0; }
替代dynamic_cast方法②
第二种方法是:当你在基类中想做一些事情,那么也就基类中同时声明一份,并且将函数设置为virtual的(但是基类中的虚函数什么都不做)
代码如下:
class Window { public: virtual void blink() { //条款34告诉你缺省实现代码可能是个馊主意 //什么都不做 } }; class SpecialWindows :public Window { public: virtual void blink() { //实现相关代码 } }; int main() { std::vector<std::tr1::shared_ptr<Window> > VPW; for (auto iter = VPW.begin(); iter != VPW.end(); ++iter) { (*iter)->blink(); } return 0; }
- 绝对必须避免的一件事情就是所谓的“连串dynamic_cast”,例如下面的代码,下面的代码运行又大又慢,而且基础不稳定,因为每次Window类继承体系一旦改变,所有的代码都需要再次更改
class Window {};
class SpecialWindows :public Window {};
class SpecialWindows2 :public Window {};
class SpecialWindows3 :public Window {};
//...
int main()
{
std::vector<std::tr1::shared_ptr<Window> > VPW;
for (auto iter = VPW.begin(); iter != VPW.end(); ++iter)
{
if (SpecialWindows *psw1 = dynamic_cast<SpecialWindows*>(iter->get())) {
//...
}
else if (SpecialWindows2 *psw2 = dynamic_cast<SpecialWindows2*>(iter->get())) {
//...
}
else if (SpecialWindows3 *psw3 = dynamic_cast<SpecialWindows3*>(iter->get())) {
//...
}
//...
}
return 0;
}
五、总结
- 如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的替代设计
- 如果转型是必须的,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需要将转型放进它们自己的代码
- 宁可使用新式的转型,不要使用旧式转型。前者很容易辨别出来,而且也比较有着分门别类的职掌