IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 【C++】浅谈强制类型转换(4种) -> 正文阅读

[C++知识库]【C++】浅谈强制类型转换(4种)

01、C风格类型转换

在学习C语言的时候,很多场合,我们会用到强制类型转换,那个时候,我们是很少考虑这样做是否有问题的,因为这就是C语言不好的地方,什么都可以交给我们处理,因此,错误千奇百怪。

C风格的强制转换(Type Cast)容易理解,不管什么类型的转换都可以使用使用下面的方式.

int nCarNum = 10;
double fCarWeight = 22.3;

int tmp = (int)fCarWeight;  //潜在问题:精度丢失

int a;
char b = 'b';
a = b; //隐式转换

//亦或是

const char* buf = "ABCD";
char* tp = buf;  //报错:不存在由const char* 到 char* 的转换,所以就有了下面的表达
char* tp = (char*)buf;  //OK,当然,这个没有大问题,是可以这样的,但是如果你要去修改这个buf的值就会出现问题了

C风格类似此等式: template A = (template)B;(显示转换)

02、C++ 四种强制转换类型函数

2.1、 static_cast

一般来说,我们称此方法为静态转换,形如: static_cast<type-id> (expression),也是这四种转换中最常见的,但是他有一个很bug的问题,不会在运行时进行运行时类型检查来保证转换的安全性该运算符把 expression 转换为 type-id 类型

主要用法如下:

  • 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。

进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
????????进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。

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

  • 把空指针转换成目标类型的空指针

  • 把任何类型的表达式转换成void类型

    举例如下:

    //第一种:类层次中的转换
    Class B: public A
    {
    	//略
    }
    A* p1 = new A();
    B* p2 = new B();
    p1 = static_cast<A*>(p2)  //类型安全,反过来就不一定了,使用时,一定要考虑到
    
    //第二种:基础数据类型转换
    typedef enum DATATYPE
    {
    	DATATYPE_ONE = 0,
    	DATATYPE_TWO = 1,
    }TYPE;
    
    int a;
    TYPE type;
    a = static_cast<int>(type);  //OK
    
    //第三种:空指针转目标类型的空指针
    int* p1 = NULL;
    DataPos* b = NULL;  //自定义类型
    //将int* 转 DataPos*
    b = static_cast<DataPos*>(p1);  //OK
    
    //第四种:任意类型表达式转void类型
    DataPos* dp = new DataPos();
    
    void* buf = static_cast<void*>(dp);  //OK
    

可能有很多朋友会问,在C++中可以使用C的方式嘛?

如果在 Visual Studio 中编写,且安装了 Resharper C++ 的话,如果按照 C 风格写的话,会提示你,并帮助你自动修改为 C++ 风格,如下提示:
C-style cast used instead of a C++ cast

需要注意的是,static_cast 不能转换掉 expression 的 const、volitale 或者 __unaligned 属性,如下图所示:
在这里插入图片描述

2.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++未定义的行为),如下:

const int a = 11;
const int *p1 = &a;
int* p2 = const_cast<int*>(p1);
*p2 = 20;  //OK,修改成功

cout << "a: " << a << "    &a :" << &a << endl;  // a: 11     &a: ********
cout << "*p1: " << *p1 << "    p1 :" << p1 << endl;  //*p1:20   p1: *********
cout << "*p2: " << *p2 << "    p2 :" << p2 << endl;  //*p2: 11   p2: *********

//其中,***********代表地址,地址是相同的,重点看值

这里涉及一个编译器的优化步骤,就是常量编译器会自动优化掉。
在 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 就是可变的。

2.3、 reinterpret_cast

reinterpret_cast 主要有三种强制转换用途:

  • 改变指针或引用的类型
  • 将指针或引用转换为一个足够长度的整形
  • 将整型转换为指针或引用类型

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

示例如下(示例来自:MSDN 的一个哈希函数辅助):

//expre_reinterpret_cast_Operator.cpp
//compile with: /EHsc
#include <iostream>

//Returns a hash code based on an address
unsigned short Hash(void* p){
	unsigned int val = reinterpret_cast<unsigned int>(p);
	return (unsigned short)(val ^ (val >> 16));
}

using namespace std;
int main()
{
	int a[20];
	for(int i = 0; i < 20; i++)
		cout << Hash(a+i) << endl;
}

上面示例中,通过reinterpret_cast 将void* 转化为 unsigned int 类型数据。

2.4、dynamic_cast

用法为:dynamic_cast<type-id> (expression)
几个特点如下:

  • 其他三种都是编译时完成的,dynamic_cast 是运行时处理的,运行时要进行类型检查
  • 不能用于内置的基本数据类型的强制转换
  • dynamic_cast 要求 <> 内所描述的目标类型必须为指针或引用,dynamic_cast 转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回 nullptr。
  • 在类的转换时,在类层次间进行上行转换(子类指针指向父类指针)时,dynamic_cast 和 static_cast 的效果是一样的。在进行下行转换(父类指针转化为子类指针)时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全。 向下转换的成功与否还与将要转换的类型有关,即要转换的指针指向的对象的实际类型与转换以后的对象类型一定要相同,否则转换失败。在C++中,编译期的类型转换有可能会在运行时出现错误,特别是涉及到类对象的指针或引用操作时,更容易产生错误。Dynamic_cast操作符则可以在运行期对可能产生问题的类型转换进行测试。
  • 使用 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 省略
}

03、总结

关于类型转换,C++提供了四种方式,这四种方式根据不同的场景,使用的函数也不尽相同,小结如下:
1、static_cast:类型转换不检查安全性,但是基本上都允许你转换,但常量对象或常量成员是不允许的。
2、const_cast:弥补了上述不能转换常量对象和常量成员,但是const一般是不建议转换去修改的,总结来说,const_cast 通常是无奈之举,只是 C++ 提供了一种修改 const 变量的方式,但这种方式并没有什么实质性的用处,还是不用的好。const 的变量不要让它变。
3、reinterpret_cast:type-id 必须是一个指针、引用、算术类型、函数针或者成员指针,且转换途中,映射很容易出错,很危险。
4、dynamic_cast:动态类型转换,编译期间会检查安全性,但是有个前提条件是必须有虚函数的类方可转换,取决于虚函数表的实现。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-29 23:23:10  更:2021-07-29 23:23:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/28 22:40:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码