【C++】四种强制类型转换_c++强制类型转换-程序员宅基地

技术标签: static_cast  C++  const_cast  # C++  # 面试题  强制类型转换  dynamic_cast  

C++ 四种强制类型转换

  C语言中的强制类型转换(Type Cast)有显式隐式两种,显式一般就是直接用小括号强制转换,TYPE b = (TYPE)a; 隐式就是直接 float b = 0.5; int a = b; 这样隐式截断(by the way 这样隐式的截断是向 0 取整的,我喜欢这么叫因为 0.9 会变成 0,1.9 变成 1,-0.9 变成 0,-1.9 变成 -1)。
  C++对C兼容,所以上述方式的类型转换是可以的,但是有时候会有问题,所以推荐使用C++中的四个强制类型转换的关键字:
  1、static_cast,2、const_cast,3、reinterpret_cast,4、dynamic_cast

1)static_cast

  这应该四种中是最常见的。用法为 static_cast<type-id> (expression)
  该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性
  主要用法如下:
    (1)用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
        进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
        进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
    (2)用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
    (3)把空指针转换成目标类型的空指针。
    (4)把任何类型的表达式转换成void类型。
  最常用的应该还是基本数据类型之间的转换,如下:

	const auto a1 = 11;	// int
	const auto a2 = 4;	// int

// C style
	double res1 = (double)(a1) / (double)(a2);	// 其实写一个 (double) 就行
	cout << "res1 = " << res1 << endl;			// res1 = 2.75

// C++ style
	auto res2 = static_cast<double>(a1) / static_cast<double>(a2);
	cout << "res2 = " << res2 << endl;			// res2 = 2.75
	cout << typeid(res2).name() << endl;		// double

  如果在 Visual Studio 中编写,且安装了 Resharper C++ 的话,如果按照 C 风格写的话,会提示你,并帮助你自动修改为 C++ 风格,如下图所示。

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

2)const_cast

  上边的 static_cast 不能将 const int* 转成 int*,const_cast 就可以,用法为 const_cast<type-i> (expression)。如下面代码

	const int a = 10;
	const int * p = &a;
	*p = 20;						 // Compile error: Cannot assign readonly type 'int const'
	int res1 = const_cast<int>(a);   // Compile error: Cannot cast from 'int' to 'int' via const_cast
									 // only conversions to reference or pointer types are allowed
	int* res2 = const_cast<int*>(p); // ok

  也就是说,const_cast<>里边的内容必须是引用或者指针,就连把 int 转成 int 都不行。
  对于 const_cast 的使用,其实还有很多不明白的(C++未定义的行为),比如下面三张图。

  更神奇的是,在 VS2017 中,debug 模式下,执行完 *p = 20; 这一步后,a 的值就变成了 20,但是输出 a 和 *p 还都是 11。我理解的就是 const int 类型的变量,在编译器就优化了,里边所有单独的 a 都已经变成了 11,所以怎么修改都不影响了,这种情况包括:1、直接用 11 这种常量来初始化 a,2、用同样为 const int 类型的 c 来初始化 a。如果是用 int c = 11; 这样的 c 来初始化 a,那 a 就是可变的??

  总结来说,const_cast 通常是无奈之举,只是 C++ 提供了一种修改 const 变量的方式,但这种方式并没有什么实质性的用处,还是不用的好。const 的变量不要让它变。

3)reinterpret_cast

  reinterpret_cast 主要有三种强制转换用途:
  1、改变指针或引用的类型
  2、将指针或引用转换为一个足够长度的整形
  3、将整型转换为指针或引用类型

  用法为 reinterpret_cast <type-id> (expression)
  type-id 必须是一个指针、引用、算术类型、函数针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
  我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)。因此, 你需要谨慎使用 reinterpret_cast。

