当前位置: 首页 > news >正文

C++ 高级强制类型转换

强制类型转换

传统的强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。C语言的强制类型转换(Type Cast),有 显式和隐式两种:

显示类型转换:例如[ float a=(int)b ]
隐式类型转换:例如[ float a=3.75; int b=a; ]

高级的强制类型转换
C++由于对C进行了兼容,所以上述方式的类型转换在程序结构不复杂的情况下是足够的,但是当程序牵涉到了类的层次结构强制转换等复杂情况的时候上述方式的类型转换则会出现问题。为了 使强制转换的风险更加细化,使问题的追溯更加方便,使书写格式更加规范,C++语言中新增了四个强制类型转换的关键字:
1. static_cast :用于良性的转换,一般不会导致意外发生,风险低。
2. const_cast :用于const与非const,volatile与非volatile之间的转换。
3. reinterpret_cast :高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的C++类型转换。
4. dynamic_cast:借助RTTI,用于类型安全的向下进行转型(Downcasting)

这四个关键字的语法格式都是一样的,具体为:

xxx_cast<newType-id>(expression);

其中newType-id是要转换成的新类型,expression是被转换的数据表达式,用法例如:

double scores=95.5;
int n=static_cast<int>(scores);
//结果n为95 

static_cast (静态转换)

最常见的高级强制转换运算符:用法为 static_cast< type-id >(expression)
static只能用于良性的转换,这样的转换风险较低,一般不会发生意外,但该运算符没有运行时类型检查来保证转换的安全性。

其主要用法如下:

1.用于基本数据类型的转换
例如short转int,int转double,const转非const,向上转型等…这种转换的安全性也要开发人员保证
例如:

🔥 下面的程序显示了使用类型强制转换表达式来防止发生整除法的示例
int a = 7;
int b = 3;
double r = static_cast<double>(a) / static_cast<double>(b);	 //结果为2.3333

2.用于类层次结构中的基类和派生类之间指针或引用的转换。
若进行上行转换(把派生类的指针或引用转换成基类表示),转换是安全的
若进行下行转换(把基类的指针或引用转换成派生类表示),同样由于没有动态类型检查,转换是不安全的
例如:

class Base
{public:};

class Dervied:public Base
{public:};

int main()
{
Base* base = new Base; //建立内存空间为基类Base类型,指针类型为基类的类指针base
Dervied* dervied = new Dervied;//建立内存空间为派生类Dervied类型,指针类型为派生类的类指针dervied

Dervied* DforBase = static_cast<Dervied*>(base);//🔥强制转换类型为基类的指针base为类型为派生类的指针DforBase 
Base* BforDervied = static_cast<Base*>(dervied);//🔥强制转换类型为派生的指针dervied为类型为基类的指针	BforDervied 
}

3.把空指针(void)和目标类型指针之间进行转换
例如[ void* ]转[ int* ],[ char* ]转[ void* ]等…
例如:

int *pint=static_cast<int*>(new(10*sizeof(int)));
🔥其中(new(10*sizeof(int)))为不指向任何类型的void指针

上述代码也可以这样写
void* pviod = new int(10*sizeof(int));//赋值10*4的整型值给pviod这个void指针变量
int* pint = static_cast<int*>(p);//强制转换void指针变量pvoid为int类型的指针变量pint
cout<<pviod <<"\n\n"<<*pint ;//打印void指针变量pvoid的地址和pint这个int类型指针指向的值

4.将具体类型指针,转换为void指针类型
例如:

int *a=new int(5);
void* r = static_cast<void*>(a);

注意:🎯
1.不能在两个具体类型的指针之间进行转换(static_cast下指针的转换只能是从空指针转换为具体指针或从具体指针转换为空指针)

int *pint=new int(5);
float *pfloat = static_cast<float*>(pint);  //报错不能在两个具体类型的指针之间进行转换

2.不能将整数地址转换为指针

  p3 = static_cast<float*>(0X2DF9); 

3.static_cast不能转换掉expression的const、volatile属性
4.如果涉及到类的话,static_cast只能在有相互联系的类型中进行相互转换,不一定包含虚函数


const_cast (常量转换)

“const” 限定符 用来限定变量为常量,常用于表示该变量的值不可作为左值被修改。
例如:

const int a = 5;
a=3;//报错,设定为常量的量不可作为左值被修改

而const_cast则正是用于强制去掉这种不能被修改的常数特性,但需要注意的是const_cast 不是用于去除变量的常量性,而是去除指向常数的对象的指针或引用的常量性,也就是说,其去除常量性的对象 必 须 为 指 针 或 引 用

其用法如下:

