C++类型转换:static_cast、reinterpret_cast、dynamic_cast、const_cast

类型转换种类:

  • C风格的类型转换:(类型说明符)表达式,如int valueB = (int) valueA;
  • reinterpret_cast:主要针对指针或引用,重新解释指针所指地址的内存。很强大,很少用,最好不用吧(我的想法)。reinterpret_cast执行什么动作是依赖于编译器的,所以不具有可移植性。
  • static_cast:普通类型转换,如double转int、void*类型指针转换、有继承关系的指针之间的转换、non-const对象转const对象。【最经常使用】
  • dynamic_cast:在需要保证“安全的向下转型”的情况下使用,如当转型基类指针时,无法确定此基类指针指向的是否是子类对象。
    所谓“安全的向下转型”即只有当Base class的指针确实指向Derived class对象时才能将其转为Derived class的指针。如果不安全,就会返回空指针,而static_cast返回非空指针。
    dynamic_cast在运行期转换,static_cast在编译期转换。
    虚表中存储着指向type_info的指针,从而获取对象的类型,故dynamic_cast依赖虚表,即关键字virtual。
  • const_cast:用来移除变量的const或volatile限定符。去除const的理由:
    • 我们可能调用了一个参数不是const的函数,而我们要传进去的实际参数却是const的,但是我们知道这个函数是不会对参数做修改的。于是我们就需要使用const_cast去除const限定,以便函数能够接受这个实际参数。
    • const对象想要调用非const方法。
    • const指针指向非const变量,我想修改这个变量时。
      【注】const_cast转换的是指针,如const int*是指向const的指针。

reinterpret_cast

reinterpret_cast作用:

  • 不同类型指针的相互转换。原理:根据指针类型重新解释内存中的二进制码
  • 从指针类型到一个足够大的整数类型。或从整数类型或者枚举类型到指针类型。

示例(参考链接):

#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
	int num = 0x00636261;//用16进制表示32位int,0x61是字符'a'的ASCII码
	int * pnum = &num;
	char * pstr = reinterpret_cast<char *>(pnum);
	cout<<"pnum指针的值: "<<pnum<<endl;
	cout<<"pstr指针的值: "<<static_cast<void *>(pstr)<<endl;//直接输出pstr会输出其指向的字符串,这里的类型转换是为了保证输出pstr的值
	cout<<"pnum指向的内容: "<<hex<<*pnum<<endl;
	cout<<"pstr指向的内容: "<<pstr<<endl;
	return 0;
}

输出:

pnum指针的值: 0x7ffcd6b2e054
pstr指针的值: 0x7ffcd6b2e054
pnum指向的内容: 636261
pstr指向的内容: abc

pstr输出abc的原因:linux采用小端存储(高位保存在高地址),pstr是从低位开始输出的,所以先输出a,也是就是61。

还可以看看C++标准转换运算符reinterpret_cast来理解reinterpret_cast,但是我暂时还看不太懂里面所提到的“用来辅助哈希函数”

static_cast

static_cast作用:

  • int与float、double与char、enum与int之间的转换等。
  • 使用static_cast可以明确告诉编译器,这是损失精度的转换,从而防止编译时发出警告。
  • 使用static_cast可以找回存放在void*指针中的值。
  • static_cast用于有直接或间接继承关系的指针或引用之间转换。没有继承关系的指针不能用此转换,所以不能像reinterpret_cast进行随意的指针转换。
    static_cast它不做运行时的检查,不如dynamic_cast安全。static_cast仅仅是依靠类型转换语句中提供的信息来进行转换,而dynamic_cast则会遍历整个类继承体系进行类型检查,因此dynamic_cast在执行效率上比static_cast要差一些。
    static_cast效率比dynamic_cast高,请尽可能用。

下面举例说明:

1.使用static_cast可以明确告诉编译器,这是损失精度的转换,从而防止编译时发出警告:

double a = 1.999;
int b = static_cast<double>(a); //相当于a = b ; 加上了static_cast<double>,代表我知道这是会精度损失

2.使用static_cast可以找回存放在void*指针中的值:

double a = 1.999;
void * vptr = & a;
double * dptr = static_cast<double*>(vptr);
cout<<*dptr<<endl;//输出1.999

