【C++】类型转换

发布于:2025-07-05 ⋅ 阅读:(14) ⋅ 点赞:(0)

1. C中的类型转换

在C语言中,类型转换是一个非常重要的概念,它发生在多种情况下:当赋值运算符左右两侧的变量类型不同时(如int赋值给float);当函数调用时实参类型与形参类型不匹配时;或者函数返回值类型与接收返回值的变量类型不一致时。C语言提供了两种类型转换机制:

1.1 隐式类型转换(自动类型转换)

  • 由编译器在编译阶段自动完成
  • 遵循特定的转换规则,按照数据类型优先级(如double > float > int > char)自动提升
  • 转换失败会导致编译错误
  • 示例:
    int i = 10;
    float f = i;  // 隐式将int转换为float
    

1.2 显式强制类型转换

  • 需要程序员明确指定转换类型
  • 使用强制类型转换运算符:(type)expression
  • 可以用于指针类型转换等复杂场景
  • 示例:
    float f = 3.14;
    int i = (int)f;  // 显式将float强制转换为int
    

1.3 类型转换的限制条件

  1. 转换必须具有实际意义:

    • 数值类型之间可以相互转换(如int与float)
    • 指针类型转换需要符合内存访问规则
    • 不能随意转换毫无关联的类型(如int与结构体)
  2. 常见支持转换的类型组合:

    • 整数类型之间(char, short, int, long等)
    • 浮点类型之间(float, double)
    • 整数与浮点类型之间
    • 指针与整数类型之间(需谨慎使用)
  3. 转换可能导致的数据问题:

    • 精度损失(如float转int会丢失小数部分)
    • 数据溢出(如long转short可能超出范围)
    • 指针类型转换可能导致非法内存访问

在实际编程中,应该谨慎使用类型转换,特别是强制类型转换,因为它可能掩盖潜在的类型不匹配问题,导致运行时错误。

int main()
{
	int i = 1;
	// 隐式类型转换
	// 隐式类型转换主要发生在整形和整形之间,整形和浮点数之间,浮点数和浮点数之间
	double d = i;
	printf("%d, %.2f\n", i, d);
	int* p = &i;
	// 显式的强制类型转换
	// 强制类型转换主要发生在指针和整形之间,指针和指针之间
	int address = (int)p;
	printf("%p, %d\n", p, address);
	// malloc返回值是void*,被强转成int*
	int* ptr = (int*)malloc(8);

	// 编译报错:类型强制转换: 无法从“int *”转换为“double”
	// 指针是地址的编号,也是一种整数,所以可以和整形互相转换
	// 但是指针和浮点数毫无关联,强转也是不支持的
	// d = (double)p;
	return 0;
}

运行结果:


2. C++中的类型转换

• C++兼容C语言,因此支持C语言的所有隐式类型转换和显式强制类型转换方式。

• C++还支持内置类型与自定义类型之间的相互转换:

  • 内置类型转换为自定义类型:需要通过构造函数实现
  • 自定义类型转换为内置类型:需要定义operator 类型()成员函数

如:

// 内置类型和自定义类型之间
// 1、自定义类型 = 内置类型 ->构造函数支持
// 2、内置类型 = 自定义类型 ->operator 内置类型 支持
class A
{
public:
	// 构造函数加上explicit就不支持隐式类型转换了
	//explicit A(int a)
	A(int a)
		:_a1(a)
		, _a2(a)
	{}
	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{}
	// 加上explicit就不支持隐式类型转换了
	// explicit operator int()
	operator int() const
	{
		return _a1 + _a2;
	}
private:
	int _a1 = 1;
	int _a2 = 1;
};
class B
{
public:
	B(int b)
		:_b1(b)
	{}
	
private:
	int _b1 = 1;
};
int main()
{
	// 单参数的转换
	string s1 = "1111111";
	A aa1 = 1;
	A aa2 = (A)1;

	// 多参数的转换
	A aa3 = { 2,2 };
	const A& aa4 = { 2,2 };

    // 自定义类型转内置类型
	int z = aa1.operator int();
	int x = aa1;
	int y = (int)aa2;
	cout << x << endl;
	cout << y << endl;
	cout << z << endl;
	std::shared_ptr<int> foo;
	std::shared_ptr<int> bar(new int(34));
	//if (foo.operator bool())
	if (foo)
		std::cout << "foo points to " << *foo << '\n';
	else
		std::cout << "foo is null\n";
	if (bar)
		std::cout << "bar points to " << *bar << '\n';
	else
		std::cout << "bar is null\n";
	
	return 0;
}

