✍个人博客:Pandaconda-CSDN博客
📣专栏地址:http://t.csdnimg.cn/fYaBd
📚专栏简介:在这个专栏中,我将会分享 C++ 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
52. C++ 的四种强制转换 reinterpret_cast、const_cast、static_cast、dynamic_cast
reinterpret_cast
reinterpret_cast <type-id> (expression)
type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以用于类型之间进行强制转换。可以将指针值转换为一个整型数,但不能用于非指针类型的转换。
举个例子:
#include <iostream>
using namespace std;
int main(int argc, char** argv)
{
int num = 0x00636261;//用16进制表示32位int,0x61是字符'a'的ASCII码
int * pnum = #
char * pstr = reinterpret_cast<char *>(pnum);
cout<<"pnum指针的值: "<<pnum<<endl;
cout<<"pstr指针的值: "<<static_cast<void *>(pstr)<<endl;//直接输出pstr会输出其指向的字符串,这里的类型转换是为了保证输出pstr的值
cout<<"pnum指向的内容: "<<hex<<*pnum<<endl;
cout<<"pstr指向的内容: "<<pstr<<endl;
return 0;
}
在 Ubuntu 14.04 LTS 系统下,采用 g++ 4.8.4 版本编译器编译该源文件并执行,得到的输出结果如下:
第 6 行定义了一个整型变量 num,并初始化为 0x00636261(十六进制表示),然后取 num 的地址用来初始化整型指针变量 pnum。接着到了关键的地方,使用 reinterpret_cast 运算符把 pnum 从int*
转变成char*
类型并用于初始化 pstr。
将 pnum 和 pstr 两个指针的值输出,对比发现,两个指针的值是完全相同的,这是因为 “reinterpret_cast 运算符并不会改变括号中运算对象的值,而是对该对象从位模式上进行重新解释”。如何理解位模式上的重新解释呢?通过推敲代码 11 行和 12 行的输出内容,就可见一斑。
很显然,按照十六进制输出 pnum 指向的内容,得到 636261;但是输出 pstr 指向的内容,为什么会得到 ”abc” 呢?
在回答这个问题之前,先套用《深度探索 C++ 对象模型》中的一段话,“一个指向字符串的指针是如何地与一个指向整数的指针或一个指向其他自定义类型对象的指针有所不同呢?从内存需求的观点来说,没有什么不同!它们三个都需要足够的内存(并且是相同大小的内存)来放置一个机器地址。指向不同类型之各指针间的差异,既不在其指针表示法不同,也不在其内容(代表一个地址)不同,而是在其所寻址出来的对象类型不同。也就是说,指针类型会教导编译器如何解释某个特定地址中的内存内容及其大小。” 参考这段话和下面的内存示意图,答案已经呼之欲出了。
const_cast
const_cast <type_id> (expression)
该运算符用来修改类型的 const 或 volatile 属性。除了 const 或 volatile 修饰之外, type_id 和 expression 的类型是一样的。用法如下(只能用于指针或者引用):
-
常量指针被转化成非常量的指针,并且仍然指向原来的对象。
-
常量引用被转换成非常量的引用,并且仍然指向原来的对象。
-
const_cast 一般用于修改底指针。如 const char *p 形式。
static_cast
static_cast <type-id> (expression)
该运算符把 expression 转换为 type-id 类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
-
用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
-
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的。
-
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
-
-
用于基本数据类型之间的转换,如把 int 转换成 char,把 int 转换成 enum。这种转换的安全性也要开发人员来保证。
-
把空指针转换成目标类型的空指针。
-
把任何类型的表达式转换成 void 类型。
注意:static_cast 不能转换掉 expression 的 const、volatile、或者 __unaligned 属性。
dynamic_cast
有类型检查,基类向派生类转换比较安全,但是派生类向基类转换则不太安全
dynamic_cast <type-id> (expression)
该运算符把 expression 转换成 type-id 类型的对象。type-id 必须是类的指针、类的引用或者 void*。
如果 type-id 是类指针类型,那么 expression 也必须是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。
dynamic_cast 运算符可以在执行期决定真正的类型,也就是说 expression 必须是多态类型。如果下行转换是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果下行转换不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
dynamic_cast 主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
向下类型转换
在类层次间进行上行转换时,dynamic_cast 和 static_cast 的效果是一样的。
在进行下行转换时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全。
举个例子:
#include <bits/stdc++.h>
using namespace std;
class Base
{
public:
Base() :b(1) {}
virtual void fun() {};
int b;
};
class Son : public Base
{
public:
Son() :d(2) {}
int d;
};
int main()
{
int n = 97;
//reinterpret_cast
int *p = &n;
//以下两者效果相同
char *c = reinterpret_cast<char*> (p);
char *c2 = (char*)(p);
cout << "reinterpret_cast输出:"<< *c2 << endl;
//const_cast
const int *p2 = &n;
int *p3 = const_cast<int*>(p2);
*p3 = 100;
cout << "const_cast输出:" << *p3 << endl;
Base* b1 = new Son;
Base* b2 = new Base;
//static_cast
Son* s1 = static_cast<Son*>(b1); //同类型转换
Son* s2 = static_cast<Son*>(b2); //下行转换,不安全
cout << "static_cast输出:"<< endl;
cout << s1->d << endl;
cout << s2->d << endl; //下行转换,原先父对象没有d成员,输出垃圾值
//dynamic_cast
Son* s3 = dynamic_cast<Son*>(b1); //同类型转换
Son* s4 = dynamic_cast<Son*>(b2); //下行转换,安全
cout << "dynamic_cast输出:" << endl;
cout << s3->d << endl;
if(s4 == nullptr)
cout << "s4指针为nullptr" << endl;
else
cout << s4->d << endl;
return 0;
}
//输出结果
//reinterpret_cast输出:a
//const_cast输出:100
//static_cast输出:
//2
//-33686019
//dynamic_cast输出:
//2
//s4指针为nullptr
从输出结果可以看出,在进行下行转换时,dynamic_cast 安全的,如果下行转换不安全的话其会返回空指针,这样在进行操作的时候可以预先判断。而使用 static_cast 下行转换存在不安全的情况也可以转换成功,但是直接使用转换后的对象进行操作容易造成错误。
用于多态类型检查
dynamic_cast 底层原理
dynamic_cast 的底层原理依赖于运行时类型信息 (RTTI, Runtime Type lnformation) C++ 编译器在编译时为支持多态的类生成 RTTI,它包含了类的类型信息和类层次结构。我们都知道当使用虚函数时,编译器会为每个类生成一个虚函数表 (vtable) ,并在其中存储指向虚函数的指针。
伴随虚函数表的还有 RTTI (运行时类型信息),这些辅助的信息可以用来帮助我们运行时识别对象的类型信息。
《深度探索 C++ 对象模型》中有个例子:
首先,每个多态对象都有一个指向其 vtable 的指针,称为 vptr。
RTTI (就是上面图中的 type_info 结构) 通常与 vtable 关联。dynamic_cast 就是利用 RTTI 来执行运行时类型检查和安全类型转换。以下是 dynamic cast 的工作原理的简化描述:
-
首先,dynamic_cast 通过查询对象的 vptr 来获取其 RTTI (这也是为什么 dynamic_cast 要求对象有虚函数)。
-
然后,dynamic_cast 比较请求的目标类型与从 RTTI 获得的实际类型。如果目标类型是实际类型或其基类,则转换成功。
-
如果目标类型是派生类,dynamic_cast 会检查类层次结构,以确定转换是否合法。如果在类层次结构中找到了目标类型,则转换成功;否则,转换失败。
-
当转换成功时,dynamic_cast 返回转换后的指针或引用。
-
如果转换失败,对于指针类型,dynamic_cast 返回空指针;对于引用类型,它会抛出个 std::bad_cast 异常。
因为 dynamic_cast 依赖于运行时类型信息,它的性能可能低于其他类型转换操作 (如,static 是编译器静态转换,编译时期就完成了 static_cast)。
53. RTTI 是什么?其原理是什么?
RTTI
是 C++ 中的一种运行时类型识别机制,它允许程序在运行时查询一个对象的实际类型信息。RTTI 主要用于多态场景中,例如,当一个基类指针指向一个派生类对象时,通过 RTTI 可以判断出该指针所指向的对象的实际类型。
可以通过使用两个关键字 typeid
和 dynamic_cast
来实现 RTTI:
-
typeid 运算符,用于返回表达式的类型,可以通过基类的指针获取派生类的数据类型。
-
dynamic_cast 运算符,具有类型检查的功能,用于将基类的指针或引用安全地转换成派生类的指针或引用。
虚函数表中起始部分存放 RTTI 指针标识动态类型,从而能实现多态。
RTTI 是一种运行时机制,需要在程序运行时才能进行类型信息的查询和转换。由于这种机制会增加程序的运行时开销。使用虚函数实现多态,而不是手动进行类型转换是一种避免额外 RTTI 开销的方法。
54. 系统 崩溃的原因有哪些?
-
分段错误:这是程序崩溃的主要原因。这些可能是造成这种原因的原因:尝试访问系统中不存在的内存位置、试图在只读存储器位置上进行写操作。
-
堆栈溢出:在某些情况下,可能无法终止内存位置的递归。
-
缓冲区溢出:这是一种异常,程序在将数据写入缓冲区时会溢出缓冲区的边界并覆盖相邻的内存位置。
-
内存泄漏:如果我们通过某个程序分配一些内存,并保持原样。一段时间后,将分配但未使用巨大的内存,因此一段时间后将缺少内存。然后程序开始崩溃。