c++开发程序员面试拼多多面试题整理(含答案)

题很多,先上题后上答案,便于大家思考。


一.拼多多服务端实习岗位面试

1、 一个C++源文件从文本到可执行文件经历的过程

2、#include 的顺序以及尖叫括号和双引号的区别

3、进程和线程,为什么要有线程

4、C++11有哪些新特性

5、malloc的原理,brk系统调用干什么的,mmap呢

6、C++的内存管理方式,STL的allocator,最新版本默认使用的分配器

7、hash表的实现,包括STL中的哈希桶长度常数。

8、hash表如何rehash,怎么处理其中保存的资源

9、Redis的rehash怎么做的,为什么要渐进rehash,渐进rehash怎么实现的

10、Redis的定时机制怎么实现的,有哪些弊端,你将如何改进这个弊端

11、Redis是单线程的,为什么这么高效

12、Redis的数据类型有哪些,底层怎么实现

13、Redis和memcached的区别

14、TCP的模型,状态转移

15、用过哪些设计模式,单例模式,观察者模式的多线程安全问题

16、用过多线程吗,以前的多线程代码还能怎么优化,线程池的实现

17、epoll怎么实现的,reactor模型组成

18、线程间的同步方式,最好说出具体的系统调用

19、哈希表的桶个数为什么是质数,合数有何不妥?





答案分析:

1、 一个C++源文件从文本到可执行文件经历的过程

对于C/C++编写的程序,从源代码到可执行文件,一般经过下面四个步骤:

1).预处理,产生.ii文件

2).编译,产生汇编文件(.s文件)

3).汇编,产生目标文件(.o或.obj文件)

4).链接,产生可执行文件(.out或.exe文件)

2、#include 的顺序以及尖叫括号和双引号的区别

1. #include的顺序的区别:

头文件的引用顺序对于程序的编译还是有一定影响的。如果要在文件a.h中声明一个在文件b.h中定义的变量,而不引用b.h。那么要在a.c文件中引用b.h文件,并且要先引用b.h,后引用a.h,否则汇报变量类型未声明错误,也就是常见的某行少个“;”符号。

2. #include尖括号和双引号的区别:

1)#include<> ,认为该头文件是标准头文件。编译器将会在预定义的位置集查找该头文件,这些预定义的位置可以通过设置查找路径环境变量或者通过命令行选项来修改。使用的查找方式因编译器的不同而差别迥异。

2)#include"",认为它是非系统头文件,非系统头文件的查找通常开始于源文件所在的路径。查找范围大于<>。

3、进程和线程,为什么要有线程

1、和进程相比,它是一种非常"节俭"的多任务操作方式。在linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。(资源)

2、运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间。据统计,一个进程的开销大约是一个线程开销的30倍左右。(切换效率)

3、线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进城下的线程之间贡献数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。

(通信) 除以上优点外,多线程程序作为一种多任务、并发的工作方式,还有如下优点:

1、使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。(CPU设计保证)

2、改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序才会利于理解和修改。(代码易维护)

4、C++11有哪些新特性

1)关键字及新语法:auto、nullptr、for

2)STL容器:std::array、std::forward_list、std::unordered_map、std::unordered_set

3)多线程:std::thread、std::atomic、std::condition_variable

4)智能指针内存管理:std::shared_ptr、std::weak_ptr

5)其他:std::function、std::bind和lamda表达式

5、malloc的原理,brk系统调用干什么的,mmap呢

malloc的实现方案:

1)malloc函数的实质是它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。

2)调用malloc()函数时,它沿着连接表寻找一个大到足以满足用户请求所需要的内存块。 然后,将该内存块一分为二(一块的大小与用户申请的大小相等,另一块的大小就是剩下来的字节)。 接下来,将分配给用户的那块内存存储区域传给用户,并将剩下的那块(如果有的话)返回到连接表上。

3)调用free函数时,它将用户释放的内存块连接到空闲链表上。

4)到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段, 那么空闲链表上可能没有可以满足用户要求的片段了。于是,malloc()函数请求延时,并开始在空闲链表上检查各内存片段,对它们进行内存整理,将相邻的小空闲块合并成较大的内存块。

brk和mmap:

从操作系统角度来看,进程分配内存有两种方式,分别由两个系统调用完成:brk和mmap(不考虑共享内存)。

1、brk是将数据段(.data)的最高地址指针_edata往高地址推;

2、mmap是在进程的虚拟地址空间中(堆和栈中间,称为文件映射区域的地方)找一块空闲的虚拟内存。 

这两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。

在标准C库中,提供了malloc/free函数分配释放内存,这两个函数底层是由brk,mmap,munmap这些系统调用实现的。

6、C++的内存管理方式,STL的allocator,最新版本默认使用的分配器

C++的内存管理方式:

在c++中内存主要分为5个存储区:

栈(Stack):局部变量,函数参数等存储在该区,由编译器自动分配和释放.栈属于计算机系统的数据结构,进栈出栈有相应的计算机指令支持,而且分配专门的寄存器存储栈的地址,效率分高,内存空间是连续的,但栈的内存空间有限。

堆(Heap):需要程序员手动分配和释放(new,delete),属于动态分配方式。内存空间几乎没有限制,内存空间不连续,因此会产生内存碎片。操作系统有一个记录空间内存的链表,当收到内存申请时遍历链表,找到第一个空间大于申请空间的堆节点,将该节点分配给程序,并将该节点从链表中删除。一般,系统会在该内存空间的首地址处记录本次分配的内存大小,用于delete释放该内存空间。

全局/静态存储区:全局变量,静态变量分配到该区,到程序结束时自动释放,包括DATA段(全局初始化区)与BSS段(全局未初始化段)。其中,初始化的全局变量和静态变量存放在DATA段,未初始化的全局变量和静态变量存放在BSS段。BSS段特点:在程序执行前BSS段自动清零,所以未初始化的全局变量和静态变量在程序执行前已经成为0.

文字常量区:存放常量,而且不允许修改。程序结束后由系统释放。程序代码区:存放程序的二进制代码 

SGI 版本STL的默认配置器std::alloc参见:

《STL源码剖析》

1)考虑到小型区块所可能造成的内存碎片问题,SGI设计了双层配置器。第一级配置器直接使用malloc()和free();第二级则视情况采取不同的策略:当配置区块超过128bytes时,视为“足够大”,便调用第一级配置器;当配置区块小于128bytes时,视之为“过小”,为了降低额外负担,便采用memory pool(内存池)整理方式,而不在求助于第一级配置器。

2)内存池的核心:内存池和16个自由链表(各自管理8,16,...,128bytes的小额区块)。在分配一个小区块时,首先在所属自由链表中寻找,如果找到,直接抽出分配;若所属自由链表为空,则请求内存池为所属自由链表分配空间;默认情况下,为该自由链表分配20个区块,若内存池剩余容量不足,则分配可分配的最大容量;若内存池连一个区块都无法分配,则调用chunk_alloc为内存池分配一大块区块;若内存不足,则尝试调用malloc分配,否则返回bad_alloc异常。

7、hash表的实现,包括STL中的哈希桶长度常数。

hash表的实现主要涉及两个问题:散列函数和碰撞处理。1)hash function (散列函数)。最常见的散列函数:f(x) = x % TableSize .

2)碰撞问题(不同元素的散列值相同)。解决碰撞问题的方法有许多种,包括线性探测、二次探测、开链等做法。SGL版本使用开链法,使用一个链表保持相同散列值的元素。虽然开链法并不要求表格大小必须为质数,但SGI STL仍然以质数来设计表格大小,并且将28个质数(逐渐呈现大约两倍的关系)计算好,以备随时访问,同时提供一个函数,用来查询在这28个质数之中,“最接近某数并大于某数”的质数。

8、hash表如何rehash,怎么处理其中保存的资源

先想想为什么需要rehash:

因为,当loadFactor(负载因子)<=1时,hash表查找的期望复杂度为O(1). 因此,每次往hash表中添加元素时,我们必须保证是在loadFactor <1的情况下,才能够添加。

模仿C++的vector扩容方式,Hash表中每次发现loadFactor==1时,就开辟一个原来桶数组的两倍空间(称为新桶数组),然后把原来的桶数组中元素全部转移过来到新的桶数组中。注意这里转移是需要元素一个个重新哈希到新桶中的。

9、Redis的rehash怎么做的,为什么要渐进rehash,渐进rehash怎么实现的

为了避免rehash对服务器造成影响,服务器不是一次将ht[0]里面的所有键值对全部rehash到ht[1],而是分多次、渐进式地将ht[0]里面的键值对慢慢地rehash到ht[1].