运行结果:

注意:单参数和多参数转换的本质,是通过隐式类型转换调用构造函数,构造一个临时对象,然后再拷贝给原对象,不过编译器优化成直接构造。

如果我们加上explicit就不支持隐式类型转换,我们这么写就会报错,因为单参数和多参数转换此时并不能通过隐式类型转换来构造,如下图所示:

• 对于自定义类型之间的转换,只需在目标类型中定义接收源类型参数的构造函数即可实现。例如,要将A类型对象转换为B类型,只需在B类中定义接收A类型参数的构造函数。

如:

class B
{
public:
	B(int b)
		:_b1(b)
	{}
	// 支持A类型对象转换为B类型对象
	B(const A & aa)
		: _b1(aa) // 调用aa.operator int()
	{}
private:
	int _b1 = 1;
};

int main()
{
	// A类型对象隐式转换为B类型
	B bb1 = aa1;
	B bb2(2);
	bb2 = aa1;
	const B& ref1 = aa1; // 必须加const,引用临时对象,临时对象具有常性
	return 0;
}

通过调试窗口查看结果:


3. C++显式强制类型转换

3.1 类型安全

• 类型安全是指编程语言在编译和运行时提供保护机制,避免非法的类型转换和操作,导致出现内存访问错误等,从而减少程序运行时的错误。类型安全的语言通常具有以下特征:

  • 严格的类型检查机制
  • 禁止或限制隐式类型转换
  • 提供明确的类型转换操作符
  • 运行时类型识别(RTTI)支持

• C语言不是类型安全的语言,主要表现在:

  • 允许广泛的隐式类型转换,如int和指针之间的转换
  • 强制类型转换(cast)操作过于自由,缺乏安全性检查
  • 典型问题示例:
    int a = 10;
    double* p = (double*)&a;  // 潜在的内存访问问题
    *p = 3.14;                // 可能导致未定义行为
    

• C++虽然兼容C语言,支持隐式类型转换和强制类型转换,但为了改进类型安全性,引入了四种显式的命名强制类型转换:

  1. static_cast:用于基本类型转换和具有继承关系的类之间的转换
    double d = 3.14;
    int i = static_cast<int>(d);
    
  2. reinterpret_cast:用于低级别的类型重新解释
    int* p = new int(10);
    long addr = reinterpret_cast<long>(p);
    
  3. const_cast:用于添加或移除const/volatile限定符
    const int a = 10;
    int* p = const_cast<int*>(&a);
    
  4. dynamic_cast:用于安全地进行多态类型转换
    Base* b = new Derived();
    Derived* d = dynamic_cast<Derived*>(b);
    

这些显式转换操作符的目的是:

  • 使代码中的类型转换意图更加明确
  • 限制潜在危险的转换操作
  • 提供编译时和运行时的类型检查
  • 提高代码的可读性和可维护性
void insert(size_t pos, char ch)
{
	// 这里当pos==0时,就会引发由于隐式类型转换
	// end跟pos比较时,提升为size_t导致判断结束逻辑出现问题
	// 在数组中访问挪动数据就会出现越界,经典的类型安全问题
	int end = 10;
	while (end >= pos)
	{
		// ...
		cout << end << endl;
		--end;
	}
}
int main()
{
	insert(5, 'x');
	//insert(0, 'x');

	// 这里会本质已经出现了越界访问,只是越界不一定能被检查出来
	int x = 100;
	double* p1 = (double*)&x;
	cout << *p1 << endl;

	const int y = 0;
	int* p2 = (int*)&y;
	(*p2) = 1;
	// 这里打印的结果是1和0,也是因为我们类型转换去掉了const属性
	// 但是编译器认为y是const的,不会被改变,所以会优化编译时放到
	// 寄存器或者直接替换y为0导致的
	cout << *p2 << endl;
	cout << y << endl;
	return 0;
}

insert在0下标处插入就会出现越界,也会造成死循环

这里我们来看看y的示例运行结果:

这里打印的结果是1和0,也是因为我们类型转换去掉了const属性,但是编译器认为y是const的,不会被改变,所以会优化编译时放到寄存器或者直接替换y为0导致的