const_cast<type_id>(expression);

该运算符用来修改类型的const属性或volatile属性,除了const 或volatile修饰之外,type_id和expression的类型都是一样的。

const属性和volatile属性
const和volatile类型修饰符,语法上类似,用于变量或函数参数的声明,也可以用于限制非静态成员函数
const:
表示只读类型(目的是指定类型的安全性,保护对象不会被任意意外的修改)
volatile:
当读取一个变量的时候为了提高读取的速度,编译器优化时有时会把变量读取到一个寄存器中,以后再取变量值的时候,就直接从这个寄存器中取值,而优化器在用到volatile变量的时候,必须每次都小心的重新读取这个变量的值,而不是使用保存到寄存器中的备份值,所以,volatile声明适用于多线程应用中被几个任务共享的变量。

使用const_cast进行转换后:

常量指针被转换为非常量指针,并且仍然指向原来的对象.
常量引用被转换为非常量引用,并且仍然指向原来的对象.
常量对象被转换成非常量对象

用法演示实例
一.使用const_cast静态强制转换关键字将[ const int* ]类型的常量指针转换为[ int* ]类型的普通指针:

.去除常数指针的常数性
//volatile int n = 100; //🔥建立一个volatile变量 n
const int n = 100;
int* p = const_cast<int*>(&n);//🔥&n取出的n的指针类型此时是const int*类型的,这个静态指针通过const_cast被转换为普通指针(int*)
*p = 123;
cout << "静态变量n==" << n << endl;
cout << "指向静态变量n的指针p==" << *p << endl;

🔥const_cast语句中expression表达式 [&n] 用来获取静态常量n的地址,这个指针地址的类型为const int*,即静态常量指针类型
🔥这个类型的指针必须使用const_cast转换为int*类型后才能赋值给int*类型的指针p
🔥由于指针p指向了n,并且n占用的是栈内存,有写入权限,所以可以通过p修改n的值

二.使用const_cast静态强制转换关键字将[ const int & ]类型的常量引用转换为[ int & ]类型的普通引用:

.去除常数引用的常数性
const int n = 100;
int& p = const_cast<int&>(n);
p = 123;
//运行结果一致

三.使用const_cast静态强制转换关键字将[ const Base *a]类型的常量对象指针转换为[ Base *a ]类型的常量对象指针:

 class Base
{
	public:
	int num;
};

const Base* a; //建立一个Base类的常量指针a
Base* b = const_cast<Base*>(a); //去除常量对象指针a的常量性并对a进行操作
b->num = 55;//b指针指向的对象为一般对象,其成员变量值可被读写
//结果为 a->num 为100 b->num=55;

🌟const_cast对对象的具体应用:https://www.cnblogs.com/Braveliu/p/3616953.html

运行结果:在这里插入图片描述
可以看出,虽然我们使用const_cast成功将静态指针n赋值给了p,也通过了这个指针p对n这个静态常量进行了操作,但是 n和*p输出的值却不一致(也就是虽然获取了n的地址,但是改变不了n这个常量), 这是因为C++对常量的处理更像是编译时的define宏定义,是一个值替换的过程,代码中所有使用n的地方在编译时期全部就给替换成了 “100” ,也就是n在程序编译时被修改成了以下形式:

cout << "静态变量n==" <<100<< endl;

之所以要这么设计,是为了保证就算我们在程序运行期间修改n的值,也不会影响输出cout语句输出常量n(保证n的常量性)

为什么需要const_cast强制转换关键字?
const_cast是一种C++运算符,主要是用来去除复合类型中const和volatile属性(没有真正去除,只是去除了常量的指针的常量性),因为静态变量本身的const属性是不能被去除的,想要修改该变量的值,一般是通过const_cast去除指针(或引用)的const属性,再进行间接修改

注意:🎯
1.const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间
2.#define是C++的预处理功能,叫宏定义; const是常量,可以理解成不可以改变值的变量
3.使用 const_cast 进行强制类型转换可以突破 C/C++ 的常数限制,修改常数的值,因此有一定的危险性


reinterpret_cast (重释转换)

reinterpret意为"重新解释",顾名思义reinterpret_cast这种转换仅仅是对二进制位的重新解释,并不会借助已有的转换规则对数据进行调整,非常简单粗暴,所以风险很高。

reinterpret_cast可以认为是static_cast的一种补充,一些static_cast不能完成的转换,就可以用reinterpret_cast来完成,例如两个具体类型指针之间的转换,int和指针之间的转换等等…

