金山C++一面,强度拉满!配答案,建议收藏!!

科技   2024-11-10 19:59   河北  

1. C++程序的内存分布

C++程序的内存通常分为以下几个部分:

栈(Stack):用于存储局部变量和函数调用的信息。栈上的内存是自动管理的,函数调用结束后自动释放。

堆(Heap):用于动态分配内存,通过 new 和 delete 进行管理。堆上的内存需要手动管理。

全局/静态区(Global/Static Area):用于存储全局变量和静态变量。这些变量在整个程序运行期间都存在。

代码区(Code Section):用于存储程序的机器码。

详细解答可以阅读:面试题:C++ 内存四区

2. 堆和栈的区别

分配方式:栈上的内存是自动分配和释放的,而堆上的内存需要手动管理。

生命周期:栈上的变量在函数调用结束后自动销毁,堆上的变量需要显式释放。

访问速度:栈上的访问速度快,因为栈是连续的内存区域;堆上的访问速度相对较慢。

大小限制:栈的大小通常有限制(如几 MB),而堆的大小可以更大。

3. 内存泄漏怎么办

使用智能指针:如 std::unique_ptr 和 std::shared_ptr,它们会自动管理内存。

代码审查:定期检查代码,确保每个 new 都有一个对应的 delete。

内存检测工具:使用工具如 Valgrind、Visual Studio 的内存检测工具等,帮助发现内存泄漏。

RAII(Resource Acquisition Is Initialization):使用 RAII 技术,确保资源在对象销毁时自动释放。

4. 智能指针有哪几种

std::unique_ptr:独占所有权的智能指针,不允许复制。

std::shared_ptr:共享所有权的智能指针,允许多个指针共享同一个对象。

std::weak_ptr:弱引用智能指针,用于解决 std::shared_ptr 的循环引用问题。

5. 循环引用计数最后是多少

在循环引用的情况下,std::shared_ptr 的引用计数不会自动减为零,导致内存泄漏。通常需要使用 std::weak_ptr 来打破循环引用。

6. shared_ptr线程安全吗

引用计数操作是线程安全的:多个线程可以安全地对同一个 std::shared_ptr 进行引用计数操作。

对象访问不是线程安全的:对 std::shared_ptr 所指向的对象的访问需要额外的同步机制(如 std::mutex)来保证线程安全。

7. 多线程使用shared_ptr如何保护数据安全

使用互斥锁:在多线程环境中,使用 std::mutex 或 std::lock_guard 来同步对 std::shared_ptr 所指向的对象的访问。

使用 std::atomic<std::shared_ptr<T>>:如果需要更细粒度的控制,可以使用 std::atomic<std::shared_ptr<T>>。

8. 条件变量伪唤醒

条件变量的伪唤醒是指条件变量在没有实际通知的情况下被唤醒。为了避免伪唤醒,通常需要在循环中检查条件变量的状态。

std::unique_lock<std::mutex> lock(mtx);
while (!condition) {    cv.wait(lock);}


9. unique_ptr转移所有权

使用 std::move 将 std::unique_ptr 的所有权转移给另一个 std::unique_ptr。

std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
std::unique_ptr<int> ptr2 = std::move(ptr1); // 转移所有权


10. move实现方式

std::move 实际上是一个类型转换函数,将左值转换为右值引用,以便调用移动构造函数或移动赋值运算符。

template <typename T>
typename std::remove_reference<T>::type&& move(T&& arg) {
return static_cast<typename std::remove_reference<T>::type&&>(arg);
}


11. 完美转发有什么用

完美转发用于在模板函数中保持参数的原始类型和属性,避免不必要的拷贝和类型转换。

template <typename T, typename... Args>
void forward_example(T&& t, Args&&... args) {
function(std::forward<T>(t), std::forward<Args>(args)...);
}


12. 模板的特化和偏特化

全特化:为特定类型的模板参数提供专门的实现。

偏特化:为部分类型的模板参数提供专门的实现。

template <typename T>struct MyTemplate {    void func() /* 通用实现 */ }};
// 全特化template <>struct MyTemplate<int> {    void func() /* 特化实现 */ }};// 偏特化template <typename T>struct MyTemplate<T*> {    void func() /* 偏特化实现 */ }};


