c++基础——强制类型转换符

(一)概念:

隐式类型转换是编译器自动隐式进行的,需要在代码中体现,而显式类型转换由程序员明确指定。

C++支持C风格的强制转换(将类型名作为强制类型转换运算符的做法是C语言的老式做法),但是C风格的强制转换可能带来一些隐患,让一些问题难以发现。

所以C++ 引入了四种功能不同的强制类型转换运算符以进行强制类型转换。

强制转换运算符是一种特殊的运算符,它把一种数据类型转换为另一种数据类型。强制转换运算符是一元运算符,它的优先级与其他一元运算符相同。

大多数的 C++ 编译器都支持大部分通用的强制转换运算符。

形式为:**强制类型转换运算符<type> (expression) **

type为转换后的数据类型,expression为待转换的表达式

c++的四种强制类型转换运算符分别为:static_cast、reinterpret_cast、const_cast和dynamic_cast。

(二)风险:

强制类型转换是有一定风险的,有的转换并不一定安全,如把整型数值转换成指针,把基类指针转换成派生类指针,把一种函数指针转换成另一种函数指针,把常量指针转换成非常量指针等。

C++ 引入新的强制类型转换机制,主要是为了克服C语言强制类型转换的以下三个缺点。

  • 没有从形式上体现转换功能和风险的不同。

例如,将int强制转换成double是没有风险的,而将常量指针转换成非常量指针,将基类指针转换成派生类指针都是高风险的,而且后两者带来的风险不同(即可能引发不同种类的错误),C语言的强制类型转换形式对这些不同并不加以区分。

  • 将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。

  • 难以在程序中寻找到底什么地方进行了强制类型转换。

    强制类型转换是引发程序运行时错误的一个原因,因此在程序出错时,可能就会想到是不是有哪些强制类型转换出了问题。

如果采用C语言的老式做法,要在程序中找出所有进行了强制类型转换的地方,显然是很麻烦的,因为这些转换没有统一的格式。

而用 C++ 的方式,则只需要查找_cast字符串就可以了。甚至可以根据错误的类型,有针对性地专门查找某一种强制类型转换。例如,怀疑一个错误可能是由于使用了reinterpret_cast导致的,就可以只查找reinterpret_cast字符串。

(三)四种转换操作:

1. const_cast:

const_cast 运算符用于修改类型的const /volatile属性。除了const或volatile属性之外,目标类型必须与源类型相同(即type和expression结果类型相同)。这种类型的转换主要是用来操作所传对象的const属性,可以加上const属性,也可以去掉const属性(常用)。

#include <iostream>
using namespace std;
int main() {
	const string s = "Inception";
	string& p = const_cast <string&> (s);
	string* ps = const_cast <string*> (&s);
	p += "1";
	*ps += "2";
	cout << typeid(p).name() << endl;
	cout << typeid(ps).name() << endl;
	cout << p << endl;
	return 0;
}
//运行结果:
//class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >
//class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >* __ptr64
//Inception12
2. reinterpret_cast:

reinterpret_cast 运算符用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。

reinterpret_cast 功能十分强大,可以实现任意类型之间的转换。

这个转换是“最不安全”的。不推荐使用。

#include <iostream>
using namespace std;
class A
{
public:
	int i;
	int j;
	A(int n1,int n2) :i(n1), j(n2) { }
};
void show1(int) { cout << "show1" << endl; return; }
int show2(int, char*) { cout << "show2" << endl; return 1; }
int main()
{
	A a(100,200);

	//强行让r引用a
	int& r = reinterpret_cast<int&>(a); 
	r = 300;  //把a.i变成了300
	std::cout << a.i << "," << a.j << std::endl;  // 输出 300,200

	//强行让pa指向n
	int n = 400;
	A* pa = reinterpret_cast<A*> (&n); 
	pa->i = 500;  // n变成400
	//pa->j = 600;  //此条语句越界操作内存,不安全,会导致程序崩溃
	cout << n << endl;  // 输出 500

	long long la = 0x12345678abcdLL;
	pa = reinterpret_cast<A*>(la); //la太长,只取低32位0x5678abcd拷贝给pa
	unsigned int u = reinterpret_cast<unsigned int>(pa);//pa逐个比特拷贝到u
	cout << hex << u << endl;  //输出 5678abcd

	typedef void (*PF1) (int);
	typedef int (*PF2) (int, char*);
	//两个不同类型的函数指针之间可以互相转换,但是pf1和pf2必须初始化
	PF1 pf1=show1;  PF2 pf2=show2;
	pf1(1); //show1
	cout << pf2(1, (char*)("1")) << endl; //show2,返回1
	pf2 = reinterpret_cast<PF2>(pf1);
	pf1(1); //show1
	cout << pf2(1, (char*)("1")) << endl; //show1,返回1,(表现非常奇怪,不安全)
	return 0;
}
//运行结果:
//300, 200
//500
//5678abcd
//show1
//show2
//1
//show1
//show1
//b0e74080
3. static_cast:

编译器隐式执行的任何类型转换都可以由static_cast显式完成。

static_cast主要用于基本数据类型之间的转换,如把char转换为 int,把int转换为double等。

使用static_cast可以明确告诉编译器,损失精度的转换是在知情的情况下进行的,也可以让阅读程序的其他程序员明确转换的目的而不是由于疏忽。

