面试题宝库

map和unordered_map

map底层是基于红黑树,内部元素排列是有序的。而unordered_map底层则是基于哈希表,其元素的排列无序。
红黑树是一个二叉查找树,不像平衡二叉树要求所有节点左右子树高度差不超过1,红黑树只要求从一个节点到所有叶结点的路径中,最长路径不超过最短路径的两倍,所以红黑树只追求树的大致平衡。
因为对树平衡程度的不同要求,平衡二叉树在插入和删除的过程中会花费比较大的代价来维护树的平衡,所以平衡二叉树不适合插入、删除太多的场景。而红黑树只要求弱平衡,它做到了当插入和删除时,只需最多旋转3次就能实现一定程度的平衡,所以能将查询、插入和删除的时间复杂度维持在对数级别(O(logn))
红黑树如何插入和删除的?
插入:
(1)如果父节点为黑色,直接插入不处理
(2)如果父节点为红色,叔叔节点为红色,则父节点和叔叔节点变为黑色,祖先节点变为红色,将节点操作转换为祖先节点
(3)如果当前节点为父亲节点的右节点,则以父亲结点为中心左旋操作
(4)如果当前节点为父亲节点的左节点,则父亲节点变为黑色,祖先节点变为红色,以祖先节点为中心右旋操作
删除:
(1)先按照排序二叉树的方法,删除当前节点,如果需要转移即转移到下一个节点
(2)当前节点,必定为这样的情况:没有左子树。
(3)删除为红色节点,不需要处理,直接按照删除二叉树节点一样
(4)如果兄弟节点为黑色,兄弟节点的两个子节点为黑色,则将兄弟节点变为红色,将着色转移到父亲节点
(5)如果兄弟节点为红色,将兄弟节点设为黑色,父亲结点设为红色节点,对父亲结点进行左旋操作
(6)如果兄弟节点为黑色,左孩子为红色,右孩子为黑色,对兄弟节点进行右旋操作
(7)如果兄弟节点为黑色,右孩子为红色,则将父亲节点的颜色赋值给兄弟节点,将父亲节点设置为黑色,将兄弟节点的右孩子设为黑色,对父亲节点进行左旋

hashmap的底层:

基于哈希表的Map接口的非同步实现,基于数组和链表实现,基本存储单元是Entry,通过put()和get()方法储存和获取对象
使用put方法储存键值对时,先计算key的哈希值与数组长度取余,得到bucket位置,然后将键值对存储进去
储存的时候,如果索引位置没元素,就直接存,否则就调用equals方法与原有的key比较,相同的话就用新的value替代,否则就与下一个结点比较。如果没有相同元素,就将k-v添加到链表头部
使用get方法时,也是先得到bucket位置,如果索引位置没有元素则返回null,如果有就按照链表顺序挨个调用equals方法比较
jdk1.8之前用数组+链表,1.8之后用数组+链表+红黑树

hash冲突:

装填因子(装填因子=数据总数 / 哈希表长) 默认初始容量为 16,默认负载因子为 0.75
加载因子是表示Hsah表中元素的填满的程度.若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了
1.开放地址方法
  (1)线性探测
   按顺序决定哈希值时,如果某数据的哈希值已经存在,则在原来哈希值的基础上往后加一个单位,直至不发生哈希冲突。 
  (2)再平方探测 3)伪随机探测
2.链式地址法(HashMap的哈希冲突解决方法)
  对于相同的哈希值,使用链表进行连接。使用数组存储每一个链表。
  优点:
  (1)拉链法处理冲突简单,且无堆积现象
  (2)各链表上的结点空间是动态申请的,更适合无法确定表长的情况;
  (3)开放定址法为减少冲突,要求装填因子α较小
  缺点:
  指针占用较大空间时,会造成空间浪费,若空间用于增大散列表规模进而提高开放地址法的效率。
3.建立公共溢出区
4.再哈希法

HashMap扩容机制

扩容就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组。底层是resize方法中的transfer方法将原有的Entry数组的元素拷贝到新的Entry数组里,扩容都是以2的N次幂进行扩容 一般是2倍。

实现一个vector?是1.5还是2倍,各有什么优缺点?

1.5倍优势:可以重用之前分配但是释放的内存
2倍劣势:每次申请的内存都不可以重用
以2倍的方式扩容,导致下一次申请的内存必然大于之前分配内存的总和,导致之前分配的内存不能再被使用,所以最好倍增长因子设置为(1,2)之间:在这里插入图片描述