以下是哈希表渐进式rehash的详细步骤:

为 ht[1] 分配空间, 让字典同时持有 ht[0] 和 ht[1] 两个哈希表。

在字典中维持一个索引计数器变量 rehashidx , 并将它的值设置为 0 , 表示rehash工作正式开始。

在rehash进行期间, 每次对字典执行添加、删除、查找或者更新操作时, 程序除了执行指定的操作以外, 还会顺带将 ht[0] 哈希表在 rehashidx 索引上的所有键值对rehash到 ht[1] , 当rehash工作完成之后, 程序将 rehashidx 属性的值增一。

随着字典操作的不断执行, 最终在某个时间点上, ht[0] 的所有键值对都会被rehash至 ht[1] , 这时程序将 rehashidx 属性的值设为 -1 , 表示rehash操作已完成。

渐进式rehash的好处在于它采取分而治之的方式, 将rehash键值对所需的计算工作均滩到对字典的每个添加、删除、查找和更新操作上, 从而避免了集中式rehash而带来的庞大计算量。

10、Redis的定时机制怎么实现的,有哪些弊端,你将如何改进这个弊端

Redis服务器是一个事件驱动程序,服务器需要处理以下两类事件:文件事件(服务器对套接字操作的抽象)和时间事件(服务器对定时操作的抽象)。Redis的定时机制就是借助时间事件实现的。

一个时间事件主要由以下三个属性组成:id:时间事件标识号;when:记录时间事件的到达时间;timeProc:时间事件处理器,当时间事件到达时,服务器就会调用相应的处理器来处理时间。一个时间事件根据时间事件处理器的返回值来判断是定时事件还是周期性事件。

弊端:Redis对时间事件的实际处理时间并不准时,通常会比时间事件设定的到达事件稍晚一些。

改进:多线程?一个处理文件事件,一个处理时间事件? (不确定)。

11、Redis是单线程的,为什么这么高效

虽然Redis文件事件处理器以单线程方式运行,但是通过使用I/O多路复用程序来监听多个套接字,文件事件处理器既实现了高性能的网络通信模型,又可以很好地与Redis服务器中其他同样以单线程运行的模块进行对接,这保持了Redis内部单线程设计的简单性。

12、Redis的数据类型有哪些,底层怎么实现

1)字符串:整数值、embstr编码的简单动态字符串、简单动态字符串(SDS)

2)列表:压缩列表、双端链表

3)哈希:压缩列表、字典

4)集合:整数集合、字典

5)有序集合:压缩列表、跳跃表和字典

13、Redis和memcached的区别

Redis和memcached的区别:

1)数据类型 :redis数据类型丰富,支持set liset等类型;memcache支持简单数据类型,需要客户端自己处理复杂对象

2)持久性:redis支持数据落地持久化存储;memcache不支持数据持久存储。

3)分布式存储:redis支持master-slave复制模式;memcache可以使用一致性hash做分布式。

4)value大小不同:memcache是一个内存缓存,key的长度小于250字符,单个item存储要小于1M,不适合虚拟机使用

5)数据一致性不同:redis使用的是单线程模型,保证了数据按顺序提交;memcache需要使用cas保证数据一致性。CAS(Check and Set)是一个确保并发一致性的机制,属于“乐观锁”范畴;原理很简单:拿版本号,操作,对比版本号,如果一致就操作,不一致就放弃任何操作

6)cpu利用:redis单线程模型只能使用一个cpu,可以开启多个redis进程

14、TCP的模型,状态转移

TCP四层模型:

状态转移:

熟悉三次握手 和 四次释放的TCP状态转移。

15、用过哪些设计模式,单例模式,观察者模式的多线程安全问题

设计模式

1)TemplateMethod模式:《effectivec++》 条款35 :借助Non-virtualInterface手法实现TemplateMethod模式

2)Strategy模式:《effectivec++》 条款35:借助FunctionPointers实现Strategy模式、借助std::function完成Strategy模式、古典Strategy模式

16、用过多线程吗,以前的多线程代码还能怎么优化,线程池的实现

线程的创建

#include <pthread.h>

int pthread_create(pthread_t *restrict thread,

                    const pthread_attr_t *restrict attr,

                    void *(*start_routine)(void*),

                    void *restrict arg);


线程终止:

从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。