3.static_cast用于有直接或间接继承关系的指针或引用之间转换:

class ANIMAL
{
public:
    ANIMAL():_type("ANIMAL"){};
    virtual void OutPutname(){cout<<"ANIMAL";};
private:
    string _type ;
};
class DOG:public ANIMAL
{
public:
    DOG():_name("大黄"),_type("DOG"){};
    void OutPutname(){cout<<_name;};
    void OutPuttype(){cout<<_type;};
private:
    string _name ;
    string _type ;
};


int main()
{
    //基类指针转为派生类指针,且该基类指针指向基类对象。
    ANIMAL * ani1 = new ANIMAL ;
    DOG * dog1 = static_cast<DOG*>(ani1);
    //dog1->OutPuttype();//错误,在ANIMAL类型指针不能调用方法OutPutType();在运行时出现错误。

    //基类指针转为派生类指针,且该基类指针指向派生类对象
    ANIMAL * ani3 = new DOG;
    DOG* dog3 = static_cast<DOG*>(ani3);
    dog3->OutPutname(); //正确

    //子类指针转为派生类指针
    DOG *dog2= new DOG;
    ANIMAL *ani2 = static_cast<DOG*>(dog2);
    ani2->OutPutname(); //正确,结果输出为大黄

    //
    system("pause");

}

参考:
C++强制类型转换操作符 static_cast
staitic_cast原理与使用

dynamic_cast

