一、强制类型转换

在C++中,类型转换涉及继承、多态、虚函数等概念。C语言可以对内置类型进行强制转换,但不够安全。C++标准提供了类型层次转换的两个关键字:static_castdynamic_cast

二、static_cast 关键字(编译时类型检查)

用法static_cast <type-id> (expression)

该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性。

2.1 以下几种用法

  1. 基本数据类型之间的转换:如把 int 转换为 char,把 int 转换成 enum
  2. 把空指针转换成目标类型的空指针
  3. 把任何类型的表达式类型转换成 void 类型
  4. 用于类层次结构中父类和子类之间指针和引用的转换
#include <iostream>
using namespace std;

// 基本数据类型转换
void basic_conversion() {
    int i = 1000;
    char c = static_cast<char>(i);  // 可能丢失精度
    cout << "char value: " << c << endl;
    
    void* void_ptr = static_cast<void*>(&i);  // 转换为void指针
    int* int_ptr = static_cast<int*>(void_ptr);  // 转回int指针
    cout << "int value: " << *int_ptr << endl;
}

2.2 上行转换与下行转换

对于类层次转换,存在两种形式:

  • 上行转换(子类转父类):安全的
  • 下行转换(父类转子类):不安全的
class Base {
public:
    virtual void fun() {}
};

class Derived : public Base {};

void conversion_example() {
    // 上行转换:安全
    Derived* d = new Derived();
    Base* b1 = static_cast<Base*>(d);  // 安全
    
    // 下行转换:不安全(编译时检查,运行时可能出错)
    Base* b2 = new Base();
    Derived* d2 = static_cast<Derived*>(b2);  // 不安全!
    
    delete d;
    delete b2;
}

三、dynamic_cast 关键字(运行时类型检查)

用法:同 static_cast

dynamic_cast 主要用于类层次结构中父类和子类之间指针和引用的转换,具有运行时类型检查功能。

关键要求

  • 必须有虚函数(RTTI支持)
  • 必须本身就是目标类型,只是指针指向不同
class Base {
public:
    virtual ~Base() {}  // 必须有虚函数
};

class Derived : public Base {};

void dynamic_cast_example() {
    Base* b1 = new Derived();
    
    // 安全的向下转换
    Derived* d1 = dynamic_cast<Derived*>(b1);
    if (d1 != nullptr) {
        cout << "转换成功" << endl;
    }
    
    Base* b2 = new Base();
    // 不安全的向下转换
    Derived* d2 = dynamic_cast<Derived*>(b2);
    if (d2 == nullptr) {
        cout << "转换失败,返回nullptr" << endl;
    }
    
    delete b1;
    delete b2;
}

四、使用示例

4.1 直接强转

#include <iostream>
#include <string>
using namespace std;

// 父类
class SingleChip {
public:
    string type;
    
    // 虚函数
    virtual void CloseLED() {
        cout << "SingleChip Close LED" << endl;
    }
    
    void OpenLED() {
        cout << "SingleChip Open LED" << endl;
    }
};

// 子类
class STM32 : public SingleChip {
public:
    void OpenLED() {
        cout << type << " Open LED" << endl;
    }
    
    void CloseLED() {
        cout << type << " Close LED" << endl;
    }
};

int main() {
    cout << "---没有转型前---" << endl;
    SingleChip sc = SingleChip();
    sc.type = "单片机";
    sc.CloseLED();  // SingleChip Close LED
    sc.OpenLED();   // SingleChip Open LED
    
    return 0;
}

4.2 dynamic_cast 关键字强转

#include <iostream>
#include <string>
using namespace std;

class SingleChip {
public:
    string type;
    
    virtual void CloseLED() {
        cout << "SingleChip Close LED" << endl;
    }
    
    void OpenLED() {
        cout << "SingleChip Open LED" << endl;
    }
    
    virtual ~SingleChip() {}  // 虚析构函数(满足RTTI要求)
};

class STM32 : public SingleChip {
public:
    string info;
    
    STM32(string info) {
        this->info = info;
    }
    
    void OpenLED() {
        cout << type << " Open LED info = " << info << endl;
    }
    
    void CloseLED() {
        cout << type << " Close LED" << endl;
    }
};

int main() {
    cout << "---没有转型前---" << endl;
    SingleChip* sc = new STM32("2021-10-04");
    sc->type = "单片机";
    sc->CloseLED();  // SingleChip Close LED
    sc->OpenLED();   // SingleChip Open LED
    
    cout << "---向下转型:父类转子类---" << endl;
    STM32* stm32 = dynamic_cast<STM32*>(sc);
    if (stm32 == nullptr) {
        cout << "stm32 为空" << endl;
    } else {
        stm32->OpenLED();   // 单片机 Open LED info = 2021-10-04
        stm32->CloseLED();  // 单片机 Close LED
    }
    
    delete sc;
    return 0;
}

4.3 static_cast 关键字强转

#include <iostream>
#include <string>
using namespace std;

class SingleChip {
public:
    string type;
    
    virtual void CloseLED() {
        cout << "SingleChip Close LED" << endl;
    }
    
    void OpenLED() {
        cout << "SingleChip Open LED" << endl;
    }
    