4)dynamic_cast

  用法为 dynamic_cast<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 进行转换的,基类中一定要有虚函数,否则编译不通过(类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义)。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表(C++中的虚函数基本原理这篇文章写得不错,https://blog.csdn.net/xiejingfa/article/details/50454819)。

class base {
    
public:
	void print1() {
     cout << "in class base" << endl; }
};

class derived : public base {
    
public:
	void print2() {
     cout << "in class derived" << endl; }
};

int main() {
    
	derived *p, *q;
	// p = new base;	//  Compilr Error: 无法从 "base * " 转换为 "derived * "

	// Compile Error: Cannot cast from 'base*' to 'derived*' via dynamic_cast: expression type is not polymorphic(多态的)
	// p = dynamic_cast<derived *>(new base);

	q = static_cast<derived*>(new base);	// ok, but not recommended

	q->print1();	// in class base
	q->print2();	// in class derived
}

  从上边的代码可以看出用一个派生类的指针是不能直接指向一个基类的对象的,会出现编译错误。用 dynamic_cast 的话也会编译错误,提示我们基类不是多态的,也就是基类中没有虚函数。可以看到 static_cast 是可以编译通过的,且输出结果看起来都是对的,但是 VS 还是会提示说 Do not use static_cast to downcast from a base to a derived class,如下图所示。

  static_cast 强制类型转换时并不具有保证类型安全的功能,而 C++ 提供的 dynamic_cast 却能解决这一问题,dynamic_cast 可以在程序运行时检测类型转换是否类型安全。当然 dynamic_cast 使用起来也是有条件的,它要求所转换的 expression 必须包含多态类类型(即至少包含一个虚函数的类)。

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

class B :public A {
    
public:
	void print(){
    
		cout << "in class B" << endl;
	};
};

class C {
    
	void pp(){
    
		return;
	}
};

int main() {
    
	A *a1 = new B; // a1是A类型的指针指向一个B类型的对象
	A *a2 = new A; // a2是A类型的指针指向一个A类型的对象
	B *b1, *b2, *b3, *b4;
	C *c1, c2;
	b1 = dynamic_cast<B*>(a1);	// not null,向下转换成功,a1 之前指向的就是 B 类型的对象,所以可以转换成 B 类型的指针。
	if (b1 == nullptr) cout << "b1 is null" << endl;
	else               cout << "b1 is not null" << endl;

	b2 = dynamic_cast<B*>(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<B*>(a1);	// not null
	if (b3 == nullptr) cout << "b3 is null" << endl;
	else               cout << "b3 is not null" << endl;

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

	a1->print();	// in class B
	a2->print();	// in class A
	
	b1->print();	// in class B
	// b2->print(); // null 引发异常
	b3->print();	// in class B
	b4->print();	// in class A

	c1 = dynamic_cast<C*>(a1);	// 结果为null,向下转换失败
	if (c1 == nullptr) cout << "c1 is null" << endl;
	else               cout << "c1 is not null" << endl;

	// c2 = static_cast<C*>(a1);	// 类型转换无效, Cannot cast from 'A*' to 'C*' via static_cast
	// delete 省略
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Bob__yuan/article/details/88044361

智能推荐

linux 使用 navicat 报错的各种问题_linux 安装navicat 提示报错-程序员宅基地

文章浏览阅读1.1k次。我是凝思系统,使用的一些系统库可能比较旧吧,容易报错,记录一下解决过程查阅资料是 glib 版本的问题[Solved]navicat report error after update system / Applications & Desktop Environments / Arch Linux Forums我用的是 glib 的 2.68.4 分支,旧的版本有些符号有问题https://github.com/GNOME/glib/tree/2.68.4参照 1_04_GLib库入门与实践_GLi_linux 安装navicat 提示报错

【Unity3D游戏开发实战】Unity3D实现休闲类游戏《2048》——算法、源代码_unity小游戏2048源码-程序员宅基地

文章浏览阅读1w次,点赞29次,收藏133次。推荐阅读CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客QQ群:1040082875大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。一、前言最近有粉丝要参加游戏创作大赛,问我需要准备学习什么知识,以及参加比赛的注意事项:参加这类比赛是非常有好处的,不仅提高了技术,也增长了见识。因为是兴趣驱动学习,在完善自己心爱游戏的过程中,要不断的去学习,不断的提高自己。更能在这个过程中找到志同道合的好朋友。那今天就._unity小游戏2048源码

【python】Open3D,Write PLY failed解决方法_write pcd failed: unable to generate header.-程序员宅基地

文章浏览阅读6.5k次。写了一个简单的函数,将三维点云(ndarray)保存为.ply文件:def save_points_as_ply(points, ply_path): """ 将点云保存为.ply文件,保存成功会打印'ply_path 已保存' :param points: ndarray, (-1,3) :param ply_path: str,'xxx/xxxx.ply' """ pcd = o3d.geometry.PointCloud() pcd.point_write pcd failed: unable to generate header.

详解 Android Views 元素的 layout_weight 属性-程序员宅基地

文章浏览阅读75次。所有View(视图)元素中都有一个XML属性android:layout_weight,其值为0,1,2,3...等整数值。使用了之后,其对应界面中的元素比例就会发生变化,变大或者变小。layout_weight属性其实就是一个元素重要度的属性,用于在线性布局中为不同的view元素设置不同的重要度。  所有的视图都有一个layout_weight值,其默认值为0,表示视图多大就占据..._android view获取当前的layout_with 的值

hosts文件修改后无法保存问题_linux hosts文件无法保存-程序员宅基地

文章浏览阅读8.6k次,点赞11次,收藏14次。hosts文件在windows目录下的位置(我的是win10系统,其他系统大同小异)C:\Windows\system32\drivers\etc\hostslinux系统hosts位置/etc/hostsLinux系统一般来说linux系统出现无法修改的情况是比较少的,基本没有,只要你处于root权限下是都可以修改的,因为root默认是有rwx权限的如果不能修改,r..._linux hosts文件无法保存

Java中自定义异常的两个小例子_public int getlength(){return length}-程序员宅基地

文章浏览阅读962次。Java 异常处理异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如说,你的代码少了一个分号,那么运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那么你是因为你用0做了除数,会抛出 java.lang.ArithmeticException 的异常。异常发生的原因有很多,通常包含以下几..._public int getlength(){return length}

随便推点

机器学习概述-程序员宅基地

文章浏览阅读5.2k次,点赞6次,收藏80次。机器学习是一种人工智能技术,通过对数据的学习和分析,让计算机系统自动提高其性能。简而言之,机器学习是一种从数据中学习规律和模式的方法,通过数据来预测、分类或者决策。_机器学习

操作系统安全---实验三:Windows7操作系统安全_操作系统的基本安全设置实验总结-程序员宅基地

文章浏览阅读3.8k次,点赞3次,收藏39次。目录一、实验目的及要求二、实验原理三、实验环境四、实验步骤及内容4.1账户与口令4.2审核与日志4.3安全模板五、实验总结六、分析与思考一、实验目的及要求了解Windows账户与密码的安全策略设置,掌握用户和用户组的权限管理、审核,以及日志的启用,并学会使用安全模版来分析配置计算机。二、实验原理Windows系列是目前世界上使用用户最多的桌面操作系统。由于历史原因,Windows的很多用户都直接以管理员权限运行系统,对计算机安全构成很大隐患。从Wi..._操作系统的基本安全设置实验总结

Python + Selenium自动化测试 -- 自定义Log类_selenium python 创建log类-程序员宅基地

文章浏览阅读3.2k次,点赞2次,收藏15次。本文用日志来记录我们测试脚本做的事情,其实最好的办法是写事件监听(对于小白的我,暂时不会,先从日志学起)。 下面写一个日之类,用来输出不同级别的日志信息到本地文件夹下的日志文件里。 目标输出效果: 解决思路: 1. 封装Log类,类名为Logger; 2. 在Logger类中创建记录器logger; 3. 创建一个handler,用于写入日志文件,写到磁盘;再创建一个handler,_selenium python 创建log类

canal 整合 springboot_canalboot-程序员宅基地

文章浏览阅读632次。mysql 开启bin_logvi /etc/my.cnf末尾增加如下配置log_bin=mysql-bin binlog-format=ROW #选择row模式server-id = 1expire_logs_days=5 #日志过期时间为5天 重启mysql [5.7]service mysqld restart 修改canal 配置vi canal/conf/canal.properties#唯一标识 新增canal.id =123 _canalboot

零基础HTML教程(14)--hr:黄昏的地平线_html水平线-程序员宅基地

文章浏览阅读1w次,点赞11次,收藏13次。本文目录1. 水平线的概念2. 水平线的用法3. 小结1. 水平线的概念HTML中有一个比较特别的标签,叫做水平线,写作<hr>。该标签可以在网页上显示一条横线,一般用来分隔不同的网页内容。2. 水平线的用法使用方法很简单,在需要分割的地方,添加一个<hr>标签即可。例如:<!DOCTYPE html><html><head> <title>水平线实例</title> <meta c_html水平线

python查看已安装包的版本_python 如何查看networks的版本-程序员宅基地

文章浏览阅读4k次,点赞7次,收藏9次。pip freeze就不要说了,当你安装1000个包的时候就不会用这种蠢办法。第一种办法,打开终端/CMDpip freeze | findstr numpy这是windows下的,numpy只是个例子,要查什么自己改pip freeze | grep numpylinux下的第二种方法,python里去看。打开python命令行界面。一般来说包的版本都会用一个.__versio..._python 如何查看networks的版本

推荐文章

热门文章

相关标签