类型转换
- 隐式类型转换
- 显式类型转换
语法
xxx_cast <类型> (表达式)
1. static_cast
- 用法
用于非多态类型之间的转换,不提供运行时的检查来确保转换的安全性。
主要有如下
- 基本数据类型转换
-
int
转换成enum
- 基类和子类之间指针和引用的转换
- 上行转换,把子类的指针或引用转换成父类,这种转换是安全的(通常使用默认转换)。
- 下行转换,把父类的指针或引用转换成子类,这种转换是不安全的,也需要程序员来保证(通常使用
dynamic_cast
)。
1.1 基本数据类型转换
-
int
转换成char
int n = 97;
cout << n << '\t' << (char)n << '\t' << static_cast<char>(n) << endl;
-
int
转换成float
int n = 1;
cout << n/2 << '\t' << (float)n/2 << '\t' << static_cast<float>(n)/2 << endl;
1.2 int
转换成enum
enum Week{
SUN,MON,TUE,WED,THU,FRI,SAT
};
Week day = 0;
编译上述代码出现如下错误:
- g++编译错误:
error: invalid conversion from ‘int’ to ‘Week’
- clang++编译错误:
error: cannot initialize a variable of type 'const Week' with an rvalue of type 'int'
把代码Week day = 0;
改为Week day = static_cast<Week>(0);
可以消除上面的错误。
1.3 基类和子类之间指针和引用的转换
已知存在继承关系的两个类Base
与Derive
。
class Base{
public:
void Print(){cout << "Base" << endl;}
};
class Derive:public Base{
public:
void Print(){cout << "Derive" << endl;}
};
1.3.1 上行转换
// 对象
Derive d;
Base b = static_cast<Base>(d);
b.Print();
// 引用
Base& fb = static_cast<Base&>(d);
fb.Print();
// 指针
Base* pb = static_cast<Base*>(new Derive);
pb->Print();
通常使用隐式转换
// 对象
Derive d;
Base b = d;
b.Print();
// 引用
Base& fb = d;
fb.Print();
// 指针
Base* pb = new Derive;
pb->Print();
1.3.2 下行转换
这种转换不安全,通常使用dynamic_cast
。
1.4 指针/引用转换
void
指针转换成目标类型的指针,这种转换是不安全的,也需要程序员来保证;
如下代码编译出错,因为不能把void*
转换成其他具体指针。
void* pd = new Derive;
Base* pb = pd; // error: invalid conversion from ‘void*’ to ‘Base*’
pb->Print();
把Base* pb = pd;
改为Base* pb = static_cast<Base*>(pd);
,可以解决上述编译错误。
static_cast
跟传统小括号转换方式几乎是一致的。
问题:为什么要用static_cast
代替传统小括号转换方式?
(来自《C++ prime 4th》5.12.7)
标准 C++ 为了加强类型转换的可视性,引入命名的强制转换操作符,为程序员在必须使用强制转换时提供了更好的工具。例如, 非指针的 static_cast 和const_cast 要比 reinterpret_cast 更安全。结果使程序员(以及读者和操纵程序的工具)可清楚地辨别代码中每个显式的强制转换潜在的风险级别。
虽然标准 C++ 仍然支持旧式强制转换符号,但是我们建议,只有在 C 语言或标准 C++ 之前的编译器上编写代码时,才使用这种语法。
2. const_cast
常量赋值给非常量时,会出现下面编译错误。
const int a = 10;
// 指针
const int* cp = &a;
int* p = cp;// error: invalid conversion from ‘const int*’ to ‘int*’
// 引用
const int& cf = a;
int& f = cf;// error: binding ‘const int’ to reference of type ‘int&’ discards qualifiers
const_cast
主要作用是移除类型的const
属性。
- 常量指针被转化成非常量的指针,并且仍然指向原来的对象;
- 常量引用被转换成非常量的引用,并且仍然指向原来的对象;
-
const_cast
一般用于修改底指针。如const char *p
形式。
const int a = 10;
// 指针
const int* cp = &a;
int* p = const_cast<int*>(cp);
// 引用
const int& cf = a;
int& f = const_cast<int&>(cf);
- 在类中的应用情景
1.const对象想使用类中非const成员函数
2.const成员函数中想修改某个成员变量
#include <iostream>
using namespace std;
//const成员函数修改const成员变量的两种方法
//1.使用const_cast
//2.成员变量前加关键字mutable
class Test
{
int m_n;
int m_count;
//mutable允许成员变量在const成员函数中被改变
public:
Test(int n):m_n(n),m_count(0){}
void Add(int m)
{
m_n = m+m_n;
}
void Print()const
{
//在const函数中this指针是const,所以this指针下的成员都是const
cout<<m_n<<endl;
//cout<<"count= "<<++m_count<<endl;//这一句编译会报错,错误:increment of member ‘Test::m_count’ in read-only object
cout<<"count= "<<++const_cast<int&>(m_count)<<endl;//在const成员函数中修改辅助的成员变量
}
void PrintCount()const {cout<<m_count<<endl;}
};
int main()
{
//const_cast<>() :把const对象/指针/引用转成非const对象/指针/引用
const Test t(10);
t.Print();
//t.Add(5);//这一句编译会报错,因为const对象只能调用const成员函数
//使用const_cast<>()将t转为非const对象
const_cast<Test&>(t).Add(5);
t.Print();
t.PrintCount();
}
3. dynamic_cast
用于类的指针、类的引用或者void *
转化。
主要用于以下三种情况:
- 上行转换,把子类的指针或引用转换成父类,与
static_cast
相同。 - 下行转换,把父类的指针或引用转换成子类,比
static_cast
安全。 - 交叉转换,兄弟之间指针转换,
static_cast
会出现编译错误。
如果时指针,进行正确的转换,获得对应的值;否则返回NULL,如果是引用,则在运行时就会抛出异常;
-
dynamic_cast
使用条件
1.存在父子关系
2.父类中要存在多态
3.子类指针/引用指向父类指针/引用
下行转换
#include <iostream>
using namespace std;
class Base {
public:
void Print() { cout << "Base" << endl; }
virtual ~Base(){}
};
class Derive : public Base {
public:
void Print() { cout << "Derive" << endl; }
};
int main() {
Base * pB = new Derive;
pB->Print();
Derive *pD = dynamic_cast<Derive*>(pB);
pD->Print();
}
交叉转换
#include <iostream>
using namespace std;
class Base {
public:
void Print() { cout << "Base" << endl; }
virtual ~Base(){}
};
class Derive1 : public Base {
public:
void Print() { cout << "Derive1" << endl; }
};
class Derive2 : public Base {
public:
void Print() { cout << "Derive" << endl; }
};
int main() {
Derive1* pD1 = new Derive1;
pD1->Print();
Derive2 *pD2 = dynamic_cast<Derive2*>(pD1);
pD2->Print();
}
-
dynamic_cast
使用场景
1.如果我们想使用父类指针/引用调用子类特有的成员函数
2.可以通过返回值来判断继承关系(假设你定义了猫,狗,猪三个纯虚函数,并且猫有1000种,狗有1000种,猪有1000种,在狗的类中,都有“吠”这个成员函数,现在你想通过代码让所有的狗叫,你不可能定义1000个狗的对象,那样太麻烦了,现在的问题是怎么做才能从3000个物种种得到你的1000只狗,这样就可以使用返回值来判断,假设Dog是所有狗的基类,vec是所有3000种动物的集合)
Dog *g = new Dog;
foreach(auto i : vec)
{
if(dynamic_cast<i*>(g) != NULL)
g->func();
}
由于dynamic_cast
转化失败时,会返回空值,这样不是继承于Dog的类都不会调用到func()这个函数,只有Dog派生出来的类才会调用。
扩展问题
- 如果不是多态会有什么情况?编译错误
-
dynamic_cast
为什么只在多态的继承关系才有效?由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表。
4. reinterpret_cast
修改了操作数类型,重新解释了给出的对象的比特模型而没有进行二进制转换。
主要用于以下六种情况:
- 从指针类型到一个足够大的整数类型
- 从整数类型或者枚举类型到指针类型
- 从一个指向函数的指针到另一个不同类型的指向函数的指针
- 从一个指向对象的指针到另一个不同类型的指向对象的指针
- 从一个指向类函数成员的指针到另一个指向不同类型的函数成员的指针
- 从一个指向类数据成员的指针到另一个指向不同类型的数据成员的指针
4.1 指针类型与整数类型的转化
把指针值(地址)转化成整数,把整数转化成指针值。
#include <iostream>
#include <vector>
using namespace std;
void Func(){
cout << "Func" << endl;
}
int main() {
// 变量指针类型与整数类型转化
{
int n = 100;
int addr = reinterpret_cast<int>(&n);
cout << "addr:" << hex << addr << dec << " "<< &n << endl;
int* b = reinterpret_cast<int*>(addr);
cout << "value:" << *b << endl;
}
// 函数指针类型与整数类型转化
{
int f = reinterpret_cast<int>(Func);
typedef void (*pFunc)();
pFunc pf = reinterpret_cast<pFunc>(f);
pf();
}
}
4.2 函数指针的转化
FuncNum
函数指针转化成FuncAddr
,使用FuncAddr
的参数列表。
#include <iostream>
#include <vector>
using namespace std;
void FuncNum(int n){
cout << "num:" << n << endl;
}
void FuncAddr(int* p) {
cout << "addr:" << p << endl;
}
int main() {
int n = 10;
FuncNum(n);
FuncAddr(&n);
typedef void (*pfNum)(int n);
typedef void (*pfAddr)(int* n);
pfNum pfunc = FuncNum;
pfunc(n);
reinterpret_cast<pfAddr>(pfunc)(&n);
}
4.3 对象指针的转化
A
类对象指针转化成B
类的对象指针,使用B
类的成员函数。
#include <iostream>
#include <vector>
using namespace std;
class A{
public:
void Func(){
cout << "A" << endl;
}
};
class B {
public:
void Test() { cout << "B" << endl; }
};
int main() {
A* pA = new A;
pA->Func();
B* pB = reinterpret_cast<B*>(pA);
pB->Test();
}
4.4 类函数成员的转化
A
类对象使用B
类的成员变量。(A
类与B
类没有任何关系。)
#include <iostream>
#include <vector>
using namespace std;
class A{
public:
void Func(){
cout << "A" << endl;
}
};
class B {
public:
void Test() { cout << "B" << endl; }
};
int main() {
// 对象的函数指针
{
A a;
a.Func();
typedef void (A::*Func_t)();
Func_t pfTest = reinterpret_cast<Func_t>(&B::Test);
(a.*pfTest)();
}
// 对象指针的函数指针
{
A *pA = new A;
pA->Func();
typedef void (A::*Func_t)();
Func_t pfTest = reinterpret_cast<Func_t>(&B::Test);
(pA->*pfTest)();
}
}
4.5 类数据成员的转化
#include <iostream>
#include <vector>
using namespace std;
class Test{
public:
Test(int data) : data(data) {}
private:
int data;
};
int main() {
Test test(0x61626364);
char* p = reinterpret_cast<char*>(&test);
for (int i = 0; i != sizeof(Test) / sizeof(char); i++) {
cout << p[i] << endl;
}
}
谨慎使用
reinterpret_cast
小结
No. | 转换 | 转换对象 | 作用 | 转换时机 |
---|---|---|---|---|
1 | static_cast |
基本类型、指针、引用 | 实现传统的小括号转化功能 | 在编译期间实现转换 |
2 | const_cast |
const 类型的对象、指针、引用 |
移除变量const 限定 |
在编译期间实现转换 |
3 | dynamic_cast |
类的指针、类的引用或者void *
|
多态父类指针/引用转化成子类指针/引用 | 在运行期间实现转换,并可以返回转换成功与否的标志/抛出异常 |
4 | reinterpret_cast |
指针、引用、算术类型 | 万能强制类型转换 | 在编译期间实现转换 |
扩展阅读
-
<<C++程序设计语言(第1-3部分)(原书第4版)>>
- 22.2.1
dynamic_cast
- 22.2.3
static_cast
和dynamic_cast
- 22.2.1
网友评论