北京心影随行科技有限公司 C++ 一面 面经

1. C++中深拷贝和浅拷贝的区别是什么?

答案:

浅拷贝只复制对象的成员变量值,如果成员是指针,只复制指针的地址。这会导致多个对象的指针指向同一块内存,当一个对象析构释放内存后,其他对象的指针就变成了悬空指针,再次访问会导致程序崩溃。

深拷贝不仅复制成员变量的值,还会为指针成员重新分配内存并复制内容。每个对象拥有独立的内存空间,互不影响。实现深拷贝需要自定义拷贝构造函数和赋值运算符。

什么时候必须使用深拷贝:类中包含指针成员、动态分配的资源(如文件句柄、网络连接)、需要管理资源生命周期的情况。

根据C++的三五法则,如果需要自定义析构函数、拷贝构造函数、拷贝赋值运算符中的任何一个,通常需要自定义全部三个(C++11还包括移动构造函数和移动赋值运算符)。

2. C++中有哪些常用的锁?分别适用于什么场景?

答案:

mutex(互斥锁):最基本的锁,同一时刻只允许一个线程持有。适用于保护临界区资源,防止数据竞争。

recursive_mutex(递归锁):允许同一线程多次获取同一把锁,内部维护计数器。适用于递归函数或相互调用的函数中需要加锁的场景。

shared_mutex(读写锁):允许多个读者同时访问,或一个写者独占访问。读读不互斥,读写互斥,写写互斥。适用于读多写少的场景,可以提高并发性能。

timed_mutex(定时锁):在指定时间内尝试获取锁,超时则放弃。可以避免无限期等待,防止死锁。

spin_lock(自旋锁):通过忙等待获取锁,不会让出CPU。适合锁持有时间极短(微秒级)的场景,避免线程切换开销。

lock_guard:RAII机制的锁管理器,构造时加锁,析构时自动解锁。简单安全,但不能手动控制。

unique_lock:比lock_guard更灵活的锁管理器,支持延迟加锁、手动加锁解锁、条件变量、所有权转移等。

选择建议:简单场景用mutex+lock_guard,需要灵活控制用unique_lock,递归调用用recursive_mutex,读多写少用shared_mutex,极短临界区用spin_lock。

3. STL容器中哪些存在线程安全问题?如何解决?

答案:

STL标准库中的所有容器(vector、list、map、set、queue等)都不是线程安全的。

不安全的原因包括:多线程同时读写导致数据竞争、一个线程修改容器导致其他线程的迭代器失效、vector扩容时可能导致其他线程访问无效内存、容器的size和capacity等内部状态可能不同步。

解决方案:

方案一:使用互斥锁保护容器的所有访问操作。每次读写容器前加锁,操作完成后解锁。

方案二:使用读写锁优化性能。读操作使用shared_lock允许多个线程同时读,写操作使用unique_lock独占访问。适合读多写少的场景。

方案三:使用线程安全的容器库,如Intel TBB的concurrent_vector和concurrent_hash_map,或Boost的lockfree容器。

方案四:每个线程使用独立的容器(thread_local),最后合并所有线程的结果。这种方式无需加锁,性能最好。

方案五:使用无锁编程技术,通过原子操作实现无锁数据结构。但这需要深入理解内存模型,实现难度较高。

最佳实践:优先使用成熟的线程安全容器库,读多写少场景使用读写锁,尽量减小锁的粒度和持有时间,避免在持有锁时调用外部函数。

4. 常用的设计模式有哪些?分别解决什么问题?

答案:

单例模式:确保一个类只有一个实例,并提供全局访问点。应用场景包括配置管理器、日志系统、数据库连接池、线程池等。实现时需要注意线程安全问题,C++11的静态局部变量可以保证线程安全的懒汉式单例。

工厂模式:封装对象创建过程,解耦对象的创建和使用。简单工厂通过参数决定创建哪种对象,工厂方法将创建逻辑延迟到子类。应用场景包括根据配置创建不同类型对象、数据库驱动选择、日志记录器选择、图形界面组件创建等。