reinterpret_cast是四种强制转换中功能最为强大的,它可以暴力完成两个完全无关类型的指针之间或指针和数之间的互转,比如用char类型指针指向double值。它对原始对象的位模式提供较低层次上的重新解释(即reinterpret),完全复制二进制比特位到目标对象,转换后的值与原始对象无关但比特位一致,前后无精度损失
例如:

double d = 15.5;
char* pchar = reinterpret_cast<char*>(&d);//将d以二进制(位模式)方式重新解释为char,并赋值给指针pchar
double* pdouble = reinterpret_cast<double*>(pchar);//将char类型的指针p重新解释为double,并赋值给指针pdouble
cout << *pdouble << endl;//显示pdouble的值  结果为15.5

reinterpret_cast允许将任何指针转换为任何其他类型,也允许将任何整数类型转换为任何指针类型已经反向转换。

reinpreter_cast的用法:

reinpreter_cast<type_id>(expression);

其中 type_id必须是一个指针,引用,算术类型,函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成以一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。

reinterpret_cast的使用场景:

从指针类型到一个足够大的整数类型
从整数类型或者枚举类型到指针类型
从一个指向函数的指针到另一个不同类型的指向函数的指针
从一个指向对象的指针到另一个不同类型的指向对象的指针
从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针
等等…

虽然上面的各种转换在大多数情况下并没有实际意义,但reinterpret_cast体现了 C++ 语言的设计思想: 用户可以做任何操作,但要为自己的行为负责。

使用示例:

char str[] = "ABCD123";
cout << &str<< endl;//数组str的名字即是数组str的指针,所以此处用&str获取其地址

int* pint = reinterpret_cast<int*>(str);
cout << pint<< endl;//经过重释 此时的指向char类型变量str的指针为int*类型的pint

char* pchar = reinterpret_cast<char*>(pchar);
cout << pchar<< endl;//经过再次重释 此时的指向char类型变量str的指针为int*类型的pint的类型又被重释为char*类型的pchar

运行结果:
在这里插入图片描述
注意:🎯
1.滥用 reinterpret_cast 运算符可能很容易带来风险, 除非所需转换本身是低级别的,否则应使用其他强制转换运算符之一
2.reinterpret_cast 运算符不能去除 const、volatile 或 __unaligned 特性


dynamic_cast (动态转换)

上述三种强制转换方式都是再编译时就完成的,而 dynamic_cast是在运行时处理的,其运行时要进行类型检查(RTTI机制)


关于RTTI机制:
RTTI(Run Time Type Identification)即通过运行时类型识别,程序能够使用基类的指针或引用检查这些指针或引用所指的对象的实际派生类型,RTTI机制提供了运行时确定对象类型的方法。

RTTI提供了两个十分有用的操作符:
一.typeid操作符: 用来获取各种数据的类型信息(包括普通变量,字面量,自定义对象,结构体,表达式等…)

以下是typeid操作符常用的应用场景:

	🔥typeid经常被用来判断参数的类型
	Base a(100);
	int b;
	cout << typeid(a).name()<< endl;//typeid的操作对象可以自定义的类对象
	cout << typeid(b).name() << endl;//typeid的操作对象可以是c++的内置数据类型
	cout << typeid(float).name() << endl;//typeid的操作对象可以是数据类型
	cout << typeid(10-5-80).name() << endl;//typeid的操作对象可以是一个表达式
	cout << typeid(3.52).name() << endl;//typeid的操作对象可以是一个字面量的类型信息

	🔥typeid还经常被用来判断两个类型是否相等
	if (typeid(a).name() == typeid(b).name())
	{
		cout << "a的类型和b的类型一致!";
	}
	else
	{
		cout << "a的类型和b的类型不一致!";
	}

在这里插入图片描述
使用typeid进行类型检查:

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

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

int main()
{
	A* pa = new B();//指针类型为基类指针的类指针pa指向派生类B对象
	cout << typeid(pa).name() << endl; //打印pa指向的对象的类型
	cout << typeid(*pa).name() << endl;//打印*pa指针的类型
	return 0;
}

当基类中 不存在虚函数时,typeid是编译时期的事情,也就是静态类型检查

//不存在虚函数时
class A * __ptr64 //pa指向的对象的类型
class A //*pa指针的类型

当基类中存在在虚函数时,typeid是运行时期的事情,也就是动态类型检查

//存在虚函数时
class A * __ptr64 //pa指向的对象的类型
class B //*pa指针的类型

更多关于typeid的信息详见:https://www.jianshu.com/p/3b4a80adffa7


二.dynamic_cast操作符:将基类类型的指针或引用安全的转换为派生类型的指针或引用

