Skip to content

类型转换

约 1278 字大约 4 分钟

c++

2024-11-02

隐式类型转换

类型转换,即一个变量的类型向另一个类型转换。C++支持 C 风格的类型转换。

隐式类型转换的场景:

  • 赋值
  • 实参到形参
  • 函数返回

严格来说,运算造成的类型转换属于运算符重载的范畴,但通常也可以看作是一种类型转换。

基础类型

// 变量运算
int a1 = 10;
double b1 = 3.14;
a1 + b1; // 右值为 double 类型

// 赋值
int a2 = 10;
double b2 = a2;

// 参数传递
void test(int a2) {}
char b2 = 10;

// 函数返回
double test2() {
    int a = 10;
    return a;
}

// 任意基础类型的指针向 void* 转换
int * i;
void* p = i;

实际上任何指针类型(基础类型或自定义类型)都可以隐式转换为 void* 类型的指针。故 void* 又称为万能指针。

自定义类型

子类 --> 父类:隐式向上转换

向上转换指的是将派生类对象赋值给基类对象的转换。

继承类型(私有继承,保护继承,公有继承)直接影响了向上转换的合法性。

例如,私有继承规定了子类对象在类外不允许访问基类成员,因此私有继承基类的子类对象不可在类外隐式的向上转换,但是可以在类内隐式向上转换。

struct A {
    A(x): a(x) {}
    int a;
};

struct B: private A {
    B(x = 0, y = 0): A(x), b(y) {}
    A getA() {
        return *this; // 合法的类内隐式向上转换
    }
    A* getA_ptr() {
        return this; // 合法的类内隐式向上转换
    }
    int b;
}

struct C : public A {
    C(x = 0, y = 0): A(x), c(y) {}
    int c;
}

int main() {
    C c0;
    A a0 = c0; // 合法的类外隐式向上转换
    B b0;
    A a1 = b0; // 报错
    A a2 = b.getA(); // 合法

    C* cptr = new C();
    A* aptr = cptr; // 合法
    B* bptr = new B();
    A* aptr = bptr; // 报错
    A* aptr1 = b.getA_ptr(); // 合法
    return 0;
}

这里的隐式向上转换还包括了子类指针向父类指针的转换。

如果父类存在虚函数,实现了多态。

重载隐式类型转换

类内可以使用重载的方式来定义隐式类型转换。

struct A {
    operator int() {
    }
    int a;
};

struct B {
    B(int x): b(x) {}
    operaotr std::string() {
        return std::to_string(b);
    }
    int b;
}

int main() {
    A a;
    B b(a); // 合法,A 实现了向 int 的隐式类型转换
    string s = b; //合法,B 实现了向 string 的隐式类型转换
    return 0;
}

显式类型转换

隐式类型转换并不总是非常明确,当隐式类型转换有多条转换路径时,编译器会选择一个最合适的转换路径。但这样给开发者带来了很多不确定性。

void show(int x) {
    cout << "show1" << endl;
}
void show(double x) {
    cout << "show2" << endl;
}

int main() {
    char a = 'a';
    show(a);
    // 可以向 int 转换,也可以向 double 转换
    // 当代码量巨大时,关注隐式类型转换就成了开发负担

    return 0;
}

C++ 支持 C 风格的类型转换,但不推荐使用。因为 C 风格的类型转换没有类型安全检查,并且语义不明确。

// C 风格显式类型转换
int a = 10;
double b = (int)a;
double c = int(a); // 有时也可以这么写

C++11标准引入了显式类型转换,明确了不同类型转换的用途。

static_cast

主要用于非多态的类型转换,编译期进行类型检查

void show(int x) {cout << "show1" << endl;}
void show(double x) {cout << "show2" << endl;}
int main() {
    char a = 'a';
    show(static_cast<double>(a));
    return 0;
}

子类和父类之间的类型转换有安全风险,这种场景需要谨慎使用,或者使用 dynamic_cast。

dynamic_cast

用于多态的类型转换,运行时进行类型检查

如果转换失败,则返回nullptr,或者抛出std::bad_cast异常。

dynamic_cast 依赖于RTTI(Run-Time Type Information)即运行时类型信息,这是通过虚表来实现的。所以 dynamic_cast 只能应用在包含虚表指针的对象上。

Base* b = new Derived(); // 向上转换
/* ... */
Derived* p = dynamic_cast<Derived*>(b); // 向下转换
if (p == nullptr) {
    // 转换失败
}
else {
    // 转换成功
}

const_cast

const_cast 用于去掉 const 或 volatile 的修饰。虽然const_cast可以去除 const 修饰,但是修改const 变量本身是一个未定义行为,必须谨慎使用。

const_cast 只能用于指针引用

struct A {
    void show() const { cout << a << endl; }
    void print() { cout << a << endl; }
    int a;
};

int main() {
    const A a = {10};
    a.show(); // 合法
    a.print(); // 报错,const对象只能调用const成员函数

    // 合法,使用 const_cast 去掉const属性
    const_cast<A*>(&a)->print();
    return 0;
}

reinterpret_cast

直接进行类型转换,重新解释底层二进制到目标类型,无任何类型检查

只能应用在指针或者引用类型上。

非常强大也非常危险的类型转换工具,具体解释往往依赖于编译器和平台,需要谨慎使用。

int x = 10;
char* p = reinterpret_cast<char*>(&x); // 将 int* 转换为 char*
int* q = reinterpret_cast<int*>(p);    // 将 char* 转换回 int*