目录
1 C++四种显式类型转化
C语言的类型转换分为隐式类型转换和显式(强制)类型转换。
一般意义相近的类型都是支持隐式类型转换的,比如都是数值大小的int,double等浮点数和整型家族的成员。
如果两个类型所表示的意义不相关,但是又有一些相似之处,比如指针和整型,他们的意义完全不同,但是他们都存储的是整数数值,指针存的数值表示地址编号,整型存的数值表示数据大小,那么他们之间的类型转换就需要用户显式处理。
但是C语言的类型转换尤其是隐式类型转换有一个最大的缺陷,就是用户看不到,是由编译器自主完成的,那么当出现由类型引发的错误时,对我们的错误定位会造成很大的影响。
那么C++为什么要提出新的类型转换的方式呢?原因就是因为C语言的类型转换的一些缺点,比如:
1 隐式类型转换可能会导致京都的损失。 比如double隐式类型转换为int
2 显式类型转换将所有的情况混合在一起,代码不够清晰。
于是C++就提出了四种类型转换,这四种都是显式类型转换,相对来说更加清晰。
这四种类型转换分别是
static_cast , reinterpret_cast , const_cast , dynamic_cast
1.1 static_cast
static_cast用于相似类型的转换,比如整型家族与浮点型家族的相互之间的转换,简单来说,就是以前C语言中可以隐式类型转换的在C++中都可以用static_cast 来进行类型转换。
语法:
static_cast <目标类型> (被类型转换的对象 )
代码如下:
double d = 1.1;
int a = static_cast<int>(d);
short s = static_cast<short>(d);
1.2 reinterpret_cast
在C语言中如果使用强制类型转换的场景,那么在C++中就可以使用reinterpret_cast来进行强制类型转换。
比如 32位下 int 转为 指针类型,或者不同类型指针之间的转化
int a = 100;
double* pd = reinterpret_cast<double*>(a);
char* pc = reinterpret_cast<char*>(pd);
1.3 const_cast
const_cast用来消除变量的const属性。
这句话在我们看来可能有点荒谬,但是他是没问题的。
在C语言中,被const修饰的变量具有常量的属性,但是由于他是存在栈区的,所以又称为常变量,他的本质上还是变量,其实还是可以修改的,只是该变量不能作为左值来进行修改。
如下:
#include<stdio.h>
int main()
{
const int a = 10;
int* pa = (int*)&a;
(*pa)++;
printf("%d\n", a);
printf("%d\n",*pa);
return 0;
}
但是在C++中,被const修饰的对象表示将其放进符号表,那么这样一来,当我们只需要读取该数据而不发生写入时,就不会去访问该对象的内存,而是直接从符号表(简单理解为从寄存器中拿)读取数据。
于是,上面的代码,我们将文件的类型改为 .cpp 之后,结果如下:
那么我们如何让编译器在读取的时候去读内存中的对象而不是直接从符号表拿呢?我们讲过的一个关键字 volatile ,保持内存可见性。
那么不管发不发生写入,都是去内存中读取该对象,不会优化为直接在符号表读。当然这样一进符号表的意义也就没有了。
而 const_static 的作用就是将 const 对象转换为非 const 对象,因为在C++中,const修饰的变量还是存在栈区的,所以其实还是可变的。
1.4 dynamic_cast
这是C++相对于C语言增加的类型转换。
用于将一个父类指针或引用转换为子类指针或引用,前提是父类必须有虚函数。
在继承章节我们学过 切片/切割的概念, 也就是父类的指针或者引用指向子类,这是天然支持的,中间不发生类型转换,这叫做向上转换,但是在有些场景下,我么需要将这些 虽然类型是父类,但是实际指向的是子类对象的指针或引用转换回子类类型,我们就可以使用dynamic_cast,dynamic用于向下转换。
注意,父类必须有虚函数,如果没有虚函数,是无法进行向下转型的。可能他的底层是需要通过虚表地址来检查该父类指针或引用实际指向的到底是父类对象还是子类对象。虽然要求父类有虚函数,但是并没有强制一定要完成虚函数的重写达成多态。
如果是指针,向下转换失败时,它的返回值是 0 ,所以我们在使用dynamic_cast 得到一个指针之后,注意要先判断它是不是 nullptr ,如果是nullptr就说明转型失败了。
class A
{
public:
virtual void func()
{
cout << "A" << endl;
}
private:
int _a{ 0 };
};
class B : public A
{
public:
//void func() //可以重写也可以不重写
//{
// cout << "B" << endl;
//}
private:
int _b{1};
};
void test(A*pa)
{
B* pb = dynamic_cast<B*>(pa);
if (pb)
cout << "success" << endl;
else
cout << "dynamic_cast failed" << endl;
}
int main()
{
A a;
B b;
test(&a);
test(&b);
}
而如果是引用的向下转换,失败时会抛出异常 bad_cast ,所以我们要注意使用try catch捕获异常。
C++设计了这些类型转换的方式,让用户能够灵活且便捷的进行类型的转换,所以C++不是类型安全的语言
2 RTTI
RTTI意思就是运行时类型识别。
起初RTTI设计出来是在运行期间用来判断基类的指针或引用实际指向的对象的类型的。但是现在的RTTI已经不只局限于基类和派生类的识别了,而是可以识别所有的类型。
C++使用 dynamic_cast ,typeid 和 decltype 来支持 RTTI。也就是说这三个运算符或者说关键字都是运行时识别类型,我们要注意他们的使用场景。