天天看点

C++ 类型转换 :C语言的类型转换、C++的四种强制类型转换、explicitC语言中的类型转换C++ 强制类型转换

文章目录

  • C语言中的类型转换
    • 隐式类型转换
    • 显式类型转换
  • C++ 强制类型转换
    • static_cast
    • reinterpret_cast
    • const_cast
    • dynamic_cast
    • explicit

C语言中的类型转换

C语言中的类型转换,通常发生在传参时参数类型不匹配,又或是接收的返回值类型不一致、赋值双方类型不同的情景,就会需要用到类型转换。主要分为隐式类型转换与显示类型转换两种。

隐式类型转换

编译器在编译阶段自动进行,通常适用于相近的类型,如果不能转换则会编译失败。

例如:

void print(int i)
{
	cout << i << endl;
}
int main()
{
	double d = 1.9;
	print(d);

	return 0;
}
           

显式类型转换

需要用户自己处理,通常用于不相近类型的转换。

如:

int main()
{
	int i = 9;
	int* p = &i;
	//将指针转换为地址
	int addr = (int)p;
	cout << addr;
}
           

从上面可以看出来,C语言的类型转换使用起来很简单,但是也有很大的缺点。

  1. 隐式类型转换可能会因为整形提升或者数据截断导致精度的丢失,并且有时候会因为忽略隐式类型转换导致错误发生
  2. 显示类型转换代码不够清晰,没有很好的将各种情况划分开,而是全部混在一起使用

C++ 强制类型转换

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast

static_cast

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换。(即对应C语言中的隐式类型转换)

int main()
{
	double d = 1.9;
	int i = static_cast<int>(d);
	
	cout << i;
}
           

reinterpret_cast

reinterpret_cast是一种较为危险的类型转换,通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型,通常适用于指针、引用、以及整数之间的类型转换。

int main()
{
	int i = 9;
	int* p = &i;

	double* p2 = reinterpret_cast<double*>(p);
	cout << *p2 << ' ' << *p;
}
           
C++ 类型转换 :C语言的类型转换、C++的四种强制类型转换、explicitC语言中的类型转换C++ 强制类型转换

如上面所说,这种转换十分容易导致错误的发生,因为指针类型其实是其指向的地址的类型,决定了指针看待这段地址的方式,它该如何读取数据。这里我把int改成了double,使得他原本应该读取4个字节,而变成了8个字节,就导致了数据的变化。如果使用不当很容易会造成越界访问导致程序崩溃。

const_cast

const_cast通常用于删除变量的const属性

如果想要修改const变量的值,就需要用volatile来取消编译器优化。因为const变量创建后会被放入寄存器中,只有我们取const变量的地址时,他才会在内存中申请空间,而我们修改的是const变量在内存中的值,但是由于编译器优化,当使用const变量时就会到寄存器中去取值,所以需要用volatile取消编译器优化,让其每次在内存中取值。

不加volatile时

int main()
{
	const int ci = 10;
	int* pi = const_cast<int*>(&ci); // 对应c语言强制类型转换中去掉const属性的(不相近类型)
	*pi = 20;

	cout << ci << ' ' << *pi;
}
           
C++ 类型转换 :C语言的类型转换、C++的四种强制类型转换、explicitC语言中的类型转换C++ 强制类型转换

加volatile时

int main()
{
	volatile const int ci = 10;
	int* pi = const_cast<int*>(&ci); // 对应c语言强制类型转换中去掉const属性的(不相近类型)
	*pi = 20;

	cout << ci << ' ' <<  *pi;
}
           
C++ 类型转换 :C语言的类型转换、C++的四种强制类型转换、explicitC语言中的类型转换C++ 强制类型转换

dynamic_cast

dynamic_cast是一种动态的类型转换,是C++新增的概念,用于将一个父类对象的指针/引用转换为子类对象的指针或引用。

派生类可以赋值给基类的对象、指针或者引用,这样的赋值也叫做对象切割。

例如Human类和Student类

C++ 类型转换 :C语言的类型转换、C++的四种强制类型转换、explicitC语言中的类型转换C++ 强制类型转换

从这幅图可以看出来,当把派生类赋值给基类时,可以通过切割掉多出来的成员如_stuNum的方式来完成赋值。

但是基类对象如果想赋值给派生类,则不可以,因为他不能凭空多一个_stuNum成员出来。

但是基类的指针或者引用却可以强制类型转换赋值给派生类对象, 如:

这个过程有可能成功,也有可能会因为越界导致出现问题。 如果使用C语言的强制类型转换,很可能就会出现问题,因为其没有安全保障。而如果使用dynamic_cast,则能够保证安全,因为其会先检查转换是否能够成功,如果不能成功则返回0,能则直接转换。

但是dynamic_cast的向下转换只支持继承中的多态类型,也就是父类之中必须包含虚函数。

int main()
{
	Human h1;
	Student s1;

	Human* hPtr1 = &s1;//指向派生类对象
	Human* hPtr2 = &h1;//指向基类对象

	//传统方法
	Student* pPtr = (Student*)hPtr1;//没问题
	Student* pPtr = (Student*)hPtr2;//有时候没有问题,但是会存在越界风险
	
	//dynamic_cast
	Student* pPtr = dynamic_cast<Student*>(hPtr2);
	return 0;
}
           

注意:

  1. dynamic_cast只能用于含有虚函数的类
  2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0

dynamic_cast是如何识别父类的指针指向的是父类对象还是子类对象的呢?其原理就是在运行时通过查找虚函数表上面的标识信息,来确认其指向的到底是父类还是子类,这也就是为什么只能用于含有虚函数的类。

这种在运行中进行类型识别的方法,也叫做RTTI,C++中有很多支持RTTI的方法,如dynamic_cast,typeid,decltype

explicit

explicit关键字主要用来阻止转换构造函数而进行隐式类型转换的发生

class Date
{
public:
	Date(int year = 0, int month = 4, int day = 24)
		:_year(year),
		 _month(month),
		 _day(day)
	{}

	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2020, 4, 24);
	
	Date d2 = 2020;//C++98
	Date d3 = { 2020, 4 }; //C++11
	Date d4 = { 2020, 4, 24 }; //C+11
}
           

对于这里的d2,我们用2020给它赋值,而d3和d4分别用了列表来给它赋值,并且这四个对象它们最后的值是一模一样的,那是为什么呢?这里的2020明明是一个整型,d3和d4是一个列表,为什么能够给对象赋值呢?

这里就牵扯到了隐式的类型转换

这里其实是先用这个整型值来调用了全缺省的构造函数来创建了一个临时对象,再使用这个对象来为d2,d3,d4赋值。

这是一种很容易引起误会的写法,所以c++提供了关键字explicit,用这个关键字修饰的函数就会禁止隐式类型的转化

C++ 类型转换 :C语言的类型转换、C++的四种强制类型转换、explicitC语言中的类型转换C++ 强制类型转换

这时这种隐式类型转换就不会发生了

继续阅读