目录
1.C语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。
(1) 两种形式的类型转换
- 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
- 显式类型转化:需要用户自己处理
- 类型转换应为相近类型 : int, short, char之间就可以互相转换,因为他们都是用来表示数据的大小,只是范围不一样;char因为编码的原因用来表示字符,严格来说如果是有符号的话,它是从-128 - 127这个范围的整数
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);
}
(2)C语言类型转换的缺陷 : 转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换
2.为什么C++需要四种类型转换
(1)C风格的转换格式很简单,但是有不少缺点的:
- ①隐式类型转化有些情况下可能会出问题:比如数据精度丢失
- ② 显式类型转换将所有情况混合在一起,代码不够清晰
3.C++强制类型转换
(1)static_cast
- static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换
int main()
{
double d = 12.34;
int a = static_cast<int>(d); //相近类型转换
cout << a << endl;
int *p = static_cast<int*>(a); //不相近类型转换 -- 报错
return 0;
}
(2)reinterpret_cast
- reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型
①常规用法
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
// 这里使用static_cast会报错,应该使用reinterpret_cast
//int *p = static_cast<int*>(a);
int *p = reinterpret_cast<int*>(a); //不相近类型转换
return 0;
}
②错误用法
- reinterpret_cast可以让编译器以FUNC的定义方式去看待DoSomething函数
- 所以非常的BUG,下面转换函数指针的代码是不可移植的,所以不建议这样用
- C++不保证所有的函数指针都被一样的使用,所以这样用有时会产生不确定的结果
typedef void(*FUNC)();
int DoSomething(int i)
{
cout << "DoSomething : " << i << endl;
return 0;
}
void Test()
{
FUNC f = reinterpret_cast<FUNC>(DoSomething);
f();
}
int main()
{
Test();
return 0;
}
(3)const_cast
- const_cast最常用的用途就是删除变量的const属性,方便赋值
- C+ +把这个单独分出来,意思提醒用的人注意. const属性被去掉以后,会被修改。小心跟编译器优化冲突误判
①const_cast使用测试
void Test1()
{
//const修饰的叫常变量
const int a = 2;
//a = 10; //a不可修改
//const int* p = &a; //还是不能改变
// *p = 10;
int* p = const_cast<int*>(&a); //const_cast用于去掉const属性
*p = 10; //可修改
cout << a << endl; //2 从寄存器中取
cout << *p << endl; //10 从内存中取
}
②对于结果不同的解释
编译器看到a是const在语法层面上认为 a是无法被改变的,(每次访问a的时候就不需要去内存里取了)于是将a的值存在寄存器,最终导致a的值在内存中被改了,但是你取值的时候是在寄存器,造成了预期与结果不一致volatile关键字能防止编译器优化。
③关于常量区补充 :
- 常量区的那一段是在硬件上面保护的,只要有写指令它的编译不会报错,因为它看不出来你的指令是啥,运行的时候如果你要常量区进行写入就会报错。(程序运行起来时进程,通过页表将虚拟地址空间和物理地址进行映射,进程知道那一块是常量区)
- 常量区为什么禁止写入 : 常量区存储的有编译好的指令,随便修改是会导致很多问题的
(4)dynamic_cast
①dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
- 向上转型:子类对象指针/引用 -> 父类指针/引用(不需要转换,赋值兼容规则)
- 向下转型:父类对象指针/引用 -> 子类指针/引用(用dynamic_cast转型是安全的)
- dynamic_cast只能用于含有虚函数的类 ,因为运行时类型检查需要运行时的类型信息,而这个信息是存储在虚函数表中的,只有定义了虚函数的类才有虚函数表。
- dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
③测试用例
- pa有两种情况 : pa指向父类对象,pa指向子类对象
- 如果pa是指向父类对象,那么不做任何处理
- 如果pa是指向子类对象,那么请转回子类,并访问子类对象中_b成员
- dynamic_cast : 如果pa指向的父类对象,那么则转换不成功,返回nullptr; 如果pa指向的子类对象,那么则转换成功,返回对象指针
- dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
class A
{
virtual void f() {}
public:
};
class B : public A
{
public:
int _b = 0;
};
void func(A* pa)
{
B* pb1 = static_cast<B*>(pa);
B* pb2 = dynamic_cast<B*>(pa);
if (pb2 == nullptr)
{
cout << "转换失败!" << endl;
}
else
{
pb2->_b++;
}
cout << "pb1:" << pb1 << endl;
cout << "pb2:" << pb2 << endl;
}
int main()
{
A aa;
B bb;
func(&aa);
func(&bb);
}
4.explicit
- explicit用来修饰构造函数,从而禁止单参数构造函数的隐式转换
class A
{
public:
explicit A(int a)
{
cout << "A(int a)" << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
private:
int _a;
};
int main()
{
A a1(1);
//A a2 = 1; //error
return 0;
}
①在语法上,代码中的A a2 = 1等价于以下两句 :
A tmp(1); //构造
A a2(tmp); //拷贝构造
②在早期的编译器中,当编译器遇到A a2 = 1这句代码时,会先构造一个临时对象,再用这个临时对象拷贝构造a2。但是现在的编译器已经做了优化,当遇到A a2 = 1这句代码时,会直接按照A a2(1)的方式进行处理,这也叫做隐式类型转换。
③但对于单参数的自定义类型来说,A a2 = 1这种代码的可读性不是很好,因此可以用explicit修饰单参数的构造函数,从而禁止单参数构造函数的隐式转换
5.RTTI
①RTTI(Run-Time Type Identification)就是运行时类型识别。
②C++通过以下几种方式来支持RTTI:
- typeid:在运行时识别出一个对象的类型。
- dynamic_cast:在运行时识别出一个父类的指针(或引用)指向的是父类对象还是子类对象。
- decltype:在运行时推演出一个表达式或函数返回值的类型。
6.常见测试题
①C++中的4种类型转换分别是:____ 、____ 、____ 、____。
- 分别是static_cast、reinterpret_cast、const_cast和dynamic_cast。
②4种类型转换的应用场景。
- static_cast用于相近类型的类型之间的转换,编译器隐式执行的任何类型转换都可用static_cast。
- reinterpret_cast用于两个不相关类型之间的转换。
- const_cast用于删除变量的const属性,方便赋值。
- dynamic_cast用于安全的将父类的指针(或引用)转换成子类的指针(或引用)。