类型转换
- 隐式类型转换
- 显式类型转换
语法
xxx_cast <类型> (表达式)
1.static_cast
用于非多态类型之间的转换,不提供运行时的检查来确保转换的安全性。
主要有:
- 基本数据类型转换
int
转换成enum
3. 基类和子类之间指针和引用的转换
- 上行转换,把子类的指针或引用转换成父类,这种转换是安全的(通常使用默认转换)。
- 下行转换,把父类的指针或引用转换成子类,这种转换是不安全的,也需要程序员来保证(通常使用
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.3 指针/引用转换
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
代替传统小括号转换方式?
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);
注意
const_cast<>
在<>
中通常只用指针或者引用类型。- 基本类型常量,因为存在常量展开的情况,
const_cast<>
并不会改变后面的值。
const int a = 10;
cout << "a:" << a << endl;
const_cast<int&>(a) = 11; // 已经改变a所在内存的值
cout << "a:" << a << endl; // 常量展开,看不到改变
cout << "*(&a)" << *(&a) << endl; // 直接访问内存可以看到改变
int *p = &a;
cout << "*p:" << *p << endl; // 直接访问内存可以看到改变
在const
成员函数中修改成员变量
通常在const
成员函数中是不能修改成员变量。
在const
函数中所有成员变量都是const
在const
函数中this指针
是const
类型,所以所有成员不能
被直接改变。
例如:
提供一个打印出Set/Get次数的功能
#include <iostream>
using namespace std;
class Integer{
int n;
int setter;
int getter;
public:
Integer(int n):n(n),setter(0),getter(0){}
void Set(int n){
++setter;
this->n=n;
}
int Get()const {
++getter;
return n;
}
void PrintCount()const{
cout << "set:"<< setter << ",get:" << getter << endl;
}
};
int main(){
Integer n(10);
n.Set(2);
cout << n.Get() << endl;
n.Set(4);
cout << n.Get() << endl;
n.Set(5);
cout << n.Get() << endl;
n.Set(-1);
cout << n.Get() << endl;
n.Set(2);
cout << n.Get() << endl;
n.PrintCount();
}
- 方法1
用const_cast<>()
把需要修改的成员变量转成非const
类型
int Get()const {
++const_cast<int&>(getter);
return n;
}
- 方法2
使用const_cast<>()
把this
转换成非const
类型,然后修改成员变量。
int Get()const {
++(const_cast<Integer*>(this)->getter);
return n;
}
- 方法3
在需要修改的成员变量声明前加上关键字mutable
。
mutable int getter;
mutable的成员变量可以在const函数中修改。
3. dynamic_cast
用于类的指针、类的引用或者void *
转化。
主要用于以下三种情况:
- 上行转换,把子类的指针或引用转换成父类,与static_cast相同。
- 下行转换,把父类的指针或引用转换成子类,比static_cast安全。
- 交叉转换,兄弟之间指针转换,
static_cast
会出现编译错误。
如果时指针,进行正确的转换,获得对应的值;否则返回NULL,如果是引用,则在运行时就会抛出异常;
下行转换
#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
只在多态有效。
扩展
- 如果不是多态会有什么情况?编译错误
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 | 指针、引用、算术类型 | 万能强制类型转换 | 在编译期间实现转换 |
cout
打印地址
如果要打印地址需要强制转换
#include <iostream>
using namespace std;
void f(){}
int main(){
int n = 10;
int* p = &n;
const char* s = "abc";
cout << p << " " << (void*) p << endl;
cout << s << " " << (void*) s << endl;
cout << f << " " << (void*) f << endl;
}
试着把上面改成C++的转换方式
参考答案:
<< " " > << static_cast<const void*>(s) << endl; cout << f << " " << > reinterpret_cast<void*>(f) << endl; ```
成员指针
C++与C语言相比多了类的语法。类中有成员变量和成员函数。这两个都是有地址的,都可放在放入指针。
1. 成员变量指针
#include <iostream>
using namespace std;
struct Point{
int x,y,z;
Point(int x,int y,int z):x(x),y(y),z(z){}
int X(){return x;}
int Y(){return y;}
int Z(){return z;}
};
int main(){
// 成员变量
// 类和结构体是不占空间的,所以只有偏移,没有地址
printf("&Point::x=%p\n",&Point::x);
printf("&Point::y=%p\n",&Point::y);
printf("&Point::z=%p\n",&Point::z);
Point p(100,200,300);
printf("&p.x=%p\n",reinterpret_cast<void*>(&p.x));
printf("&p.t=%p\n",reinterpret_cast<void*>(&p.y));
printf("&p.z=%p\n",reinterpret_cast<void*>(&p.z));
// cout不能打印偏移
cout << &Point::x << "," << &Point::y << "," << &Point::z << endl;
// cout可以打印地址
cout << &p.x << "," << &p.y << "," << &p.z << endl;
}
总结
&类名::成员变量
获取成员偏移&对象.成员变量
获取成员的地址
2. 成员变量指针
printf("&Point::X=%p\n",&Point::X);
printf("&Point::Y=%p\n",&Point::Y);
printf("&Point::Z=%p\n",&Point::Z);
// 全是1
cout << &Point::X << "," << &Point::Y << "," << &Point::Z << endl;
// cout可以打印地址
cout << reinterpret_cast<void*>(&Point::X) << ","
<< reinterpret_cast<void*>(&Point::Y) << ","
<< reinterpret_cast<void*>(&Point::Z) << endl;