C++面经总结

1 宏定义和内联函数的区别

① 宏定义是预处理,在编译阶段之前就替换,不进行类型检查,只是简单的字符串替换。
② 内联函数是在编译阶段展开,进行函数的类型检查,如果正确,直接用内敛函数的代码替换函数调用,省去了函数调用的开销。
③ const 与宏定义除了以上区别,#define 只是简单的展开,内存中有很多个备份;const是只读常量,在程序运行过程中只有一份备份。方便调试 , #define 不方便调试

2 函数重载

函数可以根据 参数列表个数和类型的不同进行重载,不能根据返回值进行重载,是静态多态的一种实现;
虚函数是动态多态的一种实现;
模板是“多对一”,多态是“一对多”。 模板是重载的一种特殊形式。也是静多态的一种形式,在编译的时候确定的
表面上看:模板与多态本身就是两个东西。模板实例化是发生在编译期(Compile-time)的,而多态中的函数调用决议是发生在运行时(Run-time)。
实际上:模板是静多态,模板是在编译时确定的,而平时我们说的多态是动多态,是在运行时确定。也就是多态分为两种:静多态和动多态。

3 说下内存分配

C++ 内存分配 分为 : 内核空间占1G,用户空间占3G;
栈 : 函数过程中的局部变量定义在栈区;
堆 : malloc 和 new 之类的对象定义在堆;
全局变量/静态存储区 : 存放全局变量、静态数据 全局区分为已初始化全局区(data)和未初始化全局区(bss);
常量区:存放常量字符串;
代码区: 存放函数体;

pass:栈和堆的区别:
① 栈是编译器自动申请和释放,堆需要程序员自己申请和释放;
② 栈的生长方向是向下的,即由高到低;堆的生长方向是向上的,即由低到高;
③ 空间大小不一样 栈VC6 下默认是1M,而堆最大可以到4G;

4 如何判断大小端

大端:高对低,低对高;小端:低对低,高对高;
可以用共同体去判断:

// 
Union TEST
{
	short a;
	char b[sizeof(a)];
}; 
int mian() 
{
	TEST test;
	test.a=0x1234;
	if(test.b[0]==0x12 && test.b[1]==0x34)
		big
	else
		small;
}
// 如果a在别的文件中定义为 static 变量,那么这样是拿不到a的值的。

5 hello.c编译之后是啥?经历哪些过程?

编译过后成.o 目标文件。
预处理->.i 文件->g++编译- >.s 文件-> 汇编生成.o 文件
预处理过程中:宏定义替换,条件编译,头文件,注释 ,#pragma

6 二叉树的公共祖先,写代码,如果没有指向父节点的指针呢?

剑指offer面试题

7 进程间通信方式

管道(有名管道,无名管道),消息队列,共享内存,套接字,信号量

8 输入有序数组A和B,合并后输出有序数组C,func(A[],B[],C[])

leetcode 原题

9 系统态用户态,说一个系统态的API


10 static 的作用 extren

static 隐藏,共享,全局,节省资源 ;
extern 全局跨文件 被 extern “C” 修饰的变量和函数是按照 C 语言方式编译和链接的
如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。

11 一个空类占多少空间:1字节,标识类的地址。

一个字节,因为,这是为了让对象的实例能够相互区别,否则无法使用此对象啊;

12 EPOLL的水平触发和边缘触发

epoll有两种模式,Edge Triggered(简称ET) 和 Level Triggered(简称LT).在采用这两种模式时要注意的是,如果采用ET模式,那么仅当状态发生变化时才会通知,而采用LT模式类似于原来的select/poll操作,只要还有没有处理的事件就会一直通知。

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认(这句话不理解)。

13 top k

如果是最大的10个数,用小顶堆;如果是最小的十个数,用大顶堆;时间复杂度O(n*logn)

14 输入一个URL中间发生了什么

第一步、浏览器中输入域名www.baidu.com

第二步、域名解析 解析域名就要用到DNS协议,首先主机会查询DNS的缓存,如果没有就给本地DNS发送查询请求。DNS查询分为两种方式,一种是递归查询,一种是迭代查询。如果是迭代查询,本地的DNS服务器,向根域名服务器发送查询请求,根域名服务器告知该域名的一级域名服务器,然后本地服务器给该一级域名服务器发送查询请求,然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的,因此会用到UDP协议。

第三步、浏览器与目标服务器建立TCP连接

第四步、浏览器通过http协议向目标服务器发送请求

浏览器向主机发起一个HTTP-GET方法报文请求。请求中包含访问的URL,也就是http://www.baidu.com/ ,KeepAlive,长连接,还有User-Agent用户浏览器操作系统信息,编码等。

