Algorithm-Pattern
  • Introduction
  • C & C++
    • C语言
      • C/C++编译器
      • 宏的使用
      • 编译过程
      • 指针 & 数组
      • 柔性数组
      • 函数指针 & 回调函数
      • C标准库之stdio
      • C标准库之string
      • C标准库之errno
      • C标准库之stdarg
      • C标准库之regex
    • C++基础语法
      • 自增(++) & 自减(--)
      • c语言到c++
      • 可变模板参数
      • 强制类型转换
      • C/C++类型转换的本质
      • 指针 & 引用
      • const的用法
      • static的用法
      • 重要的关键字(一)
      • 重要的关键字(二)
      • 内存申请和释放
      • 内联函数
      • 函数 & 运算符重载
      • 面向对象之封装
      • 构造函数 & 析构函数
      • 面向对象之继承
      • 面向对象之多态
      • 泛型编程
      • 异常
      • 再谈函数指针
    • C++并发编程
      • C++的锁
      • 并发与多线程
    • C++高级特性
      • 函数对象
      • 移动语义 & 完美转发
      • lambda表达式
      • RTTI技术
      • RAII技术
      • 智能指针
      • 模板的特化
      • C++静态库和动态库
      • 内存溢出和内存泄漏
    • STL基础
      • String
      • array/vector/list
      • deque/priority_queue
      • set/map
      • unordered_set/unordered_map
      • algorithm_1
      • functional
      • allocator
    • C++标准库
      • IO
      • Tuple
      • regex
      • bitset
      • numeric
    • STL深入源码
      • vector内部实现
      • deque内部实现
      • sort函数实现
    • 第三方库
      • JsonCpp
      • ProtoBuf
  • 数据结构
    • 线性表
    • 字符串
    • 栈和队列
    • 二叉树
    • 平衡二叉树
    • 平衡多路搜索树
    • 树结构的延申
    • 图
    • 二进制
    • 散列表
  • 算法基础
    • 排序算法
    • 查找算法
    • 数学问题
    • 并查集
    • 递归算法
    • 附加——主定理
    • Catalan数
  • 算法设计思想
    • 滑动窗口思想
    • BFS/DFS
    • 二分法
    • 回溯法
    • 贪心算法
    • 分治法
    • 动态规划
    • 分支限界算法
    • 有限状态机(FSM)
  • LeetCode系列
    • 死磕二叉树
    • 股票买卖问题
    • 打家劫舍问题
    • 跳跃游戏问题
    • 括号匹配问题
    • 石子游戏问题
    • 子序列问题
    • 数组 & 矩阵
    • 排列 & 组合
  • 经典算法问题
    • 几何问题
    • 区间问题
    • 背包问题
    • 石子堆问题
    • 表达式求值
  • 面试题
    • 数据结构和算法基础
    • 程序设计题
      • 实现双线程交替打印
      • C++实现读写锁
      • 实现阻塞队列
      • 实现环形队列
      • 实现线程池
      • 实现智能指针
      • 实现string类
      • 实现高性能local cache
      • 实现内存池
      • 生产者-消费者模型
      • 设计定时器
    • 经典的算法题
    • C++面试题总结
    • 面试算法题总结
由 GitBook 提供支持
在本页
  • 1、static_cast
  • 2、const_cast
  • 3、reinterpret_cast
  • 4、dynamic_cast
  • 总结

这有帮助吗?

  1. C & C++
  2. C++基础语法

强制类型转换

上一页可变模板参数下一页C/C++类型转换的本质

最后更新于4年前

这有帮助吗?

C语言中的强制类型转换(Type Cast)有显式和隐式两种,显式一般就是直接用小括号强制转换:Type(Value)或(Type)Value。隐式转换存在隐式截断的问题。

C++对C兼容,所以上述方式的类型转换是可以的,但是有时候会有问题,所以C++提供了四个强制类型转换的关键字:static_cast,const_cast,reinterpret_cast 和 dynamic_cast。

