C语言中的类型转换
在C语言中, 如果赋值运算符左右两侧类型不同, 或者形参与实参类型不匹配, 或者返回值类型与
接收返回值类型不一致时, 就需要发生类型转化, C语言中总共有两种形式的类型转换:
隐式类型转换和显式类型转换
1. 隐式类型转化是关联度很强, 意义相近的类型之间的转换, 编译器在编译阶段自动进行, 能转就转, 不能转就编译失败,
2. 显式类型转化是有一定关联的类型, 需要用户自己处理.
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++为了加强类型转换的可视性, 引入了四种命名的强制类型转换操作符:
1. static_cast
2. reinterpret_cast
3. const_cast
4. dynamic_cast
static_cast
static_cast用于非多态类型的转换(静态转换), 编译器隐式执行的任何类型转换都可用
static_cast, 但它不能用于两个不相关的类型进行转换, static_cast对应于C语言的隐式类型转换.
void Test2()
{int a = 12;double b = static_cast<double>(a);//对应double b = a;C语言的隐式类型转换cout << b << endl;
}
一种错误写法是这样的:
void test2()
{ //错误写法int* pa = static_cast<int*>(a);cout << pa << endl;
}
这就不属于相关类型即(隐式类型转换)的范畴, 这就要用到reinterpret_cast转换了
reinterpret_cast
reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释, 适用于不相关类型之间的转换,即重新解释一种类型的含义, 用于将一种类型转换为另一种不同的类型, 对应C语言中的显示类型转换.
void Test2()
{int a = 12;//static_castdouble b = static_cast<double>(a);//对应double b = a;C语言的隐式类型转换cout << b << endl;//reinterpret_castint* pa = reinterpret_cast<int*>(a);cout << pa << endl;
}
const_cast
const_cast最常用的用途就是删除变量的const属性, 方便赋值.
常用方法: 利用const_cast 转换为同类型的非 const 引用或者指针
- <>内为转换的目标,()内为要转换的值
- const_cast只针对指针、引用、this指针
void Test4()
{const int a = 2;int* p = const_cast<int*>(&a);*p = 3;cout << a << endl;cout << *p << endl;cout << &a << endl;cout << p << endl;
}
这里我们将 a 的地址通过 const_cast 转换之后赋值给指针变量 p, 取消了&a的底层const属性, 然后通过p将a的值修改为3, 通过输出可以看到a输出的值是2, *p的值是3, 而p和&a实际确实是同一块地址, 为什么呢?
因为变量a在定义时被 const 修饰, 而编译器认为const修饰值不会被修改, 所以编译器会进行一些优化, 比如将a的值放入一个寄存器中, 以后每次使用 a 都直接从该寄存器中读取, 而不再从内存中读取, 提高了效率, 这就导致我们虽然通过指针变量p修改了内存中a的值, 但寄存器中保存的仍然是a修改之前的值, 所以打印出来的是 2.
要解决这个问题很简单, 我们在定义常变量 a 时使用 volatile 关键字进行修饰即可, volatile 关键字的作用是保持内存可见性, 即取消编译器的优化, 每次都从内存中读取变量的值.
void Test4()
{volatile const int a = 2;int* p = const_cast<int*>(&a);//等价于int* p = (int*)&a;*p = 3;cout << a << endl;cout << *p << endl;cout << (void*) & a << endl;cout << p << endl;
}
这里需要注意:
再举个例子:
void Test5()
{char ch = 'x';cout << &ch << endl;
}
void Test5()
{char ch = 'x';cout << (void*)&ch << endl;
}
回到主题, 之前const_cast的例子其实可以反映出为什么 C++ 要重新专门去设计一系列的类型转换, 比如const_cast
强制类型转换操作符来用于 const 类型和非const类型之前的转换, 比如这里它就提醒了程序员这里有const属性的删除, 要考虑是否需要加volatile之类的注意事项. 虽然直接把 &a 强制转换为int*也可以, 但不容易看出来问题出在哪.
dynamic_cast
之前在继承中提到过, 基类对象不能赋值给派生类对象:
向上转型: 子类对象指针/引用->父类指针/引用(不需要转换, 赋值兼容规则)
向下转型: 父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
C++为我们提供了更安全的父类与子类对象之间的转换: dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针/引用(动态转换), 也就是说dynamic_cast应对的是向下转型.
注意:
1. dynamic_cast只能用于父类含有虚函数的类
2. dynamic_cast会先检查是否能转换成功, 能成功则转换, 不能则返回0
class A
{
public:virtual void f() {}
};class B : public A
{};void fun(A* pa)
{// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回B* pb1 = static_cast<B*>(pa);B* pb2 = dynamic_cast<B*>(pa);cout << "pb1:" << pb1 << endl;cout << "pb2:" << pb2 << endl;
}int main()
{A a;B b;fun(&a);cout << "------------------------------" << endl;fun(&b);return 0;
}
当fun传入&a时, dynamic_cast会检查发现pa指向的是一个A类型的对象, 所以不能转换为B类型对象的指针, 所以给pb2返回了一个0, 而static_cast则只是相近类型的类型转换, 不会进行检查.
给A和B类添加成员变量, 并且我在fun内想通过pb2去修改_a和_b:
class A
{
public:virtual void f() {}int _a = 0;
};class B : public A
{
public:int _b = 1;
};void fun(A* pa)
{B* pb2 = (B*)pa;pb2->_a++;pb2->_b++;
}int main()
{A a;B b;fun(&a);fun(&b);return 0;
}
程序崩溃了, 因为对原本就指向A类型对象的指针强转为B类型指针, 再去访问B类型的对象, 就是越界访问了, 是错误的行为.
我们可以用dynamic_cast这样修改:
void fun(A* pa)
{B* pb = dynamic_cast<B*>(pa);if (pb){pb->_a++;pb->_b++;cout << "转换成功" << endl;}else{cout << "转换错误" << endl;}
}
注意
强制类型转换关闭或挂起了正常的类型检查, 每次使用强制类型转换前, 程序员应该仔细考虑是否还有其他不同的方法达到同一目的, 如果非强制类型转换不可, 则应限制强制转换值的作用域, 以减少发生错误的机会, 建议避免使用强制类型转换.
RTTI
RTTI: Run-time Type identification的简称, 即运行时类型识别
C++通过以下方式来支持RTTI:
- typeid: 在运行时识别出一个对象的类型.
- decltype: 在运行时推演出一个表达式或函数返回值的类型.
- dynamic_cast: 在运行时识别出一个父类的指针/引用指向的是父类对象还是子类对象.
注意: C++ 中的 auto 并不属于 RTTI, auto 是一种变量类型推导机制, 它能够根据变量的初始化表达式自动推导出变量的类型, 属于编译时识别, 而 RTTI 是一种运行时类型识别机制.