类型转换
- 一、C语言中的类型转换
- 二、C++强制类型转换
- 1. static_cast
- 2. reinterpret_cast
- 3. const_cast
- 4. dynamic_cast
一、C语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
- 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
- 显式类型转化:需要用户自己处理
例如:
void Test() { int i = 1; // 隐式类型转换 double d = i; printf("%d, %.2f\n", i, d); int* p = &i; // 显示的强制类型转换 int address = (int)p; printf("%x, %d\n", p, address); }
缺陷:转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换。
二、C++强制类型转换
标准 C++ 为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast
1. static_cast
static_cast 用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用 static_cast,但它不能用于两个不相关的类型进行转换。它对应的是C语言的隐式类型转换。
例如,相近的类型可以用 static_cast,即意义相似的类型,如下:
int main() { double d = 12.34; int a = static_cast
(d); cout << a << endl; return 0; } 但是有一定关联,意义不相似的类型不可以用 static_cast,例如:
int* ptr = static_cast
(a); 以上语句会发生报错;那么以上语句应该用什么呢?我们往下看。
2. reinterpret_cast
reinterpret_cast 操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。
像上面这种意义不相似的类型我们应该使用 reinterpret_cast,例如:
int main() { int a = 12; int* ptr = reinterpret_cast
(a); return 0; } 3. const_cast
const_cast 最常用的用途就是删除变量的 const 属性,方便赋值。
例如:
int main() { const int a = 2; int* p = const_cast
(&a); *p = 3; return 0; } 但是这里有一个奇怪的现象,我们将 a 的值和 *p 的值打印出来,并且将它们的地址打印出来观察:
我们会发现,a 和 p 的地址是一样的,但是当我们修改 *p 的时候,a 的值为什么不变呢?
其实这里和编译器的优化有关系,const 修饰的变量,编译器通常会对它进行优化,它通常会认为 const 修饰的变量不会被修改,所以编译器不会每次都去内存去取数据,它会将数据放在寄存器,甚至用一个常量去替代,类似于宏一样,当我们需要打印数据时,就直接用初始数据替代我们的 const 变量;所以当我们内存中的数据被修改了,但是编译器没有去内存中去取数据,所以 a 的值没有受影响。
如果我们想让编译器每次都去内存中去取数据呢?我们可以使用关键字 volatile,我们在 const 变量前加上这个关键字,就是告诉编译器不需要对该 const 变量进行优化,每次都去内存中取数据,如下:
int main() { volatile const int a = 2; int* p = const_cast
(&a); *p = 3; cout << a << endl; cout << *p << endl; cout << &a << endl; cout << p << endl; return 0; } 我们可以看到 a 和 *p 的值就一样了。但是我们又发现了另外一个问题,为什么 &a 的值是 1 呢?这是因为 cout 对 &a 识别的时候匹配错了,我们只需要将 &a 强转成如下即可:
如果以上的转换我们使用C语言的强制类型转换可以吗?我们可以尝试一下:
int main() { const int a = 2; int* p = (int*)&a; *p = 3; cout << a << endl; cout << *p << endl; cout << &a << endl; cout << p << endl; return 0; }
如上图,也是可以完成转换的。那么C++为什么要使用这几种类型转换的方式呢?其实C++是为了增强程序的可读性,为了将它们区分开来,例如意义相类似的就用 static_cast;意义不相似的就用 reinterpret_cast;const_cast 就说明这个类型转换不安全。
4. dynamic_cast
dynamic_cast 用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换),这个是C语言不具备的。
- 向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
怎么理解向上转换呢?假设有一个父类 A,一个子类 B,以下场景是不需要转换的,因为符合赋值兼容规则:
int main() { B objb; A obja = objb; A& ra = objb; return 0; }
其中 A& ra = objb; ra 引用的是objb 中父类的部分,即发生了切割,ra 就是 objb 中父类的部分的别名。
- 向下转型:父类对象指针/引用->子类指针/引用(用 dynamic_cast 转型是安全的)
向下转换的规则:父类对象不能转换成子类对象,但是父类指针和引用可以转换成子类指针和引用。
如果我们直接使用强制类型进行向下转换,是不安全的,例如以下场景:
有两个类,分别是父类和子类:
class A { public: virtual void f(){} int _a = 1; }; class B : public A { public: int _b = 2; };
当我们分别定义两个类的对象,传给 func 函数:
void func(A* pa) { B* ptr = (B*)pa; ptr->_a++; ptr->_b++; } int main() { A a; B b; func(&a); func(&b); return 0; }
如果是 func(&b); 那么在 func 函数内就是将父类的对象重新转换为子类,是没有问题的,因为在传入前它本身就是子类的对象。
但是如果是 func(&a); 就会存在越界问题,因为在传入时是父类的对象,在 func 函数内部将该父类对象强制转换成子类对象,那么它本身是父类对象,现在强转为子类对象后,它就可以访问不属于自己的空间 _b,也就是越界访问了,所以是存在问题的。
所以说向下转换直接进行转换是不安全的!
所以C++提供了一种安全的类型转换方式:dynamic_cast,我们可以使用 dynamic_cast 对上面的代码进行修改:
void func(A* pa) { B* ptr = dynamic_cast(pa); if (ptr) { cout << "转换成功" << endl; ptr->_a++; ptr->_b++; } else { cout << "转换失败" << endl; } } int main() { A a; B b; func(&a); func(&b); return 0; }
其中,dynamic_cast 会自动帮我们识别它之前是父类的对象还是子类的对象,从而帮我们实现转换,如果它之前是父类,现在转换为子类,那么就是不可以的,会转换失败,转换失败会返回空;如果它之前是子类,变成父类后又转换为子类,是可以的,就帮我们进行转换。dynamic_cast 还需要一个前提,就是父类必须要有虚函数。
对上面的代码进行测试,当传入父类的对象,转换失败:
当传入子类的对象,转换成功:
总结:
- dynamic_cast 只能用于父类含有虚函数的类;
- dynamic_cast 会先检查是否能转换成功,能成功则转换,不能则返回 0.
- 向下转型:父类对象指针/引用->子类指针/引用(用 dynamic_cast 转型是安全的)
猜你喜欢
网友评论
- 搜索
- 最新文章
- 热门文章