1、概念
C++类型转换是将一种数据类型转换为另一种数据类型的过程。
2、分类
C++中的类型转换可以从3个角度来划分:
- 根据类型转换是由程序员显式指定,还是由编译器自动完成,分为显式类型转换和隐式类型转换;
- 根据参与类型转换的变量类型,分为标准类型转换和用户定义类型转换;
- 根据类型转换的运算规则,分为按照存储值转换和按照数值转换。
2.1、显式和隐式类型转换
下面是显式类型转换和隐式类型转换的例子:
1、显式类型转换,在下面的代码中,变量b是double类型,使用显式类型转换将其转换为int类型,并将结果赋值给变量c。
// 将double类型的变量b转换为int类型
double b = 3.14;
int c = (int)b;
2、隐式类型转换,在下面的代码中,变量a是int类型,变量b是double类型,当它们参与除法运算时,系统会自动将变量a转换成double类型,使得运算结果也是double类型。这就是隐式类型转换。
// 变量a会被隐式转换为double类型,然后进行除法运算
int a = 5;
double b = 3.14;
double c = a / b;
2.2、标准类型转换和用户定义类型转换
标准类型转换指C++ 基本类型之间的转换,可以概括为以下几类:
- 数值、字符、bool、枚举类型之间的转换;
- 指针类型之间、引用类型之间的转换;
- 限定符的添加和删除:比如const,volatile。
用户定义类型转换指类类型与基本类型、或者类类型之间的转换。用户定义类型转换,需要程序员实现以下两种成员函数:
- 转换构造函数:定义在类中的一种特殊的构造函数,它可以将其它类型转换为该类的一个对象。
- 类类型转换操作符:定义在类中的一种特殊的成员函数,它可以将一个对象转换为另一个基本类型。
举个例子:
//重载int运算符,把Object转化为int类型
class Object{
public:
//转换构造函数
Object(int num):m_number(num){}
//类类型转换操作符:定义int运算符
operator int(){return number;}
private:
int m_number;
};
int main() {
int number = 2;
Object obj = number;//把int类型转换为Object类型
int newNumber = (int)obj;//把Object类型转换为int类型
return 0;
}
2.3、按照存储值转换和按照数值转换
按照存储值转换通常是指在某些情况下进行数据类型的强制转换,这种转换是按照字符在计算机中存放的二进制值进行转换的,包括:int、short、char、bool、void、指针类型、signed和unsigned类型之间的转换。
按照数值转换通常是针对数值类型进行的,这种转换是按照数值大小进行等值转换,包括:浮点数类型和非浮点数类型之间的转换,比如double和int之间的转换。
3、指针类型转换
C++提供了4种运算符,用于指针(引用)类型转换,4种运算符号的区别如下:
虽然说上述运算符号还可用于把指针类型转换为非指针类型,但应用场景少,不考虑。
3.1、reinterpret_cast
reinterpret_cast用于任意指针(引用)类型之间的转换。转换运算过程中,只是简单地把源变量的二进制值拷贝到新的类型变量中,不作任何类型检查。例如,对于以下代码:
int main() {
int* p1 = new int(10);
char* p2 = reinterpret_cast<char*>(p1);
std::cout << *p1 << std::endl; // 输出10
std::cout << *p2 << std::endl; // 输出未定义的值
return 0;
}
在这个例子中,将一个int类型的指针p1转换为了char类型的指针p2,虽然可以取得p2所指向的地址,但是由于reinterpret_cast忽略了指针类型所指向的对象类型,因此输出的值是未定义的。因此,在使用reinterpret_cast时,需要非常小心和谨慎。
3.2、static_cast
static_cast用于子类指针(引用)和父类指针(引用)之间的相互转换,在编译期执行类型检查。在C++中,使用static_cast将基类指针转换为子类指针是一种常见的操作,但是在实际使用时,可能会出现以下错误,假设我们有如下的类层次结构:
class Base {
public:
virtual ~Base() {}
};
class Derived : public Base {
public:
int data;
};
我们创建一个Base类的指针,并将其指向基类对象:
Base* basePtr = new Base;
此时,如果试图使用static_cast将basePtr指针转换为Derived指针类型,会导致编译时不会发现错误,但是在运行时则会产生未定义行为,例如:
Derived* derivedPtr = static_cast<Derived*>(basePtr);
int data = derivedPtr->data;
这是因为类型转换时,将一个指向基类对象的指针转换为子类指针,但由于子类对象不存在,所以在使用子指针访问子类成员时会发生未定义行为。
3.3、dynamic_cast
dynamic_cast用于子类指针(引用)和父类指针(引用)之间的相互转换,在运行时进行类型检查。当用户试图使用dynamic_cast将基指针转换为子类指针类型,如果子类对象不存在,那么会返回nullptr。
使用dynamic_cast进行类型转换,并检查返回的指针是否为nullptr,就可以解决上述例子中static_cast引起的错误:
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
int data = derivedPtr->data;
} else {
std::cout << "转换失败" << std::endl;
}
3.4、const_cast
const_cast是C++中用于取消指针或引用对象的const限定符的一种操作符,它用于将const类型转换为非const类型。const_cast的学习难点在于理解它的使用场景,如果我们要把const类型转换为非const类型,为什么不一开始就把类型声明为const类型呢?这是因为const类型数据可能是第三方库定义的,当我们希望在不修改第三方库的源码的情况下,修改const数据,需要用到该运算符。
4、总结
类型转换在程序中具有重要的作用和意义,但在使用过程中需要注意类型转换的安全性,主要有以下两个方面:
- 类型转换合法性。在进行类型转换时,需要保证转换的数据类型是合法的,例如不要把指针类型转换为字符类型,没有任何意义。
- 精度损失和溢出问题。在进行类型转换时,需要考虑数据类型的精度,在某些情况下,类型转换可能会导致精度损失,例如将浮点数转换为整数时,小数部分会被截断,可能会导致精度丢失。另外,在一些情况下,类型转换也可能会导致数据溢出,例如将一个很大的整数转换为一个较小的整数类型,将导致数据溢出。