dynamic_cast用于在类的继承层次之间进行类型转换,它 和static_cast相似,既允许向上转型(Upcasting),也允许向下转型(Downcasting),但是static_cast在进行下行转换时由于没有RTTI机制进行运行时检查,没有dynamic_cast安全

继承体系中的向上转型(即将派生类指针转换为基类指针)
dynamic_cast的类向上转型是无条件的,即不会进行任何检查,所以都能成功。

继承体系中的向下转型(即将基类指针转换为派生类指针)
dynamic_cast的类向下转型的前提必须是安全的,要借助RTTI进行检查,所以只有一部分能够成功。

static_cast(静态转换)和dynamic_cast (动态转换)的异同:
dynamic_cast 与 static_cast 是相对的,dynamic_cast 是“动态转换”的意思,static_cast 是“静态转换”的意思。 由于dynamic会在程序运行期间借助RTTI进行类型转换,这就要求基类必须包含虚函数,而static在编译期间完成类型转换,能够更加及时的发现错误。

我们前面提到过,static_cast静态转换也可以将基类指针转换为派生类指针(类的下行转换),但是由于static_cast没有是编译时完成的,没有动态类型检查,所以 dynamic_cast比static_cast更加严格和安全。但dynamic_cast在执行效率上要比static_cast要差一些

dynamic_cast的语法格式为:

dynamic_cast<type-id>(expression)

该运算符把expression转换成type-id类型的对象, type-id和expression必须同时是指针类型或引用类型(换句话说.dynamic_cast只能转换指针类型和引用类型,其他类型(int,float,数组,类对象,结构体…等都不行))

dynamic_cast的作用:
将一个基类对象指针(或引用)转换为继承类指针(下行转换),dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理,即会做出一定的判断…

dynamic_cast最常见的用法是 将一个指向派生类对象的基类指针通过dynamic_cast转换及RTTI检查变成一个指向派生类对象且为派生类指针的指针,例如:

Base *base=Dervied;
Dervied *der=dynamic_cast<Dervied*>(base);

若对类指针进行dynamic_cast转换, 失败将返回一个NULL,成功后返回正常转换后的对象指针;
若对类引用进行dynamic_cast转换, 失败将抛出一个异常,成功后返回正常转换后的对象指针;

dynamic_cast向上转型(Upcasting)
向上转型时(派生类指针转换为基类指针),只要待转换的两个类型之间存在继承关系,并且基类中包含虚函数(由于这些信息在程序编译期间就能确定),所以一定就能转换成功。因为向上转型始终是安全的,所以dynamic_cast并不会进行任何运行期间的检查(RTTI),这个时候的dynamic_cast和static_cast就没有什么区别了。

dynamic_cast向下转型(Downcasting)

由于经过dynamic_cast向下转型后(基类指针转换为派生类指针) 可能会出现派生类指针指向基类对象的情况

因为子类总是含有一些父类没有的成员变量或方法函数,而子类肯定含有父类所有的成员,所以父类指针指向子类对象时,没有问题,不会出现非法访问的问题。但是如果用子类指针指向父类对象的话,一旦访问子类特有的方法或变量,就会出现非法访导致程序崩溃!因为被子类对象所指向由父类创建的对象,根本没有要访问的内容,这些内容是子类特有的,只有用子类初始化对象才有(且在构建任意对象实例时,程序是先调用父类构造器后才会调用派生类的构造器), 所以不允许派生类的指针指向基类对象。而基类的指针可以指向派生类对象

所以dynamic_cast向下转型时为了保证安全 需要进行运行时类型检查(RTTI)来判断基类指针或派生类指针是否真正指向了派生类对象,而static_cast正是因为没有运行时检查所以不够安全。

dynamic_cast转换实例演示:

class Base
{
public:
	Base() {};
	virtual void show() { cout << "this is Base class\n\n"; }
};

class Derived :public Base 
{
public:
	Derived() {};
	virtual void show() { cout << "this is Derived class\n\n"; }
	int data;//子类独有成员
};