编译器默认会优化代码(如缓存变量到寄存器、省略"冗余"访问)。如果你不想编译器这么做,可以使用volatile 关键字,volatile 强制每次访问都直接从内存读取/写入,确保数据是最新值。

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

cout << *p2 << endl;
cout << y << endl;


3.2 C++中4个显式强制类型转换运算符

1. static_cast - 静态类型转换

用途:最常用的类型转换,用于编译时确定的类型转换
特点

  • 基本数据类型之间的转换,如将int转换为double
  • 将void*指针转换为其他指针类型
  • 在类层次结构中进行向上转换(派生类指针/引用转换为基类)
  • 将非const类型转换为const类型

示例:

// 基本类型转换
double d = 3.14;
int i = static_cast<int>(d);  // 3

// 类层次结构中的向上转换(安全)
class Base {};
class Derived : public Base {};
Derived* d = new Derived;
Base* b = static_cast<Base*>(d);  // 向上转换

// 类层次结构中的向下转换(不安全!)
Base* base = new Base;
Derived* derived = static_cast<Derived*>(base);  // 可能引发未定义行为

// void* 转换
int x = 10;
void* v = static_cast<void*>(&x);
int* p = static_cast<int*>(v);

注意事项:

  • 不能用于去除const属性
  • 不能用于无关类型指针之间的转换
  • 不会进行运行时类型检查

2. reinterpret_cast - 重新解释转换

用途:低级别的位模式重新解释
特点

  • 指针类型之间的任意转换
  • 指针和整数之间的转换
  • 不同类型引用之间的转换

示例:

// 指针与整数互转
int* p = new int(42);
uintptr_t addr = reinterpret_cast<uintptr_t>(p);

// 不同类型指针互转
float f = 3.14f;
unsigned int bits = reinterpret_cast<unsigned int&>(f);

// 函数指针转换
using FuncPtr = void(*)();
auto func = reinterpret_cast<FuncPtr>(&someFunction);

// 不相关类指针转换(危险!)
class A {};
class B {};
A* a = new A;
B* b = reinterpret_cast<B*>(a);  // 高风险

风险提示:

  • 可能导致内存访问越界
  • 最危险的转换(可能破坏类型安全)
  • 平台依赖性强
  • 通常用于底层编程或特殊硬件操作

3. const_cast - 常量性移除

用途:添加或移除 const/volatile 属性
特点

  • 去除const/volatile属性
  • 添加const/volatile属性

示例:

// 移除 const
const int ci = 10;
int* modifiable = const_cast<int*>(&ci);  
*modifiable = 20;  // 危险!原始常量对象被修改

// 合法使用:调用非 const 重载
const std::string str = "hello";
auto& nonConstStr = const_cast<std::string&>(str);
nonConstStr.clear();  // 合法前提是 str 原本非常量

// 添加 const
int x = 5;
const int* cx = const_cast<const int*>(&x);

注意事项:

  • 修改原const变量可能导致未定义行为
  • 主要用于调用非const参数的旧式API
  • 不能改变基础类型

4. dynamic_cast - 动态类型转换

用途:在继承层次结构中进行安全的向下转换
特点

  • 向下转换(基类到派生类)
  • 横向转换(同一层次不同派生类之间)
  • 运行时类型检查

示例:

class A
{
public:
	virtual void f() {}
	int _a = 1;
};

class B : public A
{
public:
	int _b = 2;
};

void fun1(A* pa)
{
	// 指向父类转换时有风险的,后续访问存在越界访问的风险
	// 指向子类转换时安全
	B* pb1 = (B*)pa;
	cout << "pb1:" << pb1 << endl;
	cout << pb1->_a << endl;
	cout << pb1->_b << endl;

	pb1->_a++;
	pb1->_b++;
	cout << pb1->_a << endl;
	cout << pb1->_b << endl;
}

void fun2(A* pa)
{
	// dynamic_cast会先检查是否能转换成功(指向子类对象),能成功则转换,
	// (指向父类对象)转换失败则返回nullptr
	B* pb1 = dynamic_cast<B*>(pa);
	if (pb1)
	{
		cout << "pb1:" << pb1 << endl;
		cout << pb1->_a << endl;
		cout << pb1->_b << endl;
		pb1->_a++;
		pb1->_b++;
		cout << pb1->_a << endl;
		cout << pb1->_b << endl;
	}
	else
	{
		cout << "转换失败" << endl;
	}
}

