【C++】深度解析C++的四种强制转换类型(小白一看就懂!!)

sunny-ll 2024-10-08 13:05:01 阅读 71

目录

一、前言

二、C风格的强制类型转换 

🥝隐式类型转换 

🍉显示类型转换 

三、为什么C++需要四种类型转换 

四、C++强制类型转换 

🍓 静态转换(static_cast)

✨用法 

✔️语法 

 🌱例子

🍋动态转换(dynamic_cast) 

✨用法 

✔️语法 

🌱例子 

🍇常量转换(const_cast) 

✨用法 

✔️语法 

🌳例子 

🍍重新解释转换(reinterpret_cast) 

✨用法 

✔️语法 

🌳例子 

五、总结 

 六、共勉


一、前言

在之前我们学过,变量的数据类型可以强制转换为其他数据类型。但由于这种C风格的类型转换可能会出现一些问题,即过于松散的情况,因此 C++ 提出了更加规范、严格的类型转换,添加了四个类型转换运算符,进而更好的控制类型转换过程。

类型转换符:

<code>static_castdynamic_castconst_castreinterpret_cast

因此,我们可以根据自身的目的选择合适的运算符,进行类型转换,也能让编译器能检查程序的行为是否和正常的逻辑相吻合。 

二、C风格的强制类型转换 

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换显式类型转换

隐式类型转化(截断或提升):编译器在编译阶段自动进行,能转就转,不能转就编译失败显式类型转化(强转):需要用户自己处理

🥝隐式类型转换 

隐式类型转换虽然简化了编程过程,但由于是编译器自动执行的,它会在某些情况下带来不可预期的问题,主要体现在以下方面: 

隐式转换可能导致数据丢失:在隐式类型转换中,可能会将一个较大范围的数据类型转换为一个较小范围的类型,导致数据丢失或精度损失。例如,double 转换为 int,会丢失小数部分。 

#include <stdio.h>

int main() {

double pi = 3.14159;

int truncatedPi = pi; // 隐式转换,丢失小数部分

printf("Truncated Pi: %d\n", truncatedPi); // 输出:3

return 0;

}

🍉显示类型转换 

显式类型转换可以给程序员更多的控制权,但也可能引发一些问题,尤其是在不恰当地使用时。 

不安全的转换:显式转换可以绕过编译器的类型检查,允许将完全不相关的类型相互转换。例如,将指针转换为整数类型,或将不同类型的指针相互转换,可能导致严重的运行时错误或未定义行为。 

#include <stdio.h>

#include <stdint.h> // 包含 uintptr_t

int main() {

int a = 100;

int *ptr = &a;

// 使用 uintptr_t 来存储指针的整数值

uintptr_t address = (uintptr_t)ptr;

// 将整数转换回指针

int *newPtr = (int*)address;

printf("Dereferenced value: %d\n", *newPtr); // 100

return 0;

}

三、为什么C++需要四种类型转换 

C风格的转换格式很简单,但是有不少缺点的: 

隐式类型转换有些情况下可能会出问题:比如数据精度丢失显式类型转换将所有情况混合在一起,代码不够清晰

因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。

四、C++强制类型转换 

 标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:

static_castdynamic_castconst_castreinterpret_cast

下面来分开来讨论。 


🍓 静态转换(static_cast)

✨用法 

 静态转换可以用两种用法:

基本数据类型的转换(如将int类型转换为char类型)静态转换用于类层次结构中父类和子类之间指针或引用的转换。(有继承关系的)

✔️语法 

static_cast <要转换的类型>(变量名或表达式)

 🌱例子

基本数据类型转换的例子

1. 整数类型之间的转换 

#include <iostream>

int main() {

int num = 97;

// 将int类型转换为char类型

char ch = static_cast<char>(num);

std::cout << "Integer: " << num << std::endl;

std::cout << "Character: " << ch << std::endl; // 输出对应的ASCII字符

return 0;

}

说明

在这个例子中,static_castint 类型的 num 转换为 char 类型,输出的结果是对应 ASCII 值 97 的字符 'a'


2. 浮点数与整数之间的转换 

#include <iostream>

int main() {

double pi = 3.14159;

// 将double类型转换为int类型(截断小数部分)

int truncatedPi = static_cast<int>(pi);

std::cout << "Original value (double): " << pi << std::endl;

std::cout << "Truncated value (int): " << truncatedPi << std::endl;

return 0;

}

说明

pi 是一个 double 类型的浮点数,通过 static_cast 将其转换为 int,结果 truncatedPi3,小数部分被截断。


3. 指针与 void* 之间的转换

#include <iostream>

