给个offer就要 level
获赞
38
粉丝
38
关注
4
看过 TA
199
门头沟学院
2026
C++
IP属地:广西
正在认真学习计算机的小趴菜~
私信
关注
04-02 21:12
已编辑
门头沟学院 C++
1. 读写锁如何实现?2. 如何实现线程池?线程池里放了多个任务后,这些任务怎么分配到各线程的?3.哈希表的原理是什么?4.怎么实现对一个树结构进行广度优先遍历?5.栈内存和堆内存的区别?栈为什么分配速度快?它具体怎么分配?6.当使用new创建一个新的数组,它指针是虚拟地址还是物理地址?什么时候回真正映射到物理内存?7.https加密原理是怎么样的?8.如果有个假冒服务器,它也可以跟你握手吗?1. 读写锁是一种并发控制机制,允许多个线程同时读取共享资源,但写操作需要独占访问;初始化一个互斥锁(用于保护共享转态),初始化一个条件变量(用于阻塞等待的线程);读锁:如果没有写线程正在访问,允许读线程进入;写锁:如果没有读线程或写线程正在访问,允许写线程进入;解锁:读线程解锁时,介绍读取计数;写线程解锁时,通知等待的线程;2. 线程池,通过三个类实现,(1)线程类,用于控制线程的启动和停止,以及维护一个指向事件循环的指针;(2)程池类:用于管理线程,包括初始化线程数量,已经放置一个任务队列,每来一个事件就放到队列里,如果有空闲线程就唤醒去执行;(3)任务函数的接口类,写一个基类,自己通过子类来自定义函数;线程池收到任务后,会把任务放到共享的任务队列里面,每个线程会在循环里去拿任务,拿到任务时要加锁互斥,谁先拿到就执行。另外还可以考虑给任务添加优先级3. 通过哈希函数将将键值映射到数组索引,再用数组存储键值对。举例:像C++中的unorder_map,使用链地址法解决冲突,在哈希冲突时把多个元素放到同一个桶里链表中。当存储的元素跟数组大小的比值超过一定阈值,会进行自动扩容;4.  广度优先遍历就是对每一层进行遍历,用队列实现;先把根节点入队,出队时访问,然后把它的子节点按顺序入队,一直到队列为空;5. 栈由操作系统自动分配回收,存储函数的现参、局部变量、返回地址等;堆是通过new/delete或者malloc/free由程序自己分配释放,能分配更大的内存,但可能会出现内存碎片等问题;操作系统在底层对栈提供支持,会分配专门的寄存器存放栈的地址,另外它的入栈出栈操作也十分简单,并且由专门的指令执行,所以下来会很快;堆的操作是由C/C++函数库提供,在分配内存的时候需要一定的算法寻找合适大小的内存。并且获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存,因此堆比较慢。6. 并不是物理内存,而是虚拟地址,后面需要通过页表和MMU(内存管理单元)来映射到物理内存;操作系统采用懒加载策略,只有在程序访问这块内存时,才会将虚拟内存页映射到物理内存。也就是说,程序触发缺页中断时,操作系统才会分配物理内存并更新页表来完成映射。7. 客户端会发送一个Client random + TLS版本号 + 支持的密码套件列表的信息给服务端,服务器回应一个Server random + 自己的数字证书;客户端通过证书认证机构(CA)来验证证书是否合法,确认服务器身份后,用服务器的公钥加密一个pre-master发回给服务器;服务器用私钥解密得到该数;后面的就使用这个生成的会话秘钥client random + Server random + pre-master进行对称加密传输;8. 如果是一个假冒服务器,它的证书没有权威CA的签名,或者证书域名不匹配,客户端会提示不信任,阻止连接。CA:是证书颁发机构,负责签发;证书:由CA颁发的电子文件,包含公钥、身份信息和CA的签名等;
0 点赞 评论 收藏
分享
03-25 14:40
门头沟学院 C++
1. 什么是虚函数、纯虚函数?2. 什么函数不能声明为虚函数,析构为什么要声明为虚函数?3. 没有虚函数的话,C++如何实现多态4. 同一个类的不同对象的虚函数表是同一个吗5. 基类的虚函数表存放在内存的什么区,虚表指针vptr的初始化时间?6. 虚函数内部调用非虚函数是调用指针类还是对象类?7. 纯虚函数?使用场景有哪些?8. 纯虚继承解决什么问题?1. 虚函数是在面向对象编程中用于实现动态多态特性的机制;通过将基类的成员函数声明为虚函数,可以在派生类中重写这些函数,从而根据对象的实际类型确定调用函数版本;纯虚函数是一个在基类声明的虚函数,但没有在基类中提供实现。通过在函数声明的结尾使用=0来标记;2. 构造函数:因为在对象构建之前还未创建虚函数表;静态函数:静态函数没有this指针,与对象无关,不能声明为虚函数;内联函数:虚函数可以是内联的,但是当虚函数表现为多态的时候不可以是内联,因为内联是发生在编译期间的,而动态是发生在运行状态;析构函数可以是虚函数,而且建议都这么做,当将基类中的析构函数声明为虚函数的时候,在执行析构的时候,可以确保派生类的析构函数也被执行;3.  使用函数指针,使用模版(在编译时实现多态),使用策略模式(运行在运行时选择算法和行为);4. 同一个类的不同对象的虚函数表是相同的,因为虚函数表是按类生成的,而不是按对象生成的。每个对象的虚函数指针指向类的虚函数表,从而实现多态调用;5. 存储在常量区(只读数据段),vptr的初始化发生在对象的构造函数被调用时;6. 当一个虚函数内部调用一个非虚函数时,调用的是指针类型所指向的类中的函数,而不是对象实际类型的类中的函数;7. 纯虚函数是C++中用于定义抽象基类的工具。一个包含纯虚函数的类被称为抽象类,不能被实例化。纯虚函数的目的是让基类定义一个接口,而不提供具体的实现,具体的实现由派生类来完成。场景:定义通用接口、实现多态、延迟绑定;8. 纯虚继承(也称为虚继承)是C++中用于解决多重继承带来的某些问题的一种机制。以下是纯虚继承主要解决的问题:解决函数调用的歧义,避免成员变量的重复,解决菱形继承中的重复继承问题(菱形继承是指一个派生类从两个基类派生,而这两个基类又有一个共同的基类。如果不使用纯虚继承,派生类会从两个基类各集成一份共同基类的成员,导致重复继承和访问歧义;纯虚函数继承确保派生类只继承一份共同基类的成员);
0 点赞 评论 收藏
分享
03-25 14:40
已编辑
门头沟学院 C++
1. 介绍下几种典型的锁2. 为什么不能把所有函数写成内联函数?3. 虚函数的作用与多态?4. 虚函数的底层实现?1. 读写锁:可以允许多个读者同时进行读 写者必须互斥 写者优先于读者;互斥锁:一次只能一个线程拥有互斥锁,其它线程只有等待;条件变量:通过允许线程阻塞和等待另一个线程发送信号,一般和互斥锁一起使用;自旋锁:如果进程无法取得锁,进程不会立刻放弃CPU时间片,而是一直循环尝试读取锁,直到获取为止;2. 内联函数以代码复杂为代价,以省去函数调用的开销来提高执行效率。如果你的内联函数代码比较长,会消耗更多的内存空间,函数体内有循环,函数执行时间要比函数调用开销还大;3. 虚函数的主要作用是实现多态的机制。基类定义为虚函数,子类对其进行重写该函数,当父类的指针指向子类对象的地址时,父类指针根据赋给它的不同子类指针,动态的调用子类的该函数,而不是父类的函数。4. 虚函数的底层实现依懒虚函数表,虚表指针;每个类使用一个虚函数表,每个类对象用一个虚表指针。如果派生类重写了基类的虚函数,该派生类的虚函数表将保存重写的虚函数的地址,而不是基类的虚函数地址。如果基类中虚函数没有在派生类中重写,那么派生类继承基类中的虚方法,并且派生类中虚函数表将保存基类中未被重写的虚函数地址。
0 点赞 评论 收藏
分享
03-22 19:36
已编辑
门头沟学院 C++
未尔科技 实习 日薪200+10餐补 双非硕
0 点赞 评论 收藏
分享
03-22 11:53
已编辑
门头沟学院 C++
1. 被free回收的内存是立即返回给操作系统吗?2. sizeof和strlen的区别?3. new/delete、malloc/free的区别4. 堆快一点还是栈快一点?5. 在main执行之前和执行之后执行的代码可能是什么?6. final和override的关键字?7. 拷贝初始化和直接初始化?8. select、poll、epoll9.Reactor?10. 阻塞、非阻塞、同步、异步?11. Proactor?1. 被free回收的内存首先会被ptmalloc使用双链表保存起来,当有用户申请内存的时候,会直接从这里面找出合适的内存进行返回。这样可以避免操作系统的频繁调用,另外它还会将小内存进行合并,防止产生过多的内存碎片;2. sizeof是运算符,strlen是库函数。sizeof的参数可以是任何数据的类型或数据,strlen的参数只能是字符串的指针,且结束符为'\0'的字符;3. 前者是C++运算符,后者是C/C++库函数;new自动计算要分配的内存对象,malloc需要计算;new是类型安全的,malloc不是;调用new的给自定义对象分配内存的时候,会调用构造函数并分配相应的内存,delete的时候会释放内存并执行析构;4.  栈快一点,操作系统堆栈提供支持,有分配专门的寄存器存放栈的地址,栈的出入栈也十分简单,并且有专门的指令执行,所以栈的效率更高更快;而堆在分配的内存的时候要使用算法寻找合适大小的内存,并且在获取堆的内容的时候需要两次访问,一次指针,一直是指针保存的地址5. 初始化全局变量和静态变量,即.data段数据,将未初始化的全局变量进行赋值;全局对象初始化;将argc、argv传递给main函数;___attribute___(constructor);结束后,全局的析构函数会在main函数执行完后析构,可用一个atexit注册一个函数会在main函数之后执行;___attribute___(destructor)6. override的关键字,指定了子类的这个虚函数必须重写父类的,如果函数名不小心打错了,编译器会进行报错,不会通过;当不希望某个类被继承、或不希望虚函数被重写可以在类名和虚函数后面添加final关键字;7. 直接初始化直接调用与实参匹配的构造函数;拷贝初始化首先使用指定构造函数创建一个临时对象,然后用拷贝构造函数将那个零食对象拷贝到正在创建的对象;8. select函数将已连接的socket都放到一个文件描述符集合,然后调用select函数将文件描述符拷贝到内核中,去遍历进行检测那个套接字发生事件,将其进行标记,后续再复制到用户态,进行遍历找到标记的套接字。这其中发生了2次遍历文件描述符集合和2次拷贝文件描述符集合。poll突破文件描述符个数的限制(1024),通过一个动态数组,以链表的形式管理。其它与select还是一样的。epoll通过在内核中使用红黑树来跟踪进程所有待检测的文件描述符,把需要监控的socket通过epoll_ctl函数加入到内核中的红黑树,第二,epoll使用时间驱动的机制,内核里维护了一个链表来记录就绪事件,当某个socket事件发生时,通过回调函数内核将其加入到就绪事件列表中,当用户调用epoll_wait函数时,只会返回有事件发生的文件描述符的个数。9. reactor模型是 I/O 多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程 / 线程。单reactor单进程:reactor对象通过epoll监听事件,收到事件后通过dispatch进行分发,如果是连接建立的事件交个Acceptor对象进行处理,如果不是就交给Handle对象通过read->业务处理->send的流程来完成完整的业务流程。单reactor多线程:对于不是连接建立事件,则交由Handler对象处理,他不负责业务处理,只负责数据的接受和发送,数据会发送给线程池来进行业务处理,处理完成后将业务结果返回给handler对象,然后发送给client;多reactor多线程:主线程只负责接受新连接,子线程负责完成后续的业务处理。主线程只需要把新连接传给子线程,子线程进行业务处理后,直接可以将处理结果发送给客户端。10. 阻塞I/O:当用户执行read,线程会被阻塞,一直等到内核数据准备好[1],并把数据从内核缓冲区拷贝到应用缓冲区中[2],当拷贝完成,read才返回。非阻塞I/O:可以在数据未准备好久立即返回,然后应用程序不断轮训内核,直到数据准备好,[2]操作还是要的;异步I/O:上面的[1]和[2]的步骤都不需要等待,都交由内核完成。11. Reactor模式是基于待完成的I/O事件,而Proactor模式是基于[已完成]的I/O事件。
0 点赞 评论 收藏
分享
03-20 11:47
门头沟学院 C++
1.如果在头文件创建一个函数但是在源文件中没有实现它,会有bug吗?如果用到了会有bug吗?在那个阶段会报错2.如何解决循环包含的问题?3.动态库和静态库的区别?4.当使用结构体或对象作为unordermap、map中的key,会有哪些操作?5.TCP的三次握手以及完成连接队列的创建时机?6.介绍一下accept的底层原理?7.TCP的四次挥手?8.MySQL InnoDB存储数据采用什么结构?B+树的特征?B+树有哪两种类型(InnoDB)?聚集索引B+树和辅助索引B+树9.工厂模式解决了什么问题?什么场景下使用? 为了影藏对象构造的复杂,10. 在面对一个困难的问题时,你是怎么去分析的?解决的思路是什么?提出多个解决方案,哪一个方案最合适。这些都是重要加分项,不要错过1. 函数没有使用,在编译、链接都不会出现错误;函数使用了,编译没问题,链接会出错,如果该函数声明为inline(流程简单、频繁调用的函数),在编译阶段就会报错。2. 循环依赖 使用前置声明(当类结构体被用做指针或引用时,无需包含完整定义,前置声明替代头文件包含);依赖扩散 pimpl惯用法;重复包含 pragma once 或头文件保护宏。3. 静态库在编译的时候会被直接链接到可执行文件当中,动态库是在运行阶段才会确定。静态库缺点就是会造成程序体量比较大,并且在可执行文件后续的迭代更新中,每次都需要重新进行编译和链接可执行文件,适合小型项目。动态库可执行文件较小,多个程序共享同一份库代码,在后续的迭代中,只需要更新动态库,方便代码的维护性,适合大型项目。4. 对于unordermap需要重建一个哈希函数、重载==运算符、对于map需要重载operator <或自定义比较函数5. 首先客户端会发送一个SYN包请求建立连接,服务接受到之后返回一个SYN-ACK包,这个时候也会创建一个未完成连接队列,将客户端套接字加入进去,客户端收到服务端发来的包之后会发送一个ACK包,这个时候代表连接建立好了。此时,就会把客户单的套接字从未完成连接队列移除,移动到完成连接队列。6.当客户端发起连接请求时,服务端和客户端会通过TCP进行三次握手建立连接,完成之后,客户端的连接会被放入已完成连接队列,等待服务调用accept处理。之后。accept会从完成连接队列中取出一个客户端连接,并创建一个新的套接字文件描述符,用于客户端通信。7.主动关闭方(客户端)发送一个FIN报文给服务端,表示没有数据发送了;服务端收到FIN报文之后,会返回一个ACK包,表示确认收到;服务端这边没数据发送了之后,也会发送一个FIN报文给客户端;客户端收到ACK报文之后就会进入一个待关闭的状态,然后过2MSL就会进行关闭;服务端那边收到之后就会立马关闭。8. B+树的基础知识有待欠缺,需要进行补习9. 它是一种创建性设计模式,为了将对象的复杂创建逻辑封装起来,使得客户端代码不需要依赖具体的类,而是通过工厂来创建对象。最后下面的图片,感觉写的很好,所以贴上来了,是在B站上看到的,如有侵权,联系删除。#面试常问题系列#
0 点赞 评论 收藏
分享

创作者周榜

更多
关注他的用户也关注了:
牛客网
牛客企业服务