和上一篇文章一样了,还是提起一下大约一年前我来公司面试所遇到的一道题目,题目很简单:C++有多少种cast,它们的名称和功能各是什么。(我之前的文章曾经提到过,但后来我发现自己写得并不够简明)答案如下:
一共四种cast。1、static_cast,支持子类指针到父类指针的转换,并根据实际情况调整指针的值,反过来也支持,但会给出编译警告,它作用最类似C风格的“强制转换”,一般来说可认为它是安全的;2、dynamic_cast,支持子类指针到父类指针的转换,并根据实际情况调整指针的值,和static_cast不同,反过来它就不支持了,会导致编译错误,这种转换是最安全的转换;3、reinterpret_cast,支持任何转换,但仅仅是如它的名字所描述的那样“重解释”而已,不会对指针的值进行任何调整,用它完全可以做到“指鹿为马”,但很明显,它是最不安全的转换,使用它的时候,你得头脑清醒,知道自己在干什么;4、const_cast,这个转换能剥离一个对象的const属性,也就是说允许你对常量进行修改。
这样回答即使得不了满分,拿个八九十分应该也没问题了,我后来还专门写了些测试程序来验证过,对于第一第二第三种转换都没什么问题,而const_cast却似乎不能正常工作,代码如下:
int main(int argc, char* argv[])
{
const int ic=100;
const_cast<int &>(ic)=200;
printf("%d\n", ic);
return 0;
}
结果不是我期待的200,而是100,我一开始以为这是由于优化选项的问题,于是调整编译器选项,全部不优化,但还是一样的结果,我开始怀疑这是VC++的bug,于是使用Linux的g++来编译,结果一样,看来这和编译器没有关系,那我究竟做错了哪里呢?或者我对const_cast理解错了呢?我开始改进我的代码,我尝试不同的类型:
class CTest
{
public:
CTest(int i){m_val = i;printf("construction [%d]\n", m_val);};
~CTest(){printf("destruction\n");};
void SelfAdd(){m_val++;};
int m_val;
};
int main(int argc, char* argv[])
{
const CTest test(1000);
CTest test2(1050);
const_cast<CTest &>(test)= test2;
printf("%d\n", test.m_val);
return 0;
}
这次总算得到了我想要得到结果,打印出了1050,说明const_cast并没有问题,但前一个程序为什么不能正常工作?我继续尝试了不同的类型,比如char,short,float,double等,发现了规律,凡是对结构体或类进行这个转换,都是成功的,但对char,short等基本类型的转换都没有成功。我进一步改进代码,为了查看它们的值是否真的已经被修改,我使用了指针:
int main(int argc, char* argv[])
{
const int ic = 100;
const int *pc=⁣
const_cast<int &>(ic)++;
printf("%d,%d\n", ic, *pc);
return 0;
}
这次打印出来的结果是“100,101”,这就说明常量ic的值确实已经被改变了,但为什么直接打印ic就得不到正确的结果?那估计还是前边想到的“优化”的原因,可我一再确认我并没有使用优化编译选项,而且g++的表现也如此。看来只好使用最后一招了,直接查看printf究竟做了些什么,在printf处设置断点,调试程序,然后打开disassembly视图查看反汇编代码,一切真相大白。
原来虽然我没有使用优化,但系统还是对ic这个const进行了预编译般的替换,将它替换成“64h”(十六进制的64就是十进制的100),这究竟是不是C++的规范?我不知道,但我肯定这不是一般用户想要的结果,对我来说,算是个C++的bug吧。通过解决这个问题,我也学会了些东西,如果以后遇到类似这种表面上再显浅不过,但就是不能正常工作的代码片断,要学会查看反汇编代码,也许一切问题迎刃而解,另外使用const_cast的时候应该注意些什么东西,嗯,自己思考一下吧。Java没有const_cast,很多语言都没有,(我只知道C++有)既然已经被定义为常量,就是不希望它被改变,但现在又允许你改变它,这不是很可笑吗?但难道它不是C++强大又灵活的又一体现吗?不过话说回来要看你怎么用了。