Bootstrap

C/C++(九)C语言与C++中的类型转换

目录

一、C语言中的类型转换

1、隐式类型转换

2、显式类型转换

3、示例代码与缺陷

二、C++中的类型转换

1、static_cast

2、reinterpret_cast

3、const_cast

​编辑

4、dynamic_cast

 三、小总结


C语言的类型转换可读性较差,难以区分;C++为了增强程序的可读性,在兼容C语言类型转换的基础上,又增加了独属于C++的四种类型转换。

本章会从C语言类型转换到C++类型转换,逐一进行介绍。

一、C语言中的类型转换

在C语言里,当赋值运算符左右两侧类型不同 / 函数形参与实参类型不匹配 / 函数返回值类型与接受返回值的类型不一致时,就会发生类型转换。

C语言中的类型转换分为两种:隐式类型转换显式类型转换

1、隐式类型转换

编译器在编译阶段自己进行的类型转换,通常是意义相近 / 相关的类型之间,可以进行类型转换,能转就转,转不了就会编译失败。

缺陷有些情况下可能会出现一些问题,比如数据精度丢失。

2、显式类型转换

需要用户自己来处理的转换。

缺陷:代码可读性较差,所有的显式转换都是以同一种形式书写,这让我们跟踪错误转换变得困难。

3、示例代码与缺陷

void Test ()
{
     int i = 1;

     double d = i;               // 隐式类型转换,数据精度丢失
     printf("%d, %.2f\n" , i, d);

     int* p = &i;
     int address = (int) p;     // 显式的强制类型转换
     printf("%x, %d\n" , p, address);
}

二、C++中的类型转换

C++标准为了增强程序可读性,引入了四种命名的强制类型转换操作符

static_cast,reinterpret_cast、const_cast、dynamic_cast 

1、static_cast

static_cast 主要用于非多态类型的转换(编译时就进行了转换,所以是静态转换)

编译器中任何的隐式类型转换都可以用 static_cast,代码可读性更高。一般用于意义相近的类型的转换;不能用于两个不相关类型之间的转换。

#include <iostream>
using namespace std;

int main()
{
	double d = 20.15;
	int a = static_cast<int>(d);
	cout << a << endl;
	return 0;
}

2、reinterpret_cast

reinterpret_cast 一般用于对有一定的关联但意义不相近的类型之间进行转换,可以将整形转换为指针,也可以把指针转换为数组;可以在指针和引用之间进行肆无忌惮的转换。

(使用的时候一定要慎之又慎,因为其实际上是从底层对数据进行了重新解释,对平台有很强依赖性,可移植性很差;而且如果类型之间差距过大,还可能造成数据损坏和未定义行为!)

#include <iostream>
#include <cstdint>

int main() {
    // 示例 1: 将整型转换为指针
    int value = 42;
    void* ptr = reinterpret_cast<void*>(value);
    std::cout << "Pointer from integer: " << ptr << std::endl;

    // 再次转换回整型
    int original_value = reinterpret_cast<intptr_t>(ptr);
    std::cout << "Integer from pointer: " << original_value << std::endl;

    // 示例 2: 将指针转换为整型
    int* int_ptr = new int(123);
    uintptr_t address = reinterpret_cast<uintptr_t>(int_ptr);
    std::cout << "Address as integer: " << address << std::endl;

    // 再次转换回指针
    int* original_int_ptr = reinterpret_cast<int*>(address);
    std::cout << "Value at address: " << *original_int_ptr << std::endl;

    delete int_ptr;

    // 示例 3: 在不同类型的指针之间转换
    class A {};
    class B {};

    A* a = new A();
    B* b = reinterpret_cast<B*>(a);

    // 注意:这里的转换没有任何实际意义,除非你知道这两个类有某种特殊的关联
    std::cout << "Pointer to A: " << a << std::endl;
    std::cout << "Pointer to B (cast from A): " << b << std::endl;

    delete a;

    // 示例 4: 在指针和引用之间转换
    int ref_value = 789;
    int& ref = ref_value;

    // 将引用转换为指针
    int* ref_ptr = &ref;
    std::cout << "Pointer from reference: " << ref_ptr << std::endl;

    // 将指针转换为引用
    int& ref_from_ptr = *reinterpret_cast<int*>(ref_ptr);
    std::cout << "Reference from pointer: " << ref_from_ptr << std::endl;

    return 0;
}