观察者模式:定义对象间一对多的依赖关系,当一个对象状态改变时,所有依赖者都会收到通知并自动更新。应用场景包括GUI事件处理系统、MVC架构中Model和View的关系、消息订阅系统、股票价格监控等。

策略模式:定义一系列算法,将每个算法封装起来,使它们可以互相替换。应用场景包括排序算法选择、压缩算法选择、支付方式选择、路径规划算法等。策略模式比继承更灵活,可以在运行时动态切换算法。

装饰器模式:动态地给对象添加额外的职责,比继承更灵活。应用场景包括IO流的层层包装、GUI组件添加滚动条和边框、日志增强添加时间戳和级别、HTTP请求响应处理添加认证和加密等。

适配器模式:将一个类的接口转换成客户期望的另一个接口,使原本接口不兼容的类可以一起工作。应用场景包括第三方库接口适配、旧系统接口兼容、数据格式转换、不同数据库驱动的统一接口等。

这些设计模式的核心思想是面向接口编程、依赖倒置、开闭原则,可以提高代码的可维护性、可扩展性和复用性。

5. HTTP协议的工作原理是什么?常见状态码有哪些?

答案:

HTTP是超文本传输协议,是应用层协议,基于请求-响应模式,无状态。默认端口HTTP是80,HTTPS是443。

工作流程:首先建立TCP连接(三次握手),然后客户端发送HTTP请求,服务器处理请求并返回HTTP响应,最后关闭连接(四次挥手)或保持连接(Keep-Alive)。

HTTP请求包括请求行(方法、URL、版本)、请求头(Host、User-Agent、Cookie等)、空行、请求体。常用方法有GET获取资源、POST提交数据、PUT更新资源、DELETE删除资源、HEAD获取响应头等。

HTTP响应包括状态行(版本、状态码、原因短语)、响应头(Content-Type、Set-Cookie等)、空行、响应体。

常见状态码:

1xx信息性状态码:100 Continue表示客户端应继续发送请求,101 Switching Protocols表示切换协议如WebSocket。

2xx成功状态码:200 OK请求成功,201 Created资源创建成功,204 No Content成功但无返回内容,206 Partial Content部分内容用于断点续传。

3xx重定向状态码:301 Moved Permanently永久重定向搜索引擎会更新索引,302 Found临时重定向,304 Not Modified资源未修改使用缓存。

4xx客户端错误:400 Bad Request请求语法错误,401 Unauthorized未认证需要登录,403 Forbidden已认证但无权限,404 Not Found资源不存在,405 Method Not Allowed请求方法不被允许,429 Too Many Requests请求过于频繁被限流。

5xx服务器错误:500 Internal Server Error服务器内部错误,502 Bad Gateway网关错误上游服务器响应无效,503 Service Unavailable服务不可用可能在维护或过载,504 Gateway Timeout网关超时。

HTTP/1.1支持持久连接,HTTP/2支持多路复用和头部压缩,HTTP/3基于QUIC协议使用UDP传输彻底解决队头阻塞问题。HTTPS是HTTP加上SSL/TLS加密,可以防止窃听和篡改,但需要CA证书且性能略低。

6. 网络编程中的select、poll、epoll有什么区别?

答案:

这三个都是IO多路复用技术,用于单个线程监控多个文件描述符的IO事件,避免为每个连接创建线程,适合高并发服务器。

select的特点:使用fd_set位图存储文件描述符,有数量限制通常是1024。每次调用需要将fd_set从用户空间拷贝到内核空间,内核遍历所有fd检查是否就绪,返回就绪的fd数量,用户还需要再次遍历找出具体哪些fd就绪,时间复杂度O(n)。而且fd_set在每次调用后会被修改,需要重新设置。优点是跨平台,Windows、Linux、Unix都支持。

poll的改进:使用pollfd结构体数组存储文件描述符,没有数量限制。不会修改events字段,无需重新设置。但仍需要拷贝pollfd数组到内核,仍需要遍历所有fd,时间复杂度O(n)。不支持Windows。

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++八股文全集 文章被收录于专栏

本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。

全部评论

相关推荐

评论
点赞
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务