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资料赠送
精彩文章合集
文章推荐