在上篇与中篇中,咱们探讨了隐式类型转换及其与函数重载之间的相干话题。本篇将要探讨的即为类型转换的另一大分支——强制类型转换。
9. C格调的强制类型转换
在C语言中,强制类型转换存在两种等价模式:Type(Value)或(Type)Value。
参考以下代码:
int main()
{
(int *) malloc(0); // (Type)Value模式的强制类型转换
int(0.); // Type(Value)模式的强制类型转换
}
上述代码中,咱们别离应用了C语言的提供的两种强制类型转换的等价模式将void 转为了int ,以及将double转为了int。
10. static_cast
在C++中,static_cast相当于C语言中的强制类型转换语法。static_cast用于在编译期对某种类型的变量进行强制类型转换。
参考以下代码:
int main()
{
static_cast<int *>(malloc(0));
static_cast<int>(0.);
}
上述代码中,咱们应用了static_cast别离将void 转为了int ,以及将double转为了int。
11. const_cast
const_cast是C++中专用于解决与const相干的强制类型转换关键字,其性能为:为一个变量从新设定其const形容。即:const_cast能够为一个变量强行减少或删除其const限定。
须要明确的是,即便用户通过const_cast强行去除了const属性,也不代表以后变量从不可变变为了可变。const_cast只是使得用户接管了编译器对于const限定的管理权,故用户必须恪守“不批改变量”的承诺。如果违反此承诺,编译器也不会因而而引发编译时谬误,但可能引发运行时谬误。
上面探讨const_cast的主要用途。
考查以下代码:
struct A { A &test() { return *this; } };
int main()
{
A().test();
}
这段代码看上去运行失常。但如果:
struct A { A &test() { return *this; } };
int main()
{
const A a;
a.test(); // Error!
}
咱们试图用一个const对象去调用非const成员函数,此时,为了调用此成员函数,const A this就须要转换为A this,这显然是不行的。
通过上述探讨,咱们能够将代码批改为如下:
struct A { const A &test() const { return *this; } };
int main()
{
const A a;
a.test();
}
咱们将this指针申明为const A ,解决了此问题。但不难发现,如果咱们通过一个非const对象调用此办法,其返回值也会被转为const,从而不再能够持续调用任何承受A this的成员函数。这显著不是咱们想要的后果:
struct A
{
const A &test() const { return *this; }
A &test2() { return *this; }
};
int main()
{
A().test().test2(); // Error!
}
怎么解决此问题呢?依据C++函数重载的规定,咱们能够为test成员函数同时定义const与非const版本:
struct A
{
A &test() { return *this; }
const A &test() const { return *this; }
A &test2() { return *this; }
};
int main()
{
A().test().test2();
const A a;
a.test();
}
对于A的非const实例而言,test的非const版本是准确匹配,故编译器将抉择此版本,从而返回一个A &;同时,对于A的const实例而言,const版本的test是其惟一可用的版本,返回一个const A &。
至此,问题解决了。咱们基于const的有无重载出了两个版本的成员函数,从而使得const对象与非const对象可能各自调用不同的版本,互不影响。
在理论状况中,咱们定义的两个版本的重载函数除了有无const以外往往没有任何区别,此时就能够应用const_cast定义第二个重载版本,而无需写两遍截然不同的函数体。
参考以下代码:
struct A
{
A &test() { ... }
// 通过const_cast强行去除this的const限定后调用非const版本
// 返回值通过隐式类型转换再转回const A &
const A &test() const { return const_cast<A *>(this)->test(); }
};
上述代码中,咱们首先定义了一个非const版本的test成员函数,这个成员函数将提供给A this调用;在定义test成员函数的const版本时,咱们通过const_cast<A >(this),将此版本的const A this指针转换为非const版本须要的A this类型指针,而后调用了非const版本的test成员函数,并返回其调用后果,非const版本的test成员函数的返回值将通过隐式类型转换转为const A &。
由此可见,通过const_cast,咱们仅需一行代码就能够实现第二个函数重载版本的定义。
12. dynamic_cast
上文提到,动静类型为继承类的指针或援用能够存储在动态类型为基类的变量中,且不会产生隐式类型转换。对于一个变量而言,尽管其动静类型的确是继承类,但因为编译期与运行期的差异,其也无奈逾越“可应用的成员名称由动态类型决定”这一规定。尽管继承类能够通过虚函数的形式肯定水平上解决此种状况,然而,如果某个成员函数不是基类虚函数,而只存在于继承类中呢?dynamic_cast为咱们提供了解决方案。
当一个动态类型为基类指针或援用的变量的确寄存了继承类指针或援用时,从基类向继承类的类型转换,即向下类型转换实践上是可行的,dynamic_cast即用于在运行时实现向下类型转换。须要留神的是,dynamic_cast的应用必须同时满足以下所有条件:
- 被转换的变量的类型为基类指针或援用,且其的确寄存了一个继承类指针或援用
- 基类具备虚表,即基类必须至多定义了一个虚函数
参考以下代码:
struct A { virtual void test() {} }; // 基类含有虚函数
struct B: A { void test2() {} }; // 继承类特有函数
int main()
{
// 动态类型为基类指针的变量寄存继承类指针
A *b = new B;
// 通过向下类型转换调用继承类特有函数
dynamic_cast<B *>(b)->test2();
}
上述代码中,咱们首先定义了具备虚函数的基类A,而后定义了具备继承类特有函数的类B。此时,因为test2成员函数并未在基类中注册为虚函数,咱们将无奈通过动态类型为A 的变量b调用此函数。但因为咱们能够确定变量b的动静类型为B ,则能够于运行时通过dynamic_cast将变量b的动态类型转为B *,而后调用继承类的特有函数test2。
13. reinterpret_cast
reinterpret,即“从新解释”,顾名思义,这个强制类型转换的作用是提供某个变量在底层数据上的从新解释。当咱们对一个变量应用reinterpret_cast后,编译器将忽视任何不合理行为,强行将被转换变量的内存数据重解释为某个新的类型。须要留神的是,reinterpret_cast要求转换前后的类型所占用内存大小统一,否则将引发编译时谬误。
参考以下代码:
int main()
{
reinterpret_cast<int *>(0); // 强行将一个整数的内存数据解释为一个int *
}
14. 探讨
编程语言的强类型与弱类型相干话题,多年来业界始终探讨不休,有的语言倒退出了高度弱类型的语法体系,而有的语言则绝对谨严,要求用户尽可能多的应用显式类型转换。C++作为一门经典的弱类型语言,其类型转换的相干话题天然非常宏大。
纵观C++的类型转换语法体系,其连续了C++一贯的无所不包格调,不仅为用户提供了自定义类型转换的极大自由度,也在语法层面为类型转换可能会带来的各种盘根错节的状况作出了谨严的规定。激进看来,如果对C++的类型转换没有深刻的了解,或不心愿大量应用隐式类型转换时,咱们不应适度的依赖诸如非explicit转换构造函数,自定义的类型转换操作符,以及波及隐式类型转换的各种重载确定等语法组分。但作为C++语法体系的一个重要局部,深刻了解C++对于类型转换的各种话题,必然是非常重要的。
樱雨楼
2019.8 于苏州
发表回复