本篇博客整理了类型转换的形式和情景,介绍了C++中的四个类型转换操作符,旨在让读者更加了解量的类型和特性。
目录
一、C语言的类型转换
在C语言中有两种形式的类型转换:
- 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
- 显示类型转化:由用户指定
而类型转换一般会发生在以下情景:
- 赋值运算符或逻辑运算符的左右操作数类型不同;
- 形参与实参类型不匹配;
- 返回值类型与接收值类型不一致。
class A
{
public:
//explicit A(int a) //explicit关键字可以阻止自定义类型转换发生
A(int a) //单参数的构造支持隐式类型转换
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(const A& a) //此处编译器将构造+拷贝构造优化为构造
{}
private:
//...
};
int main()
{
//c:
int i = 1;
// 隐式类型转换 - 发生在含义相关或意义相近的内置类型之间
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显示的强制类型转换
int address = (int)p;
printf("%p, %d\n", p, address);
//cpp:
//单参数的构造支持隐式类型转换
A aa1 = 1;
//自定义类型之间发生转换需内部有关联(例如,构造+拷贝构造)
B bb1 = aa1;
//毫无关联的内置类型之间无法转换
//vector<int> v;
//string s;
//v = (vector<int>)s;
return 0;
}
虽然C语言的类型转换较为简单,但其中有不少缺陷,例如:
//隐式类型转换是有隐患的,
//例如在操作数之间,数据精度丢失:
void insert(size_t pos, char ch)
{
int end = 10;
while (end >= pos) //end会被强转为size_t,造成死循环
{
cout << end << endl;
//...
--end;
}
int main()
{
insert(0, 'x');
}
//显示类型转换也是有隐患的
//例如访问的安全问题:
int main()
{
const int n = 10; //n是一个常变量,
//n = 11; //不能直接修改,
//只可以通过类型转换间接修改
//但转换有安全隐患
int* p = (int*)&n;
(*p)++;
cout << n << endl; //10
cout << *p << endl; //11
//n实际的值已经变为11,但打印的结果仍是10,这是因为编译器做了优化。
//编译器认为常变量无法修改,没有在内存中实际取n,
//而是将n放进了寄存器,或者像宏一样直接将n替换为常量
}
//(接上段代码)
//volatile关键字 - 对于其修饰的变量,编译器每次都去内存读取
int main()
{
volatile const int n = 10;
int* p = (int*)&n;
(*p)++;
cout << n << endl; //11
cout << *p << endl; //11
}
C++支持C语言,也就支持C语言中的类型转换。为了弥补C的缺陷,也为了强化类型转换的可视性,C++引入了四种命名的类型转换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast。
二、C++的四种类型转换
1.static_cast
static_cast主要用于非多态类型的转换(即用于意义相近的内置类型之间的转换),对应C语言的隐式类型转化。
int main()
{
// static_cast用于相关类型/相近类型
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl; //12
return 0;
}
2.reinterpret_cast
reinterpret_cast可以为操作数的位模式提供较低层次的重新解释,将一种类型转换为另一种不同的类型,对应C语言的显示类型转化。
int main()
{
// reinterpret_cast用于不相关类型
int* p1 = &a;
cout << p1 << endl;//010FFA30(32位平台下)
int address = reinterpret_cast<int>(p1);
cout << address << endl;//17824304
//reinterpret_cast可以将整型转换为指针,可以把指针转换为数组,
//可以在指针和引用里进行来回转换
//它是一种显示地强制转换(任何强制类型转换都应慎用)
return 0;
}
3.const_cast
const_cast最常见的用途就是去掉变量的const属性,以方便赋值,对应C语言的显示类型转化。const_cast也可以看作是一种警示,表示一个变量去除了const属性,需谨慎操作。
int main()
{
// const_cast可以去掉const属性
volatile const int n = 10;
int* p2 = const_cast<int*>(&n);
cout << *p2 << endl;//10
//去除常性之后可能留有隐患,应慎用
return 0;
}
4.dynamic_cast
dynamic_cast涉及多态,用于将一个父类对象的指针或引用,向下转换为子类对象的指针或引用。
【补】父类和子类之间的类型转换
向上转换:子类指针/引用 => 父类指针/引用(不需要转换,符合赋值兼容规则)
向下转换:父类指针/引用 => 子类指针/引用(会涉及越界,但通过dynamic_cast就是安全的)
//“通过dynamic_cast就是安全的”是指:
// 在向下转换的过程中,
// 一般子类比父类所占空间更大,因此子类的指针能访问的空间一般更多,但这更多的空间并不属于父类,
// 如果原先指向父类的指针,被强制转换成子类的指针,就涉及了访问权限变大,可能造成越界的非法访问。
//强制类型转换并不会理会指针原先指向的是父类还是子类,只管转换;
//而dynamic_cast会先检查能否安全转换,能则转换,不能则返回空指针。
class A //父
{
public:
virtual void f() {} //dynamic_cast 只能用于父类含有虚函数的类
//void f() {} //不可用
int _x = 0;
};
class B : public A //子
{
public:
int _y = 0;
};
void fun(A* pa)
{
// dynamic_cast支持向下转换的检查
B* pb = dynamic_cast<B*>(pa);
// pa是指向父类对象A的,则转换失败,返回空指针
// pa是指向子类对象B的,则转换是安全的,正常返回地址
if (pb)
{
cout << "转换成功" << endl;
pb->_x++;
pb->_y++;
}
else
{
cout << "转换失败" << endl;
}
}
int main()
{
A aa;
fun(&aa);
//转换失败
B bb;
fun(&bb);
//转换成功
return 0;
}
补、RTTI
RTTI是“Run - time Type identification(运行时类型识别)”的缩写。C++中,以下操作符涉及RTTI:
- typeid(配合name()可以获取类型名称)
- dynamic_cast(判断一个指针/引用指向的是父类还是子类)
- decltype(自动类型推导)