【c++】类型转换

2023-10-15 12:23:51 浏览数 (1)

C语言的类型转换

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。 1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败 2. 显式类型转化:需要用户自己处理

类型相近的才能发生隐式类型转换,如int和double,如果不相关,而对于指针和整型,指针是地址,整型和指针类型之间不会进行隐式类型转换,只能显式的强制类型转换:

代码语言:javascript复制
int main()
{
	int i = 1;
	//隐式类型转换
	double d = i;
	printf("%d,%.2f", i, d);

	int* p = &i;
	//显式强制类型转换
	int address = (int)p;
	printf("%x,%dn", p, address);
	return 0;
}

C 需要四种类型转换

C风格的转换格式很简单,但是有不少缺点的:

1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失 2. 显式类型转换将所有情况混合在一起,代码不够清晰

因此C 提出了自己的类型转化风格,注意因为C 要兼容C语言,所以C 中还可以使用C语言的转化风格。

C 引入四种类型装换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast


C 强制类型转换

  • static_cast

static_cast用于相近类型之间的转换,(这些类型的表示意义差不多)编译器隐式执行任何类型转换都可以使用static_cast,对于两个不相关类型之间的转换,不能使用static_cast:

代码语言:javascript复制
int main()
{
	int i = 1;
	//隐式类型转换
	double d = static_cast<double>(i);
	printf("%d,%.2f", i, d);

	int* p = &i;
	//int address = static_cast<int>(p);//错误写法
	//printf("%x,%dn", p, address);
	return 0;
}
  • reinterpret_cast

reinterpret_cast用于不相关的类型之间的转换:

代码语言:javascript复制
int main()
{
    int  i = 10;
	int* p = &i;
	int address = reinterpret_cast<int>(p);
	printf("%x,%dn", p, address);
	return 0;
}
  • const_cast

const_cast用于删除变量的const属性,转化后就可以对const变量进行修改了:比如下面的a不可以被修改,现在通过const_cast转化成int*

代码语言:javascript复制
int main()
{
	const int a = 2;
	int* p = const_cast<int*>(&a);
	*p = 4;
	cout << a << endl;//2
	cout << *p << endl;//4
	return 0;
}

代码中使用const_cast删除变量a地址的const属性,这时候就可以通过使用指针来修改a的值了。

但是编译器会认为const修饰的变量不会被修改,所以将const修饰的变量存放在寄存器中,当需要读取const变量时会直接从寄存器中读取,而我们修改的实际上是内存中a的值,所以最终打印出来a的值是没有修改之前的。

如果不想让编译器将const变量优化到寄存器中,使用volatile关键字对const变量进行修饰即可。这时候读取const变量编译器就从内存中进行读取,保持内存的可见性

  • dynamic_cast

dynamic_cast是用于将父类的指针(引用)转换成子类的指针(引用)

向上转型:子类对象指针/引用——》父类指针/引用(不需要转换,赋值兼容规则)

向下转型:父类对象指针/引用——》子类指针/引用(用dynamic_cast转型是安全的)

注意:

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

向上转型就是我们多态说的切割/切片,是语法天然支持,不需要进行转换,而向下转型语法是不支持的,需要进行强制类型转换

向下转型安全问题

如果父类的指针(或引用)指向的是一个父类对象,那么将其转换为子类的指针(或引用)是不安全,会存在越界的风险,因为转换后可能会访问子类的资源,而这些资源是父类对象没有的。 如果父类的指针(或引用)指向的是一个子类对象,那么将其转换为子类的指针(或引用)则是安全的,没有问题

使用C强制类型转换向下转型是不安全的,因为此时无论父类的指针(或引用)指向的是父类对象还是子类对象都会进行转换。

使用dynamic_cast向下转型是安全的,如果父类的指针(或引用)指向的是子类对象那么dynamic_cast会转换成功,但如果父类的指针(或引用)指向的是父类对象那么dynamic_cast会转换失败并返回一个空指针。比如:

代码语言:javascript复制
class A
{
public:
	virtual void f(){}

	int _a = 0;
};

class B :public A
{
public:
	int _b = 0;
};
void Func(A* ptr)
{
	//直接转换是不安全的
   // B* bptre = (B*)ptr
	B* bptr = dynamic_cast<B*>(ptr);
	cout << bptr << endl;
	if (bptr)
	{
		bptr->_a  ;
		bptr->_b  ;
		cout << bptr->_a << endl;
		cout << bptr->_b << endl;
	}
}
int main()
{
	A aa;
	B bb;
	Func(&aa);
	Func(&bb);
	return 0;
}

如果ptr指向父类,则转换失败,返回空,如果ptr指向子类,则转换成功

如果传入Func函数的是子类对象的地址,那么转化后的bptre与bptr都会有地址,如果传入Func函数的是父类对象的地址,那么转换后的ptre也有地址,而bptr是一个空指针。

  • explicit

explicit用于修饰构造函数,用于禁止单参数构造函数的隐式转换

代码语言:javascript复制
class A
{
public:
	explicit A(int a)
	{
		cout << "explicit A(int a)" << endl;
	}
	A(const A& A)
	{
		cout << "A(const A& a)" << endl;
	}
private:
	int _a;
};

int main()
{
	A a(10);
	//A a2 = 11;错误的写法
	return 0;
}

A a2 = 11等价于先构造A tmp(11);在拷贝A a2(tmp);

在早期编译器中,遇到A a2=11会先构造临时对象,在用临时对象拷贝构造a2;但是现在的编译器做了优化,遇到A a2=11会直接按照A a2(11)进行处理,这是隐式转换。对于单参自定义类型,A a2=11这种方式可读性不好,所以explicit修饰单参构造函数,进制单参构造函数的隐式转换。


RTTI

RTTI:Run-time Type identification的简称,即:运行时类型识别。

typeid:在运行时识别出一个对象的类型。 decltype:在运行时推演出一个表达式或函数返回值的类型。 dynamic_cast:在运行时识别出一个父类的指针(或引用)指向的是父类对象还是子类对象。


总结

1、C 中的4种类型转换分别是:static_cast,reinterpret_cast、const_cast、dynamic_cast

2、4种类型转换的应用场景:

static_cast:用于相近类型的类型之间进行转化,如int与double,编译器隐式执行的任何类型都可用static_cast reinterpret_cast:用于两个不相关类型之间的转换 const_cast:用于删除变量的const属性,可用进行修改 dynamic_cast:用于安全地将父类的指针(引用)转换成子类的指针(引用)

0 人点赞