作者前言
🎂 ✨✨✨✨✨✨🍧🍧🍧🍧🍧🍧🍧🎂
🎂 作者介绍: 🎂🎂
🎂 🎉🎉🎉🎉🎉🎉🎉 🎂
🎂作者id:老秦包你会, 🎂
简单介绍:🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂🎂
喜欢学习C语言、C++和python等编程语言,是一位爱分享的博主,有兴趣的小可爱可以来互讨 🎂🎂🎂🎂🎂🎂🎂🎂
🎂个人主页::小小页面🎂
🎂gitee页面:秦大大🎂
🎂🎂🎂🎂🎂🎂🎂🎂
🎂 一个爱分享的小博主 欢迎小可爱们前来借鉴🎂
类型转换
- **作者前言**
- C++和C的类型转换
- C++的四种强制类型转换
-
- static_cast(静态转换)
- [reinterpret_cast (强制类型转换)](#reinterpret_cast (强制类型转换))
- const_cast(强制类型转换去const)
- dynamic_cast
C++和C的类型转换
前面我们学习c++的时候知道,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换 和显式类型转换 。
隐式类型转换简单来说就是编译器会自动进行转换隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败,如下:
//隐式类型转换
int i = 10;
double y = i;
printf("i: %d, y: %.3f", i, y);
显式类型转化:需要用户自己处理,如下:
//显示类型转换
int* ptr = &i;
int x = (int)ptr;
printf("ptr: %p, x: %d", ptr, x);
我们知道,类型转换都会产生临时变量,临时变量不仅仅由类型转换而来,比如一个表达式a+b,这个是会产生临时变量。
回归原题,c++的类型转换是需要一些联系的,有一定类型的关联,
C关联如下:
整形和浮点型
指针和整形
bool和整形
bool和指针
指针类型之间(比如int*和double*)
C++的关联:
c++比C多出了自定义类型和内置类型的转换,举例子如下:
std::string str = "wwkkw";
这里会产生一个临时对象,然后再拷贝构造给str,本质就是借助构造来实现
自定义类型和自定义类型 的转换,就拿 initializer_list和容器为例子,以vector为例子:

std::vector<int> ve = { 1,2,3,4,5,6 };
这行代码执行的就是上面图的构造,或者使用显示构造更明显
// 显式使用initializer_list
std::initializer_list<int> il = {10, 20, 30};
std::vector<int> vec3(il); // 直接构造
本质也是借助构造函数来实现的,如果是自定义类型转换成内置类型就不能这样了
自定义类型->成内置类型,我们以自定义类型转换成整形为例子:
class A
{
public:
operator int()
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 3;
};
int main()
{
A aa;
int b = (int)aa;//显示转换
int c = aa;//隐式转换
return 0;
}
这里需要借助operator来进行实现,这里比较特殊, 可以理解和前置++和后置++一样的特殊。
除此之外,还有一些情况,比如无符号和有符号之间的隐式转换
void Insert(size_t pos, int x)
{
//size_t end = 10;
int end = 10;
while (end >= pos)
{
cout << end << "移开" << endl;
end--;
}
}
int main()
{
Insert(0, 1);
return 0;
}
因为计算机存储的都是补码,所以当使用size_t的时候,当end为0时再减1,就会-1,-1的补码就是1111.11111.1111.1111.1111.1111.1111.1111,因为size_t是无符号的,第一位不为有符号,所以大小就是4294967295,进行无限循环

如果使用的是int,当end为-1时,因为时int,有符号的,所以补码转换为原码就是-1.

为啥会无限循环,因为这里会进行隐式类型转换。一般时范围小的向范围大的转,啥是范围大小,
C++的四种强制类型转换
标准C++为了加强类型转换的可视性, 引入了四种命名的强制类型转换操作符:
static_cast 、reinterpret_cast 、const_cast 、dynamic_cast
static_cast(静态转换)
这个对应的就是上面讲的隐式类型转换
int main()
{
double a = 1.2;
//可观性的隐式类型转换,强烈使用
int b = static_cast<int>(a);
//隐式类型转换
int c = a;
return 0;
}
reinterpret_cast (强制类型转换)
对应的就是强制类型转换也就是显示类型转换,,需要注意的就是能进行隐式类型转换的不能使用这个。
int main()
{
double a = 1.20;
int b = (int)a;
//int c = reinterpret_cast<int>(a);//能进行隐式类型转换的不能使用
int* c = reinterpret_cast<int*>(&a);
return 0;
}
const_cast(强制类型转换去const)
const_cast最常用的用途就是删除变量的const属性,方便赋值。
坑1:
int main()
{
const int a = 10;//a的地址类型是 const int *,表示值不能被修改
int* b = const_cast<int*>(&a);
*b = 20;
cout << "a;" << a << endl;
cout << "b;" << *b << endl;
return 0;
}

产生这个结果 的原因就是,编译器的优化,编译器认为const int a = 10;不会被改变,就放到寄存器中,使用的话就从寄存器中取,图中的监视窗口获取的数据是拿内存的,在内存中,a 和b已经改了,但是在寄存器中的a没有改,打印的结果就会不一样。这个结果和使用强制类型转换的结果一样 (int b = (int* &a))。为了避免编译器把a往寄存器扔,可以添加关键字volatile,这也就是const_cast和reinterpret_cast 的主要区别,使用const_cast可以让我们有一个预防心理,是否添加关键字。
volatile const int a = 10;//a的地址类型是 const int *,表示值不能被修改
dynamic_cast
dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)
如下:
class A
{
public:
A(const int& a , const int& b)
:_a(a)
,_b(b)
{
}
virtual void funtion()
{
}
private:
int _a;
int _b;
};
class B : public A
{
public:
B(const int& a , const int& b)
:A(a, b)
{
}
};
int main()
{
B bb(10,20);
A aa = bb;
A* a = &bb;
return 0;
}
这个规则俗称切片,这个过程中,这个赋值过程不会产生临时变量,也就不存在类型转换。
向下转型 :父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)。
到这里就会有一些问题,为啥不能写一个函数进行向下转换,原因如下:
为啥不能写一个函数进行相关的基类转子类的函数,如下:
class A
{
public:
A(const int& a)
:_a(a)
{
}
virtual void funtion()
{
}
int _a;
};
class B : public A
{
public:
B(const int& a , const int& b)
:A(a)
,_b(b)
{
}
int _b;
};
void fun(A* pa)
{
B* pb = (B*)pa;
cout << pb->_a << endl;
cout << pb->_b << endl;
}
这样的写法只是适合子类,不适合基类,如果传入的地址是基类的,如图:

可以看出,类型转换为子类类型的就会在进行访问的时候,访问的内容就是错误的,也就是内存越界。这样是不安全的。
如果使用的dynamic_cast,可以避免这个问题
使用这个dynamic_cast有如下注意:
-
dynamic_cast只能用于父类含有虚函数的类
-
- dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
void fun(A* pa)
{
//B* pb = (B*)pa;
B* pb = dynamic_cast<B*>(pa);
if (pb != nullptr)
{
cout << pb->_a << endl;
cout << pb->_b << endl;
}
else
{
cout << "转换失败" << endl;
}}
效果如下:
