c中变量的数据类型可以强制转换为其他数据类型。但由于这种C风格的类型转换可能会出现一些问题,即过于松散的情况
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换 和 显式类型转换

  1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
    隐式类型转化由于是编译器自动执行的,它会在某些情况下带来不可预期的问题,主要体现在隐式类型转换中,可能会将一个较大范围的数据类型转换为一个较小范围的类型,导致数据丢失或精度损失。例如,double 转换为 int,会丢失小数部分。
  2. 显式类型转化:需要用户自己处理
    显式转换可以绕过编译器的类型检查,允许将完全不相关的类型相互转换。例如,将指针转换为整数类型,或将不同类型的指针相互转换,可能导致严重的运行时错误或未定义行为。

因此 C++ 提出了更加规范、严格的类型转换,添加了四个类型转换运算符,进而更好的控制类型转换过程。
在这里插入图片描述

static_cast

语法:static_cast<目标类型>(表达式)
static_cast 是 C++ 中的一种 静态类型转换运算符,它不会进行运行时类型检测,用于在编译时进行类型转换。它提供了比 C 风格类型转换更安全、更明确的类型转换方式,能够执行多种类型的转换操作。下面详细介绍 static_cast 的使用场景和相关注意事项。

  1. 基本数据类型之间的转换
    static_cast 可以在不同的基本数据类型之间进行转换,例如整数和浮点数之间的转换。
int main() {
    double d = 3.14;
    int i = static_cast<int>(d);  // 将 double 类型转换为 int 类型
    std::cout << "i: " << i << std::endl;

    int j = 5;
    double e = static_cast<double>(j);  // 将 int 类型转换为 double 类型
    std::cout << "e: " << e << std::endl;

    return 0;
}

在这个例子中,static_cast<int>(d) 将 double 类型的变量 d 转换为 int 类型,会截断小数部分;static_cast<double>(j) 将 int 类型的变量 j 转换为 double 类型,会在整数后面添加小数部分 .0。
2. 指针和引用的向上转换(基类和派生类之间)
在继承体系中,static_cast 可以将派生类的指针或引用转换为基类的指针或引用,这被称为向上转换,是安全的操作。

class Base {
public:
    virtual void print() {
        std::cout << "Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void print() override {
        std::cout << "Derived class" << std::endl;
    }
};

int main() {
    Derived derived;
    Base* basePtr = static_cast<Base*>(&derived);  // 派生类指针转换为基类指针
    basePtr->print();

    Derived& derivedRef = derived;
    Base& baseRef = static_cast<Base&>(derivedRef);  // 派生类引用转换为基类引用
    baseRef.print();

    return 0;
}

在这个例子中,static_cast<Base*>(&derived) 将 Derived 类对象的指针转换为 Base 类的指针,static_cast<Base&>(derivedRef) 将 Derived 类对象的引用转换为 Base 类的引用。由于派生类对象包含基类对象的所有成员,所以这种转换是安全的。
3. 指针和引用的向下转换(基类和派生类之间)
static_cast 也可以将基类的指针或引用转换为派生类的指针或引用,这被称为向下转换。但是这种转换是不安全的,因为基类对象可能并不实际指向一个派生类对象,使用时需要确保基类对象确实指向一个派生类对象。

class Base {
public:
    virtual void print() {
        std::cout << "Base class" << std::endl;
    }
};
class Derived : public Base {
public:
    void print() override {
        std::cout << "Derived class" << std::endl;
    }
    void derivedFunction() {
        std::cout << "Derived function" << std::endl;
    }
};
int main() {
    Derived derived;
    Base* basePtr = &derived;
    Derived* derivedPtr = static_cast<Derived*>(basePtr);  // 基类指针转换为派生类指针
    derivedPtr->derivedFunction();

    Base& baseRef = derived;
    Derived& derivedRef = static_cast<Derived&>(baseRef);  // 基类引用转换为派生类引用
    derivedRef.derivedFunction();

	Base* basePtr = new Base();  // 基类对象,未指向派生类
	Derived* derivedPtr = static_cast<Derived*>(basePtr);  // 危险!
	derivedPtr->derivedFunction();  // 未定义行为!

    return 0;
}

在这个例子中,由于 basePtr 和 baseRef 实际上指向的是 Derived 类对象,所以使用 static_cast 进行向下转换是安全的。但如果 basePtr 或 baseRef 并不指向一个 Derived 类对象,使用转换后的指针或引用调用 Derived 类特有的成员函数会导致未定义行为。
向下转换的安全性完全由程序员保证,static_cast 不会进行运行时检查。如果基类指针不实际指向目标派生类对象,转换后的指针操作会导致未定义行为。此时应优先使用 dynamic_cast(若涉及多态类型)。