    virtual ~SingleChip() {}
};

class STM32 : public SingleChip {
public:
    void OpenLED() {
        cout << type << " Open LED" << endl;
    }
    
    void CloseLED() {
        cout << type << " Close LED" << endl;
    }
};

int main() {
    cout << "---没有转型前---" << endl;
    SingleChip sc = STM32();
    sc.type = "单片机";
    sc.CloseLED();  // SingleChip Close LED
    sc.OpenLED();   // SingleChip Open LED
    
    cout << "---向下转型:父类转子类---" << endl;
    // 注意:这里实际上发生了对象切片,转换可能不安全
    STM32* sp2 = static_cast<STM32*>(&sc);
    sp2->OpenLED();   // 单片机 Open LED
    sp2->CloseLED();  // SingleChip Close LED(虚函数调用父类版本)
    
    return 0;
}

4.4 子类具有独有字段

#include <iostream>
#include <string>
using namespace std;

class SingleChip {
public:
    string type;
    
    virtual void CloseLED() {
        cout << "SingleChip Close LED" << endl;
    }
    
    void OpenLED() {
        cout << "SingleChip Open LED" << endl;
    }
    
    virtual ~SingleChip() {}
};

class STM32 : public SingleChip {
public:
    string info;
    
    STM32(string info) {
        this->info = info;
    }
    
    void OpenLED() {
        cout << type << " Open LED info = " << info << endl;
    }
    
    void CloseLED() {
        cout << type << " Close LED" << endl;
    }
};

int main() {
    SingleChip* sc = new STM32("2021-10-04");
    sc->type = "单片机";
    
    // 三种转换方式
    STM32* sp1 = (STM32*)sc;                    // C风格强制转换
    STM32* sp2 = static_cast<STM32*>(sc);       // static_cast
    STM32* sp3 = dynamic_cast<STM32*>(sc);      // dynamic_cast
    
    sp1->OpenLED();   // 单片机 Open LED info = 2021-10-04
    sp2->OpenLED();   // 单片机 Open LED info = 2021-10-04
    sp3->OpenLED();   // 单片机 Open LED info = 2021-10-04
    
    delete sc;
    return 0;
}

4.5 子类地址转父类指针

#include <iostream>
#include <string>
using namespace std;

class SingleChip {
public:
    string type;
    
    virtual void CloseLED() {
        cout << "SingleChip Close LED" << endl;
    }
    
    void OpenLED() {
        cout << "SingleChip Open LED" << endl;
    }
    
    virtual ~SingleChip() {}
};

class STM32 : public SingleChip {
public:
    string info;
    
    STM32(string info) {
        this->info = info;
    }
    
    void OpenLED() {
        cout << type << " Open LED info = " << info << endl;
    }
};

int main() {
    // 子类
    STM32 stm32 = STM32("2021-10-04");
    stm32.type = "STM32C8T6F103";
    
    // 子类地址转父类指针
    SingleChip* scp1 = (SingleChip*)&stm32;
    cout << scp1->type << endl;           // STM32C8T6F103
    scp1->OpenLED();                      // SingleChip Open LED
    ((STM32*)scp1)->OpenLED();            // STM32C8T6F103 Open LED info = 2021-10-04
    
    return 0;
}

4.6 总测试

#include <iostream>
#include <string>
using namespace std;

class SingleChip {
public:
    string type;
    
    virtual void CloseLED() {
        cout << "SingleChip Close LED" << endl;
    }
    
    void OpenLED() {
        cout << "SingleChip Open LED" << endl;
    }
    
    virtual ~SingleChip() {}
};

class STM32 : public SingleChip {
public:
    string info;
    
    STM32(string info) {
        this->info = info;
    }
    
    void OpenLED() {
        cout << type << " Open LED info = " << info << endl;
    }
    
    void CloseLED() {
        cout << "STM32 Close LED" << endl;
    }
};

int main() {
    cout << "没有转型前......" << endl;
    SingleChip sc = SingleChip();
    sc.type = "单片机";
    sc.CloseLED();  // SingleChip Close LED
    sc.OpenLED();   // SingleChip Open LED
    
    cout << "向下转型:父类转子类......" << endl;
    STM32* sp1 = (STM32*)&sc;
    STM32* sp2 = static_cast<STM32*>(&sc);
    STM32* sp3 = dynamic_cast<STM32*>(&sc);
    
    sp1->CloseLED();  // STM32 Close LED
    sp2->CloseLED();  // STM32 Close LED
    
    if (sp3 == nullptr) {
        cout << "sp3 为空指针" << endl;
    } else {
        sp3->CloseLED();
    }
    
    cout << "向上转型:子类转父类......" << endl;
    STM32 stm32 = STM32("test");
    SingleChip* scp0 = &stm32;                    // 兼容赋值
    SingleChip* scp1 = (SingleChip*)&stm32;       // C风格转换
    SingleChip* scp2 = dynamic_cast<SingleChip*>(&stm32);  // dynamic_cast
    SingleChip* scp3 = static_cast<SingleChip*>(&stm32);   // static_cast
    
    // 调用的仍是子类的 CloseLED(),因为父类的虚函数被子类覆盖
    scp0->CloseLED();  // STM32 Close LED
    scp1->CloseLED();  // STM32 Close LED
    scp2->CloseLED();  // STM32 Close LED
    scp3->CloseLED();  // STM32 Close LED
    
    // 非虚函数调用父类版本
    scp0->OpenLED();   // SingleChip Open LED
    scp1->OpenLED();   // SingleChip Open LED
    scp2->OpenLED();   // SingleChip Open LED
    scp3->OpenLED();   // SingleChip Open LED
    
    return 0;
}