void fun3(A& pa)
{
	// 转换失败,则抛出bad_cast异常
	try {
		B& pb1 = dynamic_cast<B&>(pa);
		cout << "转换成功" << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
}

int main()
{
	A a;
	B b;
	//fun1(&a);
	//fun1(&b);
	fun2(&a);
	fun2(&b);

	fun3(a);
	fun3(b);

	return 0;
}

注意:

向下转换分为两种情况:

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

实现原理:

  1. 通过虚表(vtable)获取运行时类型信息(RTTI)
  2. 检查类型兼容性
  3. 调整指针位置(多重继承情况下)
  4. 返回转换结果或抛出异常

特殊要求:

  • 基类必须包含至少一个虚函数
  • 需要启用RTTI支持
  • 相比static_cast有额外性能开销

最佳实践

  1. 优先使用 static_cast:满足大部分常规转换需求

  2. 慎用 reinterpret_cast:除非绝对必要且理解所有风险

  3. 避免 C 风格转换:如 (int)3.14,它可能意外执行 reinterpret_cast

  4. 类型转换前思考:是否需要转换?是否有更好的设计?


4. RTTI(Runtime Type Identification)

• RTTI的英文全称是"Runtime Type Identification",中文称为"运行时类型识别",它指的是程序在运行时期确定对象类型信息的机制。与静态类型识别不同,RTTI允许程序在运行时(而不是编译时)动态获取对象的类型信息,这在多态场景下特别有用。例如,在处理基类指针指向派生类对象时,RTTI可以帮助确定实际的对象类型。

• RTTI主要由两个运算符实现:

  • typeid:用于获取对象的类型信息,返回一个type_info对象的引用
  • dynamic_cast:用于在继承层次中进行安全的向下转型(downcasting),将基类的指针或引用转换为派生类的指针或引用,如果转换失败会返回nullptr(对于指针)或抛出bad_cast异常(对于引用)

• typeid运算符的详细说明:

  1. 语法:typeid(e),其中e可以是任意表达式或类型的名字
  2. 返回值:返回type_info或type_info派生类对象的常量引用
  3. 功能特性:
    • 支持相等(==)和不等(!=)比较操作
    • 提供name()成员函数,返回表示类型名称的C风格字符串
  4. 注意事项:
    • 不同编译器对type_info的实现可能不同,导致typeid(e).name()的返回值有差异
    • 例如,在GCC中可能返回带有修饰的名称,而在MSVC中可能返回更简洁的名称
  5. 文档参考:typeinfo官方文档

• typeid运算符的行为分析:

  1. 当运算对象是以下情况时,返回静态类型(编译时确定):
    • 非类类型(如基本数据类型int、float等)
    • 不包含任何虚函数的类类型
  2. 当运算对象满足以下条件时,返回动态类型(运行时确定):
    • 是定义了至少一个虚函数的类的左值
    • 例如:
      class Base { virtual void foo() {} };
      class Derived : public Base {};
      Base* b = new Derived;
      // 这里typeid(*b)将在运行时返回Derived的类型信息
      

• RTTI的应用场景:

  1. 调试和日志记录:在调试时输出对象的实际类型信息
  2. 序列化/反序列化:根据运行时类型信息进行正确的对象序列化
  3. 对象验证:在向下转型前验证对象的实际类型
  4. 插件系统:动态加载的模块中识别对象类型

注意:过度使用RTTI可能表明设计存在问题,良好的面向对象设计应该尽量通过虚函数实现多态行为。

int main()
{
	int a[10];
	int* ptr = nullptr;
	cout << typeid(10).name() << endl;
	cout << typeid(a).name() << endl;
	cout << typeid(ptr).name() << endl;
	cout << typeid(string).name() << endl;
	cout << typeid(string::iterator).name() << endl;
	cout << typeid(vector<int>).name() << endl;
	cout << typeid(vector<int>::iterator).name() << endl;
	return 0;
}

运行结果:

// vs2019下的运行结果
int
int[10]
int*
class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >
class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char> > >
class std::vector<int, class std::allocator<int> >
class std::_Vector_iterator<class std::_Vector_val<struct std::_Simple_types<int> > >
// gcc 9.4下运行结果
i
A10_i
Pi
NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
N9__gnu_cxx17__normal_iteratorIPcNSt7__cxx1112basic_stringIcSt11char_traitsIcES
aIcEEEEE
St6vectorIiSaIiEE
N9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE