Welcome to 9ilk's Code World

(๑•́ ₃ •̀๑) 个人主页: 9ilk
(๑•́ ₃ •̀๑) 文章专栏: C++
本篇博客主要是对C/C++中类型转换的梳理总结。
内置类型转换
在C语言中,如果赋值运算符左右两侧类型不同 ,或者形参与实参类型不匹配 ,或者返回值类型与 接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型 转换和显式类型转换。
-
隐式类型转换:编译器在编译阶段自动进行,能转就转,不能转就编译失败,通常是在整形之间/整形和浮点数之间。
-
显示类型转换:需要用户自己处理,通常是在指针和整形、指针之间发生。
cpp
//隐式类型转换 整形家族 整形与浮点型
size_t t = 21;
double d = 21;
printf("%d,%f\n", t, d);
//强制类型转换 不同类型的指针之间 指针与整形
double* pd = &d;
int* pi = (int*)pd;
int o = (int)pi;
我们可以看到,对于这两种类型转换,转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换。
内置类型转换为自定义类型
内置类型转换为自定义类型,需要在自定义类型提供对应的构造函数,这样才能进行单参和多参的隐式类型转换,此时会产生临时对象,再用这个临时对象构造(编译器不优化时):
cpp
class A
{
public:
A(int a)
:_a1(a)
, _a2(a)
{
cout << "A(int a)" << endl;
}
A(int a1, int a2)
:_a1(a1),
_a2(a2)
{
cout << "A(int a1, int a2)" << endl;
}
A(const A&)
{
cout << "A(const A&)" << endl;
}
private:
int _a1 = 1;
int _a2 = 1;
};
int main()
{
//单参和多参的隐式类型转换
A aa1 = 1;
A aa2 = {1,2};
const A& aa3 = {2,2};//隐式类型产生临时对象
return 0;
}
自定义类型转为内置类型
自定义类型可以通过内置类型转换为内置类型,需要注意的是operator 类型(类型转换符)实现,是没有返回类型的:
cpp
class B
{
public:
B()
{}
operator int()
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 1;
};
int main()
{
B b1;
int x = b1.operator int();
int y = b1;
return 0;
}
我们可以看到语法层面不管你是否显示调用operator int,底层都是转换成函数调用:

对于y这种其实是隐式类型转换的写法,我们可以用explicit关键字验证下,它能让禁止编译器禁止隐式类型转换:

自定义类型之间的转换
类型转换之间需要关联性,之前的显示和隐式就是关联性强弱的问题,这里自定义类型之间的转换也一样,需要对应构造函数支持:
cpp
class A
{
public:
A(int a)
:_a1(a)
, _a2(a)
{
cout << "A(int a)" << endl;
}
A(int a1, int a2)
:_a1(a1),
_a2(a2)
{
cout << "A(int a1, int a2)" << endl;
}
A(const A&)
{
cout << "A(const A&)" << endl;
}
int get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 1;
};
class B
{
public:
B()
{}
B(const A& aa)
:_a1(aa.get())
{
cout << "B(const A& aa)" << endl;
}
explicit operator int()
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 1;
};
int main()
{
A aa1(1);
B bb1 = aa1;
const B& ref = aa1;
return 0;
}
下面我们看这样的一个场景:
cpp
typedef _list_iterator<T,T&,T*> iterator;
typedef _list_iterator<T,const T&,const T*> const_iterator;
zlist::list<int> it = {1,2,3,4};
zlist::list<int>::const_iterator cit = it.begin();
这里将一个普通迭代器赋值给const迭代器类型,这里会报错,但是注意报错原因不是权限缩小,因为只有指针引用采用权限问题,这里的原因是两种迭代器是同一个类模板参数传不同的模板参数产生的不同类型,因此这里it对应的是普通迭代器,与const迭代器类型不匹配,也就是说,这里其实是个类型转换问题。
此时就可以函数来实现自定义类型之间的转换,我们需求是普通迭代器转换成const迭代器,因此我们写个对应的构造函数即可,当传参传的是普通迭代器时,这里相当于是个普通构造函数:
cpp
ListIterator(const ListIterator<T, T&, T*>& it)
:_node(it._node)
{
cout << "ListIterator(const ListIterator<T, T&, T*>& it)" << endl;
}
C++中的类型转换
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符: static_cast、reinterpret_cast、const_cast、dynamic_cast
static_cast
static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用 static_cast,但它不能用于两个不相关的类型进行转换
cpp
int a = 20;
double d = static_cast<int>(a); //对标隐式类型转换
reinterpret_cast
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型,用于两个不相关类型之间的转换:
cpp
int* pa = &a;
double* pd = reinterpret_cast<double*>(pa);//对标显示类型转换
const_cast
const_cast最常用的用途就是删除变量的const属性,方便赋值,它对应的是强制类型转换中有风险的去掉const属性:
cpp
const int a = 2;
int* p = (int*)&a;
int* p2 = const_cast<int*>(&a);
*p = 3;
我们打印看下结果:
cpp
cout << a << endl; //2
cout << *p2 << endl; //3
由于a本身是const int,因此在编译期就把它优化为一个立即数2,不再去内存里取值,此时我们要想它读的是最新的,可以加上volatile关键字修饰,告诉编译器每次从内存中读取最新的:
cpp
volatile const int a = 2;
int* p = (int*)&a;
int* p2 = const_cast<int*>(&a);
dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
- 向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
- 向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的),毕竟子类比父类多一部分成员的话,你访问会造成越界
注意:
-
dynamic_cast只能用于父类含有虚函数的类,因为运行时类型检测需要运行时的类型信息,而这个信息是存储在虚函数表中的,只有定义了虚函数的类才有虚函数表。
-
dynamic_cast会先检查是否能转换成功,能成功(pc指向子类对象)则转换,不成功(pc指向父类对象)则返回NULL
cpp
class A
{
public:
virtual void f() {}
int _a = 1;
};
class B : public A
{
public:
int _b = 2;
};
void fun(A* pa)
{
// dynamic_cast会先检查是否能转换成功(指向子类对象),能成功则转换,
// (指向父类对象)不能则返回NULL
B* pb1 = dynamic_cast<B*>(pa);
if (pb1)
{
cout << "pb1:" << pb1 << endl;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
pb1->_a++;
pb1->_b++;
cout << pb1->_a << endl;
cout << pb1->_b << endl;
}
else
{
cout << "转换失败" << endl;
}
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}
注意:
强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是 否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用 域,以减少发生错误的机会。强烈建议:避免使用强制类型转换。
RTTI
RTTI:Run-time Type identification的简称,即:运行时类型识别。
C++通过以下方式来支持RTTI:
-
typeid运算符
-
dynamic_cast运算符
-
decltype