IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C/C++面试题 -> 正文阅读

[C++知识库]C/C++面试题

1、C++的多态

在面向对象中,多态是指通过基类的指针或者引用,在运行时动态调用实际绑定对象函数的行为,与之相对应的编译时绑定函数称为静态绑定。所以多态分为静态多态和动态多态。

1. 静态多态:静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数就调用,没有的话就会发出警告或者报错。静态多态有函数重载、运算符重载、泛型编程等。

2. 动态多态:动态多态是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。当父类指针(引用)指向 父类对象时,就调用父类中定义的虚函数;即当父类指针(引用)指向 子类对象时,就调用子类中定义的虚函数

1. 动态多态行为的表现效果为:同样的调用语句在实际运行时有多种不同的表现形态。

2. 实现动态多态的条件: - 要有继承关系 - 要有虚函数重写(被 virtual 声明的函数叫虚函数) - 要有父类指针(父类引用)指向子类对象

3. 动态多态的实现原理 :当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类虚函数指针的数据结构, 虚函数表是由编译器自动生成与维护的。virtual 成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr 指针)。在多态调用时, vptr 指针就会根据这个对象在对应类的虚函数表中查找被调用的函数,从而找到函数的入口地址。

2、指针和引用的区别

1. 定义和性质不同。指针是一种数据类型,用于保存地址类型的数据,而引用可以看成是变量的别名。指针定义格式为:数据类型 *;而引用的定义格式为:数据类型 &;

2. 引用不可以为空,当被创建的时候必须初始化,而指针变量可以是空值,在任何时候初始化; 3. 指针可以有多级,但引用只能是一级;

4. 引用使用时无需解引用(*),指针需要解引用;

5. 指针变量的值可以是 NULL,而引用的值不可以为 NULL;

6. 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了;

7. sizeof 引用得到的是所指向的变量(对象)的大小,而 sizeof 指针得到的是指针变量本身的大小;

8. 指针作为函数参数传递时传递的是指针变量的值,而引用作为函数参数传递时传递的是实参本身,而不是拷贝副本;

9. 指针和引用进行++运算意义不一样。

注:传递指针也是拷贝传递,只不过它拷贝的不是内存单元中的内容,而是内存单元的地址,这就与直接用普通变量当形参是天壤之别了。拷贝地址就可以直接对地址所指向的内存单元进行操作,如果指针p和q分别指向变量i和j,即此时被调函数就可以直接对变量 i 和 j 进行操作了。有人会说:“被调函数用完就释放了,不就把 i 和 j 都释放了吗?”不是的,当函数调用完之后,释放的是 p 和 q,不是 i 和 j。p 和 q 中存放的是 i 和 j 的地址。所以 p 和 q 被释放之后并不会影响 i 和 j 中的值。前面讲过,修改指针变量的值不会影响所指向变量中的数据。只不过它们之间的指向关系没有了而已

3、C++重载和重写区别

1. 重载

a. 重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同或参数顺序不同。调用的时候根据函数的参数来区别不同的函数,函数重载跟返回值无关。

b. 重载的规则 - 函数名相同 - 必须具有不同的参数列表 - 可以有不同的访问修饰符

c. 重载用来实现静态多态(函数名相同,功能不一样)。

d. 重载是多个函数或者同一个类中方法之间的关系,是平行关系。
2. 重写

a. 重写(也叫覆盖)是指在派生类中重新对基类中的虚函数重新实现。即函数名和参数都一样,只是函数的实现体不一样。?

b. 重写的规则: - 方法声明必须完全与父类中被重写的方法相同 - 访问修饰符的权限要大于或者等于父类中被重写的方法的访问修饰符 - 子类重写的方法可以加virtual,也可以不加?

c. 重写用来实现动态多态(根据调用方法的对象的类型来执行不同的函数)。

d. 重写是父类和子类之间的关系,是垂直关系。

4、C和C++中struct区别,struct和class的区别

C++ 和 C 中 struct 的区别:

1. C 的结构体不允许有函数存在,C++ 的结构体允许有内部成员函数,并且允许该函数是虚函数 2. C 的结构体内部成员不能加权限,默认是 public,而 C++ 的结构体内部成员权限可以是 public、protected、private,默认 public

3. C 的结构体是不可以继承,C++ 的结构体可以从其它的结构体或者类继承

4. C 中的结构体不能直接初始化数据成员,C++ 中可以

