既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
int c;
};
int main(int argc, char* argv[])
{
C c;
A a = static_cast©; // 上行转换正常
B b = static_cast©; // 上行转换正常
C c_a = static\_cast<C>(a); // 下行转换无效
C c_b = static\_cast<C>(b); // 下行转换无效
}
从测试程序中可以看到如果对类实例使用`static_cast`进行转换,`static_cast`是会进行类型判断的,对于上行转换来说这个过程就是正常的(其实任何合法的上行转换都可以直接由隐式转换来完成,而不需要手工去强制类型转换),但是下行转换则不行,`static_cast`认为下行转换等同于两个无关联的类进行转换,会报错。但是这个错误是有解决方法的,我们从报错信息中可以看到当`static_cast`转换失败时,会使用`expression`作为传入参数来调用`type_id`的构造函数,所以我们可以把类C改成以下形式,上面的示例即可编译通过
class C : public A, public B
{
public:
C()
{
}
C(const A& v)
{
a = v.a;
}
C(const B& v)
{
b = v.b;
}
int c;
};
综上,我们可以得出使用`static_cast`对类实例进行强制类型转换时有以下特点:
1. 进行上行转换是完全安全合法的,当然这个过程由隐式转换来完成也是合法的
2. 进行下行转换时,`static_cast`会认为两个类无关联,这种转换不合法。如果此时硬要转换的话,比如类A->类B(这两个类可以无任何关系,因为实例下行转换`static_cast`就是认为他们没关联),可以在B中添加一个使用类A进行构造的构造函数,比如B(const A&),这样就可以正常使用`static_cast`来进行类A->类B的操作了
#### 2.1.5、用于没有多态的类实例指针或引用之间的转换
进行上行转换的示例如下:
#include
class A
{
public:
int a;
};
class B
{
public:
int b;
};
class C : public A, public B
{
public:
int c;
};
int main(int argc, char* argv[])
{
C c;
A\* a_ptr = static\_cast<A\*>(&c); // 上行指针转换正常
B\* b_ptr = static\_cast<B\*>(&c); // 上行指针转换正常
A& a_ref = static\_cast<A&>(c); // 上行引用转换正常
B& b_ref = static\_cast<B&>(c); // 上行引用转换正常
}
可以看到上行转换都是正常的,转换过程中不会出现任何显性和隐性错误,下面来看一下下行转换的示例:
int main(int argc, char* argv[])
{
C c;
A* a_ptr = static_cast<A*>(&c);
B* b_ptr = static_cast<B*>(&c);
A& a_ref = static_cast<A&>©;
B& b_ref = static_cast<B&>©;
C\* c_ptra = static\_cast<C\*>(a_ptr); // 下行指针转换正常
C\* c_ptrb = static\_cast<C\*>(b_ptr); // 下行指针转换正常
C& c_refa = static\_cast<C&>(a_ref); // 下行引用转换正常
C& c_refb = static\_cast<C&>(b_ref); // 下行引用转换正常
A\* a_ptr_fail = static\_cast<A\*>(b_ptr); // B\* -> A\*,无关联的两个类型,无效
}
从上面的例子可以看到,下行转换也是正常的,并且`static_cast`也会拒绝掉两个无关联类之间的转换???这和书中说的不一样啊,不是说`static_cast`下行转换不安全吗?别急,上面的例子是片面的,各位看一下下面的代码就知道了
int main(int argc, char* argv[])
{
A a;
B b;
// 以下都能转换成功,说明static\_cast根本就没有安全检查,只看到有继承关系就给转换了
C\* c_ptra = static\_cast<C\*>(&a);
C\* c_ptrb = static\_cast<C\*>(&b);
C& c_refa = static\_cast<C&>(a);
C& c_refb = static\_cast<C&>(b);
}
综上,我们可以得出使用`static_cast`对没有多态的类实例指针或引用进行强制类型转换时有以下特点:
1. 进行上行转换(派生类指针->基类指针、派生类引用->基类引用)是完全安全的,没有任何问题,当然这个过程由隐式转换来完成也是合法的
2. 进行下行转换(基类指针->派生类指针、基类引用->派生类引用)由于缺乏安全检查,所以是有问题的,要尽量避免这种用法
3. 如果两个类无继承关系,则使用`static_cast`进行转换时会失败,但是这种情况下`static_cast`会显性地展示出错误信息,是安全的
#### 2.1.6、用于具有多态的类实例指针或引用之间的转换
进行上行转换的示例如下:
#include
class A
{
public:
virtual void print()
{
std::cout << “A” << std::endl;
}
};
class B
{
public:
virtual void print()
{
std::cout << “B” << std::endl;
}
};
class C : public A, public B
{
public:
virtual void print() override
{
std::cout << “C” << std::endl;
}
};
int main(int argc, char* argv[])
{
C c;
A\* a_ptr = static\_cast<A\*>(&c); // 上行指针转换正常
B\* b_ptr = static\_cast<B\*>(&c); // 上行指针转换正常
a_ptr->print(); // 输出C,符合多态的要求
b_ptr->print(); // 输出C,符合多态的要求
A& a_ref = static\_cast<A&>(c); // 上行引用转换正常
B& b_ref = static\_cast<B&>(c); // 上行引用转换正常
a_ref.print(); // 输出C,符合多态的要求
b_ref.print(); // 输出C,符合多态的要求
}
可以看到上行转换都是正常的,转换过程中不会出现任何显性和隐性错误,下面来看一下正常的下行转换的示例:
int main(int argc, char* argv[])
{
C c;
A* a_ptr = static_cast<A*>(&c);
B* b_ptr = static_cast<B*>(&c);
A& a_ref = static_cast<A&>©;
B& b_ref = static_cast<B&>©;
C\* c_ptra = static\_cast<C\*>(a_ptr); // 下行指针转换正常
C\* c_ptrb = static\_cast<C\*>(b_ptr); // 下行指针转换正常
c_ptra->print(); // 输出C,符合多态的要求
c_ptrb->print(); // 输出C,符合多态的要求
C& c_refa = static\_cast<C&>(a_ref); // 下行引用转换正常
C& c_refb = static\_cast<C&>(b_ref); // 下行引用转换正常
c_refa.print(); // 输出C,符合多态的要求
c_refb.print(); // 输出C,符合多态的要求
}
可以看到这个也是正常的,和前面那个没有多态的差不多,接下来看一下不正常的下行转换例子:
int main(int argc, char* argv[])
{
A a;
B b;
C\* c_ptra = static\_cast<C\*>(&a);
C\* c_ptrb = static\_cast<C\*>(&b);
c_ptra->print(); // 正常输出A
c_ptrb->print(); // 段错误
C& c_refa = static\_cast<C&>(a);
C& c_refb = static\_cast<C&>(b);
c_refa.print(); // 正常输出A
c_refb.print(); // 段错误
}
上面这个例子中的下行转换是错误的,但是通过`c_ptra`可以正常调用类A的`print()`方法打印出字母A来,使用`c_ptrb`就直接段错误了,原因是类A是第一个被继承的,类B是第二个被继承的,也就是在类C中,第一个虚表指针指向的就是类A的虚表,第二个虚表指针指向的就是类B的虚表。在上面的例子那样进行错误地转换时,由于类A被继承之后它位置的特殊性导致可以使用`c_ptra`正确地调用类A的`print()`方法,而类B则不行,可能这有点难理解,下面给大家看张图就明白了,如图2-1所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210702221611916.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80Mzc5ODg4Nw==,size_16,color_FFFFFF,t_70#pic_center)
图2-1
从图2-1中可以看出,对于类C来说,它始终调用着`_vptr.A`指向的`print()`方法,当我们使用纯类B类型进行下行转换时,根本就没有这一块的数据(这个转换是不完整的、不安全的),所以就会出现段错误了。当然,使用纯类A类型进行下行转换也是不完整、不安全的,只不过位置刚好才不会出现段错误而已。综合分析上面的代码是完全错误的,一定要杜绝写出这种垃圾代码。
综上,我们可以得出使用`static_cast`对具有多态的类实例指针或引用进行强制类型转换时有以下特点:
1. 进行上行转换(派生类指针->基类指针、派生类引用->基类引用)是完全安全的,没有任何问题,当然这个过程由隐式转换来完成也是合法的
2. 进行下行转换(基类指针->派生类指针、基类引用->派生类引用)由于缺乏安全检查,所以是有问题的,并且因为具有多态的类往往具有特殊的用法,所以在这种情况下产生的后果比前面没有多态情况下的要更严重,要尽量避免这种用法
**小结**:通过上面的介绍,我们可以很直观地看到`static_cast`相比C风格的强制类型转换要安全很多,有很大程度上的类型安全检查。本节我们所有的例子都可以使用C风格的强制类型转换去做,但是转出来的结果有可能会错到天际去,并且编译器不会给你任何报错信息。。。同时我们也要认识到`static_cast`也是有明显缺点的,那就是无法消除`const`和`volatile`属性、无法直接对两个不同类型的指针或引用进行转换和下行转换无类型安全检查等,不过没关系,其它三个强制类型转换的关键字刚好能弥补`static_cast`的这些缺点。
### 2.2、const\_cast
`const_cast`的作用是去除掉`const`或`volitale`属性,前面介绍`static_cast`的时候我们知道`static_cast`是不具备这种功能的。使用格式如下:
`const_cast<type_id>(expression);`
>
> **注意事项**:const\_cast不是用于去除变量的常量性,而是去除指向常量对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用,并且const\_cast不支持不同类型指针或引用之间的转换,比如说float\*转换成int\*是不允许的,直白一点说就是type\_id和expression要基本类型保持一致,相差的话只能差const或volatile属性。
>
>
>
先来看一个错误的使用示例:
#include
int main(int argc, char* argv[])
{
int type_int = 100;
float type_float = const_cast(type_int); // 错误,const_cast只能转换引用或者指针
float* type_float_ptr = const_cast<float*>(&type_int); // 错误,从int* -> float* 无效
float& type_float_ref = const_cast<float&>(type_int); // 错误,从int& -> float& 无效
}
再来看一个不太正确的使用示例:
#include
int main(int argc, char* argv[])
{
const int type_const_int = 100;
int* type_const_int_ptr = const_cast<int*>(&type_const_int); // 转换正确
int& type_const_int_ref = const_cast<int&>(type_const_int); // 转换正确
\*type_const_int_ptr = 10;
std::cout << \*type_const_int_ptr << std::endl; // 输出10
std::cout << type_const_int << std::endl; // 输出100,没有改变
type_const_int_ref = 20;
std::cout << type_const_int_ref << std::endl; // 输出20
std::cout << type_const_int << std::endl; // 输出100,没有改变
// 以下三个输出结果一致,说明const\_cast确实只是去除了一些属性,并没有重新搞快内存把需要转换的变量给复制过去
std::cout << "&type\_const\_int:\t" << &type_const_int << std::endl;
std::cout << "type\_const\_int\_ptr:\t" << type_const_int_ptr << std::endl;
std::cout << "&type\_const\_int\_ref:\t" << &type_const_int_ref << std::endl;
}
在上面这个例子中,转换是成功了,但是`type_const_int`的常量性并没有被改变,这是因为`const_cast`并没有办法把变量的常量性去除,而且比较有意思的是我们可以看到`type_const_int`对应地址的内容确实被改变了,但是`type_const_int`的值却并没有被改变,这是好事,因为从一开始我们把它定义为常量类型时这个值就不应该再被改变了。至于后面使用`type_const_int_ptr`和`type_const_int_ref`试图去改变`type_const_int`的值,这是很危险的做法,不同编译器可能会有不同的处理,是有可能出现严重错误的,要杜绝这种用法。
从这里看起来`const_cast`好像有点鸡肋。。。但是事实上不是这样的,在某些场景下`const_cast`还是挺好用的,比如下面这个例子:
#include
void fun(const int& v)
{
int& type_int_ref = const_cast<int&>(v);
type_int_ref = 10;
}
int main(int argc, char* argv[])
{
int type_int = 100;
fun(type_int);
std::cout << type_int << std::endl; // 输出10,改变了
}
上面的例子比较粗糙,但是大致上也就是这么个用法了。如果一个变量本来就不具备`const`属性,但是在传递过程中被附加了`const`属性,这时候使用`const_cast`就能完美清除掉后面附加的那个`const`属性了。
当然,因为`const_cast`的这种特殊性,它的应用范围是远不如`static_cast`广泛的,但是在标准库中还是能找到一些使用`const_cast`的例子的,比如“著名的”`std::addressof`在源码实现中就有借助`const_cast`作为中间层来消除参数的`const`和`volatile`属性,具体大家可以看一下这篇文章《[C++11的std::addressof源码解析](https://bbs.csdn.net/topics/618668825)》。
### 2.3、reinterpret\_cast
`reinterpret_cast`意为“重新解释”,它是C++中最接近于C风格强制类型转换的一个关键字。它让程序员能够将一种对象类型转换为另一种,不管它们是否相关。使用格式如下:
`reinterpret_cast<type_id>(expression);`
注意事项如下:
>
> 1. type-id和expression中必须有一个是指针或引用类型(可以两个都是指针或引用,指针引用在一定场景下可以混用,但是建议不要这样做,编译器也会给出相应的警告)。
> 2. reinterpret\_cast的第一种用途是改变指针或引用的类型
> 3. reinterpret\_cast的第二种用途是将指针或引用转换为一个整型,这个整型必须与当前系统指针占的字节数一致
> 4. reinterpret\_cast的第三种用途是将一个整型转换为指针或引用类型
> 5. 可以先使用reinterpret\_cast把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值(由于这个过程中type-id和expression始终有一个参数是整形,所以另一个必须是指针或引用,并且整型所占字节数必须与当前系统环境下指针占的字节数一致)
> 6. 使用reinterpret\_cast强制转换过程仅仅只是比特位的拷贝,和C风格极其相似(但是reinterpret\_cast不是全能转换,详见第1点),实际上reinterpret\_cast的出现就是为了让编译器强制接受static\_cast不允许的类型转换,因此使用的时候要谨而慎之
> 7. reinterpret\_cast同样也不能转换掉expression的const或volitale属性。
>
>
>
先来看一个错误的使用示例:
#include
class A
{
public:
int a;
};
class B
{
public:
int b;
};
int main(int argc, char* argv[])
{
float type_float = 10.1;
int type_int = reinterpret_cast(type_float); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点)
char type_char = reinterpret_cast(&type_float); // 出错,我的是64位系统,这里type-id只能是long类型(注意事项第3点)
double* type_double_ptr = reinterpret_cast<double*>(type_float); // 出错,这里expression只能是整型(注意事项第4点)
A a;
B b;
long type_long = reinterpret\_cast<long>(a); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点)
B b1 = reinterpret\_cast<B>(a); // 出错,type-id和expression中必须有一个是指针或者引用(注意事项第1点)
A a1 = reinterpret\_cast<A>(&b); // 出错,B\* -> A不允许,我的是64位系统,type-id只能是long(注意事项第3点)
A\* a_ptr = reinterpret\_cast<A\*>(b); // 出错,这里expression只能是整型(注意事项第4点)
}
下面再来看正确的使用示例:
#include
class A
{
public:
int a;
};
class B
{
public:
int b;
};
int main(int argc, char* argv[])
{
float type_float = 10.1;
long type_long = reinterpret\_cast<long>(&type_float); // 正确,float\* -> long(注意事项第3点)
float\* type_float_ptr = reinterpret\_cast<float\*>(type_long); // 正确,long -> float\*(注意事项第4点)
std::cout << \*type_float_ptr << std::endl; // 正确,仍然输出10.1(注意事项第5点)
long\* type_long_ptr = reinterpret\_cast<long\*>(&type_float); // 正确,float\* -> long\*(注意事项第1点)
char type_char = 'A';
double& type_double_ptr = reinterpret\_cast<double&>(type_char); // 正确,char -> double&(注意事项第4点)
A a;
B b;
long a_long = reinterpret\_cast<long>(&a); // 正确,A\* -> long(注意事项第3点)
A\* a_ptr1 = reinterpret\_cast<A\*>(type_long); // 正确,long -> A\*(注意事项第4点)
A\* a_ptr2 = reinterpret\_cast<A\*>(&b); // 正确,B\* -> A\*(注意事项第1点)
}
程序中写的比较清楚了,大家配合前面的注意事项去看就行了,很容易就可以看懂了。`reinterpret_cast`的一个典型使用也是标准库的`std::addressof`,具体大家可以看一下这篇文章《[C++11的std::addressof源码解析](https://bbs.csdn.net/topics/618668825)》。
### 2.4、dynamic\_cast
`dynamic_cast`是本文讲的最后一个C++风格强制类型转换了,也是最特殊的一个,前面三种都是编译时完成的,而`dynamic_cast`是运行时处理的,使用格式如下:
`dynamic_cast<type_id>(expression);`
注意事项如下:
![img](https://img-blog.csdnimg.cn/img_convert/66f54065b6e19c0fee576b62b2681936.png)
![img](https://img-blog.csdnimg.cn/img_convert/f1be927e20a9244556bd340498c83ed9.png)
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**
68825)》。
### 2.4、dynamic\_cast
`dynamic_cast`是本文讲的最后一个C++风格强制类型转换了,也是最特殊的一个,前面三种都是编译时完成的,而`dynamic_cast`是运行时处理的,使用格式如下:
`dynamic_cast<type_id>(expression);`
注意事项如下:
[外链图片转存中...(img-lihTS0KE-1715564659226)]
[外链图片转存中...(img-xJ6XWlwX-1715564659226)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618668825)**