c++类型转换(四种cast)

发布于:2024-05-08 ⋅ 阅读:(24) ⋅ 点赞:(0)
#include <iostream>
#include <exception>
#include <cstring>

using namespace std;

class Base {
public: 
    virtual void dummy() { cout << "Base::dummy()" << endl; }
    void hello() { cout << "Base::hello()" << endl; } 
};

class Derived : public Base { 
public:
    int a;
    long long b;
    Derived() : a(999), b(999) {}
    void dummy() override { cout << "Derived::dummy()" << endl; }
    void hello() { cout << "Derived::hello()" << endl; } 
};

// dynamic_cast 只能够用在指向类的指针或者引用上(或者void*)。这种转换的目的是确保目标指针类型所指向的是一个有效且完整的对象。
void dynamic_cast_test() {
    try {
        Base *pba = new Derived;
        Base *pbb = new Base;
        Derived *pd;
        /*******************************************基类指针与派生类指针之间的转换******************************************************************/
        // 1、upcast(从派生类向基类的转换) 必然成功
        Base *pbc = dynamic_cast<Base*>(pba);   
        if (pbc == nullptr) { cout << "Null pointer on zero type-cast.\n"; }   

        // 2、downcast(从基类向派生类的转换)
        // 当且仅当转过去的指针所指向的目标对象有效且完整,转换失败返回nullptr
        pd = dynamic_cast<Derived*>(pba);       // 2.1、转换成功,转过去的指针所指向的目标对象有效且完整
        if (pd == nullptr) { cout << "Null pointer on first type-cast.\n"; }

        pd = dynamic_cast<Derived*>(pbb);       // 2.2、转换失败,因为pbb指向的是Base对象,没有成员a
        if (pd == nullptr) { cout << "Null pointer on second type-cast.\n"; }


        // 只能从类指针转为void* 
        void* pva = nullptr;
        Base* pbd = nullptr;
        // pbd = dynamic_cast<Base*>(pva);     // error: cannot dynamic_cast ‘pva’ (of type ‘void*’) to type 
                                            // ‘class dynamic_cast_test()::Base*’ (source is not a pointer to class)

        pva = dynamic_cast<void*>(pbd);     // 这个可以


        /************************************************基类引用与派生类引用之间的转换**********************************************************************/
        Base b1;
        Derived d1;
        
        Base &br1 = dynamic_cast<Base&>(b1);    // 等价于Base& br1 = b1;
        // upcast
        Base &br2 = dynamic_cast<Base&>(d1);    // 等价于Base& br2 = d1;

        // downcast
        // Derived &dr1 = dynamic_cast<Derived&>(b1);   // 失败,抛出异常std::bad_cast
        // Derived &dr2 = dynamic_cast<Derived&>(br1);  // 失败,抛出异常std::bad_cast
        Derived &dr3 = dynamic_cast<Derived&>(br2);  

    } catch (exception& e) {
        cout << "Exception: " << e.what();
    }
}

// const_cast只能用于添加或删除指针或者引用的底层const(还能用于添加或删除volatile属性)
void const_cast_test() {
    auto const_func = [] (const char* str) {
        cout << "in const_func: " << str << endl;
    }; 

    auto nonconst_func = [] (char* str) {
        cout << "in nonconst_func: " << str << endl;
        str[0] = '$';
        cout << "after modify: " << str << endl;
    }; 

    const char *const_str = "const";
    char s[] = "nonconst";
    char *nonconst_str = s;
    const_func(const_str);
    const_func(nonconst_str);   
    const_func(const_cast<const char*>(nonconst_str));  // 添加底层const
    // nonconst_func(const_str);   // error: invalid conversion from ‘const char*’ to ‘char*’ [-fpermissive]
    nonconst_func(nonconst_str);
    // nonconst_func(const_cast<char*>(const_str));    // 去除底层const (Segmentation fault (core dumped))
    // 对去除底层const的指针所指向的内存写,会导致 undefined behavior
}

// static_cast能够完成指向相关类的指针上的转换。
// upcast和downcast都能够支持,但不同的是,并不会有运行时的检查来确保转换到目标类型上的指针所指向的对象有效且完整。
void static_cast_test() {
    // 同时,static_cast能够完成所有隐式类型转换以及它们的相反转换,更具体点:
    // void*转向任何指针类型;假如该指针是从某指针类型上转到void *,现在再次转到相同的指针类型上去的话,保证转换后的指针本身的值不变。
    // 整数,浮点以及枚举类型向枚举类型的转换。
    // 此外,还能够完成:
    // 显式调用仅有一个参数的构造函数或者是转换操作符(conversion operator)
    // 转换到右值引用
    // 枚举类的值向整数或浮点的转换
    // 任意类型向void的转换,目标值在被求值之后舍弃。

    double d = 3.14;
    int i = static_cast<int>(d);    // 3
    int &&intRef = static_cast<int&&>(i);   // 转右值引用
    intRef = 4;
    cout << i << endl;  // 4

    Base *pba = new Derived;
    Base *pbb = new Base;
    Derived *pd;

    pd = dynamic_cast<Derived*>(pbb);       // 转换失败,因为dynamic_cast 会进行运行时检查
    if (pd == nullptr) { cout << "Null pointer on dynamic_cast.\n"; }

    pd = static_cast<Derived*>(pbb);
    if (pd == nullptr) { cout << "Null pointer on static_cast.\n"; }
}

// reinterpret_cast能够完成任意指针类型向任意指针类型的转换,即使它们毫无关联。
// 该转换的操作结果是出现一份完全相同的二进制复制品,既不会有指向内容的检查,也不会有指针本身类型的检查。
void reinterpret_cast_test() {
    class A { /* ... */ };
    class B { /* ... */ };
    A * a = new A;
    B * b = reinterpret_cast<B*>(a);

    // error: cannot dynamic_cast ‘a’ (of type ‘class reinterpret_cast_test()::A*’) to type ‘class reinterpret_cast_test()::B*’ (source type is not polymorphic)
    // b = dynamic_cast<B*>(a);    
    
    // error: invalid static_cast from type ‘reinterpret_cast_test()::A*’ to type ‘reinterpret_cast_test()::B*’
    // b = static_cast<B*>(a);
}


int main() {
    // dynamic_cast_test();
    // const_cast_test();
    // reinterpret_cast_test();
    static_cast_test();
}

typeid
typeid用来检查表达式的类型。

typeid (expression)
这个操作会返回一个定义在里面的const对象,这个对象可以同其他用typeid获取的const对象进行==或者!=操作,也可以用过.name()来获取一个表示类型名或者是类名的以空字符结尾的字符串。例如:

// typeid
#include <iostream>
#include <typeinfo>
using namespace std;

int main () {
  int * a,b;
  a=0; b=0;
  if (typeid(a) != typeid(b))
  {
    cout << "a and b are of different types:\n";
    cout << "a is: " << typeid(a).name() << '\n';
    cout << "b is: " << typeid(b).name() << '\n';
  }
  return 0;
}

输出:

a and b are of different types:
a is: int *
b is: int
当作用在类上时,要用到RTTI来维护动态对象的类型信息。假如参数表达式的类型是多态类,结果将会是派生得最完整的类,例如:

// typeid, polymorphic class
#include <iostream>
#include <typeinfo>
#include <exception>
using namespace std;

class Base { virtual void f(){} };
class Derived : public Base {};

int main () {
  try {
    Base* a = new Base;
    Base* b = new Derived;
    cout << "a is: " << typeid(a).name() << '\n';
    cout << "b is: " << typeid(b).name() << '\n';
    cout << "*a is: " << typeid(*a).name() << '\n';
    cout << "*b is: " << typeid(*b).name() << '\n';
  } catch (exception& e) { cout << "Exception: " << e.what() << '\n'; }
  return 0;
}

输出:

a is: class Base *
b is: class Base *
*a is: class Base
*b is: class Derived
注意,当作用在指针上时,仅会考虑其本身类型。当作用在类上时,才会产生出动态类型,即结果是派生得最完整的类。倘若传入的是解引用的空指针,则会抛出bad_typeid异常。

此外,name()的实现依赖于编译器及其使用的库,也许并不一定是单纯的类型名。

参考链接:

  • https://zhuanlan.zhihu.com/p/151744661
  • https://cplusplus.com/doc/tutorial/typecasting/