目录
C语言中的类型转换
隐式类型转换:
显示类型转换:
总结:
C++中的类型转换
static_cast:
reinterpret_cast:
const_cast:
dynamic_cast:
RTTI
C语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转换,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
隐式类型转换:
编译器在编译阶段自动进行,能转就转,不能转就编译失败。
主要包括:整型家族之间的类型转换、整型和浮点型之间、bool和整型之间、bool和浮点型之间、指针->bool。
C++中又引入了内置类型和类类型之间(构造函数和操作符重载的支持)和类类型之间的转换。
class A
{
public:
//单参数构造函数,支持隐式类型转换
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
//全缺省构造函数,支持隐式类型转换
B(int x = 1,int y = 2)
:_x(x),_y(y)
{}
operator int()
{
return _x + _y;
}
private:
int _x;
int _y;
};
int main()
{
char temp = 'a';
int a = temp;//整型家族之间隐式类型转换
double b = a;//整型和浮点数之间隐式类型转换
bool c = a;//bool和整型
double d = c;//bool和浮点型
int* p = &a;
c = p;//指针->bool合法的隐式转换
//p = c;//bool->指针非法的隐式转换
A a1 = a;//内置类型->类类型
B b1 = 20;
int sum1 = b1;//类类型->内置类型
int sum2 = (int)b1;
initializer_list<int> il{ 1,2,3,4,5 };
vector<int> v{ il };//类类型之间
return 0;
}
指针->bool:
- 非空指针(指向合法内存的指针)会被隐式转换为true
- 空指针(NULL)会被隐式转换为false
bool->指针:
- 布尔值(true/false)仅为
1
或0
,若隐式转为指针,true会被视为非零地址(如0x1),false可能被解释为NULL,但这与bool的原始意图无关,属于未定义行为。- 虽然可以通过强制类型转换将bool转为指针(如(int*)true),但这完全依赖编译器和系统实现,属于未定义行为,是极其危险的行为。
C++内置类型->类类型:
- 单参数构造函数或者多参数但只需要传入一个参数的构造函数,支持将内置类型隐式转换为自定义类型,在类与对象一文中有详细讲解。
- 若要禁止这种隐式转换,需要使用explicit关键字修饰构造函数。
C++类类型->内置类型:
- 需要在类内进行相关的运算符重载(如operator int()),当类对象转换为对应类型时,会自动调用。
C++类类型之间的转换:
- 同样是构造函数支持的。
缺点:
由于是编译器自动进行的转换,可能会出现意外截断或提升的问题。
显示类型转换:
需要用户通过强制类型转换手动指定转换类型。
主要包括:指针类型和整型之间、不同类型的指针之间。
int main()
{
int a = 10;
int* p1 = (int*)a;//整型->指针
int b = (int)p1;//整型->指针
double* p2 = (double*)p1;//int*->double*
return 0;
}
缺点:
需要用户手动确保安全性,对用户要求较高。
总结:
尽量避免不必要的类型转换,尤其是隐式转换。
使用显式转换时,确保转换后的值在目标类型的有效范围内。
对指针和函数指针的转换需格外谨慎。
C++中的类型转换
C语言的类型转换格式很简单,但是有不少缺点的:
1. 隐式类型转换有些情况下可能会出问题:比如数据精度丢失
2. 显式类型转换将所有情况混合在一起,代码不够清晰
因此C++提出了自己的类型转化风格,但是因为C++要兼容C语言,所以C++中还可以使用C语言的转换风格的。
C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast
static_cast:
编译时检查,用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用,属于隐式类型转换,但是相较于普通的隐式类型转换,static_cast是可视的,明确告知了存在类型转换,以及要转换的类型,提高了代码的安全性和可读性。
需要注意的是static_cast不能用于不相关的类型之间进行转换。
int main()
{
char temp = 'a';
double d = 1.23;
int a = static_cast<int>(d);//double->int
a = static_cast<int>(temp);//char->int
//int* p = static_cast<int*>(&d);//错误的,int*和double*是不相关的两个类型
return 0;
}
reinterpret_cast:
reinterpret_cast是最底层的类型转换运算符,用于执行二进制级别的重新解释。它不进行任何编译时类型安全检查,几乎可以强制转换任何指针或整数类型到其他类型,但使用时需高度谨慎,因为可能导致未定义行为。属于强制类型转换。
int main()
{
double d = 1.23;
int* p = reinterpret_cast<int*>(&d);//double*->int*
int b = reinterpret_cast<int>(p);//int*->int
return 0;
}
const_cast:
const_cast是专门用于添加或移除const和volatile限定符的类型转换运算符。它允许开发者显式绕过类型系统的常量性检查,但需谨慎使用,否则可能导致未定义行为。
int main()
{
const int a = 10;
cout << "a:" << a << endl;
int* p = const_cast<int*>(&a);
*p = 3;
cout << "*p:" << *p << endl;
cout << "a:" << a << endl;
return 0;
}
上边的代码通过const_cast<int*>将const int*类型的&a转换为int*类型,并赋值给p,再通过指针p修该a的值,编译运行:
可以发现a的值并没有改变,是因为const_cast操作符没有作用吗?但是否定的,a的值之所以没有改变是因为编译器会把const常量的值存放在寄存器中,每次读取时直接去寄存器中读取数据,而通过指针p修改的是a在内存中的数据,但是并不会影响寄存器中的数据,因此再次输出a的值时会显示没有改变。要想保持寄存器和内存中数据的一致性,可以通过volatile修饰变量实现。
int main()
{
//const int a = 10;
volatile const int a = 10;
cout << "a:" << a << endl;
int* p = const_cast<int*>(&a);
*p = 3;
cout << "*p:" << *p << endl;
cout << "a:" << a << endl;
return 0;
}
dynamic_cast:
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
向下转型:父类对象指针/引用->子类指针/引用(dynamic_cast会进行类型检查,保证安全性,常规的强转或者使用reinterpret_cast/static_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 = reinterpret_cast<B*>(pa);
B* pb3 = dynamic_cast<B*>(pa);
cout << "pb1:" << pb1 << endl;
cout << "pb2:" << pb2 << endl;
cout << "pb3:" << pb3 << endl;
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}
上述代码定义了一个含有虚函数的基类A,通过A派生出B类,通过fun函数,分别传入A*参数和B*参数,在fun函数中对参数进行类型转换,观察结果:
可以发现dynamic_cast的类型转换是更加安全的,当想要将一个实际指向父类的指针转换为子类指针时,dynamic_cast无法完成转换,保证了安全性。
RTTI
RTTI(Runtime Type Information)即运行时类型识别,是C++中用于在程序运行时获取对象类型信息的机制,主要支持dynamic_cast和typeid操作。
typeid:返回一个type_info对象的指针,描述表达式在运行时的类型。
dynamic_cast:在多态场景下,将基类指针/引用安全地转换为派生类类型,需运行时检查类型是否合法。
除了运行时获取类型信息,还有编译时获取类型信息:
decltype:是在编译时获取类型信息。
int main()
{
int a = 10;
decltype(a) b;
cout << typeid(b).name() << endl;
return 0;
}
上述代码中decltype获取a的类型后,又定义了b,通过typeid输出b的类型。