c++动态链接库与静态库

二者的不同点在于代码被载入的时刻不同。
l 静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库,因此体积较大。
l 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在,因此代码体积较小。
动态库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例

stl容器 算法 迭代器的关系区别

每个容器都有专属的迭代器,而算法通过迭代器对容器中的元素进行操作。
容器能够通过模版的方法,装下各种类型的节点元素。
迭代器是一种行为类似指针的对象。指向容器中元素的节点。
算法通过操作容器对应的迭代器,就可以间接地操作容器中的元素。而不需要关注容器的内部细节

线程安全问题

一、为什么会有线程安全问题?
当多个线程同时共享同同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读的操作不会发生线程安全问题。
二、如何解决多线程之间线程安全问题?
发生数据冲突问题(线程不安全问题),可以使用多线程之间同步synchronized或使用锁(lock),只能让当前一个线程进行执行。代码执行完成后释放锁,然后才能让其他线程进行执行。这样的话,就可以解决线程不安全问题。
三、什么是多线程之间同步?
当多个线程共享同一个资源,不会受到其他线程干扰。
四、多线程死锁?
同步中嵌套同步,导致锁无法释放

排序算法

选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法
冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法
面试之排序算法的稳定性

HTTP常见面试题

HTTP常见面试题

HTTP与TCP的区别和联系

TCP对应于传输层,HTTP对应于应用层,从本质上来说,二者没有可比性。
Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的。所以Http连接是一种短连接,是一种无状态的连接。
TCP是底层协议,定义的是数据传输和连接方式的规范。
HTTP是应用层协议,定义的是传输数据的内容的规范。
HTTP协议中的数据是利用TCP协议传输的,所以支持HTTP就一定支持TCP。

什么是逻辑地址,什么是物理地址?

逻辑地址:是指用户程序经编译后,每个目标模块以0为基地址进行的顺序编址。逻辑地址又称相对地址 。
物理地址:是指内存中各物理存储单元的地址从统一的基地址进行的顺序编址。物理地址又称绝对地址,它是数据在内存中的实际存储地址。
处理机在执行时必须使用物理地址才能从主存中存取信息,而应用程序使用的地址是逻辑地址,改地址并非处理机能正确识别的地址,故需要转换。

C++分为内存分为哪几部分?

栈区:由编译器自动分配释放,存储函数的参数值,局部变量值等,其操作方法类似于数据结构中的栈
堆区:一般由程序员申请和释放,与数据结构中的堆没有任何关系,分配方式类似于链表
全局/静态区:全局变量和静态变量是存储在一起的,在程序编译时分配
文字常量区:存储常量字符串
程序代码区:存储函数体的二进制代码
new操作符从自由存储区上为对象动态分配内存空间,而malloc函数从堆上动态分配内存
堆是操作系统维护的一块内存,而自由存储是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。

什么是内联函数

内联函数是指用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。内联函数是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。
内联函数在编译时展开,宏在预编译时展开

数组与指针的区别是什么

(1) 数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(2) 用运算符 sizeof 可以计算出数组的容量(字节数)。 sizeof§,p 为指针得到的是一个指针变量的字节数,而不是 p 所指的内存容量 。 C++没有办法知道指针所指的内存容量,除非在申请内存时记住它。

链表和数组的区别是什么

1、链表是链式存储结构,数组是顺序存储结构
2、链表通过指针连接元素与元素,而数组则是把所有元素按顺序进行存储
3、链表的插入和删除元素比较简单,不需要移动元素,且较为容易实现长度的扩充,但是查询元素比较困难,数组是查询比较快,但是删除和增加会比较麻烦。

new和malloc的区别

1 参数 new申请内存分配时不需要指定内存大小,编译器会自行计算。而malloc则需要显式地指出所需内存的尺寸。
2 返回类型 new分配成功时返回的是对象类型的指针,无须进行类型转换,符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通强制类型转换
3 分配失败 new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
4 重载 C++允许重载new/delete操作符,而malloc不允许重载。
5 内存区域 new操作符从自由存储区上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

指针和引用的区别

1 定义本质不同,指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。
2 引用不可以为空,当被创建的时候,必须初始化,以后不可以在修改,但是指定的对象其内容可以改变,而指针可以是空值,可以在任何时候被初始化
3 指针可以有多级,但是引用不行(因为它只是变量的别名)
4 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了

