Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路!

我的博客:<但凡.

我的专栏:《编程之路》《数据结构与算法之美》《C++修炼之路》《Linux修炼:终端之内 洞悉真理》

感谢你打开这篇博客!希望这篇博客能为你带来帮助,也欢迎一起交流探讨,共同成长。

目录

1、 C语言类型转换

2、C++中的类型转换

        2.1、内置类型到自定义类型

        2.2、自定义类型转换到内置类型

        2.3、自定义类型之间的转换

3、C++中的四个显式强制类型转换运算符

static_cast

reinterpret_cast

const_cast

dynamic_cast

对比总结

 4、RTTI


 

1、 C语言类型转换

        相信我们在学习C语言的时候,就已经接触过类型转换了。在C语言中,如果赋值运算符左右两侧类型不相等,或者形参实参类型不匹配,就会触发类型转换。类型转换包括隐式类型转换和显式类型转换(强制类型转换)。

#include<stdio.h>

int main()
{
	double a = 3.5;
	double b = 5.5;
	int c = a;//隐式
	int d = (int)b;//显式
	return 0;
}

        隐式类型转换主要发生在整形和整形之间,整形和浮点数之间,或者浮点数和浮点数之间。

        显式类型转换主要发生在指针和整形之间,指针和指针之间。

        当然并不是任意两个类型之间都能进行强制转换,这两个类型必须要有一定关联性。比如,我们无法从int* 转换成double,因为指针和浮点数之间毫无关联,会编译报错。

2、C++中的类型转换

           在C++中,C语言支持的强制类型转换C++都支持。C++额外新增了内置类型到自定义类型,自定义类型到自定义类型之间的转换。我们分别来看

        2.1、内置类型到自定义类型

        内置类型转换到自定义类型需要该类支持从内置类型到自定义类型的构造函数。

        在构造函数中,我们加上explicit后就不支持隐式类型转换了。

class A
{
public:
	A();
    //explicit A(const int& a)
	A(const int& a)
		:_a(a)
	{
	}
	A(int a, int b)
		:_a(a),
		_b(b)
	{
	}
private:
	int _a=0;
	int _b=0;
};

int main()
{
	int t = 1;
	A aa;
	aa = (A)t;//显式的强制类型转换
	aa = (A)1;

	A bb = 2;//隐式的强制类型转换
	return 0;
}

        2.2、自定义类型转换到内置类型

                自定义类型转换到内置类型需要一个operator 类型()的函数去支持。

class A
{
public:
	A(int a, int b)
		:_a(a)
		,_b(b)
	{
	}
	operator int() const
	{
		return _a + _b;
	}
private:
	int _a=0;
	int _b=0;
};

int main()
{
	int t = 1;
	A aa(2,3);
	t = aa;
	cout << t << endl;//5
	return 0;
}

        之前我们在学习智能指针时,C++11新增的那几个智能指针都支持了operator bool()函数。

std::shared_ptr<int> foo;
//本质上是if (foo.operator bool())
if (foo)//隐式类型转换
	std::cout << "foo points to " << *foo << '\n';
else
	std::cout << "foo is null\n";

        2.3、自定义类型之间的转换

        自定义类型之间的转换需要这两个自定义类型之间有拷贝构造函数。

class A
{
public:
	A(int a, int b)
		:_a(a)
		,_b(b)
	{
	}

int _a=0;
int _b=0;
};
class B
{
public:
	B(int a)
		:_a(a)
	{
	}
	B(const A& aa)
	{
		_a = aa._a + aa._b;
	}
private:
	int _a;
};
int main()
{
	A aa(2, 5);
	B bb = aa;//隐式类型转换
	B bb1 = (B)aa;//显式类型转换
	return 0;
}

        接下来介绍一个自定义类型之间的转换的一个应用场景。

        我们都知道const iterator和const_iterator不是一回事,const iterator不能修改迭代器的指向(不能进行++,--,赋值),但是可以修改迭代器指向的内容。const_iterator不能修改迭代器指向的内容,但是可以++,--,赋值。这和普通指针不一样,普通指针const int* 是不能修改指向的值但是可以++,--的。

        那么const_iterator和iterator之间的转换就是利用了自定义类型之间的转换。

3、C++中的四个显式强制类型转换运算符

        

  我们先来看一个场景:

int main()
{
	const int y = 0;
	int* p2 = (int*)&y;
	(*p2) = 1;

	cout << *p2 << endl;//1
	cout << y << endl;//0
	return 0;
}

        打印的值不一样,是因为我们的强制类型转换去掉了const属性。编译器认为y是const的,不会被改变,所以优化编译时会放到寄存器或直接替换y为0导致的。

        我们使用volatile让每次去拿这个变量值都去内存中而不是寄存器中,这样的话两次打印的值就是一样的了。

volatile const int y = 0;

        所以说,C++引入了四个类型转换方式让类型转换更加安全。首先我们先介绍一下什么是类型安全语言。

        类型安全语言是指编程语言在编译或运行时能够严格检查数据类型,防止因类型错误导致的操作(如将字符串与整数相加)。这类语言通常会在程序执行前捕获类型不匹配的问题,从而避免潜在的安全漏洞或运行时崩溃。像Java,python ,Rust都是类型安全语言,而C/C++是非类型安全语言。因为C/C++支持隐式类型转换和强制类型转换。

        那么这种类型转换就带来了一些问题,可能会造成越界访问之类的问题。

        C++提供了命名强制类型转换static_cast/reinterpret_cast/const_cast/dynamic_cast让类型转换相对更安全。我们在正式的项目中就使用这四种转换方式,而不用之前的括号强制类型转换了。