第五步、服务器给出响应,将指定文件发送给浏览器

状态行,响应头,响应实体内容,返回状态码200 OK,表示服务器可以响应请求,返回报文,由于在报头中Content-type为“text/html”,浏览器以HTML形式呈现,而不是下载文件。

15 正向代理和反向代理

两者的区别在于代理的对象不一样,「正向代理」代理的对象是客户端,「反向代理」代理的对象是服务端。

16 STL map

map是一类关联式容器。它的特点是增加和删除节点对迭代器的影响很小,除了那个操作节点,对其他的节点都没有什么影响。对于迭代器来说,可以修改实值,而不能修改key。
底层实现为红黑树。

性质1. 节点是红色或黑色。
性质2. 根节点是黑色。
性质3 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
性质4. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

17 C 如何判断内存泄漏

valgrind
linux top 一下 随着时间增加 内存一直增大

解释一下: TCP time_wait
time_wait状态是四次挥手中server向client发送FIN终止连接后进入的状态。

18 TCP 和udp 的区别

1)TCP和UDP区别
1) 连接

TCP是面向连接的传输层协议,即传输数据之前必须先建立好连接。

UDP无连接。

2) 服务对象

TCP是点对点的两点间服务,即一条TCP连接只能有两个端点;

UDP支持一对一,一对多,多对一,多对多的交互通信。

3) 可靠性

TCP是可靠交付:无差错,不丢失,不重复,按序到达。

UDP是尽最大努力交付,不保证可靠交付。

4)拥塞控制,流量控制

TCP有拥塞控制和流量控制保证数据传输的安全性。

UDP没有拥塞控制,网络拥塞不会影响源主机的发送效率。

5) 报文长度

TCP是动态报文长度,即TCP报文长度是根据接收方的窗口大小和当前网络拥塞情况决定的。

UDP面向报文,不合并,不拆分,保留上面传下来报文的边界。

  1. 首部开销

TCP首部开销大,首部20个字节。

UDP首部开销小,8字节。(源端口,目的端口,数据长度,校验和)

2)TCP和UDP适用场景

从特点上我们已经知道,TCP 是可靠的但传输速度慢,UDP 是不可靠的但传输速度快。因此在选用具体协议通信时,应该根据通信数据的要求而决定。

若通信数据完整性需让位与通信实时性,则应该选用TCP 协议(如文件传输、重要状态的更新等);反之,则使用 UDP 协议(如视频传输、实时通信等)

19 一致哈希

在分布式集群中,对机器的添加删除,或者机器故障后自动脱离集群这些操作是分布式集群管理最基本的功能。如果采用常用的hash(object)%N算法,那么在有机器添加或者删除后,很多原有的数据就无法找到了,这样严重的违反了单调性原则。一致性哈希算法、、、

20 布隆过滤器

直观的说,bloom算法类似一个hash set,用来判断某个元素(key)是否在某个集合中。
和一般的hash set不同的是,这个算法无需存储key的值,对于每个key,只需要k个比特位,每个存储一个标志,用来判断key是否在集合中。

算法:

  1. 首先需要k个hash函数,每个函数可以把key散列成为1个整数
  2. 初始化时,需要一个长度为n比特的数组,每个比特位初始化为0
  3. 某个key加入集合时,用k个hash函数计算出k个散列值,并把数组中对应的比特位置为1
  4. 判断某个key是否在集合时,用k个hash函数计算出k个散列值,并查询数组中对应的比特位,如果所有的比特位都是1,认为在集合中。

优点:不需要存储key,节省空间

缺点:

  1. 算法判断key在集合中时,有一定的概率key其实不在集合中
  2. 无法删除

典型的应用场景:
某些存储系统的设计中,会存在空查询缺陷:当查询一个不存在的key时,需要访问慢设备,导致效率低下。
比如一个前端页面的缓存系统,可能这样设计:先查询某个页面在本地是否存在,如果存在就直接返回,如果不存在,就从后端获取。但是当频繁从缓存系统查询一个页面时,缓存系统将会频繁请求后端,把压力导入后端。

这是只要增加一个bloom算法的服务,后端插入一个key时,在这个服务中设置一次
需要查询后端时,先判断key在后端是否存在,这样就能避免后端的压力。

21 C/C++ 中指针和引用的区别

1.指针有自己的一块空间,而引用只是一个别名;
2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;

3.指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;

4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;

5.可以有const指针,但是没有const引用;

6.指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;

7.指针可以有多级指针(**p),而引用至于一级;

8.指针和引用使用++运算符的意义不一样;

9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

21 C++智能指针

unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用。

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

21 fork函数