13. C++和C申请内存方式的区别

C++:使用 new 和 delete。

C:使用 malloc、calloc、realloc 和 free。

更详细的阅读:百度实习面试:new和malloc的区别,什么时候用new  什么时候用mallc?

面试题:new出来的对象可以使用bzero等函数初始化内部变量为0吗?——信锐技术一面

14. C++释放数组和普通对象的区别

普通对象:使用 delete。

数组:使用 delete[]。

int* ptr1 = new int;delete ptr1;
int* ptr2 = new int[10];delete[] ptr2;


15. 动态多态虚表的位置在哪

虚表本身通常存储在程序的只读数据段(或代码段)中,因为虚表的内容在程序运行时是固定的,不会被修改。每个类的虚表在程序的编译阶段生成,并在程序加载时分配内存。

16. 有序数组去重不用额外空间

使用双指针法

17. 二叉树度为0和度为2的数量关系

对于一棵二叉树,设度为0的节点数为 n0,度为2的节点数为 n2,则有:[ n0 = n2 + 1 ]

18. 哈夫曼树构建过程

1. 统计频率:统计每个字符出现的频率。

2. 创建节点:为每个字符创建一个节点,频率作为节点的权重。

3. 创建优先队列:将所有节点加入优先队列。

4. 构建哈夫曼树:

   从优先队列中取出两个最小权重的节点。

   创建一个新的内部节点,权重为这两个节点的权重之和。

   将这两个节点作为新节点的左右子节点。

   将新节点加入优先队列。

   重复上述步骤,直到优先队列中只剩下一个节点,即为哈夫曼树的根节点。

19. 快排最坏情况发生

快排的最坏情况发生在每次分区选择的枢轴都是最小或最大值时,时间复杂度为 O(n^2)。

20. 递归算法对比循环的问题

递归:代码简洁易懂,但可能会导致栈溢出,时间复杂度较高。

循环:代码可能较复杂,但通常更高效,不会导致栈溢出。

21. 优先队列的实现

优先队列通常使用二叉堆(最大堆或最小堆)来实现,支持插入和删除操作。

22. 有一个超大文件,无法一次性加载到内存,如何排序

可以使用外部排序算法,如归并排序:

1. 分块排序:将文件分成多个小块,每个小块可以加载到内存中并进行排序。

2. 合并排序:将排序后的块合并成一个有序的大文件。

23. B+树对比普通树,红黑树的区别,为什么不用B树

B+树:叶子节点包含所有关键字,且叶子节点之间有指针相连,适合磁盘存储和范围查询。

B树:每个节点可以有多个关键字,适合磁盘存储,但不支持高效的范围查询。

红黑树:自平衡二叉搜索树,适合内存中的快速查找和插入。

B+树更适合数据库和文件系统的索引,因为它们支持高效的范围查询和磁盘访问。

24. HTTP 1/2/3版本的区别

HTTP/1.1:持久连接、管道化、缓存控制。

HTTP/2:多路复用、头部压缩、服务器推送。

HTTP/3:基于 QUIC 协议,改进了连接建立和数据传输性能。

5. HTTP Cookie作用

Cookie 用于存储客户端的状态信息,如会话标识、用户偏好等。服务器可以通过设置 Cookie 来跟踪用户的会话。

26. TCP拥塞控制方法

慢启动:初始时快速增加拥塞窗口。

拥塞避免:线性增加拥塞窗口。

快重传:接收方收到乱序数据包时发送重复 ACK。

快恢复:发送方接收到三个重复 ACK 时,将拥塞窗口减半,然后进入拥塞避免阶段。

end



一口Linux 


关注,回复【1024】海量Linux资料赠送


精彩文章合集

文章推荐

【专辑】ARM
【专辑】粉丝问答
【专辑】所有原创
专辑linux入门
专辑计算机网络
专辑Linux驱动
【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式所有知识点-思维导图


一口Linux
《从零开始学ARM》作者,一起学习嵌入式,Linux,网络,驱动,arm知识。
 最新文章