C++标准转换运算符dynamic_cast总结:

  • 子类指针转基类指针,使用基类指针指向子类对象(向上转型),没有编译错误或者运行异常,方法的调用和数据的访问输出是期望的结果。

  • 基类指针转子类指针,使用子类指针指向基类对象(向下转型)。
    当向下转型是不安全时,dynamic_cast返回空指针;static_cast返回非空指针,此时如果子类指针访问了基类对象没有的数据变量,就会报错(Segmentation fault (core dumped))。
    所谓“安全的向下转型”即只有当Base class的指针确实指向Derived class对象时才能将其转为Derived class的指针。(参考:RTTI下的C++的向下转型

  • 没有继承关系的两个类的指针之间的转换。dynamic_cast依然是返回一个空指针以表示转换是不成立的;static_cast直接在编译期就拒绝了这种转换。reinterpret_cast成功进行了转换,而且返回的值并不是空指针。
    【dynamic_cast可以用于转换非指针且非引用的类型吗?答:不能】

总得说来,static_cast和reinterpret_cast运算符要么直接被编译器拒绝进行转换,要么就一定会得到相应的目标类型的值。 而dynamic_cast却会进行判别,确定源指针所指的内容,是否真的合适被目标指针接受。如果是否定的,那么dynamic_cast则会返回null。这是通过检查"运行期类型信息"(Runtime type information,RTTI)来判定的,它还受到编译器的影响,有些编译器需要设置开启才能让程序正确运行(导师的PPT详细介绍了Visual Studio的情况),因此dynamic_cast也就不能用传统的转换方式来实现了。

static_cast和dynamic_cast的区别:

  • static_cast 和 C 风格的类型转换在编译期间进行类型检查, dynamic_cast在运行期间进行类型检查,dynamic_cast运算符可以在执行期决定真正的类型
  • 在类层次间进行上行(upcast:子类指针转基类指针,使用基类指针指向子类对象)转换时,dynamic_cast和static_cast的效果是一样的;
    在进行下行(downcast:基类指针转子类指针,使用子类指针指向基类对象)转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
    (up指往“高辈分”转,down指往“低辈分”转)

使用dynamic_cast,不使用static_cast的情况(参考:百度百科)(不重要,看看就行,不用记,没什么用):

Base* pD = new Derived1;         // Derived1继承自Base和Base1
Base1* pB1 = dynamic_cast<Base1*>(pD);
// Base1* pB2 = static_cast<Base1*>(pD);

dynamic_cast与virtual:
使用dynamic_cast进行进行指针转换,如果被转换指针所指对象中没有virtual修饰的函数,此时编译会失败。dynamic_cast这样设计是合理的,因为如果要用继承,那么一定要让析构函数是虚函数;如果一个函数是虚函数,那么在子类中也要是虚函数。所以。

upcast和downcast:

  • upcast:子类指针转基类指针,使用基类指针指向子类对象
    downcast:基类指针转子类指针,使用子类指针指向基类对象
  • 安全當然是upcast方式安全了,因為Derived轉換成Base類時是隐式转换只需作結构上的調整(内部指针的offset調整)而不用做空間上的調整,粗通的說只需将多余的部分裁减掉,而downcast则需要将基类扩展为子类,就需做空间上的扩展(强制转换)就没那么安全.
  • downcast用dynamic_cast就安全了吗?
    此主要看类是否带virtual决定,因为virtual在编译期具有不确定性。
    dynamic_cast,static_cast都为强制类型转换,static_cast为静态即在编译时期转换,此方式能节约编译时间,比较适合些固态类型转换如:int转double,无virtual类等。dynamic_cast为动态即在执行期转换,浪费编译时间,比较适合些动态类转换如:带virtual类,virtual Base继承等。因为带virtual很多情况是在执行期才被确定。

【如何理解C++标准转换运算符dynamic_cast的这句话:“因此该运算符实际上只接受基于类对象的指针和引用的类转换。从这个方面来看,似乎dynamic_cast又和reinterpret_cast是一致的”,为什么就说“dynamic_cast又和reinterpret_cast是一致的”,我对reinterpret_cast的理解有错误??】

RTTI:
C++使用type_info类的对象保存每个对象的相关信息如对象名称、对象类型等。所谓RTTI就是在执行期取得对象的type_info。
C++采用了和虚函数同样的办法,使用vtable保存需要执行期获得type_info的对象的type_info对象地址。
dynamic_cast采用RTTI来进行类型检查,而RTTI依赖vtable,而只有当基类至少有一个函数具有virtual关键字时,才会存在vtable,所以要使用dynamic_cast的类型检查功能,基类中必须有使用virtual。

const_cast

const_cast转换符是用来移除变量的const或volatile限定符,下面只对const进行说明(参考链接):

const int constant = 21;
const int* const_p = &constant;
int* modifier = const_cast<int*>(const_p);  // const_cast是针对指针进行操作的
*modifier = 7;

const_cast实现原因就在于C++对于指针的转换是任意的,它不会检查类型,任何指针之间都可以进行互相转换,因此const_cast就可以直接使用显示转换(int*)来代替:

const int constant = 21;
const int* const_p = &constant;
int* modifier = (int*)(const_p);

const int constant = 21;
int* modifier = (int*)(&constant);

为何要去除const限定

#include <iostream>
using namespace std;

int main() {

	const int constant = 21;
	const int* const_p = &constant;
	int* modifier = const_cast<int*>(const_p);
	*modifier = 7;


	cout << "constant: "<< constant <<endl;
	cout << "const_p: "<< *const_p <<endl;
	cout << "modifier: "<< *modifier <<endl;

}

使用g++编译,运行得到如下结果:

constant: 21
const_p: 7
modifier: 7

“*modifier = 7;”为“未定义行为(Undefined Behavior)”。所谓未定义,是说这个语句在标准C++中没有明确的规定,由编译器来决定如何处理。所以我这里使用g++得到的结果为“constant: 21”,如果你是用了不同的编译器,你可能得到的结果为“constant: 7”。
对于未定义行为,我们所能做的所要做的就是避免出现这样的语句。所以我们除去const的目的不是为修改const变量。

链接提到的去除const的可能原因:

  • 我们可能调用了一个参数不是const的函数,而我们要传进去的实际参数却是const的,但是我们知道这个函数是不会对参数做修改的。于是我们就需要使用const_cast去除const限定,以便函数能够接受这个实际参数。
  • 在const对象想调用自身的非const方法的时候。《Effective C++:改善程序与设计的55个具体做法》阅读笔记 1——让自己习惯C++的“Item 3”中有提到:const对象调用的是const成员函数,非const对象调用的是非const成员函数。
  • 非const的变量,但用带const限定的指针去指向它,在某一处我们突然又想修改了,可是我们手上只有指针,这时候我们可以去const来修改了。
posted @ 2022-10-30 22:02  好人~  阅读(472)  评论(0编辑  收藏  举报