Fork:创建一个和当前进程映像一样的进程可以通过fork( )系统调用:
#include

#include

pid_t fork(void);

成功调用fork( )会创建一个新的进程,它几乎与调用fork( )的进程一模一样,这两个进程都会继续运行。在子进程中,成功的fork( )调用会返回0。在父进程中fork( )返回子进程的pid。如果出现错误,fork( )返回一个负值。

最常见的fork( )用法是创建一个新的进程,然后使用exec( )载入二进制映像,替换当前进程的映像。这种情况下,派生(fork)了新的进程,而这个子进程会执行一个新的二进制可执行文件的映像。这种“派生加执行”的方式是很常见的。

在早期的Unix系统中,创建进程比较原始。当调用fork时,内核会把所有的内部数据结构复制一份,复制进程的页表项,然后把父进程的地址空间中的内容逐页的复制到子进程的地址空间中。但从内核角度来说,逐页的复制方式是十分耗时的。现代的Unix系统采取了更多的优化,例如Linux,采用了写时复制的方法,而不是对父进程空间进程整体复制。

22 C++里是怎么定义常量的?常量存放在内存的哪个位置?

对于局部常量,存放在栈区;对于全局常量,编译期一般不分配内存,放在符号表中以提高访问效率;字面值常量,比如字符串,放在常量区。

22 C语言参数压栈顺序?

从右往左

23 请你说说C++如何处理返回值

生成一个临时变量,把它的引用作为函数参数传入函数内。

24 线程死锁分析:

  1. 连续多次执行 $pstack 其中PID是进程号

    查看每个线程的函数调用关系的堆栈,观察每个线程当前的执行点是否在等待一个锁。

    多次执行该命令,发现某些线程的当前执行点不变,总是在等待同一个锁,就可以怀疑是否死锁了。

    如果怀疑哪些线程发生死锁了,可以采用gdb 进一步attach线程并进行分析。

25


linux 一切皆文件,当创建一个套接字的时候,返回的是文件标识符,即一个整数 fd
int fd1;
fd1 = socket(PF_INET, SOCK_STREAM, 0);



//下面为服务器的套接字或称作监听套接字
int socket(int domin, int type, int protocal)///成果时候返回 fd  失败返回-1
创建套接字要使用相应的协议,domain是套接字中使用的协议族,type 套接字数据传输类型信息,protocol 计算机通信中使用的协议信息;
如 PF_INET 为ipv4协议族,PF_INET6 为ipv6协议族等等
第二个参数 type为传输的方式,如IPv4中有很多传输方式
如SOCK_STREAM 是面向连接的  TCP
传输是无边界的, 发送送次数 和接受次数 可以是不同的

SOCK_DGRAM 是面向消息的传输方式 UDP
传输 有边界, 发送次数和接受次数必须相同

第三个 参数 是确定了 domin 和type  里面如果还有分类的话,在进行区分。  
但是 PF_INET+SOCK_STREAM 只有tcp  PF_INET+SOCK_DGRAM 只有udp  所以可以不用传参数


如果想接收多台计算机发来的数据,那么则相应个数的套接字,那么如何区分这些套接字你,就用到了端口号。





// bind函数原型
int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen)
 绑定服务器的地址,然后listen 监听他;
结合之前的 地址介绍 端口介绍 给出以下地址结构体信息 
struct sockaddr_in  {
	sa_family_t  sin_family; // 地址族  如ip4(AF_INET)使用4字节地址组	 AF_INET6 ip6 使用16字节地址族
	unit16_t     sin_port;  //16位  tcp/udp 端口号
	struct in_adrr  sin_adrr ///32位ip地址;
		char            sin_zero[8]; ///不适用 ,为了和 struct sockaddr 保持一致;
};

从下面代码可以看出,直接向sockaddr结构体填充这些信息会带来麻烦;
struct sockaddr {
	sa_family_t  sin_family; // 地址族  如ip4(AF_INET)使用4字节地址组	 AF_INET6 ip6 使用16字节地址族
	char         sin_zero[14]; 
};

此结构体成员sa data保存的地址信息中需包含E地址和端口号, 剩余部分应填充0, 这也是bind函数要求的。 
而这对于包含地址信息来讲非常麻烦, 继而就有了新的结构体sockaddr m。
若按照之前的讲解填写 sockaddr m结构体, 则将生成符合bind函数要求的字节流。
 最后转换为 sockaddr型的结构体变量,再传递给bind函数即可。





listen 函数
int listen(int sockfd, int backlog) ///成功0  失败-1;
此时的sockfd位服务端fd///backlog为等待队列的长度,如backlog=5,最多可以使5个请求进入等待队列

 accpet 函数