int main()
{
	//🔥 上行转换 [派生类指针转换为基类指针]
	Derived* der=new Derived;
	Base *bas = dynamic_cast<Base*>(der);
	cout << "bas这个类指针属于" << typeid(bas).name() << endl;
	//🔥 经过转换后 此时的基类指针*bas的指针类型为基类,指向的对象的类型为派生类(也就是基类指针指向派生类对象)
	//基类指针指向派生类对象可以通过运行时检查 于是dynamic_cast返回转换后的类指针 bas
	bas->show();//结果为 : this is Derived class 完成多态


	//🔥向下转型的两种情况[基类指针转换为派生类指针]
	Base* base = new Derived;//🔥情况一:基类指针base指向派生类对象

	//转换基类指针为派生类指针且这个指针实际指向派生类对象
	if (Derived* PofD = dynamic_cast<Derived*>(base))
	{
		cout << "第一种转换成功!(基类指针所指对象为派生类类型)" << endl;
		//todo经过转换后 此时的派生类指针*PofD的指针类型为派生类
		//todo指向的对象的类型为派生类(也就是派生类指针指向派生类对象)
		PofD->show();
		cout << endl;
	}

	Base* base2 = new Base;//🔥情况二:基类指针base指向基类对象
	//转换基类指针为派生类指针且这个指针实际指向基类对象
	if (Derived* PofD2 = dynamic_cast<Derived*>(base2))
	{
		cout << "第二种转换成功!(基类指针所指对象为基类类型)" << endl;
		//🔥经过转换后 此时的派生类指针*PofD2的指针类型为派生类
		//🔥指向的对象的类型为基类(也就是派生类指针指向基类对象,这是不被程序允许的,所以在运行检查时将返回失败)
		PofD2->show();
		cout << endl;
	}
	else
	{
		cout << "第二种转换失败!(因为此时派生类指针指向了基类对象)" << endl;
		//dynamic_cast做运行时检查,转换失败,返回结果为0
	}
	delete(base);
	delete(base2);
	return 0;
}

注意:🎯
1.dynamic_cast是程序运行期间进行处理的,运行时需要进行类型检查。
2.dynamic_cast不能用于基本数据类型的转换。
3.dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不会通过(类中存在虚函数,就说明他有想要基类指针或引用指向派生类的情况,此时转换才有实际意义)。


来自:
https://blog.csdn.net/qq_32228793/article/details/90240023
http://c.biancheng.net/view/2343.html
https://www.jianshu.com/p/a7ac79bb6ccf
https://www.cnblogs.com/larry-xia/p/10325643.html
http://c.biancheng.net/view/1329.html
https://blog.csdn.net/Bob__yuan/article/details/88044361
https://www.cnblogs.com/zl1991/p/7515888.html
https://blog.csdn.net/weixin_44212574/article/details/89043854

相关文章:

  • thinkphp的一些类库
  • C++ 指针和引用
  • 9月24号忘记
  • C++ 避免内存泄漏
  • [转]SQL Server DBCC用法大全
  • C++ 命名空间和模块化编程
  • FG终于投出去了~
  • 资源仓库
  • open cv建立一个标准的opencv程序
  • 协议与委托(Protocol and Delegate)实例解析
  • C++ 链接和作用域
  • TeX宏包-画目录树
  • C++ 函数,类,内联模板
  • 读书笔记之: 操作系统概念(第6版)-第七部分 案例研究-Windows 2000
  • 工业机器人 传感器
  • 分享的文章《人生如棋》
  • 【跃迁之路】【519天】程序员高效学习方法论探索系列(实验阶段276-2018.07.09)...
  • 0基础学习移动端适配
  • Angular 响应式表单之下拉框
  • Java新版本的开发已正式进入轨道,版本号18.3
  • Kibana配置logstash,报表一体化
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • React组件设计模式(一)
  • Shell编程
  • vue中实现单选
  • webgl (原生)基础入门指南【一】
  • Webpack 4 学习01(基础配置)
  • 阿里云应用高可用服务公测发布
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 程序员最讨厌的9句话,你可有补充?
  • 初探 Vue 生命周期和钩子函数
  • 开年巨制!千人千面回放技术让你“看到”Flutter用户侧问题
  • 批量截取pdf文件
  • 前端工程化(Gulp、Webpack)-webpack
  • 前端之Sass/Scss实战笔记
  • 如何进阶一名有竞争力的程序员?
  • 一些css基础学习笔记
  • 云大使推广中的常见热门问题
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • #Linux(Source Insight安装及工程建立)
  • (1)(1.11) SiK Radio v2(一)
  • (1)(1.19) TeraRanger One/EVO测距仪
  • (Matalb分类预测)GA-BP遗传算法优化BP神经网络的多维分类预测
  • (react踩过的坑)antd 如何同时获取一个select 的value和 label值
  • (初研) Sentence-embedding fine-tune notebook
  • (二)学习JVM —— 垃圾回收机制
  • (附源码)ssm码农论坛 毕业设计 231126
  • (利用IDEA+Maven)定制属于自己的jar包
  • (一)Linux+Windows下安装ffmpeg
  • (转)大型网站架构演变和知识体系
  • **PHP分步表单提交思路(分页表单提交)
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .class文件转换.java_从一个class文件深入理解Java字节码结构
  • .NET I/O 学习笔记:对文件和目录进行解压缩操作