C++ 类型转换

四种强制类型转换是什么?他们的作用是什么?

C++中有四种强制类型转换(Cast)运算符,每种转换运算符在不同场景下有不同用途,了解它们的特点有助于选择最合适的转换方式。

1.static_cast

static_cast是最常用的类型转换操作,用于在相关类型之间进行转换,例如将float转换为int。它也可用于类层次结构中基类和子类之间的转换,但只限于没有虚函数的情况下,即不涉及多态。static_cast在编译时检查转换的有效性,如果转换不合法,则会报编译错误。

用途: 通用类型转换,数值类型之间、指针类型之间、基类和子类之间的转换。

2.reinterpret_cast

reinterpret_cast提供了低级别的重新解释类型能力,它允许几乎任意指针(或引用)类型之间的转换,甚至允许指针与足够大的整数类型之间的转换。这种转换风险很大,因为它并不检查语义的合理性,完全依赖于程序员确保转换的安全性。

用途: 对类型的位模式进行重新解释,用于底层或硬件相关的编程中,如指针类型之间的转换。

3.const_cast

const_cast用于修改类型的const属性,例如将const int*转换为int*。这种转换不改变实际对象的const性质,但允许我们通过非const指针或引用来操作对象。需要谨慎使用,避免对本身是const的对象进行修改,那样是未定义行为。

用途: 修改类型的const或volatile属性。

4.dynamic_cast

dynamic_cast 是 C++ 中一个具有运行时类型检查功能的强制类型转换操作符。主要用于处理类层次结构中的向下转换(从基类指针或引用转换为派生类指针或引用),并且涉及到多态(即基类有虚函数)。dynamic_cast在运行时检查转换的安全性,如果转换不合法(如试图将基类对象转为并非其真实类型的派生类),则返回空指针或抛出异常。

用途: 类层次结构中,安全的向下类型转换,特别用于处理多态性。

C语言中的类型转换

隐式类型转换

发生在当运算的操作数类型不一致时,编译器会自动将一种类型的变量转换成另一种类型,以满足操作的要求。这种类型转换是在编译时自动进行的,不需要程序员明确地指定转换操作。

显式类型转换

显式类型转换的语法格式是在需要转换的值或变量前加上圆括号,并在圆括号内部指定目标类型。

(目标类型) 值或变量

C++中的类型转换

static_cast<目标类型>(表达式) //用于相近类型之间的转换,编译器隐式执行的任何类型转换都可用

reinterpret_cast<目标类型>(表达式)//用于两个不相关类型之间的转换

const_cast<目标类型>(表达式)//用于删除变量的const属性,方便赋值

reinterpret_cast<目标类型>(表达式)//用于安全的将父类的指针(或引用)转换成子类的指针(或引用)
                                //只能用于含有虚函数的类,因为运行时类型检测需要运行时的类型信息, 
                                //这个信息是存储在虚函数表中的,只有定义了虚函数
                                //的类才有虚函数

 1.static_cast

1. 基本数据类型之间的转换

double pi = 3.141592653589793;
int pi_int = static_cast<int>(pi);

在这里,static_castdouble类型的pi转换为int类型的pi_int。转换的结果是丢失小数部分,仅保留整数部分3

2. 类指针之间的转换(向上转型)

假设有一个基类Base和一个从Base派生的类Derived:

class Base {};
class Derived : public Base {};

向上转型例子:

Derived* derived = new Derived();
Base* base = static_cast<Base*>(derived);

在这里,static_cast被用来将Derived类指针转换为Base类指针,这是安全的向上转型。

 

3. 类指针之间的转换(向下转型)

向下转型需要程序员确保转换的安全性,因为它可能会导致运行时错误。

首先,假设你真的知道某个Base类指针实际上指向一个Derived类的实例:

Base* base = new Derived();
Derived* derived = static_cast<Derived*>(base);

这里,static_castBase类指针转换为Derived类指针,作为向下转型。但是,这种用法应当谨慎使用,因为如果base实际上不指向一个Derived实例,这种转换可能会导致不安全的行为。

2.reinterpret_cast

1. 指针类型之间的转换

当你需要将一种类型的指针转换为完全不同类型的指针时,可以使用reinterpret_cast。例如,将int类型的指针转换为double类型的指针:

int* intPtr = new int(42);
double* doublePtr = reinterpret_cast<double*>(intPtr);

在这个例子中,intPtr指向一个int类型的值,通过reinterpret_cast我们将其转换为指向double的指针doublePtr。这种转换是危险的,因为intdouble可能会有不同的表示方式和大小,所以使用doublePtr时需要极为小心。

2. 整数和指针之间的转换

有时候,出于对指针进行某些位操作的需要,可能会将指针转换为整数类型,或将整数转换为指针。例如:

char* charPtr = new char('A');
std::uintptr_t ptrVal = reinterpret_cast<std::uintptr_t>(charPtr); // 指针转换为整数
char* newCharPtr = reinterpret_cast<char*>(ptrVal); // 整数转换回指针

这里,charPtr首先被转换为一个整数类型(uintptr_t确保有足夠的位能存储指针的值),然后再从这个整数类型转换回指针类型。这样的操作可以在需要对指针进行算术或位操作时使用,但使用时需要确保转换的正确性和安全性

reinterpret_cast的注意事项:

  • 使用reinterpret_cast进行的转换可能会导致平台依赖的行为,因为不同的系统可能以不同的方式处理数据表示。
  • reinterpret_cast不执行任何运行时检查来保证转换的安全性,因此完全依赖于程序员确保转换的合理性。
  • 尽量避免使用reinterpret_cast来进行指针和整数之间的转换,除非真正需要这种低级别操作,因为这会使代码变得难以理解和维护。

3.const_cast

1. 用于移除指针或引用的const

假设有以下情况:

const int num = 10;
const int* ptr = &num;

这里,ptr是一个指向const int的指针,指向一个常量整数num。如果你想通过指针ptr来修改num的值(虽然这通常不是一个好主意),你不能直接这么做,因为ptr是指向const的。这时,可以使用const_cast来移除ptrconst性:

int* modifiablePtr = const_cast<int*>(ptr);
*modifiablePtr = 20;

这样,通过modifiablePtr就可以修改num的值了。重要的是要记住,通过const_cast修改原本被声明为const的变量是未定义行为,这里的例子只是为了演示const_cast的用法。

2. 调用非const成员函数

有时候,你可能有一个指向const对象的指针或引用,但需要调用该对象的一个非const成员函数。例如:

class MyClass {
public:
    void modify() { /* 修改对象状态的代码 */ }
};

const MyClass obj;
MyClass* modifiableObj = const_cast<MyClass*>(&obj);

modifiableObj->modify();

在这个例子中,尽管objconst的,我们通过const_cast移除了指针的const性,从而可以调用modify函数。同样,如前例所述,这种做法存在风险,应谨慎使用。

4.dynamic_cast

1. 指针之间的转换:

Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);

如果 basePtr 真的指向一个 Derived 对象,则转换成功,derivedPtr 将不为 nullptr。如果转换失败(即 basePtr 不指向一个 Derived 对象),derivedPtr 将为 nullptr

2. 引用之间的转换:
Base& baseRef = derivedObj;
try {
    Derived& derivedRef = dynamic_cast<Derived&>(baseRef);
} catch (const std::bad_cast& e) {
    // 处理转换失败的情况
}

如果 baseRef 实际上真的引用了一个 Derived 对象,那么转换成功。如果转换失败,将抛出 std::bad_cast 异常。

explicit

用来修饰构造函数,从而禁止单参数构造函数的隐式转换

  • explicit 关键字用于防止构造函数的隐式类型转换。
  • 在构造函数前加上 explicit 可以确保只有在显式地调用构造函数时才能进行类型转换。
  • 这有助于提高代码的安全性和可读性,防止因意外的隐式转换而引入的错误。

例:

class MyClass {
public:
    MyClass(int x) { /* 构造函数实现 */ }
};

如果没有使用 explicit 关键字,编译器将允许下面的隐式转换:

MyClass obj = 10; // 通过隐式转换,将 int 类型转换为 MyClass 类型

为了避免这类隐式转换,可以在构造函数前加上 explicit 关键字:

class MyClass {
public:
    explicit MyClass(int x) { /* 构造函数实现 */ }
};

这样,在尝试执行隐式转换时,编译器将产生错误:

MyClass obj = 10; // 错误:不允许使用隐式转换

如果我们确实需要创建 MyClass 的实例,必须显式地进行转换:

MyClass obj(10); // 正确:显式使用构造函数
// 或者使用统一初始化语法
MyClass obj{10}; // 正确:显式使用构造函数

 RTTI(运行时类型识别)

dynamic_cast 操作符

前面提到过,dynamic_cast 用于安全地将基类指针或引用转换为派生类指针或引用。相比于其他类型转换操作符(如 static_cast 或 reinterpret_cast),dynamic_cast 在转换时进行类型安全检查,如果转换不合法(即目标类型不是源对象类型的派生类),则转换失败。对于指针类型转换,转换失败时返回 nullptr;对于引用类型转换,转换失败时抛出 std::bad_cast 异常。

typeid 操作符

typeid 操作符用于获取一个表达式或一个类型的类型信息,该信息以 std::type_info 对象的形式存在。std::type_info 对象提供了比较运算符,允许在程序运行时比较两个类型是否相同。typeid 可以用于任何表达式,包括多态基类的对象。当用于多态类型的对象时,typeid 将返回实际对象的类型,而非基类类型。

#include <iostream>
#include <typeinfo>

class Base {
public:
    virtual void func() {}
};

class Derived : public Base {};

int main() {
    Base* b = new Derived;
    if (typeid(*b) == typeid(Derived)) {
        std::cout << "b 实际上是一个 Derived 类型的对象。" << std::endl;
    }
    delete b;
    return 0;

decltype


decltype 是 C++11 引入的一个关键字,用于查询表达式的类型而不实际计算表达式的值。这个特性允许程序员在编写模板代码或元编程时更加灵活地处理类型。它非常有用,尤其是在函数返回类型推导、泛型编程等场景中,可以用来获取变量、函数返回值或表达式的确切类型。 

基本用法

语法如下:

decltype(表达式)

这将得到表达式的类型。举个例子:

int a = 3;
decltype(a) b = a; // b 的类型是 int

在这个例子中,decltype(a) 获得了 a 的类型(即 int),因此变量 b 也被定义为 int 类型。

 

与函数结合使用

decltype 也常用于推导函数返回类型。考虑下面的例子:

template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {     
    return t + u;                       
}                                       

//-> decltype(t + u): 这里使用了 C++11 的尾随返回类型语法。因为 decltype(t + u) 是在参数列表之后声明的,所以它可以使用函数参数 t 和 u。decltype(t + u) 分析了 t + u 表达式的类型,然后将这个类型作为函数的返回类型。这意味着,不论 t + u 的结果是什么类型,add 函数都将返回相 同的类型。

(-> decltype(t + u): 这里使用了 C++11 的尾随返回类型语法。因为 decltype(t + u) 是在参数列表之后声明的,所以它可以使用函数参数 t 和 u。decltype(t + u) 分析了 t + u 表达式的类型,然后将这个类型作为函数的返回类型。这意味着,不论 t + u 的结果是什么类型,add 函数都将返回相 同的类型。 )

在这个例子中,decltype(t + u) 用于推导 add 函数的返回类型,这意味着不管传给 add 函数的参数 t 和 u 是什么类型,返回类型都会恰当地匹配 t + u 表达式的类型。

注意事项

  • 当用于引用时,decltype 保持表达式的引用性质。比如,decltype((a)) b = a; 中的 b 将是 int& 类型,因为 (a) 是一个引用表达式。
  • 当用于解引用操作符时,比如对指针 p 使用 decltype(*p),结果会是指向类型的引用,而不是值类型,即使表达式没有真的被求值。
  • 当应用于函数调用时,它将产生函数返回类型。
  • 19
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值