【C++笔记】C++和C语言的类型转换对比分析
🔥个人主页:大白的编程日记
🔥专栏:C++笔记
文章目录
前言
哈喽,各位小伙伴大家好!上期我们讲了 C++的IO库。今天我们来讲一下 C++类型转换。话不多说,我们进入正题!向大厂冲锋!
一 C中的类型转换
- 在C语语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不⼀致时等场景,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式强制类型转换。
- 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
- 显式强制类型转化:需要用户自己去显示在变量前用括号指定要转换的类型
- 并不是任意类型之前都支持转换,两个类型支持转换需要有⼀定关联性,也就是说转换后要有⼀定的意义,两个毫无关联的类型是不支持转换的。
#include<stdio.h>
#include<stdlib.h>
int main()
{
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);
// malloc返回值是void*,被强转成int*
int* ptr = (int*)malloc(8);
// 编译报错:类型强制转换:⽆法从“int* ”转换为“double”
// 指针是地址的编号,也是⼀种整数,所以可以和整形互相转换
// 但是指针和浮点数毫⽆关联,强转也是不⽀持的
// d = (double)p;
return 0;
}
二. C++中的类型转换
- C++兼容C,所以C支持的隐式类型转换和显式强制类型转换C++都支持。
- C++还支持内置类型到自定义类型之间的转换,内置类型转成自定义类型需要构造函数的支持,自定义类型转成内置类型,需要一个operator类型()的函数去支持。
- C++还支持自定义类型到自定义类型之间的转换,需要对应类型的构造函数支持即可,比如A类型对象想转成B类型,则支持一个形参为A类型的B构造函数即可支持。
三. C++显示强制类型转换
3.1类型安全
- 类型安全是指编程语言在编译和运行时提供保护机制,避免非法的类型转换和操作,导致出现一个内存访问错误等,从而减少程序运行时的错误。
- C语言不是类型安全的语言,C语言允许隐式类型转换,一些特殊情况下就会导致越界访问的内存错误,其次不合理的使用强制类型转换也会导致问题,比如一个int的指针强转成double访问就会出现越界。
- C++兼容C语言,支持隐式类型转换和强制类型转换,C++也不是类型安全的语言,C++提出4个显示的命名强制类型转换
static_cast/reinterpret_cast/const_cast/dynamic_cast
就是为了让类型转换相对而言更安全。
void insert(size_t pos, char ch)
{
// 这⾥当pos == 0时,就会引发由于隐式类型转换
// end跟pos⽐较时,提升为size_t导致判断结束逻辑出现问题
// 在数组中访问挪动数据就会出现越界,经典的类型安全问题
int end = 10;
while (end >= pos)
{
// ...
cout << end << endl; --end;
}
}
int main()
{
insert(5, 'x');
//insert(0, 'x');
// 这⾥会本质已经出现了越界访问,只是越界不⼀定能被检查出来
int x = 100;
double* p1 = (double*)&x;
cout << *p1 << endl;
const int y = 0;
int* p2 = (int*)&y;
(*p2) = 1;
// 这⾥打印的结果是1和0,也是因为我们类型转换去掉了const属性
// 但是编译器认为y是const的,不会被改变,所以会优化编译时放到
// 寄存器或者直接替换y为0导致的
cout << *p2 << endl;
cout << y << endl;
return 0;
}
3.2 C++中4个显示强制类型转换运算符
static_cast用于两个类型意义相近的转换,这个转换是具有明确定义的,只要底层不包含const,都可以使用static_cast。
reinterpret_cast用于两个类型意义不相近的转换,reinterpret是重新解释的意思,通常为运算对象的位模式提供较低层次上的重新解释,也就是说转换后对原有内存的访问解释已经完全改变了,非常的大胆。所以我们要谨慎使用,清楚知道这样转换是没有内存访问安全问题的。
const_cast用于const类型到非const类型的转换,去掉了const属性,也是一样的我们要谨慎使用,否则可能会出现意想不到的结果。
dynamic_cast
用于将基类的指针或者引用安全的转换成派生类的指针或者引用。如果基类的指针或者引用时指向派生类对象的,则转换回派生类指针或者引用时可以成功的,如果基类的指针指向基类对象,则转换失败返回nullptr,如果基类引用指向基类对象,则转换失败,抛出bad_cast
异常。其次
dynamic_cast
要求基类必须是多态类型,也就是基类中必须有虚函数。因为dynamic_cast
是运行时通过虚表中存储的type_info判断基类指针指向的是基类对象还是派生类对象。
int main()
{
//对应隐式类型转换--数据的解释意义没有改变
double d = 12.34;
int a = static_cast<int>(d);
cout <<a << endl;
int&& ref = static_cast<int&&>(a);
//对应强制类型转换-—数据的解释意义已经发生改变
int* pl = reinterpret_cast<int*>(a);
//对应强制类型转换中有风险的去掉const属性
//所以要注意加volatile
volatile const int b = 0;
int* p2 = const_cast<int*>(&b);
*p2² = 1;
cout << b << endl;
cout << *p2 << endl;
return 0;
}
class A
{
public:
virtual void f() {}
int _a = 1;
};
class B :public A
{
public:
int _b = 2;
};
void funl(A* pa)
{
//指向父类转换时有风险的,后续访问存在越界访问的风险
//指向子类转换时安全
B* pbl = (B*)pa;
cout << "pbl:"<< pb1 <<endl;
cout << pbl->_a << endl;
cout << pbl->_b << endl;
pb1->_a++;
pbl->_b++;
cout << pbl->_a << endl;
cout << pbl->_b << endl;
}
void fun2(A* pa)
{
//dynamic_cast会先检查是否能转换成功(指向子类对象),能成功则转换,
//(指向父类对象)转换失败则返回nullptr
B* pbl = dynamic_cast<B*>(pa);
if (pb1)
{
cout << "pbl:" << pbl << endl;
cout << pbl->_a << endl;
cout << pbl->_b << endl;
pb1->_a++;
pb1->_b++;
cout << pbl->_a << endl;
cout << pbl->_b << endl;
}
else
{
cout <<"转换失败" <<endl;
}
}
void fun3(A& pa)
{
//转换失败,则抛出bad_cast异常
try
{
B& pb1 = dynamic_cast<B&>(pa);
cout << "转换成功" << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
}
int main()
{
A a;
B b;
//fun1(&a) ;
//funl(&b) ;
fun2(&a);
fun2(&b);
fun3(a);
fun3(b);
return 0;
}
四. RTTI
RTTI的英文全称是"RuntimeTypeIdentification",中文称为"运行时类型识别",它指的是程序在运行的时候才确定需要用到的对象是什么类型的。用于在运行时(而不是编译时)获取有关对象的信息。
RTTI主要由两个运算符实现,
typeid
和dynamic_cast;
typeid
主要用于返回表达式的类型,dynamic_cast
前面已经讲过了,主要用于将基类的指针或者引用安全的转换成派生类的指针或者引用。typeid(e)
中e可以是任意表达式或类型的名字,typeid(e)的返回值是typeinfo
或typeinfo
派生类对象的引用,typeinfo
可以只支持比较等于和不等于,name成员函数可以返回c风格字符串表示对象类型名字,typeinfo
的精确定义随着编译器的不同而略有差异,也就以为着同一个e表达式,不同编译器下,typeid(e).name()
返回的名字可能是不一样的。typeinfo的文档如下:
https://legacy.cplusplus.com/reference/typeinfo/kw=typeinfotypeid(e)
时,当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid返回的是运算对象的静态类型,当运算对象是定义了至少一个虚函数的类的左值时,typeid的返回结果直到运行时才会求得。
#include<iostream>
#include<string>
#include<vector>
#include<list>
using namespace std;
int main()
{
int a[10];
int* ptr = nullptr;
cout << typeid(10).name() << endl;
cout << typeid(a).name() << endl;
cout << typeid(ptr).name() << endl;
cout << typeid(string).name() << endl;
cout << typeid(string::iterator).name() << endl;
cout << typeid(vector<int>).name() << endl;
cout << typeid(vector<int>::iterator).name() << endl;
return 0;
// vs2019下的运⾏结果
int
int[10]
int*
class std::basic_string<char, struct std::char_traits<char>, class
std::allocator<char> >
class std::_String_iterator<class std::_String_val<struct
std::_Simple_types<char> > >
class std::vector<int, class std::allocator<int> >
class std::_Vector_iterator<class std::_Vector_val<struct
std::_Simple_types<int> > >
//gcc9。4下运行结果
i
A10_i
Pi
NSt7__cxxll12basic_stringIcStllchar_traitsIcESaIcEEE
N9__gnu_cxx17__normal_iteratorIPcNSt7__cxxll12basic_stringIcStllchar_traitsIcES
aIcEEEEE
St6vectorIiSaIiEE
N9__gnu_cxx17__normal_iteratorIPiSt6vectorIiSaIiEEEE
#include<iostream>
using namespace std;
class A
{
public:
virtual void func()
{}
protected:
int _a1 = 1;
};
class B : public A
{
protected:
int _b1 = 2;
};
int main()
{
try
{
B* pb = new B;
A* pa = (A*)pb;
if (typeid(*pb) == typeid(B))
{
cout << "typeid(*pb) == typeid(B)" << endl;
}
// 如果A和B不是继承关系,则会抛bad_typeid异常
if (typeid(*pa) == typeid(B))
{
cout << "typeid(*pa) == typeid(B)" << endl;
}
// 这⾥pa和pb是A*和B*,不是类类型对象,他会被当做编译是求值的静态类型运算
// 所以这⾥始终是不相等的
if (typeid(pa) == typeid(pb))
{
cout << "typeid(pa) == typeid(B)" << endl;
}
}
catch (const std::exception& e)
{
cout << e.what() << endl;
}
return 0;
}
后言
这就是 C++类型转换。大家自己好好消化!今天就分享到这!感谢各位的耐心垂阅!咱们下期见!拜拜~