把精度大的类型转换为精度小的类型,static_cast使用位截断进行处理。

static_cast执行的是非动态转换,没有运行时的类型检查来保证转换的安全性。

例如,对于类层次的转换:

  • static_cast 进行上行转换是安全的,即把派生类的指针转换为基类的;
  • static_cast 进行下行转换是不安全的,即把基类的指针转换为派生类的。

使用static_cast可以找回存放在void*指针中的值。

static_cast可以把任何类型的表达式转换成void*类型。

#include <iostream>
using namespace std;
class A
{
public:
	int i;
	int j;
	operator int() { return 1; }
	operator char* () { return NULL; }
};
class B :public A 
{
public:
	int z;
};
int main()
{
	A a;
	int n;
	char* p = (char*)"New Dragon Inn";
	n = static_cast <int> (a);     //调用 a.operator int,n的值变为1
	cout << n << endl;
	p = static_cast <char*> (a);   //调用 a.operator char*,p的值变为NULL
	cout << &p << endl;
	n = static_cast <int> (3.14);  //转换丢失精度,n的值变为3
	cout << n << endl;
	int x = -1;
	unsigned int y = static_cast<unsigned int>(x); // 转换为无符号整数最大值,此处为4294967295
	cout << y << endl;
	//n = static_cast <int> (p);  //编译错误,static_cast不能将指针转换成整型
	//p = static_cast <char*> (n);  //编译错误,static_cast 不能将整型转换成指针

	double da = 2.991;
	void* vp = static_cast<void*>(&da);    //double转void*类型,void*指向double类型值
	double* dp = static_cast<double*>(vp); //将void*类型的double值找回来
	cout << *dp << endl;   //输出2.991
	A* pa = new A;
	B* pb = new B;
	pa->i = 1;
	pa->j = 2;
	pb->i = 10;
	pb->j = 20;
	pb->z = 30;
	A* pab = static_cast<B*>(pb); //上行转换,派生类指针->基类指针,pab的i和j的值与pb的相同,z的值丢弃
	cout << pab->i << "," << pab->j << endl;
	B* pba = static_cast<B*>(pa); //下行转换,基类指针->派生类指针,不进行类型检查,不安全,i和j的值与pa的相同,但是z的值相当于未初始化
	cout << pba->z << "," << pba->i << "," << pba->j << endl;
	return 0;
}
4. dynamic_cast:

dynamic_cast 主要用于类层次间的上行转换或安全的下行转换(实际就是专门用于将多态基类指针或引用转换为派生类指针或引用)。在进行上行转换时,dynamic_cast 和 static_cast 的效果是一样的,但在下行转换时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全。

对于“向下转型”有两种情况:

  • 基类指针所指对象是派生类类型的,这种情况下将其转换为派生类指针转换是安全的;
  • 基类指针所指对象为基类类型,在这种情况下dynamic_cast在运行时做检查,转换失败,返回结果为NULL指针;

dynamic_cast最特殊的地方在于它支持运行时识别指针或引用。

dynamic_cast 是在运行时执行转换,进行类型检查的。如果转换未执行,则转换失败,表达式 expr 被判定为 null。dynamic_cast 执行动态转换时,type 必须是类的指针、类的引用或者 void*,如果 type 是类指针类型,那么 expr 也必须是一个指针,如果 type 是一个引用,那么 expr 也必须是一个引用。

dynamic_cast检查的来源是虚函数表。

#include <iostream>
using namespace std;

class Base {
public:
	int i;
	virtual void Show() {
		cout << "I am Base." << endl;
	}
};

class Derive : public Base {
public:
	int j;
	virtual void Show() {
		cout << "I am Derive." << endl;
	}
};

int main()
{
	// 上行转换
	Derive* d1 = new Derive();
	cout << "d1: " << d1 << endl;
	Base* b1 = dynamic_cast<Base*>(d1); //转换安全,且无影响。
	cout << "b1: " << b1 << endl;

	// 下行转换
	//基类指针指向派生类类型进行转换
	Base* b2 = new Derive;
	cout << "b2: " << b2 << endl;
	if (Derive* d2 = dynamic_cast<Derive*>(b2))
	{
		cout << "第一种情况转换成功" << endl;
		cout << "d2: " << d2 << endl;
		d2->Show();
	}
	else cout << "第一种情况转换失败" << endl;
	//基类指针指向基类类型进行转换
	Base* b3 = new Base;
	cout << "b3: " << b3 << endl;
	if (Derive* d3 = dynamic_cast<Derive*>(b3))
	{
		cout << "第二种情况转换成功" << endl;
		d3->Show();
	}
	else
	{
		cout << "第二种情况转换失败" << endl;
		cout << "d3: " << d3 << endl;
	}
	delete b1;
	delete b2;
	delete b3;

	//引用在其他方面表现和指针一样,但因为没有NULL类型,不能用dynamic_cast强制转换时抛出错误。
	Base bbb;
	Base& bbbb = bbb;
	//Derive& dddd = dynamic_cast<Derive&>(bbbb); 
	return 0;
}
//运行结果:
//d1 : 000002531E58F8C0
//b1 : 000002531E58F8C0
//b2 : 000002531E58F740
//第一种情况转换成功
//d2 : 000002531E58F740
//I am Derive.
//b3 : 000002531E592440
//第二种情况转换失败
//d3 : 0000000000000000
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaohei07

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值