前言
赋值运算符左右两侧变量类型不同,或形参与实参的类型不匹配,或返回值类型与接收值的变量类型不一致,那么就需要进行类型转换。
C语言中的类型转换
· 隐式类型转换
编译器自动进行,能转就转,不能转就编译失败。
· 显示类型转换
显示转换,需要用户自己处理,以指定类型方式做转换。
· C++有四种类型转换,为什么需要四种?
1 C的隐式类型转换在某些情况下会出现精度丢失。
2 C的显示类型转换将所有情况混合,转换可视性较差。
3 功能不是很全面,比如C++中加了const_cast,转换const类型为非const类型,使得能够修改指向值。
因此,C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符,分别是static_cast、reinterpret_cast、const_cast和dynamic_cast。
C++类型转换
static_cast
static_cast用于相近类型间的转换,增加了可视性。
可用手动控制转为double、float、等自定义类型,别走编译器的默认类型转换,否则会丢失精度。
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
int* p = &a;
// int address = static_cast<int>(p); //error
return 0;
}
reinterpret_cat
用于不相关类型的转换。
int main()
{
int a = 10;
int* p = &a;
int address = reinterpret_cast<int>(p);
cout << address << endl;
return 0;
}
有一个比较bug的用法,把带参带返回值的函数指针转换成了无参无返回值的函数指针,还可以用转换后函数指针调用这个函数。
typedef void(*FUNC)();
int DoSomething(int i)
{
cout << "DoSomething: " << i << endl;
return 0;
}
int main()
{
FUNC f = reinterpret_cast<FUNC>(DoSomething);
f();
return 0;
}
const_cast
const_cast用于删除变量的const属性,转换后就对const变量值进行修改。
想修改const类型的变量时,别改原始变量(更安全),而是通过用指针修改的方式。
因为有时候静态在大部分时候不想修改,但是偶尔想修改的情况。
int main()
{
const int a = 2;
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl; //2
cout << *p << endl; //3
return 0;
}
dynamic_cast
可以把父类的指针或引用转为子类的指针或引用。也可以反过来转。这两种称为向下转型或向上转型。
其中,向上转型,是子类转父类,做个切片就行,是语法天然支持的,不需要进行转换,而向下转型是语法不支持的,需要强制类型转换。
· 向下转型的安全问题:
(先联系一下不同指针解引用的本质是解读不同大小的空间)。
1 对于子类对象,可以理解为它占用的内存空间包括父类部分和自己扩展的部分,做完切片把自己多余的部分切去了,这样的类型做向下转型是可以的,而如果是原本的父类对象做向下转型,转换后,相当于指向了更大的一片内存区,然而子类扩充的部分,父类中并没有,这样父类访问不到子类的资源,这必然有安全问题。
2 此外,C语言中不支持子类转父类后再转子类,而C++中使用dynamic_cast就可以,但是如果它一直是纯父类对象,就会返回一个空指针。
如下代码中,对pb2使用该转换是安全的,因为pa本来是指向子类对象的指针。
class A
{
public:
virtual void f()
{}
};
class B : public A
{};
void func(A* pa)
{
B* pb1 = (B*)pa; //不安全
B* pb2 = dynamic_cast<B*>(pa); //安全
cout << "pb1: " << pb1 << endl;
cout << "pb2: " << pb2 << endl;
}
int main()
{
A a;
B b;
func(&a);
func(&b);
return 0;
}
四种转换的使用场景
· static_cast用于相近类型的类型之间的转换,编译器隐式执行的任何类型转换都可用static_cast。
· reinterpret_cast用于两个不相关类型之间的转换。
· const_cast用于删除变量的const属性,方便赋值。
· dynamic_cast用于安全的将父类的指针(或引用)转换成子类的指针(或引用)。
explicit
explicit修饰构造函数,禁止单参数构造函数的隐式转换。
class A
{
public:
explicit A(int a)
{
cout << "A(int a)" << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
private:
int _a;
};
int main()
{
A a1(1);
//A a2 = 1; //error
return 0;
}
在语法上,代码中的A a2 = 1等价于以下两句代码:
A tmp(1); //先构造
A a2(tmp); //再拷贝构造
所以在早期的编译器中,当编译器遇到A a2 = 1这句代码时,会先构造一个临时对象,再用这个临时对象拷贝构造a2。但是现在的编译器已经做了优化,当遇到A a2 = 1这句代码时,会直接按照A a2(1)的方式进行处理,这也叫做隐式类型转换。
但对于单参数的自定义类型来说,A a2 = 1这种代码的可读性不是很好,因此可以用explicit修饰单参数的构造函数,从而禁止单参数构造函数的隐式转换。
RTTI:运行时类型识别
RTTI(Run-Time Type Identification)就是运行时类型识别。
C++通过以下几种方式来支持RTTI:
typeid:在运行时识别出一个对象的类型。
dynamic_cast:在运行时识别出一个父类的指针(或引用)指向的是父类对象还是子类对象。
decltype:在运行时推演出一个表达式或函数返回值的类型。