int accept(int sockfd, struct sockaddr *myaddr, socklen_t addrlen)
成功 会发挥一个套接字的文件描述符,该套接字是用于I/O的套接字 。该套接字是自动创建的,并且自动和客户端建立连接。失败-1; 参数:sockfd服务器socket,myaddr客户端的地址,addrlen客户端地址长度;


// 客户端套接字
int socket(int domin, int type, int protocal)///成果时候返回 fd  失败返回-1

int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen)///成果时候返回 fd  失败返回-1
 参数:sockfd客户端的描述符,serv_addr服务器的地址,addrlen服务器地址的长度
 客户端的地址和端口是在connect调用的时候自动分配的,在系统的内核分配


echo 回声服务端/客户端  一般用于调试和检测中。   它可以基于TCP协议,服务器就在TCP端口7检测有无消息,如果使用UDP协议,基本过程和TCP一样,检测的端口也是7TCPI/O缓冲 
 I/O缓冲在每个套接字单独存在
 I/O缓冲在创建套接字的时候自动生成
 即使关闭套接字也会继续传输 输出缓冲 中遗留的数据
 关闭套接字将会丢失输入缓冲的数据

UDP 是不需要连接的,不必调用listen和accept函数。UDP只有创建套接字的过程和数据交换过程
UDP 的数据I/O函数如下:发送信息的函数
ssize_t sendto(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
成功返回传输的字节数,失败时返回-1
sock 用于传输数据的UDP套接字文件描述符  buff用于保存待传输数据的缓冲地址值  nbytes 是待传输的数据长度,flags 是可选项参数, to指向存有目标地址信息的sockaddr结构体变量
 addrlen是传递给参数to 的地址值结构体变量长度
接受信息的函数
ssize_t recvfrom(int sock, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);
在调用sendto 函数的时候会自动分配IP和端口号。即自动调用bind 函数

UDP 传输过程中一定要注意边界问题,sendto和recvfrom要相互对应
传输的过程中  TCP套接字中需注册待传输数据的目标E和端口号,而UDP中则无需注册。因此啻 通过sendto 函数传输数据的过程大致可分为以下3个阶段。
第1阶段:向UDP套接字注册目标E和端口号。
第2阶段:传输数据。
第3阶段:删除UDP套接字中注册的目标地址信息。
每次调用sendto雨数时重复上述过程。 每次都变更目标地址, 因此可以重复利用同一UDP套 接字向不同目标传输数据。
这种未注册目标地址信息的套接字称为未连接套接字. 反之, 注册了 目标地址的套接字称为连接connected套接字。
显然,UDP套接字默认属于未连接套接字。但UDP 套接字在下述情况下显得不太合理:
IP211.210.147.82的主机82号端口共准备了3个数据,调用3次sendto函数进行传输。”
此时需重复3次上述三阶段。 因此 , 要与同一主机进行长时间通信时,将UDP套接字变成已 连按套接字会提高效率。 
上述三个阶段中, 第一个和第三个阶段占整个通信过程近1 / 3的时间, 缩短这部分时间将大大提高整体性能。
 也就是说,创建的的确是UDP套接字。 当然 , 针对UDP套接字调用connect函数并不意味着要与对方UDP套接字连接 , 这只是向UDP套接字注册目标E和端口信息。
之后就与TCP套接字一样 , 每次调用sendto函数时只需传输数据。因为已经指定了收发对象,所以不仅可以使用sendto、recvfrom函数,还可以使用write、read函数进行通信。

四次挥手,连接的断开
int showdown(int sock, int howto)// successed return 0 ,fail return -1
 sock :需要断开的套接字文件描述符,howto:传递断开方式信息(如 SHUT_RD: 断开输入流 SHUT_WR:断开输出流 SHUT_RDWR:同时断开I/O流)


 EOF是文件结尾标志

 DNS是对E地址和域名进行相互转换的系统, 其核心是DNS服务器。 提供网络服务的服务器端也是通过IP地址区分的,
但几乎不可能以非常难记的IP地址形式交换服务器端地址信息。 因此『 将容易记、 易表述的域名分配并取代E地址。
默 认DNS服务器收到自己无法解析的请求时-,向上级DNS服务器询问。通过这种方式逐级向上传递信息,到达顶级DNS服务器 根DNS服务器时, 它知道该向哪个DNS服务器询问。
向下级DNs 传递解析请求, 得到IP地址后原路返回, 最后将解析的E地址传递到发起请求的主机。 DNS就是 这样层次化管理的一种分布式数据库系统。


使用以下函数可以通过传递字符串格式的域名获取IP地址
struct hostent * gethostbyname(const char * hostname);
成功时逅因hostent结构体地址, 失败时返回 NULL 指针

 TIME_wait 
到底为什么会有Time-wait状态呢?图归中假设主机A向主机B传输ACK消息( SEQ 500.l、
ACK 7502)后立即消除套接字。但最后这条ACK消息在传递途中丢失, 未能传给主机。这时会
发生什么?主机B会认为之前向己发送的FIN消息、( SEQ 7501ACK 5001)未能抵达主机A, 继
而试图重传。但此时主机A已是完全终止的状态, 因此主机B永远无法收到从王机A最后传来的
ACK消息。相反, 若主机A的套接字处在Time - wait状态, 则会向主机B重传最后的ACK消息, 主

26 各种排序方法及TOP K

// 
int Partition(vector<int> & array, int l,int r)
{
	int temp = array[l];
	int i = l;
	int j = r;
	while (i<j)
	{
		while (array[j]>temp && j > i)
			j--;
		array[i] = array[j];
		while (array[i]<temp && j > i)
			i++;
		array[j] = array[i];
	}
	array[i] = temp;
	return i;

}





void quicksort(vector<int> & array, int l, int r) 
{
	if (r > l) 
	{
		int mid = Partition(array, l, r);
		quicksort(array, l, mid - 1);
		quicksort(array, mid + 1, r);
	}
	
}



冒泡排序
void bubblesort(vector<int> & array ,int n)
{
	
	for (int i = 0; i < n; i++)
	{
		bool flag = false;
		for (int j = i+1; j < n; j++)
		{
			if (array[i]>array[j])
			{
				swap(array[i], array[j]);
				flag = 1;
			}
		}
		if (!flag)
			return;
	}
}

 简单选择排序
void selecctSort(vector<int> & array ,int n)
{
	
	for (int i = 0; i < n; i++)
	{
		int k = i;
		for (int j = i+1; j < n; j++)
		{
			if (array[j] < array[k])
				k = j;
		}
		swap(array[k], array[i]);
	}

插入排序
void selecctSort(vector<int> & array ,int n)
{
	int j;
	for (int i = 1; i < n; i++)
	{
		if (array[i]<array[i-1])
		{
			int temp = array[i];
			for ( j = i-1; j>=0 && temp<array[j] ; --j) 这里有一个坑:如果只使用  temp=0在前面
			{											 可以防止这种情况
				array[j + 1] = array[j];
			}
			array[j + 1] = temp;
		}
	}
}



 背包问题
#include<iostream>
#include<vector>
#include<algorithm>
#include<math.h>
#include<string>

#define MAX 100000

using namespace std;

int weights[5] = { 0, 10, 3, 4, 5 };  //花费
int values[5] = { 0, 3, 4, 6, 7 }; //物体体积
int mount[5] = { 0,5,1,2,1 };
int bagw = 10;
int dp[MAX];


void bag01(int weight, int value) 
{
	for (int j = bagw; j >= weight; --j)
		dp[j] = max(dp[j], dp[j - weight] + value);
}

void Completebag(int weight, int value)
{
	for (int j = weight; j <= bagw; ++j)
		dp[j] = max(dp[j], dp[j - weight] + value);
}

void Multiplebag(int weight, int value,int count)
{
	if (count*weight >= bagw)
		Completebag(weight, value);
	else
	{
		int k = 1;
		while (k<count)
		{
			bag01(k*weight, k*value);
			count -= k;
			k = 2 * k;
		}
		bag01(count*weight, count*value);
	}
}

int main()
{

	for (int i = 0; i < 5; i++)
	{
		Multiplebag(weights[i], values[i], mount[i]);
	}
	cout << dp[bagw] << endl;
	system("pause");
	return 0;
}

翻转字符串

string reverseStr(const string &s)
{
	if (0 == s.length())
		return s;
	string  str = s;
	string res = "";
	for (int i = 0; str[i] != '\0'; i++)
	{
		if (str[i] != ' ' && str[i] != ',' && (str[i] < 'a' || str[i] > 'z'))
			continue;
		if (str[i] != ' ')
		{
			res += str[i];
		}
		else
		{
			if (str[i + 1] != ' ')
			{
				res += str[i];
			}
		}
	}

	int flag = 0;
	for (int i = 0; i<res.length() + 1; ++i)
	{
		if (res[i] == ' ' || res[i] == '\0' || res[i] == ',')
		{
			reverse(&res[flag], &res[i]);
			flag = i + 1;
		}
	}
	reverse(res.begin(), res.end());

	return res;

}


最大连续子序列
int maxSequence(vector<int> & arr)
{
	unordered_set<int> set1(arr.begin(), arr.end());
	int res = 0;
	for (auto arrs : arr)
	{
		if (set1.count(arrs-1)==0)
		{
			int temp = arrs + 1;
			while (set1.count(temp))
				temp++;
				res = max(res, (temp - arrs));
		}
		
	}
	return res;
}

堆排序 TOPk
void HeapAdjust(vector<int>& input, int s, int m)
{
	int temp = input[s];
	for (int i = 2 * s + 1; i<m; i = i * 2 + 1)
	{
		if (i<m - 1 && input[i]<input[i + 1])
			i++;
		if (temp<input[i])
		{
			input[s] = input[i];
			s = i;
		}
		else
			break;
	}
	input[s] = temp;
}



vector<int> GetLeastNumbers_Solution(vector<int> &input, int k) {
	vector<int> res;
	if (input.size() == 0)
		return res;
	if (input.size()<k)
		return res;
	if (input.size() == k)
		return input;
	res.assign(input.begin(), input.begin() + k);
	for (int i = k / 2 - 1; i >= 0; i--)
		HeapAdjust(res, i, k);

	for (int i = k; i<input.size(); i++)
	{
		if (input[i]<res[0])
		{
			res[0] = input[i];
			HeapAdjust(res, 0, k);
		}
	}

	return res;

}

快排topk
int Partition(vector<int>&a, int l, int r) 
{
	int temp = a[l];
	int i = l, j = r;
	while (j>i)
	{
		while (a[j]<=temp && j>i)
			j--;
		a[i] = a[j];
		while (a[i]>=temp && j>i)
			i++;
		a[j] = a[i];
	}
	a[i] = temp;
	return i;
}





void quickSortTopK(vector<int>&a, int l, int r, int k) 
{

		int mid = Partition(a, l, r);
		if (mid == k)
		{
			for (int i = 0; i < k; i++)
			{
				cout << a[i] << endl;
			}
			return;
		}
		if (mid>k)
		{
			quickSortTopK(a, l, mid - 1, k);
		}
		else
		{
			quickSortTopK(a, mid + 1, r, k);
		}
}

回溯法写法思路:
1. 定义全局结果数组
2. 调用递归函数
3. 返回全局结果数组
4. 定义递归函数
1) 参数,动态变化,一般为分支结果、限制条件等
2) 终止条件,将分支结果添加到全局数组
3) 剪枝条件
4) 调用递归逐步产生结果,回溯搜索下一结果


