C++ 类型转换
一、C语言中的类型转化
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与
接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型
转换和显式类型转换。
1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
2. 显式类型转化:需要用户自己处理
char a = 10, b = 20;
int c = a + b; // a和b先提升为int再相加
void foo(double d);
foo(10); // 10被转换为double类型
int x = 3.14; // x = 3(小数部分被截断)
int* p = &i;
// 显示的强制类型转换
int address = (int) p;
缺点:转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换。
二、C++中的类型转化
1.隐式类型转换
与C语言类似,编译器自动完成,但C++的隐式转换规则更严格,尤其在面向对象编程中。
1. 基本类型的隐式转换
- 遵循C语言的算术提升规则(如
char
→int
→double
)。 - 类类型可以通过构造函数或类型转换运算符隐式转换:
class MyInt { public: MyInt(int x) {} // 允许从int隐式转换为MyInt }; MyInt a = 5; // 隐式调用构造函数
2. 派生类到基类的转换
- 在继承体系中,派生类对象可以隐式转换为基类指针或引用(会切片):
class Base {}; class Derived : public Base {}; Derived d; Base* pb = &d; // 隐式向上转换(Upcasting)
2.显式类型转换(强制转换)
C++提供了四种类型安全的强制转换操作符,取代C风格的强制转换((type)value
),以增强代码可读性和安全性。
1. static_cast
- 用途:用于明确定义的、编译时已知的类型转换。
- 示例:
int main() { double d = 12.34; int a = static_cast<int>(d); cout<<a<<endl; return 0; }
- 限制:
- 不能用于无关类型(如指针和整数之间)。
- 无法去除
const
或volatile
属性。
2. dynamic_cast
- 用途:
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)。向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)。向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)。注意:1. dynamic_cast只能用于父类含有虚函数的类。
2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。
- 示例:
class A { public: virtual void f() {} }; class B : public A {}; void fun(A* pa) { // dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回 B* pb1 = static_cast<B*>(pa); B* pb2 = dynamic_cast<B*>(pa); cout << "pb1:" << pb1 << endl; cout << "pb2:" << pb2 << endl; } int main() { A a; B b; fun(&a); fun(&b); return 0; }
3. const_cast
- 用途:添加或移除
const
属性,常用于删除const属性。 - 示例:
void Test () { const int a = 2; int* p = const_cast<int*>(&a ); *p = 3; cout<< a <<endl; }
4. reinterpret_cast
-
用途:低层次的类型重新解释(如指针与整数之间的转换),不进行类型检查,用于将一种类型转换为另一种不同的类型。
- 示例:
int main() { double d = 12.34; int a = static_cast<int>(d); cout << a << endl; // 这里使用static_cast会报错,应该使用reinterpret_cast //int *p = static_cast<int*>(a); int* p = reinterpret_cast<int*>(a); return 0; }
- 风险:可能导致未定义行为,应尽量避免使用。
三、运行时类型识别(RTTI)
我们每个人都有一个身份证,身份证上记录了这个人的姓名、出生日期、性别等信息。在C++中,每个对象也有一个“身份证”,这个“身份证”记录了对象的类型信息。RTTI就像是读取身份证信息的工具,它允许我们在程序运行时查看对象的类型。
1.typeid
操作符
typeid
操作符就像是你查看某个人的身份证,它会返回一个type_info
对象,这个对象包含了类型的信息。
#include <iostream>
#include <typeinfo>
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base {};
int main() {
Base* basePtr = new Derived();
std::cout << "Type of basePtr: " << typeid(*basePtr).name() << std::endl;
delete basePtr;
return 0;
}
在这个例子中,typeid(*basePtr)
会返回一个type_info
对象,表示basePtr
指向的对象的实际类型。注意,为了使typeid
能够正确工作,基类通常需要至少有一个虚函数(即多态类型)。
2.dynamic_cast
操作符
dynamic_cast
操作符就像是在不同类型的证件之间进行安全转换。它主要用于将基类指针或引用转换为派生类指针或引用,并且只有在转换是安全的情况下才会成功。
#include <iostream>
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base {
public:
void derivedFunction() {
std::cout << "Derived function called." << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
derivedPtr->derivedFunction();
} else {
std::cout << "Conversion failed." << std::endl;
}
delete basePtr;
return 0;
}
在这个例子中,dynamic_cast
尝试将basePtr
转换为Derived*
。如果basePtr
实际上指向一个Derived
对象,转换会成功,否则会返回nullptr
。
3. RTTI的缺点
- 性能开销:RTTI会增加程序的运行时开销,特别是在频繁使用的情况下。
- 代码复杂性:过度依赖RTTI可能会使代码变得复杂,难以维护。
4. 适用场景
- 多态类型:RTTI主要用于处理多态类型,即至少有一个虚函数的类。
- 调试和日志:在调试或记录日志时,RTTI可以帮助识别对象的实际类型。
- 动态类型转换:当需要在运行时安全地转换类型时,可以使用
dynamic_cast
。