【本节目标】
1. C语言中的类型转换
2. C++强制类型转换
3. C++强制类型转换操作符
4. RTTI
1. C语言中的类型转换
在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与 接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型 转换和显式类型转换。
⭐1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
⭐2. 显式类型转化:需要用户自己处理
需要注意的是,只有相近类型之间才能发生隐式类型转换,比如int和double表示的都是数值,只不过它们表示的范围和精度不同。而指针类型表示的是地址编号,因此整型和指针类型之间不会进行隐式类型转换,如果需要转换则只能进行显式类型转换。比如:
// C
// 整形之间 隐式类型转换
// 整形和浮点数 隐式类型转换
// bool和整形 bool和指针 隐式类型转换
// 指针和整形 强制类型转换
// 不同类型的指针之间 强制类型转换
void Test()
{
int i = 1;
// 隐式类型转换
double d = i;
printf("%d, %.2f\n", i, d);
int* p = &i;
// 显示的强制类型转换
int address = (int)p;
printf("%p, %d\n", p, address);
//无法转换,他们之间没有关联
//double dd = (double)p;
// 有一个对象,如何取一个对象的前四个字节呢?
Base b;
int b1 = (int)b;//没有关联,无法转换
// 借助指针取一个对象的前四个字节
Base* ptr1 = &b;// Base*看一个Base的大小
int ptr2 = (int*)ptr1;// int*看一个int的大小
}
⭐缺陷1:隐式类型转换造成比较逻辑错误
//隐式类型转换
void Insert(size_t pos, int x)
{
//size_t end = _size;
int end = 10;
// pos传入0会死循环,end会减到-1
// 比较的时候end会隐式类型转换成size_t,再比较
while (end >= (int)pos)
{
cout << end <<"挪走" << endl;
--end;
}
}
⭐缺陷2:显式类型转换将所有情况混合在一起,代码不够清晰,转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换
⭐总结:因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。
2. C++强制类型转换
// CPP
// 构造函数只支持
// 内置类型->自定义类型之间,本质借助构造 隐式类型转换 如:string和const char*
// 自定义类型之间->内置类型, 本质要重载一个operator 类型 隐式类型转换 如:下面的A
// 自定义类型之间->自定义类型之间,本质借助构造,隐式类型转换 如:initializer_list和容器
void Test()
{
//单参数的构造函数,支持内置类型隐式转换成自定义类型
string s1 = "111111";// 构造(临时对象) + 拷贝构造 -> 构造
const string& s2 = "11111";// 此时就不会优化
// 基类和派生类不属于
// 赋值兼容转换
/*Drive d;
Base b = d;*/
}
自定义类型之间->内置类型, 本质要重载一个operator类型,我们看看如何重载。
class A
{
public:
int operator()()//重载括号运算符
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
void Test()
{
A aa;
// 但是此时使用的方式就是仿函数了
// 不是我们的强制类型转换了
int ii1 = aa();
int ii2 = (int)aa();
cout << ii1 << " " << ii2 << endl;
}
int main()
{
Test();
return 0;
}
所以要满足类型转换的形式,我们的祖师爷已经将()重载给仿函数使用了,所以对于类型转换出现了一个特殊情况
class A
{
public:
// 使用operator + int
operator int()//这里int不是运算符,是一种特殊情况重载
//不需要返回值,根据重载int推
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
void Test()
{
A aa;
// 此时是我们的类型转换了
// 我们这里可以隐式转换,它会自动去调operator
// 也可以自己强制类型转换
int ii1 = aa;
int ii2 = (int)aa;
cout << ii1 << " " << ii2 << endl;
}
3.C++强制类型转换操作符
标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast
3.1 static_cast
⭐static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用 static_cast,但它不能用于两个不相关的类型进行转换
int main()
{
double d = 12.34;
// 相当于之前的隐式类型转换
int a = static_cast<int>(d);
cout << a << endl;
return 0;
}
3.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;
}
3.3 const_cast
⭐const_cast最常用的用途就是删除变量的const属性,方便赋值
int main()
{
const int a = 2;//存储在栈上,常变量不能修改
// 可间接修改
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl;
cout << *p << endl;
return 0;
}
我们来看一下运行结果:
说明一下:
⭐代码中用const_cast删除了变量a的地址的const属性,这时就可以通过这个指针来修改变量a的值。
⭐由于编译器认为const修饰的变量是不会被修改的,因此会将const修饰的变量存放到寄存器当中,当需要读取const变量时就会直接从寄存器中进行读取,打印最终出a的值是未修改之前的值,也就是终端上输出的结果,而我们代码修改的实际上是内存中的a的值,因此监视窗口上对其进行了修改。
⭐我们这里也是强制类型转换,而reinterpret_cast也是强制类型转换,但是为什么要把去掉const属性单独拿出来,就是专门提醒,去掉const属性是有一些内存可见优化的的风险,要注意是否加了volatile关键字。
⭐如果不想让编译器将const变量优化到寄存器当中,可以用volatile关键字对const变量进行修饰,这时当要读取这个const变量时编译器就会从内存中进行读取,即保持了该变量在内存中的可见性。
3.4 dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
⭐向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
class A
{
public:
virtual void f() {}
int _a = 0;
};
class B : public A
{
public:
int _b = 1;
};
int main()
{
// 不存在类型转换
// 赋值兼容,向上转换,子->父
B bb;
A aa = bb;
A* ptr = &bb;
return 0;
⭐向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:
⭐1. dynamic_cast只能用于父类含有虚函数的类
⭐2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
void fun(A* pa)
{
// 向下转换:父->子
// pa指向子类对象,转回子类,是安全的
// pa指向父类对象,转回子类,是不安全的,存在越界的风险问题
// 不安全
//B* pb = (B*)pa;
// pa指向子类对象,转回子类,正常转换
// pa指向父类对象,转回子类,转换失败
B* pb = dynamic_cast<B*>(pa);
if (pb)
{
cout << pb << endl;
cout << pb->_a << endl;
cout << pb->_b << endl;
}
else
{
cout << "转换失败" << endl;
}
}
int main()
{
A a;
B b;
//B b = (B)a;//不支持强制类型转换
fun(&a);//转换失败
fun(&b);//正常转换
return 0;
}
我们可以借助这个来判断传入的父类指针参数传入之前是父类的指针,还是子类的指针
void fun(A* pa)
{
// 向下转换:父->子
// pa指向子类对象,转回子类,是安全的
// pa指向父类对象,转回子类,是不安全的,存在越界的风险问题
// 不安全
//B* pb = (B*)pa;
// pa指向子类对象,转回子类,正常转换
// pa指向父类对象,转回子类,转换失败
B* pb = dynamic_cast<B*>(pa);
if (pb)
{
cout << "子类指针" << endl;
}
else
{
cout << "父类指针" << endl;
}
}
int main()
{
A a;
B b;
//B b = (B)a;//不支持强制类型转换
fun(&a);
fun(&b);
return 0;
}
注意:强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用 域,以减少发生错误的机会。强烈建议:避免使用强制类型转换
4 为什么C++需要四种类型转换
C风格的转换格式很简单,但是有不少缺点的:
- 1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
- 2. 显式类型转换将所有情况混合在一起,代码不够清晰
因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的 转化风格。
5. RTTI(了解)
RTTI:Run-time Type identification的简称,即:运行时类型识别。
C++通过以下方式来支持RTTI:
- ⭐1. typeid运算符
- ⭐2. dynamic_cast运算符
- ⭐3. decltype
总结:
1、C++中的4种类型转换分别是:____ 、____ 、____ 、____。
⭐分别是static_cast、reinterpret_cast、const_cast和dynamic_cast。
2、说说4种类型转换的应用场景。
⭐static_cast用于相近类型的类型之间的转换,编译器隐式执行的任何类型转换都可用static_cast。
⭐reinterpret_cast用于两个不相关类型之间的转换。
⭐const_cast用于删除变量的const属性,方便赋值。
⭐dynamic_cast用于安全的将父类的指针(或引用)转换成子类的指针(或引用)。