满足和为target的组合
class Solution {
public:
	vector<vector<int>> out;
	vector<int> tmp;
	void result(int idx, vector<int>& candidates, int target)
	{
		if (target <= 0)
		{
			if (target == 0)
			{
				out.push_back(tmp);
			}
			return;
		}
		for (int i = idx; i < candidates.size(); i++)
		{
			tmp.push_back(candidates[i]);
			result(i, candidates, target - candidates[i]);
			tmp.pop_back();
		}
	}
	vector<vector<int>>combinationSum(vector<int>& candidates, int target) {
		result(0, candidates, target);
		return out;
	}
};

///最长上升子序列  动态解法
int LIS(vector<int>& a,int n) 
{
	vector<int>dp(n, 1);
	for (int i = 0; i < n; i++)
	{
		for (int j = 0; j < i; j++)
		{
			if (a[i] > a[j])
				dp[i] = max(dp[i], dp[j] + 1);
		}
	}
	return dp[n - 1];
}

///最长上升子序列  lower_bound 解法  贪心 
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,
找到返回该数字的地址,不存在则返回end
int LIS(vector<int>& a, int n)
{
	vector<int>dp(n, INF);
	for (int i = 0; i < n; i++)
	{
		*lower_bound(dp.begin(), dp.end(), a[i]) = a[i];
	}
	return lower_bound(dp.begin(), dp.end(), INF) - dp.begin();
	
}