1、static_cast

用法为 static_cast<type-id> (expression)。该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性。 如果在 Visual Studio 中编写,且安装了Resharper C++ 的话,如果按照 C 风格写的话,会提示你,并帮助你自动修改为 C++ 风格。

主要用法如下:

  1. 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的; 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。需要说明的继承必须为public。

  2. 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

  3. 把空指针转换成目标类型的空指针。

  4. 把任何类型的表达式转换成void类型。

  5. 用于非多态类型的转换。

需要注意的是,static_cast 不能转换掉 expression 的 const、volitale 或者 __unaligned 属性。

char c = 'a';
int* q = (int*) &c;
int *p = static_cast<int *>(&c); //编译错误

在c++ primer 中说道:c++ 的任何的隐式转换都是使用 static_cast 来实现。

2、const_cast

const_cast 是C++中专用于处理与 const 相关的强制类型转换关键字,其功能为:为一个变量重新设定其const描述。即:const_cast可以为一个变量强行增加或删除其const限定。

需要明确的是,即使用户通过const_cast强行去除了const属性,也不代表当前变量从不可变变为了可变。const_cast只是使得用户接管了编译器对于const限定的管理权,故用户必须遵守“不修改变量”的承诺。如果违反此承诺,编译器也不会因此而引发编译时错误,但可能引发运行时错误。

主要用法如下:

  1. const_cast可用于更改const成员函数内的非const类成员。

  2. const_cast可用于将const数据传递给不接收const的函数。

  3. const_cast<>里边的内容必须是引用或者指针。

  4. const_cast也可以用来抛弃volatile和__unaligned属性。

class Student
{
private:
	int roll;
public:
	Student(int r) :roll(r) {}
	void fun() const
	{
		(const_cast<Student*> (this))->roll = 5;
	}
	int getRoll() { return roll; }
};

int main() {
    Student student(3);
    std::cout << "Old roll number: " << student.getRoll() << std::endl;
    student.fun();
    std::cout << "New roll number: " << student.getRoll() << std::endl;

    // const_cast只能调节类型限定符,不能更改基础类型
    int a1 = 40;
    //const int* b1 = &a1;
    //char* c1 = const_cast <char*> (b1); // 编译程序时出错

    const volatile int* d1 = &a1;
    std::cout << "typeid of d1 " << typeid(d1).name() << '\n'; // int const volatile *
    int* e1 = const_cast <int*> (d1);
    std::cout << "typeid of e1 " << typeid(e1).name() << '\n'; // int *
    
    return 0;
}

在const成员函数fun()中,编译器将“this”视为“ const student const this”,即“this”是指向常量对象的常量指针,因此编译器不允许通过以下方式更改数据成员“这个”指针。const_cast将“this”指针的类型更改为“student const this”。

const_cast比简单类型转换更安全。从某种意义上讲,如果强制类型与原始对象不相同,则强制转换不会发生,这是比较安全的。

reinterpret,即“重新解释”,顾名思义,这个强制类型转换的作用是提供某个变量在底层数据上的重新解释。当我们对一个变量使用reinterpret_cast后,编译器将无视任何不合理行为,强行将被转换变量的内存数据重解释为某个新的类型。用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。 它不检查指针类型和指针所指向的数据是否相同。需要注意的是,reinterpret_cast要求转换前后的类型所占用内存大小一致,否则将引发编译时错误。

data_type *var_name = reinterpret_cast <data_type *>(pointer_variable);

使用 reinterpret_cast 的目的:

  1. reinterpret_cast是一种非常特殊且危险的类型转换操作符。并且建议使用适当的数据类型使用它,即(指针数据类型应与原始数据类型相同)。

  2. 它可以将任何指针类型转换为任何其他数据类型。

  3. 当我们要使用位时使用它。

  4. 它仅用于将任何指针转换为原始类型。

  5. 布尔值将转换为整数值,即0表示false,1表示true。