堆和栈的区别是什么

1、堆栈空间分配区别
栈:由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆: 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
2、堆栈缓存方式区别
栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放。
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定。所以调用这些对象的速度要相对来得低一些。

IP地址和mac地址的区别

“IP地址”是指互联网协议地址 “MAC地址”又称为物理地址、硬件地址,用来定义网络设备的位置。
区别主要有:
1.两者地址使用不同。IP地址是指Internet协议使用的地址,而MAC地址是Ethernet协议使用的地址。
2.分配依据不同。IP地址的分配是基于网络拓扑,MAC地址的分配是基于制造商。
3.地址能否更改不同。IP是可以更改的,mac地址虽然也可以更改,但是一般用不上
4.长度不同。IP地址为32位,MAC地址为48位。
5.寻址协议层不同。IP地址应用于OSI第三层,即网络层,而MAC地址应用在OSI第二层,即数据链路层。

重写与重载的区别是什么

重载实现的是编译时的多态性,重写实现的是运行时的多态性。重载发生在一个类中,同名的方法如果有不同的参数列表则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的参数列表,有兼容的返回类型。

C++编译过程主要分为四步:

预编译:展开 #include、宏定义,删除所有的注释
编译: 进行词法分析、语法分析、语义分析、优化产生的汇编代码
汇编: 将汇编代码翻译成机器码,生成二进制文件
链接:一类是静态链接,一类是动态链接
  
在cpp文件中展开include文件。
将每个cpp文件编译为一个对应的obj文件。
连接obj文件成为一个exe文件(或者其它的库文件)

多态

多态是指在不同的条件下表现出不同的状态
实现多态有以下方法:虚函数,抽象类,重载,覆盖,模版。
在编译期间实现多态 -> C++中通过重载函数的方法可以在编译期间实现多态。
使用虚函数实现多态 -> 基类指针指向基类对象时就使用基类的成员,指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态
构成多态的条件:
1、必须存在继承关系;
2、继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同)。
3、存在基类的指针,通过该指针调用虚函数

虚函数与纯虚函数的区别

  1. 虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类
  2. 虚函数可以被直接使用,也可以被子类重载以后以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类只有声明而没有定义。
  3. 虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用。
  4. 虚函数的定义形式:virtual { } 纯虚函数的定义形式:virtual { } = 0;
    虚函数和纯虚函数不能有static标识符,static函数在编译时候要求前期bind,然而虚函数却是动态绑定

什么叫虚继承?有什么作用?

虚继承,就是在被继承的类前面加上virtual关键字,这时被继承的类称为虚基类。虚继承在多重继承的时可以防止二义性

什么是虚函数表?

为实现动态联编,编译器为每个包含虚函数的类创建一个表,称为vtable,在vtable中,编译器放置了特定类的虚函数地址,在每个带有虚函数的类中编译器会秘密地设置一个虚函数表指针,称为vptr,指向对象的vtable,通过基类指针做虚函数调用时,编译器静态地插入取得这个vptr,并在vtable表种查找函数地址的代码,这样就能调用正确的函数。

什么是动态联编?

动态联编是指在程序执行的时候才将函数实现和函数调用关联,因此也叫运行时绑定或者晚绑定,动态联编对函数的选择不是基于指针或者引用,而是基于对象类型,不同的对象类型将做出不同的编译结果。C++中一般情况下联编也是静态联编,但是一旦涉及到多态和虚拟函数就必须要使用动态联编了。

c++调用函数时,内存的分配

执行某个函数时,如果有参数,则在栈上为形式参数分配空间,继续进入到函数体内部,如果遇到变量,则按情况为变量在不同的存储区域分配空间(如果是static类型的变量,则是在进行编译的过程中已经就分配了空间),函数内的语句执行完后,如果函数没有返回值,则直接返回调用该函数的地方,如果存在返回值,则先将返回值进行拷贝(如果是对象则调用其“拷贝构造函数”)传回,再返回执行远点,函数全部执行完毕后,进行退栈操作,将刚才函数内部在栈上申请的内存空间释放掉。

static关键字

static关键字的作用

const关键字

const关键字的作用及应用场景

内存泄漏

就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。

如何检测内存泄露?
第一:良好的编码习惯
第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。
第三:Boost 中的smart pointer。

野指针