单调栈 

vector<int> GetRfmaxNumber(vector<int> input) 
{
	vector<int>res(input.size(),-1);
	if (input.size() == 0 || input.size() == 0)
		return res;

	stack<int>drabstk;
	int i = 0;
	drabstk.push(i);
	while (++i<input.size())
	{
		while (!drabstk.empty() && input[drabstk.top()] < input[i] )
		{
			res[drabstk.top()] = input[i];
			drabstk.pop();
		}
		drabstk.push(i);

	}
	return res;
}

消除重复的数字 例如 123324566  返回 145

vector<int> RemoveDupnums(vector<int>&input)
{
	vector<int>res;
	if (input.size() == 0)
		return res;
	stack<int>mystack;
	int flag = 0;
	int i = 0;
	while (i < input.size() )
	{
		if (res.empty() || input[i]!= res.back())
		{
			res.push_back(input[i]);
			i++;
		}
		else 
		{
			while (i< input.size() && input[i] == res.back())
				i++;
			res.pop_back();
		}
	}
	return res;

}


消除重复的数字 例如 123324566  返回 12245
vector<int> RemoveDupnums(vector<int>&input)
{
	vector<int>res;
	if (input.size() == 0)
		return res;
	stack<int>mystack;
	int i = 0;
	while (i < input.size())
	{
		if (i ==input.size() - 1 && input[i] != input[i - 1])
		{
			res.push_back(input[i]);
		}
		if (i < input.size()-1 && input[i]!=input[i+1])
		{
		
			res.push_back(input[i]);
			i++;
		}
		else
		{
			while (i < input.size()-1 && input[i] == input[i + 1])
				i++;
			i = i + 1;
		}
	}
	return res;
}