4.7 总结

  1. 只有子类可转为父类,父类不可转为子类,因为子类包含父类所有的元素
  2. 父类对象可接受任何子类对象——此时发生自动转型——转为父类类型——所以能够再转回来
  3. 子类向父类类型转换,直接强制转换就可以了,但实际仍然指向子类对象
  4. 子类指针强制转换为父类指针对象时,并没有实际丢失它原有内存空间(比父类多的那些部分)只是暂时不可访问。所以能再转回来
  5. 父类转为子类,转置后,只能操作子类从父类继承那一部分,再转回来 dynamic_cast 在将父类 cast 到子类时,父类必须要有虚函数,多态性

五、父类转子类

5.1 父类转子类

首先父类转子类是会直接报错的:

// 以下都会报错
STM32 s1 = SingleChip();     // 报错
SingleChip s2 = SingleChip(); // 报错  
STM32 s3 = (STM32)s2;        // 报错

父类转子类是一种很不正确的行为,编辑器会报错,所以只存在【父类指针】转【子类指针】的情况。

#include <iostream>
#include <string>
using namespace std;

class SingleChip {
public:
    string type;
    
    virtual void CloseLED() {
        cout << "SingleChip Close LED" << endl;
    }
};

class STM32 : public SingleChip {
public:
    string info;
    
    void OpenLED() {
        cout << type << " Open LED info = " << info << endl;
    }
};

int main() {
    SingleChip sc = SingleChip();
    sc.type = "单片机";
    
    // 父类转子类
    STM32* sp1 = static_cast<STM32*>(&sc);
    cout << sp1->type << endl;    // 单片机
    sp1->CloseLED();              // SingleChip Close LED
    
    // 以下会报错,因为父类没有子类的信息
    // sp1->OpenLED();            // 报错:缺少info数据
    // cout << sp1->info << endl; // 报错:父类转子类,但没有子类的东西
    
    return 0;
}

5.2 本身就是子类

尽量不要父类转子类,很不安全。只有当它本身就是子类,只是被父类指针引用的时候才能安全使用父类转子类。

#include <iostream>
#include <string>
using namespace std;

class SingleChip {
public:
    string type;
    
    // 为了使用dynamic_cast()
    virtual void CloseLED() {
        cout << " Close LED" << endl;
    }
};

class STM32 : public SingleChip {
public:
    string info;
    
    STM32(string info) {
        this->info = info;
    }
    
    void OpenLED() {
        cout << type << " Open LED info = " << info << endl;
    }
};

int main() {
    // 注意:这里创建的是子类对象,只是用父类指针指向它
    SingleChip* sc = new STM32("2021-10-04");
    sc->type = "STM32C8T6F103";
    ((STM32*)sc)->info = "2022-11-04";
    
    // 第1种强转
    ((STM32*)sc)->OpenLED();                // STM32C8T6F103 Open LED info = 2022-11-04
    
    // 第2种强转  
    static_cast<STM32*>(sc)->OpenLED();     // STM32C8T6F103 Open LED info = 2022-11-04
    
    // 第3种强转
    dynamic_cast<STM32*>(sc)->OpenLED();    // STM32C8T6F103 Open LED info = 2022-11-04
    
    delete sc;
    return 0;
}

关键理解SingleChip* sc = new STM32("2021-10-04"); 压根就不是父类,仍然初始化的是子类,只是用一个父类指针去指向它。所以后面的强转只是指针类型的转换,只是换个指针去指向这个内存空间而已。

错误写法示例

int main() {
    // 只是把子类对象的值赋给这个父类(发生对象切片)
    SingleChip sc = STM32("2021-10-04");
    sc.type = "STM32C8T6F103";
    
    // 以下全部报错或行为未定义,因为info类型丢失
    // ((STM32*)&sc)->OpenLED();              // 报错
    // static_cast<STM32*>(&sc)->OpenLED();   // 报错  
    // dynamic_cast<STM32*>(&sc)->OpenLED();  // 返回nullptr
    
    return 0;
}

总结

  1. C++中层次类型转换中无非两种:上行转换和下行转换
  2. 对于上行转换,static_cast 和 dynamic_cast 效果一样,都安全
  3. 对于下行转换:你必须确定要转换的数据确实是目标类型的数据,即需要注意要转换的父类类型指针是否真的指向子类对象
    • 如果是,static_cast 和 dynamic_cast 都能成功
    • 如果不是,static_cast 能返回,但是不安全,可能会出现访问越界错误,而 dynamic_cast 在运行时类型检查过程中,判定该过程不能转换,返回 NULL
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