C++类型转换
C++为了规范C中的类型转换,加强类型转换的可视性,引入了四种强制类型转换操作符:static_cast, reinterpret_cast, const_cast, dynamic_cast
他们本质上都是模板类。
下面分别来介绍:
1.static_cast
它用于非多态类型的转换(静态转换),对应于C中的隐式类型转换,但他不能用于两个不相关类型的转换,如整形和整形指针之间的转换,虽然二者都是四个字节,但他们一个表示数据,一个表示地址,类型不相关,无法进行转换。
void Test()
{
//C中的方式
int i = 10;
double d1 = i;//隐式类型转换
//int *p = i;//无法隐式类型转换,只能强制类型转换
int *p = (int*)i;
//C++中的方式
double d2 = static_cast<double>(i);
//相当于创建一个static_cast<double>类型的匿名对象赋值给d2
int* p2 = static_cast<int*>(i);//无法转换,会报错
}
有这么一种特殊的情况:
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
void test()
{
A a1(10);//调用构造函数创建A类对象a1
A a2 = 20;
A a3 = static_cast<A>(20);
}
对于A a2 = 20;
这一行代码,等号右边是一个整型,左边是自定义A类型,两者类型完全不相关,理论上是不能进行转换的,但是编译器却没有报错,原因是A类型的构造函数只有一个参数,编译器会用20构造一个A类型的临时对象,再调用拷贝构造函数创建a2,由于在一条表达式中既有构造又有拷贝构造,编译器会进行优化,将二者合二为一,只调用了构造函数。因此,单参的构造函数会进行隐式类型转换,C++中即可用static_cast来转。但是这种隐式类型转换有时候是不安全的,尤其是在使用智能指针时,当我们将一个不需要用户自己管理释放空间的指针交给智能指针时,系统并不会报错,但是智能能指针使用完会调用析构函数来释放空间,此时就会出问题。
为了防止这种单参构造函数的隐式类型转换,C++提供了一个关键字 explicit来消除这种转换。
加上explicit之后:
class A
{
public:
explicit A(int a)
:_a(a)
{}
private:
int _a;
};
void test()
{
A a1(10);
A a2 = 20;
}
这样就有效的避免了隐不合时宜的转换。
2.reinterpret_cast
reinterpret的含义是重新解释,可将一种类型转换成另一种不相关类型,对应C中的强制类型转换,处理无法进行隐式转换的情况
void Test()
{
int i = 10;
int* p2 = reinterpret_cast<int*>(i);
}
强制类型转换有时可以很暴力的处理一些问题
如下例:
对于一个带参数的函数,如何不传参也可以调用该函数?
void Fun(int s)
{
cout << s << endl;
}
typedef void(*FUNC)();
void Test()
{
FUNC pf = reinterpret_cast<FUNC>(Fun);
pf();
}
C中的强制类型转换也可以处理。
虽然我们通过这种BUG的方式转换函数指针,但是这样的代码是不可移植的,而且有时会产生不确定的结果,所以不建议这样来用
如此处输出的s的值就为一个随机值,虽然用户在外部未传参,但是该函数在调用时会创建形参,该形参未初始化,自然是随机值
3.const_cast
他的功能就是删除变量的const属性,方便再次赋值
void Test3()
{
const int i = 10;
int *p = const_cast<int*>(&i);
*p = 20;
cout << i << endl;
cout << *p << endl;
}
此时这两个值会分别输出多少呢
我们先来看下监视窗口:
可是输出结果却是这样的:
事实上,一旦将一个变量定义成const类型,编译器会进行优化,将该值放在寄存器中,默认他是不能被改变的,每次在使用时直接从寄存器中取数据,即使我们改变了他在内存中的值,编译器对此是透明的,无法看到
查看汇编代码:
这里编译器的处理更为直接,将0Ah直接压栈,所以结果是这样的
为了防止编译器对我们的代码进行优化,我们可以借助一个关键字valatile,
他可以保证每次从内存中进行取数据。
4.dynamic_cast
这种转换用于将父类对象的指针转换成子类对象的指针或引用。
向上转型:子类对象指针->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针->子类指针/引用(用dynamic_cast转型是安全的)
class A
{
public:
virtual void f()
{
cout << "A::f()" << endl;
}
int _a;
};
class B:public A
{
public:
virtual void f()
{
cout << "B::f()" << endl;
}
int _b;
};
void Fun(A* p)
{
B* pb = (B*)p;
pb->f();
cout << pb->_a << endl;
cout << pb->_b << endl;
}
void Test4()
{
A a;
a._a = 1;
B b;
b._a = 2;
b._b = 3;
Fun(&a);
Fun(&b);
}
运行结果如下:
因为A类大小为8字节,B类为12字节,将一个A类指针强制转成B类,再用该指针去访问B类中的_b,会访问越界,读到的是随机值,一旦进行写,程序就会崩溃。
C++提供了一种更加安全的转换机制,使用dynamic_cast来进行转换。
注意:
1.dynamic_cast只能用于含有虚函数的类
2.dynamic_cast在转换时会先检查能否转换成功,能就进行转换,不能就返回0
将Fun函数进行改造:
void Fun(A* p)
{
//B* pb = (B*)p;
B* pb = dynamic_cast<B*>(p);
if (pb)
{
cout << "可以转换 p是子类指针" << endl;
pb->f();
cout << pb->_a << endl;
cout << pb->_b << endl;
}
else
{
cout << "不能转换 p是父类指针" << endl;
p->f();
cout << p->_a << endl;
}
}
这样就可以保证我们代码的安全性,