一个线程可以调用pthread_cancel终止同一进程中的另一个线程。

线程可以调用pthread_exit终止自己。

线程池的实现:https://pan.baidu.com/s/1qHUhyCLFzHuHaio7-PAYiQ 密码:emmq

17、epoll怎么实现的,reactor模型组成

epoll实现:

第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄(eventpoll的对象)来标识。

structeventpoll{

....

/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/

structrb_rootrbr;

/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/

structlist_headrdlist;

....

};

第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。

第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。

Reactor模型:

1)Handle:即操作系统中的句柄,是对资源在操作系统层面上的一种抽象,它可以是打开的文件、一个连接(Socket)、Timer等。由于Reactor模式一般使用在网络编程中,因而这里一般指Socket Handle,即一个网络连接。

2)Synchronous Event Demultiplexer(同步事件复用器):阻塞等待一系列的Handle中的事件到来,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的执行返回的事件类型。这个模块一般使用操作系统的select来实现。

3)Initiation Dispatcher:用于管理EventHandler,即EventHandler的容器,用以注册、移除EventHandler等;另外,它还作为Reactor模式的入口调用SynchronousEventDemultiplexer的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的EventHandler处理,即回调EventHandler中的handle_event()方法。

4)EventHandler:定义事件处理方法:handle_event(),以供InitiationDispatcher回调使用。

5)ConcreteEventHandler:事件EventHandler接口,实现特定事件处理逻辑。

18、线程间的同步方式,最好说出具体的系统调用

1)互斥量(mutex)

#include <pthread.h>

int pthread_mutex_destroy(pthread_mutex_t *mutex);          //销毁

int pthread_mutex_init(pthread_mutex_t *restrict mutex,

      const pthread_mutexattr_t *restrict attr);          //初始化

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 


int pthread_mutex_lock(pthread_mutex_t *mutex);            //上锁

int pthread_mutex_trylock(pthread_mutex_t *mutex);          //尝试上锁   

int pthread_mutex_unlock(pthread_mutex_t *mutex);


2)条件变量(Condition Variable)

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);            //销毁

int pthread_cond_init(pthread_cond_t *restrict cond,

      const pthread_condattr_t *restrict attr);            //初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

int pthread_cond_timedwait(pthread_cond_t *restrict cond,

      pthread_mutex_t *restrict mutex,

      const struct timespec *restrict abstime);

int pthread_cond_wait(pthread_cond_t *restrict cond,

      pthread_mutex_t *restrict mutex);

int pthread_cond_broadcast(pthread_cond_t *cond);

int pthread_cond_signal(pthread_cond_t *cond);


3)信号量(Semaphore)

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);

int sem_wait(sem_t *sem);

int sem_trywait(sem_t *sem);

int sem_post(sem_t * sem);

int sem_destroy(sem_t * sem);

————————————————


调用sem_wait()可以获得资源,使semaphore的值减1,如果调用sem_wait()时semaphore的值已经是0,则挂起等待。如果不希望挂起等待,可以调用sem_trywait()。调用sem_post()可以释放资源,使semaphore的值加1,同时唤醒挂起等待的线程。

19、哈希表的桶个数为什么是质数,合数有何不妥

质数比合数更容易避免冲撞,也就是说使用质数时,哈希效果更好,原始数据经哈希后分布更均匀。

版权声明:本文为CSDN博主「Damage233」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/Damage233/article/details/81116115

还有腾讯,阿里,京东等一线大厂面试题及答案整理,因篇幅有限,需要集锦的朋友可以后台私信我【面试】免费获取。


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • 转自:Youtherhttps://www.cnblogs.com/youtherhome/archive/201...
    njukay阅读 1,605评论 0 52
  • Q:为什么出现多线程? A:为了实现同时干多件事的需求(并发),同时进行着下载和页面UI刷新。对于处理器,为每个线...
    幸福相依阅读 1,567评论 0 2
  • 多线程系列文章源码头文件内容: #include #include #include 作为程序员,就是要减少重复劳...
    batbattle阅读 907评论 0 1
  • 线程基础 线程是进程的一个执行单元,执行一段程序片段,线程共享全局变量;线程的查看可以使用命令或者文件来进行查看;...
    秋风弄影阅读 734评论 0 0
  • Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。 Linux线程同步-----互斥锁...
    什锦甜阅读 460评论 0 1