野指针是指:指向一个已删除的对象或未申请访问受限内存区域的指针。与空指针不同,野指针无法通过简单地判断是否为NULL避免,而只能通过养成良好的编程习惯来尽力减少。对野指针进行操作很容易造成程序错误。

野指针产生原因:
1.指针变量未初始化
2.指针释放后之后未置空
有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字,它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。
3.指针操作超越了变量作用域

智能指针

关于智能指针的问题

进程间通信的各种方式

管道:以字节流的形式在多进程之间流动,分为无名管道和有名管道,属于半双工通信,管道是存储在内存中,在管道创建时,内核会给缓冲区分配一个页面的大小。
命名管道与管道的区别:
1、命名管道可以用于任何两个进程间的通信,而并不限制这两个进程同源(父子进程,如fork或exec创建),因此命名管道的使用比管道的使用要方便灵活
2、命名管道作为一种特殊的文件存放于文件系统中,而不是像管道一样存放于内存(使用完毕后消失)。当进程对命名管道的使用结束后,命名管道依然存在于文件系统中,除非对其进行删除操作,否则该命名管道不会消失

信号量:一般用在进程或线程之间的同步与互斥。本质上是一个计数器,用于控制多进程之间对共享数据对象的读取,信号量机制不是以传送数据,而是以保护共享资源或保证进程同步为目标的。不存储进程间的通信数据。

消息队列:消息队列是消息的链接表,用户可以从消息队列的末尾添加消息,也可以在消息队列的头
部读取消息,消息一旦被接收,就会从消息队列中移除,但是它可以实现消息的随机查询,比如按类型字段取消息。
消息队列面试题

共享内存:允许两个或多个进程共享同一块存储区,通过地址映射将该块物理内存映射到不同进程的地址空间中,那么一个进程对于共享内存中数据的修改,另一进程可以实时的看到,从而实现的进程间的通信机制,但是在多线程环境下,需要信号量来控制对共享内存的互斥访问。而共享内存使用 mmap 或者 shmget 函数,在内存中开辟了一块空间,映射到不同进程的虚拟地址空间中

信号:是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。主要作为进程间以及同一进程不同线程之间的同步手段。

套接字:与其他通信机制不同的是,它可用于不同机器间的进程通信

通信方式的选择:
PIPE和FIFO(有名管道)用来实现进程间相互发送非常短小的、频率很高的消息,这两种方式通常适用于两个进程间的通信
共享内存用来实现进程间共享的、非常庞大的、读写操作频率很高的数据;这种方法适用于多进程间的通信
其他考虑用socket。主要应用在分布式开发中

面向对象的设计原则

1、单一职责原则
一个类只负责一个功能领域中的相应职责。或者说,一个类,应该只有一个引起它变化的原因。

2、开闭原则
一个软件实体应当对扩展开放,对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。

3、里式替换原则
所有引用基类的地方必须能透明的使用其子类的对象。

4、依赖倒置原则
针对接口编程,而不是针对实现编程。

5、接口隔离原则
使用多个专门的接口,而不使用单一的总接口,即客户端不应该依赖那些他不需要的接口。

6、迪米特原则
一个软件实体应当尽可能少地与其他实体发生相互作用。

内存对齐

为了让内存存取更有效率,在编译阶段优化内存存取的手段
即 数据项只能存储在地址是数据项大小的整数倍的内存位置上
int类型占用4个字节,地址只能在0,4,8等位置上
double类型占用8个字节,地址只能在0,8,16等位置上面

malloc底层实现

malloc底层实现

空类的函数

C++ 空类,默认产生哪些成员函数

Redis

Redis 是一个基于内存的高性能key-value数据库。
Redis面试题

ping命令

ping命令

Docker

docker面试题和解答

设计模式

只是一种设计思想,针对不同的业务场景,最本质的目的是解耦,为了可扩展性和健壮性。

单例模式:
保证一个类只有一个实例,并且提供一个访问该全局访问点

工厂模式:
提供了一种创建对象的最佳方式。在工厂模式中,在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。实现了创建者和调用者分离,分为简单工厂、工厂方法、抽象工厂模式

观察者模式:
行为性模型关注的是系统中对象之间的相互交互,解决系统在运行时对象之间的相互通信和协作,进一步明确对象的职责。
观察者模式,是一种行为性模型,他定义对象之间一种一对多的依赖关系,使得当一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。

面试常问的几大设计模式

链表题

链表题

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值