在 C++ 中,不同数据类型之间可以相互转换。无需用户指明如何转换的称为自动类型转换(隐式类型转换),需要用户显式的指明如何转换的称为强制类型转换。
隐式类型转换是安全的,显式类型转换是有风险的,C语言之所以增加强制类型转换的语法,就是为了强调风险,让程序员意识到自己在做什么。
但是,这种强调风险的方式还是比较粗放,粒度比较大,它并没有表明存在什么风险,风险程度如何。再者,C风格的强制类型转换统一使用( )
。
为了使潜在风险更加细化,使问题追溯更加方便,使书写格式更加规范,C++ 对类型转换进行了分类,并新增了四个关键字来予以支持,它们分别是:static_cast、const_cast、reinterpret_cast、dynamic_cast。
(1)自动类型转换 和 自动类型转换
自动类型转换示例:
int a = 6;
a = 7.5 + a;
编译器对 7.5 是作为 double 类型处理的,在求解表达式时,先将 a 转换为 double 类型,然后与 7.5 相加,得到和为 13.5。在向整型变量 a 赋值时,将 13.5 转换为整数 13,然后赋给 a。整个过程中,我们并没有告诉编译器如何去做,编译器使用内置的规则完成数据类型的转换。
强制类型转换示例:
int n = 100;
int *p1 = &n;
float *p2 = (float*)p1;
p1 是int *类型,它指向的内存里面保存的是整数,p2 是float *类型,将 p1 赋值给 p2 后,p2 也指向了这块内存,并把这块内存中的数据作为小数处理。我们知道,整数和小数的存储格式大相径庭,将整数作为小数处理非常荒诞,可能会引发莫名其妙的错误,所以编译器默认不允许将 p1 赋值给 p2。但是,使用强制类型转换后,编译器就认为我们知道这种风险的存在,并进行了适当的权衡,所以最终还是允许了这种行为。
不管是自动类型转换还是强制类型转换,前提必须是编译器知道如何转换,例如,将小数转换为整数会抹掉小数点后面的数字,将int *
转换为float *
只是简单地复制指针的值,这些规则都是编译器内置的,我们并没有告诉编译器。
(2)4种类型转换简单说明
为了使潜在风险更加细化,使问题追溯更加方便,使书写格式更加规范,C++ 对类型转换进行了分类,并新增了四个关键字来予以支持,它们分别是:
关键字 | 说明 |
---|---|
static_cast | 用于良性转换,一般不会导致意外发生,风险很低。 |
const_cast | 用于 const 与非 const、volatile 与非 volatile 之间的转换。 |
reinterpret_cast | 高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的 C++ 类型转换。 |
dynamic_cast | 借助 RTTI,用于类型安全的向下转型(Downcasting)。 |
这四个关键字的语法格式都是一样的,具体为:
xxx_cast<newType>(data)
newType 是要转换成的新类型,data 是被转换的数据。
例如,老式的C风格的 double 转 int 的写法为:
double scores = 95.5;
int n = (int)scores;
C++ 新风格的写法为:
double scores = 95.5;
int n = static_cast<int>(scores);
(3)static_cast
static_cast 是 静态转换 的意思,也就是在编译期间转换,转换失败的话会抛出一个编译错误。
static_cast 只能用于良性转换,这样的转换风险较低,一般不会发生什么意外,例如:
(1)基本数据类型之间的转换,例如 short 转 int、int 转 double;
int m = 10;
long n1 = m; // int 转 long(范围小的转成范围大的,无风险)
long n2 = static_cast<long>(m); // 基本数据类型的转换可以使用static_cast转换
char c1 = m; // int 转 char(范围大的转成范围小的,精度丢失,肯能存在风险)
char c2 = static_cast<char>(m); // 基本数据类型的转换可以使用static_cast转换
cout << n1 << endl;
cout << n2 << endl;
cout << c1 << endl;
cout << c2 << endl;
(2)基类和子类之间的转换
定义两个类:Personal 和 Student类,它们之间是继承关系,Personal 是基类,Student 是派生类,代码如下:
class Personal
{
public:
void setIndex(int index)
{
mIndex = index;
}
int getIndex()
{
return mIndex;
}
private:
int mIndex;
};
class Student :public Personal
{
public:
void setAge(int age)
{
mAge = age;
}
int getAge()
{
return mAge;
}
private:
int mAge;
};
基类转派生类是有风险的,而派生类转基类是无风险的;
派生类转基类可以直接向上转型,代码如下:
Personal* personal = new Student(); // 向上转型,无风险
上转型也可以使用 static_cast 来实现:
Student* student = new Student();
Personal* personal = static_cast<Personal*>(student);
而基类转派生类属于向下转型,向下转型存在风险,不能使用 static_cast 来转换。
(3)void 指针和具体类型指针相互转换
C风格的转换是这样的:
int* a = new int;
*a = 10;
void* p = a; // int指针转void指针
int* b = (int*)p; // void指针转int指针
如果使用 static_cast 来转换的话,代码如下:
int* a = new int;
*a = 10;
void* p = static_cast<void*>(a); // int指针转void指针
int* b = static_cast<int*>(p); // void指针转int指针
(4)有转换构造函数的类与其它类型之间的转换
class Book
{
public:
Book() {}
Book(int a):mA(a) {}
int getA()
{
return mA;
}
private:
int mA;
};
class Student
{
public:
Student(const Book& book)
{
mBook = book;
}
Book getBook()
{
return mBook;
}
private:
Book mBook;
};
int main() {
Book book(10);
Student student = book;
cout << student.getBook().getA() << endl;
return 0;
}
其中,类型转换代码:
Student student = book;
可以写成:
Student student = static_cast<Student>(book);
(5)有类型转换函数的类与其它类型之间的转换
class Student
{
public:
Student():mA(10) {}
operator double() // 强制类型转换运算符的重载
{
return mA;
}
double getA()
{
return mA;
}
private:
double mA;
};
int main() {
Student student;
double n = student;
cout << n << endl;
return 0;
}
其中,类型转换代码:
double n = student;
可以写成:
double n = static_cast<Student>(student);
需要注意的是,static_cast 不能用于无关类型之间的转换,因为这些转换都是有风险的,例如:
(1)两个具体类型指针之间的转换;
(2)int 和指针之间的转换;
(3)static_cast 也不能用来去掉表达式的 const 修饰和 volatile 修饰。换句话说,不能将 const/volatile 类型转换为非 const/volatile 类型。
(4)const_cast
const_cast 比较好理解,它用来去掉表达式的 const 修饰或 volatile 修饰。换句话说,const_cast 就是用来将 const/volatile 类型转换为非 const/volatile 类型。
const 和 volatile 都是用于修饰变量的,所以下面就以 const 为例:
C风格的类型转换是:
const int constA = 10;
int* p = (int*) & constA;
使用 const_cast 关键字转换代码是:
const int constA = 10;
int* p = const_cast<int*>(&constA);
(5)dynamic_cast
dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。
向上转型是无条件的,不会进行任何检测,所以都能成功;
向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。
dynamic_cast 与 static_cast 是相对的,dynamic_cast 是 动态转换 的意思,static_cast 是 静态转换 的意思。
dynamic_cast 会在程序运行期间借助 RTTI 进行类型转换,这就要求基类必须包含虚函数
;
static_cast 在编译期间完成类型转换,能够更加及时地发现错误。
dynamic_cast 的语法格式为:
dynamic_cast <newType> (expression)
newType 和 expression 必须同时是指针类型或者引用类型。
换句话说,dynamic_cast 只能转换指针类型和引用类型
,其它类型(int、double、数组、类、结构体等)都不行。
对于指针,如果转换失败将返回 NULL;
对于引用,如果转换失败将抛出 std::bad_cast 异常。
下面开始代码演示,首先准备好基类和派生类:
class Personal
{
public:
virtual void setIndex(int index)
{
mIndex = index;
}
virtual int getIndex()
{
return mIndex;
}
private:
int mIndex;
};
class Student :public Personal
{
public:
void setAge(int age)
{
mAge = age;
}
int getAge()
{
return mAge;
}
private:
int mAge;
};
Personal 是基类,Student 是派生类。
【向上转型】
向上转型没有风险型,可以使用 static_cast 转换,也可以使用 dynamic_cast 转换。
static_cast 向上转型的类型转换已经演示过了,下面直接演示使用 dynamic_cast 实现向上转型。
Student* student = new Student();
Personal* personal = dynamic_cast<Personal*>(student);
【向下转型】
向下转型是一个比较危险的动作,只能使用 dynamic_cast 实现。
Personal* personal = new Personal();
Student* personal = dynamic_cast<Student*>(personal);
(6)reinterpret_cast
reinterpret 是 重新解释 的意思,顾名思义,reinterpret_cast 这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,非常简单粗暴,所以风险很高。
reinterpret_cast 可以认为是 static_cast 的一种补充,一些 static_cast 不能完成的转换,就可以用 reinterpret_cast 来完成,例如两个具体类型指针之间的转换、int 和指针之间的转换(有些编译器只允许 int 转指针,不允许反过来)。
【举例一:将 char* 转换为 float*】
char str[] = "zhangsan";
float* p = reinterpret_cast<float*>(str); // 将 char* 转换为 float*
cout << *p << endl;
【举例二:将 int 转换为 int*】
// 将 int 转换为 int*
int a = 100;
int* p = reinterpret_cast<int*>(a);
【举例三:类指针转int指针】
class A {
public:
A(int a = 0, int b = 0) : m_a(a), m_b(b) {}
private:
int m_a;
int m_b;
};
int main() {
A* a = new A(10, 20);
int* p = reinterpret_cast<int*>(a);
cout << *p << endl;
return 0;
}
[本章完...]