C++面试要点(Part 1)
下面题目都是面试经常问到的,答案可能比较简略,大家想了解详细的实现要再进行搜索,大神的博客写得都很清楚。这次先更新30题,其他部分看情况再更新!
1.虚函数实现原理
虚指针->虚表->虚函数地址,程序运行时根据对象类型初始化虚指针。
2.贪心算法与动态规划的比较
贪心算法是指在对问题求解时,总是做出在当前看来是最好的选择。不从整体最优上加以考虑,只做出在某种意义上的局部最优解。
动态规划也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
3.继承体系同名成员函数的关系
4.C++的四种强制类型转换的区别与联系
- reinterpret_cast为操作数的位模式提供较低层的重新解释
- const_cast修改底层const
- static_cast除了修改底层const的类型转换
- dynamic_cast将基类指针引用安全转换为派生类指针或引用
5.define和const的区别
-
对于define常量,它定义的是宏,只进行宏替换,它的生命周期止于编译器
-
对于const常量,它存在于程序的数据段,并且在堆栈中分配了内存空间
-
const常量具有数据类型, define常量没有数据类型
6.const用途
- 定义只读变量,即常量
- 修饰函数的参数和函数的返回值
- 修饰函数的定义体,这里的函数为类的成员函数,被const修饰的成员函数代表不修改成员变量的值
7.全局变量
全局变量的作用域是整个源程序,当源程序是由多个源文件构成时,它在所有的源文件中都是可见的。而静态全局变量只定义在它的源文件内是可见的,在其他源文件内是无效的。
8.static关键字
-
static 局部变量也只是在定义时被初始化一次,下一次使用该变量时,依据的是上一次运算结束后保存的结果值。
-
static函数在内存中只占据一份存储空间,而普通函数在每个被调用中都维持一份函数拷贝。
9.Sizeof和strlen区别?
- sizeof 是一个运算符,参数是表达式或者类型
- strlen 是一个字符串函数,只能用char*做参数,c风格字符串
- sizeof 运算符是在编译时计算,而strlen 函数是在运行时计算
10.如何解决多重继承的二义性?
- 用类名加作用域运算符
- 虚继承的方法
11.继承的目的
继承的主要目的就是为了实现代码重用,增加代码的复用性
12.C++内存分区
- 堆区,存放动态变量
- 栈区,局部变量和函数参数
- 全局静态区,全局变量和静态变量
- 常量区,字符串常量
- 代码区,存放程序的二进制代码
13.请说出内存泄露、内存溢出、内存越界和缓冲区溢出的区别与联系
- 内存泄漏,指的是堆内存的泄露。堆内存是程序从堆栈区中分配出来的大小任意的一个内存块,通常是由malloc() 函数和new 运算符动态申请,用完过之后要手动释放,使用的是free() 函数和delete 运算符,才能使该内存块回收以留他用。但是,如果忘记了手动释放,那么便会造成该内存块不能被再次使用,也就是说该内存块泄露了。
- 内存溢出,指的是在程序动态申请内存时,没有足够的内存空间供其使用了,此时,我们就说内存溢出了。
- 内存越界,指的是向系统申请一块内存后,使用时却超出了该内存块的范围。
- 缓冲区溢出,首先,缓冲区指的是如果有临时存取数据的需要,一般会临时分配一些内存空间来暂时存放数据,这块内存空间就被称之为缓冲区。其次,缓冲区溢出指的是向该缓冲区写入了该缓冲区无法容纳的数据,造成了缓冲区以外的存储单元数据被改写。另外,栈溢出是缓冲区溢出的一种,它分为上溢出和下溢出,上溢出指的是栈满而又向栈区增加新的栈元素,下溢出指的是栈空而又进行删除的操作等。
14.什么是面向对象(OOP)?
面向对象是一种对现实世界理解和抽象的方法、思想,通过将需求要素转化为对象进行问题处理的一种思想。
15.STL库用过吗?常见的STL容器有哪些?算法用过哪几个?
STL包括两部分内容:容器和算法
容器:
- 顺序容器:vector,string,deque,list,forward_list,array
(swap,assign,push_front, push_back, insert, emplace, emplace_front, emplace_back, front, back, [], at, erase, pop_back, pop_front, clear)
- 顺序容器的适配器:stack, queue, priority_queue
- 关联容器:map, set
算法:
Accumulate(); Find(); Equal(); fill(); fill_n(); sort(); stablesort(); unique(); findif(); back_inserter();
16.long int, int, long double, double的字长
注意long int和int一样是4byte,long double和double一样是8byte。
在标准c++中,int的定义长度要依靠你的机器的字长,也就是说,如果你的机器是32位的,int的长度为32位,如果你的机器是64位的,那么int的标准长度就是64位。
17.为什么构造函数不能是虚函数
-
从存储空间角度,虚函数对应一个vtable,这大家都知道,可是这个vtable其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。
-
从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义呀。所以构造函数没有必要是虚函数。
18.TCP为什么不能两次握手?
防止已失效的连接请求又传送到服务器端,因而产生错误
假设改为两次握手,client端发送的一个连接请求在服务器滞留了,这个连接请求是无效的,client已经是closed的状态了,而服务器认为client想要建立一个新的连接,于是向client发送确认报文段,而client端是closed状态,无论收到什么报文都会丢弃。而如果是两次握手的话,此时就已经建立连接了。服务器此时会一直等到client端发来数据,这样就浪费掉很多server端的资源。
19.TCP建立连接三次握手与四次挥手
三次握手的最主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的。
- 指建立一个TCP连接时,需要客户端服务端总共发送3 个包以确认连接的建立
- 第一次握手:Client将标志位SYN置为1(表示要发起一个连接),随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
- 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
- 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
四次挥手: 终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
20.为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,我们也未必全部数据都发送给对方了,所以我们不可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,我们的ACK和FIN一般都会分开发送。
21.进程五个状态
新建态(new),就绪态(ready),运行态(running),等待态(waiting),退出态(exit)
22.进程与线程的区别
进程是系统调度和资源分配的基本单位。
线程网络或多用户环境下,一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,——无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的。线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。
线程是指进程内的一个执行单元,也是进程内的可调度实体.
与进程的区别:
- 地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
- 资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
- 线程是处理器调度的基本单位,但进程不是.
- 二者均可并发执行.
23.C语言与C++语言特点
C:
-
作为一种面向过程的结构化语言,易于调试和维护
-
表现能力和处理能力极强,可以直接访问内存的物理地址;
-
C语言实现了对硬件的编程操作,也适合于应用软件的开发;
-
C语言还具有效率高,可移植性强等特点。
C++:
-
在C语言的基础上进行扩充和完善,使C++兼容了C语言的面向过程特点,又成为了一种面向对象的程序设计语言;
-
可以使用抽象数据类型进行基于对象的编程;
-
可以使用多继承、多态进行面向对象的编程;
-
可以担负起以模版为特征的泛型化编程。
24.进程间通信方式和线程间通信方式
进程间通信方式:
-
管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
-
信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
-
消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
-
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
-
套接字( socket ) : 套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
线程间通信:
-
全局变量;
-
Messages消息机制; PostMessage(); PostThreadMessage();
-
CEvent对象(MFC中的一种线程通信对象,通过其触发状态的改变实现同步与通信)。//微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是微软公司提供的一个类库(class libraries),以C++类的形式封装了Windows API
25.http和https的区别?
HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。
HTTPS(Secure Hypertext Transfer Protocol)安全超文本传输协议,与http主要区别在于:
- http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议;
- http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443
26.HTTP协议和TCP协议之间的区别联系
- TCP是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据;
- HTTP的默认端口号是80,TCP协议通信编程时的端口号需要自己指定(例如socket编程);
- HTTP协议是在TCP协议基础上实现的,即HTTP数据包是经过TCP/IP协议实现传输的;
- HTTP是无状态的短连接协议,TCP是有状态的长连接协议;
27.虚拟内存
传统的存储管理方式
-
一次性
- 作业必须一次性全部装入内存后,方能开始运行。这会导致两种情况发生:
- 当作业很大,不能全部被装入内存时,将使该作业无法运行;
- 当大量作业要求运行时,由于内存不足以容纳所有作业,只能使少数作业先运行,导致多道程序度的下降。
-
驻留性
- 作业被装入内存后,就一直驻留在内存中,其任何部分都不会被换出,直至作业运行结束。运行中的进程,会因等待I/O而被阻塞,可能处于长期等待状态。
虚拟内存
虚拟内存中,允许将一个作业分多次调入内存,需要时就调入,不需要的就先放在外存。
虚拟内存的实现有以下三种方式:
- 请求分页存储管理
- 请求分段存储管理
- 请求段页式存储管理
虚拟内存的意义
-
虚拟内存可以使得物理内存更加高效。虚拟内存使用置换方式,需要的页就置换进来,不需要的置换出去,使得内存中只保存了需要的页,提高了利用率,也避免了不必要的写入与擦除;
-
使用虚拟地址可以使内存的管理更加便捷。在程序编译的时候就会生成虚拟地址,该虚拟地址并不是对应一个物理地址,使得也就极大地减少了地址被占用的冲突,减少管理难度;
-
为了安全性的考虑。在使用虚拟地址的时候,暴露给程序员永远都是虚拟地址,而具体的物理地址在哪里,这个只有系统才了解。这样就提高了系统的封装性。
28.树
树是一种数据结构,它是由n(n>=1)个有限节点组成一个具有层次关系的集合。
-
结点的度:结点拥有的子树的数目。
-
叶子:度为零的结点。
-
分支结点:度不为零的结点。
-
树的度:树中结点的最大的度。
-
层次:根结点的层次为1,往下依次递增。
-
树的高度:树中结点的最大层次。
-
无序树:如果树中结点的各子树之间的次序是不重要的,可以交换位置。
-
有序树:如果树中结点的各子树之间的次序是重要的, 不可以交换位置。
-
森林:0个或多个不相交的树组成。对森林加上一个根,森林即成为树;删去根,树即成为森林。
-
二叉树是每个节点最多有两个子树的树结构
-
高度为h,并且由2h –1个结点的二叉树,被称为满二叉树。
-
一棵二叉树中,只有最下面两层结点的度可以小于2,并且最下一层的叶结点集中在靠左的若干位置上。这样的二叉树称为完全二叉树。
-
二叉查找树(Binary Search Tree),又被称为二叉搜索树。小于左子树,大于右子树
29. 树的相关算法
前序遍历
若二叉树非空,则执行以下操作:
-
访问根结点;
-
先序遍历左子树;
-
先序遍历右子树。
中序遍历
若二叉树非空,则执行以下操作:
-
中序遍历左子树;
-
访问根结点;
-
中序遍历右子树。
后序遍历
若二叉树非空,则执行以下操作:
-
后序遍历左子树;
-
后序遍历右子树;
-
访问根结点。
前序遍历和中序遍历重建二叉树
可以利用前序序列和中序序列中根节点的位置特性作为重建依据
判断一棵树是否是完全二叉树
按照层序遍历二叉树,找到第一个只有非满结点(这个节点只有两种情况,孩子为空或者只有左没有右),如果之后的节点还有非满结点,则不是。
求二叉树的宽度
因此我们需要从头结点开始,记录每一层的个数,对于当前层的每一个节点,在弹出自身之后把其左右子树压入 queue,当把当前层全部弹出队列之后,在队列中剩下的就是下一层的节点。然后比较队列的size和之前得到的maxWidth,取最大值即为队列的宽度。最终队列为空,得到的maxWidth就是二叉树的宽度。
30.红黑树
Map和set基于红黑树实现
-
每个节点不是红色就是黑色的;
-
根节点总是黑色的;
-
如果节点是红色的,则它的子节点必须是黑色的(反之不一定);
-
从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)
红-黑树主要通过三种方式对平衡进行修正,改变节点颜色、左旋和右旋。