5. C 中使用结构体需要加上 struct 关键字,或者对结构体使用 typedef 取别名后直接使用,而 C++ 中使用结构体可以省略 struct 关键字直接使用?

C++中struct 和 class 的区别:

1. struct 一般用于描述一个数据结构集合,而 class 是对一个对象数据的封装

2. struct 中默认访问控制权限是 public,而 class 中默认的访问控制权限是 private

struct A { int iNum; // 默认访问控制权限是 public } class B { int iNum; // 默认访问控制权限是 private }

3. 在继承关系中,struct 默认是公有继承,而 class 是私有继承,一般我们class B:public class A表示公有继承,不用默认的私有继承

4. class 关键字可以用于定义模板参数,而 struct 不能

5、请你说说各数据类型 sizeof 是多少,sizeof 指针是多少,sizeof 原理

各数据类型 sizeof 的结果其实就是该数据类型的字节数,不同类型的数据 sizeof 的结果是不一样的,并且不同的操作系统和编译器下同一数据类型的 sizeof 的结果也不一样,具体看编译器是如何实现的。以下是Visual Studio 中C++ 中对常见内置类型 sizeof 的代码:

#include <iostream>
using namespace std;

int mn() {
cout << sizeof(bool) << endl;
cout << sizeof(char) << endl;
cout << sizeof(short) << endl;
cout << sizeof(int) << endl;
cout << sizeof(long) << endl;
cout << sizeof(long long) << endl;
return 0;
} 

运行结果: 1 1 2 4 4 8

2. 对指针变量进行 sizeof 运算,获得的是指针变量的大小,而无论是什么类型的指针,在同一平台下结果都是一样的。在 32 位平台下是 4 个字节,在 64 位平台下是 8 个字节。

?3. sizeof 的原理:sizeof 是在编译的时候,查找符号表,判断类型,然后根据基础类型来取值。如果 sizeof 运算符的参数是一个不定长数组,则该需要在运行时计算数组长度。

sizeof可以计算数组的长度 = 数组所占的大小/单个数组元素所占的大小

int len = sizeof(array)/sizeof(array[1]);

6、为什么将析构函数设置成虚函数

析构函数:析构函数(destructor) 与构造函数相反,当对象结束其生命周期,如对象所在的函数调用完毕时,系统自动执行析构函数。析构函数往往用来做“清理善后” 的工作(例如在建立对象时用new开辟了一片内存空间,delete会自动调用析构函数后释放内存)。

1. 概念:虚析构函数,是将基类的析构函数声明为 virtual class Base { public: Base() { } // 虚析构函数 virtual ~Base() { } }

2. 作用:虚析构函数的主要作用是为了防止遗漏资源的释放,防止内存泄露。如果基类中的析构函数没有声明为虚函数,基类指针指向派生类对象时,删除此指针时,我们希望调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。但是,如果析构函数不被声明成虚函数,则编译器采用的绑定方式是静态绑定,在删除基类指针时,只会调用基类析构函数,而不调用派生类析构函数,这样就会导致基类指针指向的派生类对象析构不完全,如果派生类析构函数中做了某些释放资源的操作,则这时就会造成内存泄露。

7、请你说说导致哈希冲突的原因和影响因素,哈希冲突的解决方法

1. 哈希冲突产生的原因 哈希是通过对数据进行再压缩,提高效率的一种解决方法。但由于通过哈希函数产生的哈希值是有限的,而数据可能比较多,导致经过哈希函数处理后仍然有不同的数据对应相同的值,这时候就产生了哈希冲突。

?2. 产生哈希冲突的影响因素 装填因子(装填因子=数据总数 / 哈希表长)、哈希函数、处理冲突的方法?

3. 哈希冲突的解决方法?

a.开放地址方法:

(1)线性探测

按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上往后加一个单位,直至不发生哈希冲突。

(2)再平方探测

按顺序决定值时,如果某数据的值已经存在,则在原来值的基础上先加1的平方个单位,若仍然存在则减1的平方个单位。随之是2的平方,3的平方等等。直至不发生哈希冲突。

(3)伪随机探测

按顺序决定值时,如果某数据已经存在,通过随机函数随机生成一个数,在原来值的基础上加上随机数,直至不发生哈希冲突。

?b.链式地址法 :对于相同的值,使用链表进行连接。使用数组存储每一个链表。

c.建立公共溢出区:建立公共溢出区存储所有哈希冲突的数据。

?d.再哈希法:对于冲突的哈希值再次进行哈希处理,直至没有哈希冲突。

8、简述一下 C++ 中的内存对齐

1. 什么是内存对齐

现代计算机中内存空间都是按照 字节(byte)划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数 k(通常它为4或8)的倍数,这就是所谓的内存对齐。

2. 内存对齐的原因

  • 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的。某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。?
  • 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。

3. 内存对齐的规则?

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。可以通过预编译命令 #pragma pack(n),n = 1,2,4,8,16 来改变这一系数。

有效对齐值:是给定值 #pragma pack(n) 和结构体中最长数据类型长度中较小的那个,有效对齐值也叫对齐单位。

结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。

结构体的总大小为有效对齐值的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

9、malloc的实现原理

malloc() 的整体思想是先向操作系统申请一块大小适当的内存,然后自己管理,即内存池。 malloc() 分配空间有一个数据结构,允许它来区分边界,区分已分配的空间和空闲的空间,数据结构中包含一个头部信息和有效载荷,有效载荷的首地址就是 malloc() 返回的地址,可能在尾部还有填充,为了保持内存对齐。头部相当于该数据结构的元数据,其中包含了块大小和是否是空闲空间的信息,这样可以根据头地址和块大小的地址推出下一个内存块的地址,这就是隐式链表。 malloc() 基本的实现原理就是维护一个内存空闲链表,当申请内存空间时,搜索内存空闲链表,找到适配的空闲内存空间,然后将空间分割成两个内存块,一个变成分配块,一个变成新的空闲块。如果没有搜索到,那么就会调用 sbrk() 推进 brk 指针来申请内存空间。

搜索空闲块最常见的算法有:首次适配,下一次适配,最佳适配。 - 首次适配:第一次找到足够大的内存块就分配,这种方法会产生很多的内存碎片。 - 下一次适配:也就是说等第二次找到足够大的内存块就分配,这样会产生比较少的内存碎片。 - 最佳适配:对堆进行彻底的搜索,从头开始遍历所有块,使用数据区大小大于 size 且差值最小的块作为此次分配的块。

在释放内存块后,如果不进行合并,那么相邻的空闲内存块还是相当于两个内存块,会形成一种假碎片。所以当释放内存后,需要将两个相邻的内存块进行合并。

还有一种实现方式则是采用显示空闲链表,这个是真正的链表形式。在之前的有效载荷中加入了前驱和后驱的指针,也可以称为双向链表。维护空闲链表的的方式第一种是用后进先出(LIFO),将新释放的块放置在链表的开始处。另一种方法是按照地址的顺序来维护。

10、?vector 和 list 的区别,分别适用于什么场景?

1. 区别

- vector 底层实现是数组,list 是双向链表

- vector 支持随机访问,list 不支持

- vector 是顺序内存,list 不是

- vector 在中间节点进行插入删除会导致内存拷贝,list 不会

- vector 一次性分配好内存,不够时才进行扩容,list 每次插入新节点都会进行内存申请

- vector 随机访问性能好,插入删除性能差,list 随机访问性能差,插入删除性能好

2. 适用场景

- vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用 vector。

- list 拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。

11、vector 的扩容机制,扩容以后,它的内存地址会变化吗?

当 vector 的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步: 1. 完全弃用现有的内存空间,重新申请更大的内存空间; 2. 将旧内存空间中的数据,按原有顺序移动到新的内存空间中; 3. 最后将旧的内存空间释放。 因为 vector 扩容需要申请新的空间,所以扩容以后它的内存地址会发生改变。vector 扩容是非常耗时的,为了降低再次分配内存空间时的成本,每次扩容时 vector 都会申请比用户需求量更多的内存空间(这也就是 vector 容量的由来,即 capacity>=size),以便后期使用。

12、?set 的实现原理

set 底层使用红黑树实现,一种高效的平衡检索二叉树。

set 容器中每一个元素就是二叉树的每一个节点,对于 set 容器的插入删除操作,效率都比较高,原因是二叉树的删除插入元素并不需要进行内存拷贝和内存移动,只是改变了指针的指向。 对 set 进行插入删除操作, 都不会引起迭代器的失效,因为迭代器相当于一个指针指向每一个二叉树的节点,对 set的插入删除并不会改变原有内存中节点的改变。 set 中的元素都是唯一的,而且默认情况下会对元素进行升序排列。不能直接改变元素值,因为那样会打乱原本正确的顺序,要改变元素值必须先删除旧元素,再插入新元素。不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-13 10:54:41  更:2022-09-13 10:55:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 14:51:52-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码