本文主要讲:
一:隐式类型转换与C语言的显示类型转换(了解)
二:C++四种显示类型转换(重点)
1、const_cast
2、static_cast
3、dynamic_cast
4、reinterpret_cast
一:隐式类型转换与C语言的显示类型转换(了解)
1、隐式类型转换介绍
C语言和C++中的隐式类型转换都是自动发生的,但是二者在使用上有一些区别。
C语言中,隐式类型转换主要包括以下几种情况:
- 1. 将较小的整数类型赋值给较大的整数类型,例如将int类型赋值给long类型;
- 2. 将浮点数类型赋值给整数类型,例如将float类型赋值给int类型;
- 3. 运算符操作数类型自动转换,例如将int类型与float类型相加,结果为float类型;
- 4. 通过函数调用参数传递时,将较小类型的参数隐式转换为较大类型。
C++中,除了C语言中的隐式类型转换外,还额外引入了一些其他情况的隐式类型转换:
- 1. 派生类指针转换为基类指针;
- 2. 类型之间的兼容转换(如const类型转换、数组指针与指针转换等);
- 3. 基本类型和bool类型之间的转换。
隐式类型转换虽然方便,但是存在类型是否兼容的问题,如果数据类型不兼容可能会导致数据丢失或错误。因此尽量避免不安全的转换操作,确保程序的正确性和可移植性。
2、C语言里的显示类型转换
在C语言中,显示类型转换(Explicit Type Conversion)是通过使用强制类型转换运算符来进行的。C语言中提供了以下四种强制类型转换运算符:
- 写法一:(type)expression:将expression转化为type类型
int num = 10; float avg = (float)num / 2; // 在此,将表达式num在除法之前显式转换为float类型。
- 写法二:type(expression)
double pi = 3.1415; int approxPi = int(pi); // 在此,将双精度变量pi的值显式转换为int类型。
- 写法三:type{}
int arr[] = {(int)4.5, (int)6.7}; // 使用{}语法以int类型显式转换初始化数组元素。
- 写法四: (type)(expression)
int num = 10; float result = (float)(num); // 在这里,将表达式 num 显式转换为 float 类型。
使用这种方式需要注意类型是否兼容问题,否则强制类型转换后可能会导致数据丢失或者出错。
二:C++四种显示类型转换
1、const_cast类型转换
const_cast称为常类型转化,它主要用于移除表达式的const和volatile属性。它可以将一个const指针转换为非const指针或者将一个const引用转换为非const引用。
语法如下:const_cast<type_id>(expression)
void modifyValue(const int& value) {
int& nonConstValue = const_cast<int&>(value);
// 去除value的const属性
nonConstValue = 10;
}
通常情况下,const_cast用于解决一些编程场景中的限制。例如,在某些情况下,我们可能需要修改一个指向const对象的指针,但是由于const属性的限制,无法直接进行修改。这时候就可以使用const_cast来去除const属性,从而修改对象。
需要注意的是,const_cast只能移除掉const和volatile属性,而不能用于添加属性。这是因为添加属性可能会导致类型不匹配,从而引发错误。使用const_cast需要非常谨慎,因为它可以绕过const的限制,导致潜在的未定义行为。只有当确实需要修改const对象时才应使用const_cast。
2、static_cast类型转换
static_cast又称静态类型转换。它可以在不破坏类型安全的前提下,将一种类型的值转换为另一种具体的类型。通常将 void* 类型转换为其他实际的类型 如: int*。需要注意的是,static_cast在进行类型转换时不进行动态类型检查,因为它无法检查类型之间的安全性,如果进行不安全的转换,可能导致未定义行为或运行时错误。
语法如下:static_cast<type_id>(expression)
void* a = nullptr;
int* result = static_cast<int*>(a);
int b = 10;
float res_b = static_cast<float>(b);
// 主要用于把一个类型转换为一个具体的类型
static_cast并不能处理所有的类型转换情况,它只能处理编译器认为是安全的转换。对于不安全的类型转换,编译器会给出警告或错误。因此,在使用static_cast时,需要谨慎确保转换是合法、安全的。
重点:
static_cast保留来自void*的指针地址。也就是说把A类型转换为void*类型,再把void*类型转换为A类型,地址不变。(reinterpret_cast地址会变化,这也是这两个类型转换的区别)
int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);
cout << a << " " << b << " " << c << endl;
打印结果: 三个地址相同
3、dynamic_cast类型转换
dynamic_cast用于将一个指向基类的指针或引用转换为指向派生类的指针或者引用。它可以在运行时检查类型转换是否安全,避免了在转换类型时出现错误和潜在的未定义行为。但它的效率相对较低,因为它需要进行类型检查。。如果无法转换,或者转换类型不安全,则返回空指针。安全则返回指向派生类对象的指针或者引用。
// dynamic_cast 的语法格式如下:
dynamic_cast <new_type>(expression)
其中,new_type 是要转换的目标类型,expression 是要转换的对象。expression 必须是一个指针或引用,且该指针或引用必须是多态类型(即基类声明了虚函数)。
dynamic_cast 的用法有以下几种情况:
1. 类型转换的目标类型是指针类型:
- 如果转换成功,dynamic_cast 返回指向派生类对象的指针。
- 如果转换失败,dynamic_cast 返回空指针。
2. 类型转换的目标类型是引用类型:
- 如果转换成功,dynamic_cast 返回指向派生类对象的引用。
- 如果转换失败,dynamic_cast 抛出 std::bad_cast 异常。
例如:
class Base {
public:
virtual void foo() {
std::cout << "Base::foo()" << std::endl;
}
};
class Derived : public Base {
public:
void foo() override {
std::cout << "Derived::foo()" << std::endl;
}
};
int main() {
Base* basePtr = new Derived();
try {
Derived& derivedRef = dynamic_cast<Derived&>(*basePtr); //将Base类型的指针转换成派生类类型的引用。
derivedRef.foo(); // 输出:Derived::foo()
} catch (const std::bad_cast& e) {
std::cout << "Conversion failed: " << e.what() << std::endl;
}
delete basePtr;
return 0;
}
在上述代码中,我们同样创建了一个指向 Derived 对象的 Base 类型指针 `basePtr`,然后使用 dynamic_cast 将其转换为 Derived 类型的引用 `derivedRef`。由于 Derived 类型是 Base 类型的派生类,所以转换是合法的,我们可以通过 derivedRef 来调用派生类的成员函数。
4、reinterpret_cast类型转换
reinterpret_cast类型转换是一种非常底层的类型转换方式,采用的是底层的位模式重新解释。reinterpret_cast 可以将一个指针或引用转换为另一种类型的指针或引用,甚至可以将一个整数类型转换为一个指针类型。它不进行类型检查,因此转换过程中可能会忽略类型之间的差异和安全性。因此,使用 reinterpret_cast 需要谨慎,只在必要时使用。
reinterpret_cast 的语法格式如下:
reinterpret_cast <new_type>(expression)
其中,new_type 是要转换的目标类型,expression 是要转换的对象。expression 可以是一个指针、引用或者一个整数类型。
reinterpret_cast 的用法主要涉及以下几个方面:
1. 将指针或引用转换为另一种类型的指针或引用:
- 可以将一个指针或引用转换为任意类型的指针或引用,无论是否相关。
- 转换过程中忽略类型之间的差异和安全性,直接重新解释底层的位模式。
2. 将整数类型转换为指针类型:
- 可以将一个整数类型转换为一个指针类型,以便进行底层的位操作。
例如:
int main() {
int* intPtr = new int(10);
float* floatPtr = reinterpret_cast<float*>(intPtr);
std::cout << *intPtr << std::endl; // 输出:10
std::cout << *floatPtr << std::endl; // 输出未定义,因为 reinterpret_cast 是未定义行为
int value = 20;
int* ptr = reinterpret_cast<int*>(&value);
*ptr = 30;
std::cout << value << std::endl; // 输出:30,因为 reinterpret_cast 将整数类型解释为指针类型
delete intPtr;
return 0;
}
在上述代码中,我们首先创建了一个 int 类型的指针 `intPtr`,然后使用 reinterpret_cast 将其转换为 float 类型的指针 `floatPtr`。由于 int 和 float 类型在底层的位模式上有所不同,因此直接重新解释位模式可能导致未定义行为。接着,我们将整数类型的变量 value 使用 reinterpret_cast 转换为 int 类型的指针 `ptr`,然后通过指针修改了 value 的值。这是一个常见的底层的位操作,但需要注意确保转换操作是合法和有意义的。
所以使用 reinterpret_cast 需要非常小心,因为它会绕过类型系统的检查。错误的使用可能导致未定义行为、内存访问错误等问题。建议只在必要时使用 reinterpret_cast,而且要确保转换操作是合法且有意义的。
重点:
reinterpret_cast 保证只有当指针转换为不同的类型,然后将其 reinterpret_cast 恢复为原始类型,您将获得原始值。
int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);
cout <<a <<" " << b<<" "<<c<<endl;
输出结果:
a 和 c 地址,但未指定 b 的值。 (实际上,它通常包含与 a 和 c 相同的地址,但标准中没有指定,并且在具有更复杂内存系统的机器上可能不是这样。)
- 对于 void* 的转换,应该首选 static_cast。
- 对于模糊类型的转换,应该使用reinterpret_cast。