int main() {

int value = 42;

int* intPtr = &value;

// 将int*转换为void*

void* voidPtr = static_cast<void*>(intPtr);

// 将void*转换回int*

int* newIntPtr = static_cast<int*>(voidPtr);

std::cout << "Original value: " << *intPtr << std::endl;

std::cout << "New pointer value: " << *newIntPtr << std::endl;

return 0;

}

说明

我们首先将 int* 类型的指针转换为 void*,然后再将其转换回 int*。由于这只是类型的转换,并未改变指针的地址,因此 *intPtr *newIntPtr 的值相同。


4. 指针与空指针 (nullptr) 之间的转换

#include <iostream>

int main() {

int* ptr = nullptr;

// 将nullptr转换为void*,表示空指针

void* voidPtr = static_cast<void*>(ptr);

if (voidPtr == nullptr) {

std::cout << "The pointer is nullptr." << std::endl;

}

return 0;

}

static_cast<void*>(ptr)nullptr 转换为 void* 类型的空指针,这是在处理泛型指针时的一种常见用法 


使用 static_cast 进行类层次结构中的类型转换

1. 从子类转换为父类(Upcasting) 

这是安全的,因为子类包含了父类的所有成员,因此将子类的指针或引用转换为父类的指针或引用是安全的。 

#include <iostream>

class Animal {

public:

virtual void sound() const {

std::cout << "Animal makes a sound" << std::endl;

}

};

class Dog : public Animal // 继承

{

public:

void sound() const override // 虚函数重写

{

std::cout << "Dog barks" << std::endl;

}

};

int main() {

Dog myDog;

Animal* animalPtr = static_cast<Animal*>(&myDog); // 安全的上行转换

animalPtr->sound(); // 调用的是 Dog 的 sound,因为它是虚函数

return 0;

}

说明

这里我们将 Dog 类的对象 myDog 转换为其基类 Animal 的指针 animalPtr。这是安全的,因为 DogAnimal 的子类,并且继承了父类的所有属性和方法。使用虚函数的情况下,虽然我们将 Dog 转换为 Animal,但调用的是 Dogsound() 方法,这是由于多态性的作用。


2. 从父类转换为子类(Downcasting) 

将父类转换为子类是不安全的,只有当我们确信父类指针实际上指向的是一个子类对象时,才能进行这种转换。否则,转换后的对象可能无法正常工作,甚至导致未定义行为。 

#include <iostream>

class Animal {

public:

virtual void sound() const {

std::cout << "Animal makes a sound" << std::endl;

}

};

class Dog : public Animal {

public:

void sound() const override {

std::cout << "Dog barks" << std::endl;

}

void fetch() const {

std::cout << "Dog fetches the ball" << std::endl;

}

};

int main() {

Animal* animalPtr = new Dog(); // Animal指针指向Dog对象

Dog* dogPtr = static_cast<Dog*>(animalPtr); // 下行转换

dogPtr->sound(); // 调用 Dog 的 sound 方法

dogPtr->fetch(); // 调用 Dog 的 fetch 方法

delete animalPtr;

return 0;

}

说明

这里 animalPtr 是一个指向基类 Animal 的指针,但它实际上指向一个 Dog 对象。因此,我们可以安全地使用 static_castanimalPtr 转换为 Dog* 类型的指针 dogPtr,然后调用 Dog 类中特有的 fetch() 方法。注意:这种转换前提是 animalPtr 实际上指向一个 Dog 对象。如果它指向的是其他类型的对象,比如另一个继承自 Animal 的子类对象,那么这种转换会导致未定义行为。


🍋动态转换(dynamic_cast) 

动态转换dynamic_cast)是 C++ 中的一种类型转换操作,专门用于处理多态类型(即包含虚函数的类层次结构)。dynamic_cast 可以在运行时对类的类型进行检查,确保类型转换的安全性。 

✨用法 

相比于静态类型转换(如 static_cast),dynamic_cast 会在运行时进行检查,并且只有在以下两种情况下使用:

上行转换(Upcasting):从子类转换为父类。下行转换(Downcasting):从父类转换为子类,且要求父类中至少有一个虚函数(即是多态类)。

dynamic_cast 在多态类型中非常有用,尤其是在进行 下行转换 时,它可以在运行时检查父类指针或引用是否实际指向一个子类对象。如果转换失败,dynamic_cast 会返回 nullptr(对于指针类型)或抛出异常(对于引用类型),从而避免不安全的类型转换。 

✔️语法 

dynamic_cast<要转换的类型>(变量名或表达式)

🌱例子 

1:安全的上行转换(Upcasting) 

上行转换是指将子类对象的指针或引用转换为父类的指针或引用。这种转换是安全的,因为子类总是包含父类的所有成员。 

#include <iostream>

class Animal {

public:

virtual void makeSound() const {

std::cout << "Animal sound" << std::endl;

}

};