class A {
public:
    int a;
    A(int i) :a(i) {}
    void fun_a()
    {
        std::cout << "In class A\n";
    }
};

class B {
public:
    int b;
    B(int i) :b(i) {}
    void fun_b()
    {
        std::cout << "In class B\n";
    }
};

void testReinterpretCast() {
    B *x = new B(5);
    A* y = reinterpret_cast<A*>(x);
    y->fun_a();                      // In class A
    std::cout << y->a << std::endl;  // 5
}

dynamic_cast即用于在运行时实现向下类型转换。其转换格式:

dynamic_cast <type-id> (expression)

将expression转换为type-id类型,type-id必须是类的指针、类的引用或者是void *;如果type-id是指针类型,那么expression也必须是一个指针;如果 type-id 是一个引用,那么expression也必须是一个引用。

特点如下:

  1. 其他三种都是编译时完成的,dynamic_cast 是运行时处理的,运行时要进行类型检查。

  2. 不能用于内置的基本数据类型的强制转换。

  3. dynamic_cast 要求 <> 内所描述的目标类型必须为指针或引用。dynamic_cast 转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回 nullptr。

  4. 在类的转换时,在类层次间进行上行转换(子类指针指向父类指针)时,dynamic_cast 和 static_cast 的效果是一样的。在进行下行转换(父类指针转化为子类指针)时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全。 向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。

  5. 使用 dynamic_cast 进行转换的,基类中一定要有虚函数,否则编译不通过(类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义)。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。

class AA {
public:
    virtual void print() {
        cout << "in class AA" << endl;
    };
};

class BB :public AA {
public:
    void print() {
        cout << "in class BB" << endl;
    };
};

void testDynamicCast() {
    AA* a1 = new BB; // a1是A类型的指针指向一个B类型的对象
    AA* a2 = new AA; // a2是A类型的指针指向一个A类型的对象

    BB* b1, * b2, * b3, * b4;

    b1 = dynamic_cast<BB*>(a1);	// not null,向下转换成功,a1 之前指向的就是 B 类型的对象,所以可以转换成 B 类型的指针。
    if (b1 == nullptr) 
        cout << "b1 is null" << endl;
    else               
        cout << "b1 is not null" << endl;

    b2 = dynamic_cast<BB*>(a2);	// null,向下转换失败
    if (b2 == nullptr) 
        cout << "b2 is null" << endl;
    else               
        cout << "b2 is not null" << endl;

    // 用 static_cast,Resharper C++ 会提示修改为 dynamic_cast
    b3 = static_cast<BB*>(a1);	// not null
    if (b3 == nullptr) 
        cout << "b3 is null" << endl;
    else               
        cout << "b3 is not null" << endl;

    b4 = static_cast<BB*>(a2);	// not null
    if (b4 == nullptr) 
        cout << "b4 is null" << endl;
    else               
        cout << "b4 is not null" << endl;

    a1->print();	// in class BB
    a2->print();	// in class AA

    b1->print();	// in class BB
    //b2->print();    // null 引发异常
    b3->print();	// in class BB
    b4->print();	// in class AA
}

纵观C++的类型转换语法体系,其延续了C++一贯的包罗万象风格,不仅为用户提供了自定义类型转换的极大自由度,也在语法层面为类型转换可能会带来的各种错综复杂的情况作出了严谨的规定。

保守看来,如果对C++的类型转换没有深入的理解,或不希望大量使用隐式类型转换时,我们不应过度的依赖诸如非explicit转换构造函数,自定义的类型转换操作符,以及涉及隐式类型转换的各种重载确定等语法组分。但作为C++语法体系的一个重要部分,深入理解C++关于类型转换的各种话题,必定是十分重要的。

3、reinterpret_cast

4、dynamic_cast

总结

✏️
✏️
✏️
✏️
✏️