3、const_cast

const_cast 最常用的用途是去除 / 增加一个常变量的 const 常量属性,也是四大类型转换中唯一的一个可以操作常变量的转换。参数必须是常变量的指针 / 引用。

#include <iostream>
using namespace std;

int main()
{
	const int num = 2014;
	int& p = const_cast<int&>(num);
	cout << num << " " << p << endl;
	p = 2015;
	cout << num << " " << p << endl;
	return 0;
}

 

观察运行结果我们会有个疑问,p 不是 num 的别名吗,为什么 P 改变了,并没有改变 num呢?

这是因为,const_cast 也是个静态转换,在编译期间,编译器会对 const 类型的常变量进行优化,会把 const 常变量直接放入寄存器甚至直接以常量替换,取常变量的值不会从内存里面取,因此 p 在内存中的更改并不会导致寄存器中 num 的取值变化。所以严格意义上说,对常变量的更改也算是一种未定义行为,要尽量避免。 

(PS:如果想让结果正确,可以在 const 前面加上 volatile 关键字,这个关键字会指示编译器不要优化 const ,这样每次取值就直接从内存中取了,结果也就正确了)

#include <iostream>
using namespace std;

int main()
{
	volatile const int num = 2014;
	int& p = const_cast<int&>(num);
	cout << num << " " << p << endl;
	p = 2015;
	cout << num << " " << p << endl;
	return 0;
}

4、dynamic_cast

dyanmic_cast是一个动态转换,就是在运行时才进行转换,一般用于向下转换,即把父类的指针 / 引用转换成子类的指针 / 引用。

向上转换:子类对象的指针 / 引用 -> 父类对象的指针 / 引用(不需要强转,赋值兼容原则即可)

向下转换:父类对象的指针 / 引用 -> 子类对象的指针 / 引用(利用 dynamic_cast)

一些注意事项:

1、dynamic_cast 的父类必须含有虚函数,即只能用于多态类型的转换。

2、父类对象无论如何都转换成子类对象,但是父类的指针 / 引用可以通过 dynamic_cast 安全转换为子类的指针 / 引用。(static_cast / 直接转换都是不安全的,子类会比父类多出一块空间,如果转换后的父类指针 / 引用访问了这块不存在的空间,会造成越界;而dynamic_cast会先检查转换是否合法,合法才转换,不合法会直接返回)

#include <iostream>
using namespace std;

class A
{
public:
    virtual void test()
    {
        printf("我是A\n");
    }
};

class B : public A
{
public:
    virtual void test()
    {
        printf("我是B\n");
    }
};

void Test(A* pa)
{
    // 使用 static_cast 进行转换
    B* pb1 = static_cast<B*>(pa);  // static_cast 不会检查是否越界,可能会转换失败
    if (pb1)
    {
        pb1->test();  // 如果 pa 实际上不是 B 的实例,这可能会导致未定义行为
    }
    else 
    {
        cout << "static_cast 转换失败" << endl;
    }

    // 使用 dynamic_cast 进行转换
    B* pb2 = dynamic_cast<B*>(pa);  // dynamic_cast 会检查
    if (pb2)
    {
        pb2->test();  // 如果转换成功,调用 B 的 test 方法
    }
    else 
    {
        cout << "dynamic_cast 转换失败" << endl;
    }
}

int main()
{
    A* a = new A();
    A* b = new B();

    cout << "测试 A 类型的对象:" << endl;
    Test(a);  // pa 实际上是 A 类型的对象

    cout << "测试 B 类型的对象:" << endl;
    Test(b);  // pa 实际上是 B 类型的对象

    delete a;
    delete b;

    return 0;
}

  1. 测试 A 类型的对象

    • static_cast 成功将 A* 转换为 B*,但调用 test 方法时,由于 pa 实际上是 A 类型的对象,所以输出 我是A

    • dynamic_cast 失败,因为 pa 实际上不是 B 类型的对象,所以输出 dynamic_cast 转换失败

  2. 测试 B 类型的对象

    • static_cast 成功将 B* 转换为 B*,调用 test 方法时,输出 我是B

    • dynamic_cast 也成功,因为 pa 实际上是 B 类型的对象,所以输出 我是B

 三、小总结

强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是

否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用

域,以减少发生错误的机会。强烈建议:避免使用强制类型转换。

;