static_cast

static_cast用于编译时已知的类型转换,通常用于非多态类型之间的转换。它比C-style强制转换更安全,但仍需谨慎使用。只要底层不包含const都可以使用。

基本语法:

static_cast<new_type>(expression)

常见用途:

  • 基本数据类型之间的转换,如int转double
  • 指针/引用在类层次结构中的上行转换(派生类到基类)
  • 空指针转换为目标类型的空指针
  • 枚举值与整型之间的转换

注意事项:

  • 不进行运行时类型检查
  • 不能移除const/volatile属性(注意)
  • 不能用于不相关类型指针间的转换

reinterpret_cast

reinterpret_cast用于低级别的重新解释位模式的转换,是最危险的转换方式。

基本语法:

reinterpret_cast<new_type>(expression)

常见用途:

  • 指针与整数之间的转换
  • 不相关类型指针之间的转换
  • 函数指针之间的转换

注意事项:

  • 完全依赖程序员保证安全性
  • 可能导致未定义行为
  • 通常用于特定低级编程场景

示例:

int* p = new int(65);
char* ch = reinterpret_cast<char*>(p);

const_cast

const_cast专门用于修改类型的const或volatile属性。

基本语法:

const_cast<new_type>(expression)

常见用途:

  • 移除const属性,允许修改原本const的对象
  • 移除volatile属性(不要对变量进行优化)
  • 添加const属性(较少使用)

注意事项:

  • 不能用于改变实际类型
  • 修改真正的const对象会导致未定义行为
  • 主要用于与遗留代码交互

示例:

const int a = 10;
int* b = const_cast<int*>(&a);
*b = 20; // 未定义行为

dynamic_cast

dynamic_cast主要用于类层次结构中安全的多态类型转换,需运行时类型检查。

基本语法:

dynamic_cast<new_type>(expression)

常见用途:

  • 类层次间的下行转换(基类到派生类)
  • 跨继承体系的交叉转换
  • 检测对象实际类型

注意事项:

  • 仅适用于含虚函数的类
  • 需要RTTI支持
  • 失败时返回nullptr(指针)或抛出异常(引用)
  • 性能开销较大

示例:

Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b);
if(d) { /* 转换成功 */ }

对比总结

特性 static_cast reinterpret_cast const_cast dynamic_cast
检查时机 编译时 无检查 编译时 运行时
安全性 中等 最低 最高
适用场景 常规转换 低级转换 const操作 多态转换
性能开销
虚表要求 不需要 不需要 不需要 需要

选择原则:

  • 优先考虑static_cast
  • 涉及const属性用const_cast
  • 多态类型转换用dynamic_cast
  • 万不得已才用reinterpret_cast
int main()
{
	// 对应隐式类型转换--数据的解释意义没有改变

	double d = 12.34;
	int a = static_cast<int>(d);
	cout << a << endl;
	int&& ref = static_cast<int&&>(a);
	// 对应强制类型转换--数据的解释意义已经发⽣改变

	int* p1 = reinterpret_cast<int*>(a);
	// 对应强制类型转换中有⻛险的去掉const属性
	// 所以要注意加volatile
	volatile const int b = 0;
	int* p2 = const_cast<int*>(&b);
	*p2 = 1;
	cout << b << endl;
	cout << *p2 << endl;
	return 0;
}

 4、RTTI

        RTTI(Run-Time Type Information,运行时类型信息)是编程语言中在程序运行时获取和操作对象类型信息的机制。它允许程序在运行时动态识别对象的实际类型,常用于多态场景下的类型检查、转换和反射操作。

        RTTI主要由两个运算符实现,typetd和dynamic_cast。typeid主要用于返回表达式的真实类型(因为对象可能被typedef过)。dynamic_cast主要用于将基类的指针或者引用安全的类型转换成派生类的指针或者引用。

        以下是typeid的两个常用场景:

        (1)获取类型名称(不同编译器不同)

        typeid(表达式或类型的名字)的返回值是typeinfo或typeinfo的派生类对象的引用,typeinfo可以只支持比较等于和不等于。name成员函数可以返回C风格字符串表示对象类型的名字。

#include<iostream>
#include<list>
#include <typeinfo>

using namespace std;

int main()
{
	int x;
	cout << typeid(x).name()<<endl; // 输出编译器相关的类型名称(如 "i" 表示 int)
	double y;
	cout << typeid(y).name() << endl;
	list<int> ll = { 1,2,3 };
	auto it = ll.begin();
	cout << typeid(it).name() << endl;
	return 0;
}

        输出结果:

         (2)多态类型检查

class Base { public: virtual ~Base() {} };  
class Derived : public Base {};  

Base* obj = new Derived;  
if (typeid(*obj) == typeid(Deduced)) {  
    std::cout << "对象是 Derived 类型";  
}

         如果等号两边不是继承关系,则会抛出bad_typeid异常。

        好了,今天的内容就分享到这,我们下期再见!

 

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