移除字符串的空格

string RemoveSpace(string s)
{
	int i = 0,j=0;
	while ( j<s.size() + 1)
	{
		if (s[j]!=' ')
		{
			s[i++] = s[j++];
		}
		else
		{
			while (j<s.size() + 1 && s[j]==' ')
				j++;
			s[i++] = s[j++];
		}
	}
	return s;
}

二叉树的第K层的结点数

int GetNodeNumKthLevel(BinaryTree *pRoot, int k )
{
    if (pRoot == NULL || k < 1)
        return 0;
    if (k == 1)
        return 1;
    int numLeft = GetNodeNumKthLevel(pRoot->lchild, k - 1);
    int numRight = GetNodeNumKthLevel(pRoot->rchild, k - 1);
    return (numLeft + numRight);
}


贝壳面经汇总

1 内存泄漏是什么,怎么引起的

内存泄漏是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的分类:

1、堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak。

2、系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。

3、没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露。

2 TCP建立连接为啥不是两次握手,第三次握手的ACK没收到怎么办

如果此时ACK在网络中丢失,那么Server端该TCP连接的状态为SYN_RECV,并且依次等待3秒、6秒、12秒后重新发送SYN+ACK包,以便Client重新发送ACK包。
Server重发SYN+ACK包的次数,可以通过设置/proc/sys/net/ipv4/tcp_synack_retries修改,默认值为5。

如果重发指定次数后,仍然未收到ACK应答,那么一段时间后,Server自动关闭这个连接。 但是Client认为这个连接已经建立,如果Client端向Server写数据,Server端将以RST包(用于强制关闭tcp连接)响应,方能感知到Server的错误。

3 环形链表

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head==NULL || head->next==NULL)
            return NULL;
        ListNode * slow=head;
        ListNode * fast=head;
        ListNode * meet=NULL;
        while(fast && fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow)
            {
                meet=slow;
                break;
            }
               
        }
        if(meet==NULL)
            return NULL;
        while(head!=meet)
        {
            head=head->next;
            meet=meet->next;
        }
        if(head==meet)
             return head;
        return NULL;
    }
};

4 进程通信方式 TCP/UDP 画三次握手 四次挥手过程 七层网络结构 详细介绍每一层 然后每层之间是怎么交互的

5 https原理

6 两个链表的第一个公共字节点

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA==NULL||headB==NULL)
            return 0;
        int num1=0;
        int num2=0;
        ListNode *p=headA;
        while(p)
        {
            num1++;
            p=p->next;
        }
        p=headB;
         while(p)
        {
            num2++;
            p=p->next;
        }
        
        if(num1>=num2)
        {
            num1=num1-num2;
            while(num1--)
            {
                headA=headA->next;
            }
        }else
        {
            num2=num2-num1;
            while(num2--)
            {
                headB=headB->next;
            }
        }
        
        while(headA && headB)
        {
            if(headA==headB)
                return headA;
            headA=headA->next;
            headB=headB->next;
        }
        return 0;
        
    }
};

7 合并两个有序链表

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head==NULL || head->next==NULL)
            return NULL;
        ListNode * slow=head;
        ListNode * fast=head;
        ListNode * meet=NULL;
        while(fast && fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow)
            {
                meet=slow;
                break;
            }
               
        }
        if(meet==NULL)
            return NULL;
        while(head!=meet)
        {
            head=head->next;
            meet=meet->next;
        }
        if(head==meet)
             return head;
        return NULL;
    }
};

8 进程间通信的方式

9 进程、线程间同步的方式

10 锁机制、自旋锁、以及如何避免死锁

11 5.select、poll、epoll间的区别,epoll的两种工作模式

12 判断两个二叉树是否相等