dynamic_cast

语法:dynamic_cast<目标类型>(表达式)
这是c++提供的专门用于多态类型转换(父类指针/引用 → 子类指针/引用)的运算符,主要用于在继承体系中进行安全的类型转换,尤其是在运行时进行类型检查,它会进行运行时类型检测,当表达式不能背转换成目标类型,

  1. 指针的向下转换
    在继承体系中,当你有一个基类指针,并且这个指针实际上指向一个派生类对象时,可以使用 dynamic_cast 将基类指针转换为派生类指针。与 static_cast 不同的是,dynamic_cast 会在运行时检查转换是否合法。
class Base {
public:
    virtual ~Base() {} // 基类必须有至少一个虚函数,以便 dynamic_cast 正常工作
};

class Derived : public Base {};

int main() {
    Base* basePtr = new Derived();
    Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
    if (derivedPtr) {
        std::cout << "转换成功" << std::endl;
    } else {
        std::cout << "转换失败" << std::endl;
    }
    delete basePtr;
    return 0;
}

在上述代码中,basePtr 实际上指向一个 Derived 类对象,所以 dynamic_cast<Derived*>(basePtr) 转换成功,derivedPtr 不为空。
2. 指针的跨继承体系转换(安全检查)
当有多重继承时,dynamic_cast 可以安全地进行跨继承体系的指针转换。

class Base1 {
public:
    virtual ~Base1() {}
};

class Base2 {
public:
    virtual ~Base2() {}
};

class Derived : public Base1, public Base2 {};

int main() {
    Derived* derived = new Derived();
    Base1* base1Ptr = derived;
    Base2* base2Ptr = dynamic_cast<Base2*>(base1Ptr);
    if (base2Ptr) {
        std::cout << "跨继承体系转换成功" << std::endl;
    } else {
        std::cout << "跨继承体系转换失败" << std::endl;
    }
    delete derived;
    return 0;
}

这里 Base1 和 Base2 是两个不同的基类,Derived 同时继承自它们。通过 dynamic_cast 可以将 Base1 指针安全地转换为 Base2 指针。
3. 引用的向下转换
对于引用类型,dynamic_cast 也可以进行向下转换。但与指针不同的是,如果转换失败,dynamic_cast 会抛出 std::bad_cast 异常。

class Base {
public:
    virtual ~Base() {}
};

class Derived : public Base {};

void testReference(Base& baseRef) {
    try {
        Derived& derivedRef = dynamic_cast<Derived&>(baseRef);
        std::cout << "引用转换成功" << std::endl;
    } catch (const std::bad_cast& e) {
        std::cout << "引用转换失败: " << e.what() << std::endl;
    }
}

int main() {
    Derived derived;
    Base& baseRef = derived;
    testReference(baseRef);

    Base base;
    Base& baseRef2 = base;
    testReference(baseRef2);

    return 0;
}

在 testReference 函数中,当传入的 baseRef 实际上引用的是 Derived 对象时,转换成功;当引用的是 Base对象时,转换失败,会捕获 std::bad_cast 异常。

const_cast

主要用于移除或添加 const/volatile 修饰符,这个运算符不改变底层数据,仅改变类型系统约束。需要注意的是滥用可能导致未定义行为(UB)。

const int x = 10;
int* px = const_cast<int*>(&x);  // 移除 const
*px = 20;  // UB!x 原本是 const,实际行为未定义

int x = 10;
const int* px = &x;
int* py = const_cast<int*>(px);  // 合法,因为 x 本身不是 const
*py = 20;  // 合法

void legacyApi(char* str);  // 旧 API 需要非 const 参数
const char* msg = "hello";
legacyApi(const_cast<char*>(msg));  // 安全,只要 API 不修改字符串

volatile 是一个类型修饰符,用于告诉编译器不要对这个变量进行优化,确保每次访问都直接从内存中读取或写入,而不是使用寄存器中的缓存值。

reinterpret_cast

低级别指针/整数转换(如 T* → U* 或 T* → uintptr_t)。它会重新解释原数据的二进制数据(无类型检查)。在四个类型转换运算符中说最不安全,及其可能导致未定义行为。
reinterpret_cast 的合法场景,如:

  • 系统编程中处理内存地址(如 void* → uintptr_t)。
  • 序列化或反序列化时按字节重新解释数据(需确保内存布局兼容)。
int i = 42;
float* pf = reinterpret_cast<float*>(&i);  // int* → float*(危险!)

uintptr_t addr = reinterpret_cast<uintptr_t>(pf);  // 指针 → 整数

struct A { int x; };
struct B { int y; };
A a{10};
B* pb = reinterpret_cast<B*>(&a);  // 重新解释内存布局(危险!)
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