class Dog : public Animal {

public:

void makeSound() const override {

std::cout << "Dog barks" << std::endl;

}

};

int main() {

Dog myDog;

Animal* animalPtr = dynamic_cast<Animal*>(&myDog); // 上行转换(子类到父类)

animalPtr->makeSound(); // 输出: Dog barks,调用的是子类的重载方法

return 0;

}

说明

在这个例子中,myDog Dog 类的对象,我们将 Dog 类的指针转换为其基类 Animal 的指针。这是安全的,因为 DogAnimal 的派生类。虽然我们使用的是父类的指针,但由于 makeSound() 是一个虚函数,调用的是 Dog 类中的重载方法。


2:安全的下行转换(Downcasting) 

下行转换是指将父类对象的指针或引用转换为子类的指针或引用。在这种情况下,必须确保父类的指针实际上指向的是子类对象,否则转换可能会失败。 

#include <iostream>

class Animal {

public:

virtual ~Animal() {} // 基类需要至少有一个虚函数

virtual void makeSound() const {

std::cout << "Animal sound" << std::endl;

}

};

class Dog : public Animal {

public:

void makeSound() const override {

std::cout << "Dog barks" << std::endl;

}

void fetch() const {

std::cout << "Dog fetches the ball" << std::endl;

}

};

class Cat : public Animal {

public:

void makeSound() const override {

std::cout << "Cat meows" << std::endl;

}

};

int main() {

Animal* animalPtr = new Dog(); // 父类指针指向一个Dog对象

// 使用 dynamic_cast 进行下行转换

Dog* dogPtr = dynamic_cast<Dog*>(animalPtr);

if (dogPtr) { // 如果转换成功

dogPtr->makeSound(); // 输出: Dog barks

dogPtr->fetch(); // 输出: Dog fetches the ball

} else {

std::cout << "Conversion failed!" << std::endl;

}

delete animalPtr;

return 0;

}

说明

animalPtr 是一个指向基类 Animal 的指针,但它实际上指向一个 Dog 对象。我们使用 dynamic_cast 尝试将 Animal* 转换为 Dog*dynamic_cast 会在运行时检查,如果 animalPtr 指向的是 Dog,则转换成功,返回有效的指针;如果指向的是其他派生类(例如 Cat),则转换失败,dogPtrnullptr。在下行转换中,dynamic_cast 可以防止不安全的类型转换,因为它会在运行时确保转换的正确性。


🍇常量转换(const_cast) 

const_cast 是 C++ 中专门用于 常量属性 的类型转换操作符。它的主要作用是 移除添加 对象的 constvolatile 修饰符。与其他类型转换操作符不同,const_cast 只能用于修改对象的常量性,而不能用于在不同类型之间进行转换。 

✨用法 

移除 const 限定符:允许将指向常量的指针或引用转换为指向非常量的指针或引用,以便修改常量对象(注意:修改真正的常量对象会导致未定义行为)。添加 const 限定符:也可以用于给指针或引用添加 const 限定符。

✔️语法 

const_cast <要转换的类型>(变量名或表达式)

🌳例子 

 移除 const 属性

#include <iostream>

void modify(int* p)

{

*p = 100; // 修改指针指向的值

}

int main()

{

const int a = 10; // a 是常量

const int* p = &a; // p 是指向常量的指针

// 使用 const_cast 去除 const 属性,试图修改常量 a

modify(const_cast<int*>(p)); // 不安全,可能导致未定义行为

std::cout << "a = " << a << std::endl; // 输出未定义结果

return 0;

}

说明

这里我们尝试通过 const_castconst int* 转换为 int*,然后修改指针指向的值 a问题:由于 a 是一个常量,试图修改它的行为是不安全的,可能导致 未定义行为(UB)。一些编译器可能不会检测出错误,程序可能会继续运行,但结果无法预测。


2:移除 const,修改非常量对象 

#include <iostream>

void modify(int* p) {

*p = 200; // 修改指针指向的值

}

int main() {

int b = 20; // b 是非常量

const int* p = &b; // p 是指向常量的指针,但指向非常量

// 使用 const_cast 去除 const 属性,安全修改 b

modify(const_cast<int*>(p));

std::cout << "b = " << b << std::endl; // 输出: b = 200

return 0;

}

说明

在这个例子中,p 是指向 b 的常量指针,虽然 p 被修饰为 const,但 b 本身是非常量。因此,通过 const_cast 移除 pconst 修饰符是安全的,可以修改 b 的值。结果 b 被安全地修改为 200


3:添加 const 限定符 

有时你需要将非常量对象强制转换为常量对象来保证代码的安全性,防止某些函数无意中修改对象。 

#include <iostream>