13 哈希表避免冲突的方式

14 数组和链表分别比较适合用于什么场景

  数组应用场景:数据比较少;经常做的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建的线性表较稳定。

链表应用场景:对线性表的长度或者规模难以估计;频繁做插入删除操作;构建动态性比较强的线性表。

数组是将元素在内存中连续存储的;

        优点:因为数据是连续存储的,内存地址连续,所以在查找数据的时候效 率比较高;

        缺点:在存储之前,我们需要申请一块连续的内存空间,并且在编译的时候就必须确定好它的空间的大小。在运行的时候空间的大小是无法随着你的需要进行增加和减少而改变的,当数据两比较大的时候,有可能会出现越界的情况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增加、插入、删除数据效率比较低链表是动态申请内存空间,不需要像数组需要提前申请好内存的大小,

    链表只需在用的时候申请就可以,根据需要来动态申请或者删除内存空间,对于数据增加和删除以及插入比数组灵活。还有就是链表中数据在内存中可以在任意的位置,通过应用来关联数据(就是通过存在元素的指针来联系)。

15 哈希表避免冲突的方式

16 你熟悉的排序方式,以及其适合的应用场景

当n比较小时,可采用直接插入排序和直接选择排序。
(1)当记录规模较小时,考虑直接插入排序较好;否则因为直接选择移动的记录数少于直接插入,应选直接选择排序为宜。
若文件初始状态基本有序(指正序),则应选用直接插入排序、冒泡排序或随机的快速排序为宜。
若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
(1)快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
(2)堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
(3)若要求排序稳定,则可选用归并排序。但前面介绍的从单个记录起进行两两归并的排序算法并不值得提倡,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子序列,然后再两两归并之。因为直接插入排序是稳定 的,所以改进后的归并排序仍是稳定的。
希尔排序是对直接插入排序的一种优化,可以用于大型的数组,希尔排序比插入排序和选择排序要快的多,并且数组越大,优势越大。
处理大数据,适合用堆排序,在数据量特别大的时候效果明显。
C++面经总结_第1张图片

16 .select、poll、epoll间的区别,epoll的两种工作模式。B树、B+树、跳表

17 如何实现布隆过滤器

一、什么是缓存击穿

查询一个在缓存内必然不存在的数据,导致每次请求都要去存储层去查询,这样缓存就失去了意义。如果在大流量下数据库可能挂掉。缓存击穿是黑客攻击系统的常用手段。

二、怎么解决缓存击穿问题?

采用布隆过滤器来实现。

什么是布隆过滤器?

它是一种空间效率极高的概率型算法和数据结构,用于判断一个元素是否在集合中(类似Hashset)。它的核心是一个很长的二进制向量和一系列的hash函数。

java中如何使用布隆过滤器?

使用谷歌的guava实现布隆过滤器。

bloom Filter布隆过滤器优劣势?

优势:

1)全量存储但不存储元素本身,在某些保密要求非常严格的场合有优势

2)空间效率高

3)插入/查询时间都是常数,远远超过一般算法

劣势:

1)存在误算率,随着存入的元素数量增加,误算率也随着增加

2)一般情况下不能从布隆过滤器删除元素

3)数组长度以及hash函数个数确定过程复杂

布隆过滤器的使用场景?

1)垃圾邮件地址过滤(地址数量很庞大)

2)爬虫URL地址去重

3)解决缓存击穿问题

4)浏览器安全浏览网址提醒

5)google 分布式数据库Bigtable以及Hbase使用布隆过滤器来查找不存在的行或列,以减少磁盘查找的IO次数

6)文档存储检索系统也可以采用布隆过滤器来检测先前存储的数据、

布隆过滤器位数确定
C++面经总结_第2张图片

18 一致哈希 和布隆过滤器

19 贝壳的笔试题

第一题
vector Minabs(vector input)
{
	vectorres;
	int flag = 0;
	long long min = 1e7;
	for (int i = 1; i < input.size(); i++)
	{
		if (min>abs(input[i]- input[i-1]))
		{
			min = abs(input[i] - input[i - 1]);
			flag = i;
		}
	}
	res.push_back(input[flag - 1]);
	res.push_back(input[flag]);
	return  res;
}
第一题
vector Minabs(vector input)
{
	vectorres;
	int flag = 0;
	long long min = 1e7;
	for (int i = 1; i < input.size(); i++)
	{
		if (min>abs(input[i]- input[i-1]))
		{
			min = abs(input[i] - input[i - 1]);
			flag = i;
		}
	}
	res.push_back(input[flag - 1]);
	res.push_back(input[flag]);
	return  res;
}


你可能感兴趣的:(面经,C++,算法)