C++四种强制类型详解

发布时间 2023-05-31 13:15:09作者: 白菜茄子

向上转型(上行转换)

  • 派生类对象转换为基类对象(包括指针和对象),直接转换由编译器完成,是绝对安全的
  • 内存里面:基类对象相当于只是换了个地址,换成了派生类中存储基类成员的内存地址,但是派生类对象中有的,基类没有的变量仍然存在内存中(保留了再次从基类转换成派生类的可能性)

向下转型(下行转换)

  • 向下转型是将基类指针或引用转换回派生类指针或引用的操作,但是向下转型是一种不安全的操作,因为基类指针或引用可能指向的并不是派生类对象,因此在进行向下转型之前,最好使用dynamic_cast进行类型检查,以确保安全转换
#include <iostream>
using namespace std;

class Base {
public:
    virtual ~Base() {} // 基类声明虚析构函数,以便正确释放内存
};

class Derived : public Base {
public:
    void anotherFunction() {
        // 实现派生类的一些功能
    }
};

int main() {
    Base* basePtr = new Base(); // 创建派生类对象,并用基类指针指向它

    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr); // 向下转型

    if (derivedPtr) {//如果不是派生类转换而来的basePtr,再转换会Derived会返回一个NULL
        derivedPtr->anotherFunction(); // 可以访问派生类的成员函数
    }


    delete basePtr; // 删除动态分配的对象

    return 0;
}

四种强制类型转换

  • C++支持C风格的强制转换,但是C风格的强制转换可能带来一些隐患,所以C++提供了一组适用于不同场景的强制转换的函数
    static_cast
  • 用处:
    • 用于类层次结构中基类和派生类之间指针或引用的转换
      • 上行转换是安全的
      • 进行下行转换,由于没有动态类型检查,所以是不安全的
    • 用于基类数据类型之间的转换,例如把int转换成char,这种转换的安全也要开发人员保证
    • 把空指针转换成目标类型的空指针
    • 把任何类型的表达式转换成void类型
double x = 1.2;
int y = static_cast<int>(x);//会损失一部分精度
cout<<y<<endl;

int x1 = -1;
unsigned int x2 = static_cast<unsigned int>(x1);
cout<<x1<<" "<<x2<<endl;//-1 4294967295因为无符号和有符号的表示方法不同,所以显示的结果也不同

//A是基类,B是派生类
A* a2 = new B(1,2);
B* b2 = static_cast<B*>(a2);//安全的

A* a3 = new A(1);
B* b3 = static_cast<B*>(a3);//不安全的

void *v1 = nullptr;
A* a4 = static_cast<A*>(v1);
void *v2 = static_cast<void *>(a4);

dynamic_cast

  • 其他三种都是编译时完成,dynamic_cast是运行时处理,运行时要进行类型检查
  • 注意
    • 不能用于内置的基本数据类型的强制转换
    • 使用dynamic_cast进行转换时,基类中一定要有虚函数,否则编译不通过。这是因为运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表
  • 用处:主要用于类之间的转换
    • 上行转换时,dynamic_cast和static_cast的效果是一样的,但是一般使用static_cast
    • 下行转换时,dynamic_cast具有类型检查,比static_cast更安全,向下转换成功与否与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同(或者要转换的是实际的父类),否则转换失败
  • dynamic_cast要求<>所描述的目标类型必须为指针或引用,转换成功的返回指向类的指针或引用
    • 失败,指针返回空指针
    • 引用会抛出一个bad_cast的异常
    • 所以如果是指针需要进行是否为空的判断,引用需要try catch一下
class A{
public:
    int a;
    A(int a):a(a){}
    virtual ~A(){}
};

class B:public A{
public:
    int b;
    B(int a,int b):A(a),b(b){};
};

class C:public B{
public:
    int c;
    C(int a,int b,int c):B(a,b),c(c){}
};


int main(){
    A* a2 = new C(1,2,3);
    B* b2 = dynamic_cast<B*>(a2);
    if(b2== nullptr) cout<<"qwe";
    else cout<<"asd";
}

const_cast

  • <>里的类型也只能是指针或引用
  • 去除const属性
    • 当我们有一个指向const对象的指针或引用时,如果确保不会修改该对象的值,但需要将其传递给一个接收非const对象的函数或方法时,可以使用const_cast去除const属性,这样做是为了允许调用这个函数,但是如果实际上对该对象进行了修改,将导致未定义行为
const int num = 10;
int &num1 = const_cast<int&>(num);
  • 转换掉volatile属性
volatile int num = 10;
int& num1 = const_cast<int&>(num);

reinterpret_cast

  • 改变指针或引用的类型
  • 将指针或引用转换为一个足够长度的整型 long long int,或者uint_64等等
  • 将整型转换为指针或引用类型
int num = 0x00636261;
int *pNum  = &num;
char* pStr = reinterpret_cast<char*>(pNum);
cout<<pStr<<endl;//abc 
  • 一个指向字符串的指针和一个指向整型的指针没有什么不同(内存角度),实际输出的不同是指针类型会教导编译器如何解释某个特定地址中的内存内容及其大小