为什么要使用c++风格的类型转换
我们都知道c++的类型转换既可以使用c风格的类型转换,如(type)expression,也可以使用c++风格的类型转换,c风格转换基本上是万能转换,互不想干的类型可以转换,基类指针到子类指针也可以转换,基本上是换汤又换药啊,我记得在某处看到个例子,将一个结构体指针直接转为int指针就输出了,当时我人都傻了,居然有这种操作:
struct Node {
int x; //
Node* next;
Node() : x(-1), next(nullptr){}
};
int main()
{
Node* testNode = new Node;
int* p = (int*)testNode;
delete testNode;
return 0;
}
当然编译运行都没有任何问题,并且解引用p可以得到值-1,但是我只要将结构体的第一行变量string str, 那么这样转换就不能得到节点的数据了,所以我们写代码还是老老实实按照规范来,不要投机取巧,不然出bug时真的叫天天不应,叫地地不灵了,这种转换太粗鲁了,风马牛不相及都可以转,并且使用(type)形式,在代码中难以识别,使用代码分析工具的时候相当难识别是否使用了类型转换。
4种转换的作用
- static_cast:功能基本和c风格转换一致,但也有例外,不能把 struct 转换成 int 类型或者把 double 类型转换成指针类型,也不能去除表达式的 const 属性。
- const_cast:唯一的作用就是去除表达式的const 或 volatileness 属性。
- dynamic_cast: 把基类指针或者引用转为子类指针、者兄弟指针或引用,沿着继承关系向下转换。如果转换的不是该派生类类型,那么转换失败并返回返回空指针(用于指针转换时候)或者抛出异常(用于引用转换时候)。只能用于有虚函数的对象上,否则编译报错。
- reinterpret_cast:这真的是一个脱缰的野马,除非万不得已,否则引火烧身,这是一个强大的类型转换,可以用于任何类型的指针或者引用的转换,也可以用于指针到整形、整数到指针的转换。例如:可以把一个函数指针int (*fptr)()转换为void (*fptr)(),去掉返回值,或者将一个int类型转为int(*fptr)()。C++之父Bjarne Stroustrup的FAQ网页和MSDN的Visual C++也都指出:错误的使用reinterpret_cast很容易导致程序的不安全,只有将转换后的类型值转换回到其原始类型,这样才是正确使用reinterpret_cast方式。
火速实操
#include<iostream>
#include<string>
class Poultry { // 自定义测试类型 家禽类
public:
Poultry(const std::string& dogName) : m_name(dogName) {}
virtual const std::string& GetName() const = 0; // 一般来说家禽类不给实例化,哈哈
virtual ~Poultry() {};
protected:
std::string m_name;
};
// dog也是家禽的一种,但我们得把野狗排除一下,这儿没有野狗
class Dog :public Poultry {
// ...
public:
Dog(const std::string &dogName) : Poultry(dogName) {}
virtual const std::string& GetName() const override
{
return m_name;
}
};
class SpotDog : public Dog { // 这是一种带斑点得家禽狗,俗称家禽斑点狗
public:
SpotDog(const std::string& dogName) : Dog(dogName) {}
virtual const std::string& GetName() const override
{
return m_name;
}
};
void FooDog(Poultry *poultry) // 测试函数
{
// 传入指针,使其发生动态绑定
// ... do something
}
void FooSpotDog(SpotDog* spotDog)
{
// ... do something
}
void Foo() { /* ...*/ } // 函数
typedef void(*FooPtr)(); // 函数指针
int FooInt() { return 0; } // 函数
typedef int(*FooIntPtr)(); // 又一个函数指针
int main(int argc, char *agrv[])
{
Poultry *dog = new Dog("小黑"); // 一只狗
// 某些狗出生就有名字了,比如总是小黑小黑的叫,某一天你叫它小白,它就听不懂了
const Poultry* constDog = dog;
FooDog(constDog); // 错误 函数接收一个Pooultry*
FooDog(static_cast<Poultry*>(constDog)); // 错误 static_cast 只能去掉常量属性
FooDog(dynamic_cast<Poultry*>(constDog)); // 错误 不能去掉常量属性,仅仅使用于继承类链的向下转换
FooDog(const_cast<Poultry*>(constDog)); // 正确 const_cast 专门用来去掉常量属性的
Dog* spotDog = new SpotDog("斑点狗");
FooSpotDog(const_cast<SpotDog*>(spotDog)); // 失败,只能去掉const属性,再无别的用途
FooSpotDog(static_cast<SpotDog*>(spotDog)); // 成功,但是不好,没有转换失败
FooSpotDog(reinterpret_cast<SpotDog*>(spotDog)); // 成功,但是不好,没有提示转换失败,而且这个转换很野
FooSpotDog(dynamic_cast<SpotDog*>(spotDog)); // 成功,最佳选择,如果转换失败是会返回空指针的,
int b = 0;
double d = dynamic_cast<double>(b) / 5.0; // 错误,double没有虚函数 没有继承系统
// 最后来看看reinterpret_cast的用法,一个字,野
FooIntPtr funFooInt = &FooInt;
FooPtr funFoo = reinterpret_cast<FooPtr>(funFooInt); // ok 任何指针都可以转换
funFooInt = reinterpret_cast<FooIntPtr>(funFoo); // ok
int* fun = reinterpret_cast<int*>(funFoo); // ok
int* funInt = reinterpret_cast<int*>(funFooInt); // ok
funFooInt = reinterpret_cast<FooIntPtr>(fun); // ok
funFoo = reinterpret_cast<FooPtr>(funInt); // 统统都ok,一个字,干就完了。
return 0;
}