C++ 命名类型转换

发布于:2022-10-27 ⋅ 阅读:(400) ⋅ 点赞:(0)

传统艺能😎

小编是双非本科大二菜鸟不赘述,欢迎米娜桑来指点江山哦
在这里插入图片描述
1319365055

🎉🎉非科班转码社区诚邀您入驻🎉🎉
小伙伴们,满怀希望,所向披靡,打码一路向北
一个人的单打独斗不如一群人的砥砺前行
这是和梦想合伙人组建的社区,诚邀各位有志之士的加入!!
社区用户好文均加精(“标兵”文章字数2000+加精,“达人”文章字数1500+加精)
直达: 社区链接点我


在这里插入图片描述

类型转换🤔

任何一门语言里面都会遇到赋值符号两边不同的情况,或者形参和实参不匹配,这种情况就存在类型转换,特别是 C++ 这种强类型语言,即使不发生显示的强制转换也会进行隐式类型转换

其实在C语言阶段就早有提出两种类型转换方式:

  1. 显示类型转换:需要手动转换成需要的类型对象:(指定类型)变量
  2. 隐式类型转换:在代码编译阶段自动执行,能转就转,不能转就编译失败

当然并不是显示转换就可以随心所欲的转换, 只有相近类型之间才能发生隐式类型转换 \color{red} {只有相近类型之间才能发生隐式类型转换} 只有相近类型之间才能发生隐式类型转换,比如 int 和 double 表示的都是数值,不过它们的范围和精度不同。而指针类型表示的是地址编号,因此整型和指针类型之间不会进行隐式类型转换,如果需要转换则只能进行显式类型转换:

int main()
{

	//显式类型转换
	int* p = &i;
	int address = (int)p;
	cout << p << endl;
	cout << address << endl;
	return 0;
	
	//隐式类型转换
	int i = 1;
	double d = i;
	cout << i << endl;
	cout << d << endl;
}

四大类型转换🤔

介于类型转换的场合非常多,且存在一些复杂场景的类型转换,单纯的 C 语言的类型转换虽然简单,但是存在精度丢失、可视性差等缺陷,于是 C++ 标准推出了四大命名类型转换操作符:

static_cast
reinterpret_cast
const_cast
dynamic_cast

static_cast😋

static_cast 用于相近类型之间的转换,编译器隐式执行的任何类型转换都可以用 static_cast,但它不能用于不相关类型之间的转换:

int main()
{
	double d = 12.34;
	int a = static_cast<int>(d);
	cout << a << endl;

	int* p = &a;
	// int address = static_cast<int>(p); //error
	return 0;
}

reinterpret_cast😋

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

int main()
{
	int a = 10;
	int* p = &a;
	int address = reinterpret_cast<int>(p);
	cout << address << endl;
	return 0;
}

他除了用于两个不相关类型的转换,还有一个强有力的功能:可以将带参带返回值的函数指针转换成了无参无返回值的函数指针,并且此时转换后函数指针还可以调用这个函数:

typedef void(*FUNC)();
int DoSomething(int i)
{
	cout << "DoSomething: " << i << endl;
	return 0;
}
int main()
{
	FUNC f = reinterpret_cast<FUNC>(DoSomething);
	f();
	return 0;
}

用转换后的函数指针调用该函数时没有传入参数,因此这里打印出参数 i 的值是一个随机值

const_cast😋

const_cast 用于删除变量的 const 属性,转换后就可以对 const 变量的值进行修改:

int main()
{
	const int a = 2;
	int* p = const_cast<int*>(&a);
	*p = 3;
	cout << a << endl;  //2
	cout << *p << endl; //3
	return 0;
}

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

由于编译器认为 const 修饰的变量是不会被修改的,因此会将 const 修饰的变量存放到寄存器当中,当需要读取 const 变量时就会直接从寄存器中进行读取, 而我们修改的实际上是内存中的 a 的值,因此最终打印出 a 的值是未修改之前的值 \color{red} {而我们修改的实际上是内存中的 a 的值,因此最终打印出a的值是未修改之前的值} 而我们修改的实际上是内存中的a的值,因此最终打印出a的值是未修改之前的值

如果不想让 const 变量被优化到寄存器当中,可以用 volatile 关键字对 const 变量进行修饰,这时当要读取这个 const 变量时编译器就会从内存中进行读取,即保持了该变量在内存中的可见性

dynamic_cast😋

dynamic_cast 用于将父类的指针(或引用)转换成子类的指针(或引用),这就不得不提一下向上转型和向下转型了

其实早在继承和多态的博客中说过,向上转型是子类的指针(或引用)→ 父类的指针(或引用);向下转型是 父类的指针(或引用)→ 子类的指针(或引用)。向上转型就是所说的切割/切片,是语法天然支持的,不需要进行转换,而向下转型是语法不支持的,需要进行强制类型转换

  1. 其中向下转型是存在安全问题的:如果父类指向的是一个父类对象,那么将其转换为子类是不安全的,因为转换后可能会访问到子类的资源,而这个资源是父类对象所没有的
  2. 如果父类的指针(或引用)指向的是一个子类对象,那么将其转换为子类的指针(或引用)则是安全的

若果要用 C 语言的强制类型转换进行向下转型也是不安全的,因为无论父指向的是父类对象还是子类对象都会进行转换。而使用 dynamic_cast 进行向下转型则是安全的,但如果父类指向的是父类对象那么 dynamic_cast 会转换失败并返回空指针!

class A
{
public:
	virtual void f()
	{}
};
class B : public A
{};
void func(A* pa)
{
	B* pb1 = (B*)pa;               //不安全
	B* pb2 = dynamic_cast<B*>(pa); //安全

	cout << "pb1: " << pb1 << endl;
	cout << "pb2: " << pb2 << endl;
}
int main()
{
	A a;
	B b;
	func(&a);
	func(&b);
	return 0;
}

dynamic_cast 只能用于含有虚函数的类,因为运行时类型检查需要运行时的类型信息,而这个信息存储在虚函数表中,只有定义了虚函数的类才有虚函数表!

explicit🤔

explicit 用来修饰构造函数,从而禁止单参数构造函数的隐式转换:

class A
{
public:
	explicit A(int a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& a)
	{
		cout << "A(const A& a)" << endl;
	}
private:
	int _a;
};
int main()
{
	A a1(1);
	//A a2 = 1; //error
	return 0;
}

在语法上,代码中的A a2 = 1等价于以下两句代码:

A tmp(1);  //先构造
A a2(tmp); //再拷贝构造

可见和上述这种用临时变量拷贝构造的方法不同,现在的编译器做了优化,当遇到 A a2 = 1这句代码时,会直接按照 A a2(1) 的方式进行处理,这也叫做隐式类型转换

对于单参数的自定义类型来说,A a2 = 1 这种代码可读性不是很好,因此可以用 explicit 修饰单参数构造函数,从而禁止单参数构造函数的隐式转换

RTTI🤔

RTTI 就是运行时类型识别,C++ 通过以下几种方式来支持 RTTI:

  1. typeid:在运行时识别出一个对象的类型。
  2. dynamic_cast:在运行时识别出一个父类的指针(或引用)指向的是父类对象还是子类对象。
  3. decltype:在运行时推演出一个表达式或函数返回值的类型
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到