void print(const int* p) {

std::cout << "Value: " << *p << std::endl;

}

int main() {

int c = 50;

int* p = &c;

// 将非常量指针转换为常量指针,保证不会修改对象

print(const_cast<const int*>(p)); // 安全地将 p 转换为 const int*

return 0;

}

说明

这里使用 const_cast 将非常量指针 p 转换为常量指针,并将其传递给 print 函数,保证函数不会修改指针指向的对象。这种做法确保了 print 函数只能读取数据,而不能修改 c


🍍重新解释转换(reinterpret_cast) 

 reinterpret_cast 是 C++ 中最强大、但也最危险的类型转换操作符之一。它允许在不同类型之间进行低级别的类型转换。与其他类型转换操作符不同,reinterpret_cast 并不会进行任何类型检查,它仅仅是重新解释二进制位的含义,直接将一个类型的位模式重新解释为另一个类型。

✨用法 

指针类型之间的转换:将一个指针类型转换为另一个指针类型,甚至是将指针转换为整数类型(或反之)。指针和整数之间的转换:允许将指针转换为整数类型,或将整数转换为指针类型。非相关类型之间的转换:允许在不相关的类型之间进行转换,比如将 float* 转换为 int*,或者将对象类型转换为字节序列。

✔️语法 

reinterpret_cast<要转换的类型>(变量名或表达式)

🌳例子 

 1:指针类型之间的转换

#include <iostream>

class A {

public:

void show() {

std::cout << "This is class A" << std::endl;

}

};

class B {

public:

void display() {

std::cout << "This is class B" << std::endl;

}

};

int main() {

A objA;

B* bPtr = reinterpret_cast<B*>(&objA); // 将 A* 转换为 B*

// 这里的 bPtr 指向的其实是 A 的对象,但我们把它当作 B 来操作

bPtr->display(); // 可能导致未定义行为

return 0;

}

说明

这里我们将 A 类型的指针转换为 B 类型的指针,并调用了 B 的方法。这种转换是危险的,因为 AB 并不相关,强行转换会导致内存错误或未定义行为。风险:指针的二进制位未改变,但内存布局不同,因此调用 bPtr->display() 时可能会崩溃或输出不正确的结果。


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

reinterpret_cast 可以将指针转换为整数类型,或将整数转换为指针。通常用于系统编程或底层操作,如操作内存地址。 

#include <iostream>

int main() {

int x = 42;

int* intPtr = &x;

// 将指针转换为整数类型 uintptr_t (C++标准中定义的足够大以容纳指针的类型)

uintptr_t address = reinterpret_cast<uintptr_t>(intPtr);

std::cout << "Address as integer: " << address << std::endl;

// 将整数转换回指针

int* newPtr = reinterpret_cast<int*>(address);

std::cout << "Dereferenced value: " << *newPtr << std::endl;

return 0;

}

说明

uintptr_t 是一个无符号整数类型,它可以存储指针的数值。通过 reinterpret_cast,我们可以将指针转换为整数,然后再转换回来。这种转换通常在底层操作中使用,例如在操作系统内核中管理内存地址或处理硬件寄存器。风险:如果不小心修改了 address,然后再将其转换回指针,可能会导致非法的内存访问。


3:类型之间的强制转换(例如 float* 转换为 int*) 

#include <iostream>

int main() {

float f = 3.14f;

int* intPtr = reinterpret_cast<int*>(&f); // 将 float* 转换为 int*

std::cout << "Float value: " << f << std::endl;

std::cout << "Reinterpreted as int: " << *intPtr << std::endl; // 输出 f 的位模式所代表的整数

return 0;

}

说明

reinterpret_castfloat 类型的指针转换为 int*,并通过 int* 解引用来读取数据。这会输出 float 的位模式所对应的整数值,而不是 float 的数值本身。这种操作会重新解释数据的位模式,因此结果可能非常难以理解。如果需要处理底层内存或二进制文件,这种操作可能有用,但在普通应用程序中应避免。


五、总结 

每种转换工具都有其特定的用途,在实际开发中选择合适的转换方式可以避免潜在的错误并提高代码的可读性和安全性。

static_cast:用于基本数据类型和类层次中的安全转换,通常用于类型之间的已知转换,不涉及运行时检查。dynamic_cast:主要用于类层次的下行转换,提供运行时类型检查,确保转换安全性,常用于多态环境。const_cast:用于移除或添加 const/volatile 修饰符,必须谨慎使用,不能修改真正的常量。reinterpret_cast:用于底层的、低级别的强制转换,不进行类型检查,应避免不必要的使用,因其可能导致未定义行为。

 

 六、共勉

以下就是我对 【C++】强制转换 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新【C++】,请持续关注我哦!!!   



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。