当前位置: 首页 > news >正文

搅拌机东莞网站建设技术支持汽车网页设计作品欣赏

搅拌机东莞网站建设技术支持,汽车网页设计作品欣赏,wordpress index.txt, 天堂资源地址在线官网目录 1.C程序编译链接过程 2.浅拷贝和move有区别吗 3.深拷贝和浅拷贝的区别 4.空类的大小 5.类的继承有几种方式#xff0c;区别是什么#xff1f; 六、extern 关键字的作用 七、static关键字的作用 八、指针和引用的区别 九、C内存分配方式 十、结构体对齐…目录 1.C程序编译链接过程 2.浅拷贝和move有区别吗  3.深拷贝和浅拷贝的区别  4.空类的大小  5.类的继承有几种方式区别是什么  六、extern 关键字的作用 七、static关键字的作用  八、指针和引用的区别  九、C内存分配方式  十、结构体对齐原则 十一、数据结构中有了二叉树为什么要有平衡二叉树 平衡二叉树的调节机制 生活中的类比重新排队 AVL 树 vs 红黑树 现实生活中的类比不同的队伍管理策略 十二、哈希表插入元素一般有那几个重要的步骤 1. 计算哈希值Hash Function 2. 处理冲突Handling Collisions 3. 插入元素Insert Element 4. 检查装载因子Check Load Factor 总结 十三、但是红黑树 和 哈希表有什么区别呢  数据结构类型 2. 数据存储方式 3. 查找时间复杂度 4. 顺序性 5. 内存使用 6. 插入和删除操作 7. 适用场景 总结 十四、不用第三个变量如何交换ab 十五、什么是锁为什么需要锁 十六、什么时互斥锁  互斥锁的作用 十七、互斥锁和自旋锁有什么区别  1. 互斥锁 2. 自旋锁 主要区别 十八、什么是死锁怎么构成死锁的  死锁的构成 例子 1: 共享停车位 总结 十九、什么是单例模式 单例模式的最重要目的 类比解释 二十、Vector的底层原理和扩容机制 二十一、红黑树 和 avl 的区别插入多个数据选择 avl还是红黑树 二十二、C多态的实现原理 1. 静态绑定 vs 动态绑定 2. 虚函数与虚函数表vtable 3. 多态实现的流程 4. 运行时的表现 5. 多态的条件 总结 二十三、多态分几种类型  1. 编译时多态静态多态 1.1 函数重载 1.2 运算符重载 2. 运行时多态动态多态 2.1 虚函数 2.2 抽象类与纯虚函数 3. 总结 二十四、多态有那些特点  1. 接口一致性 2. 动态绑定 3. 继承和虚函数 5. 提高代码的复用性 6. 抽象性 7. 灵活性 8. 支持运行时类型识别 总结 二十五、什么情况下会导致内存泄漏 1. 动态分配的内存未释放 2. 在异常处理时未释放内存 3. 没有为类的析构函数释放内存 4. 循环引用 二十六、什么是智能指针你在一般什么情况下使用智能指针  智能指针的类型和使用场景 1. std::unique_ptr独占所有权 2.std::shared_ptr共享所有权 3. std::weak_ptr弱引用 总结 一般情况下使用智能指针的场景 二十七、智能指针的本质是什么  二十八、new 和 malloc 的区别  二十九、什么是线程同步为什么需要线程同步  为什么需要线程同步 1. 互斥锁Mutex 5. 信号量Semaphore 4. 自旋锁Spinlock 总结 三十、进程的通信有哪些 我最熟悉的一种共享内存Shared Memory 优点 缺点 1. 管道Pipe 2. 消息队列Message Queue 3. 共享内存Shared Memory 4. 信号量Semaphore 6. 套接字Socket 7. 文件File 8. 信号量Semaphore和互斥锁Mutex 总结 三十一、多进程和多线程的优缺点  总结 三十二、进程和线程的区别  1. 定义 总结 三十三、线程的通信方式有哪些分别有什么不同  总结 三十四、 Linux 如何查看进程状态 1. ps 命令 2. top 命令 三十五、 linux 如何查看线程状态 1. ps 命令 三十六、 如何查看网络连接情况 1. netstat 命令已被 ss 替代但仍然常用 2. ss 命令netstat 的现代替代工具 三十七、TCP 和 UDP 的区别  1. 连接性 2. 可靠性 5. 数据传输效率 8. 传输单位 应用建议 三十八、 左值和右值引用 1. 左值 (Lvalue) 与 右值 (Rvalue) 5. 移动语义和右值引用的应用场景重点 总结 三十九、虚函数指针是什么时候初始化的  总结 四十、析构函数不是虚函数会有什么问题  如何解决 总结 四十一、map使用find查找和[]查找的区别为啥要有find  1. 为什么需要 find 总结 四十二、this 指针有什么作用这个指针的值是从那里来的  1.this 指针的作用 3. this 指针的特性 5. 总结 四十三、多线程同步方式。条件变量和信号量的区别 1. 条件变量 (Condition Variable) 关键特性 使用场景 2. 信号量 (Semaphore) 关键特性 使用场景 4. 总结 四十四、vector 扩容机制 。vector中删除元素是否会释放内存  1. 扩容机制原理 四十五、vector在删除时是否会产生迭代器失效 1. 在循环中使用失效迭代器 2. 扩容后迭代器失效 四十六、使用 unique_ptr 和 裸指针 有什么却别效率有什么区别  总结 四十七、大小端有什么区别分别有什么用 1. 大端模式Big-Endian 2. 小端模式Little-Endian 5. 大小端在实践中的作用 总结 四十八、什么是B树它有什么特点  B树的结构和特点 总结 四十九、虚函数实现机制 五十、构造函数能否声明为虚函数 1. 构造函数 2. 内联函数 3. 静态函数 4. 友元函数 总结 五十一、const 和 define 的区别 五十二、 struct 和 class 的区别 五十三、lambda 表达式的用法 1. 什么是 Lambda 表达式 2. Lambda 表达式的优点 3. 应用场景 4. 优化性能 1. 对数组排序的例子 2. 对字符串数组排序的例子 五十四、什么是内联函数 2. 内联函数的优点 3. 内联函数的限制 五十五、虚函数表存在什么区域 1. 虚函数表的存储区域 2. 虚表指针vptr的位置 五十六、动态连接和静态链接的区别 1. 静态链接 (Static Linking) 2. 动态链接 (Dynamic Linking) 总结 五十七、MySQL中什么是索引  建立索引的原则是什么  1. 选择性高的列优先创建索引 2. 经常出现在 WHERE 子句中的列 3. 在 JOIN 和 GROUP BY 中使用的列 4. 避免为频繁更新的列创建索引 5. 避免为小表创建过多索引 五十八、什么是事务  1. 事务的四大特性ACID 2. 事务的控制语句 3. 事务的使用 4. 事务隔离级别 总结 五十九、什么叫慢查询 六十、MySQL 是 主要通过什么数据结构实现为什么用B树 六十一、C 中 volatile 的作用 输出结果的潜在问题 volatile 的作用 总结 1.C程序编译链接过程 1. 预处理(Preprocessing):  预处理器处理源代码文件中的预处理指令如 宏定义、条件编译指令 和 文件包含指令 等生成预处理后的代码。 2.编译(Compilation): 编译器将预处理后的代码 转换为汇编指令并进行语法和语义分析生成相应平台的汇编代码文件。 3.汇编(Assembly): 汇编器将汇编代码转换为机器码生成目标文件(通常是.obj或.o文件)。 4.链接(Linking) 链接器将所有目标文件以及所需的库文件合并解决代码中的外部引用并生 成最终的可执行程序。 2.浅拷贝和move有区别吗  浅拷贝:  复制对象的指针或引用新旧对象共享相同 的底层数据资源。 移动操作(move): 将资源从一个对象转移到另一个对象原对 象资源被置为null或有效但未定义的状态不再拥有资源。只有移动构造函数或移动赋值操作符才参与移动操作。 3.深拷贝和浅拷贝的区别  浅拷贝: 仅复制对象的指针或引用而不复制实际数据 新旧对象会共享同一份资源。 这意味着如果原对象和拷贝对象都包含指向相同数据的指针修改其中一个对象的数据会影响到另一个对象。 深拷贝: 复制对象的同时创建了一份数据的副本。 新旧对象各自拥有独立的资源互不影响。 这意味着深拷贝会创建完全独立的对象修改一个对象的数据不会影响到另一个对象。 默认的拷贝构造函数是深拷贝还是浅拷贝为什么 默认的拷贝构造函数执行的是浅拷贝因为他只复制对象的各个成员的值对于指针成员仅复制值得值而不复制指针所指向的数据。如果成员包含指向动态分配内存的指针那么多个对象可能会指向相同的资源。 怎么实现深拷贝  定义拷贝构造在拷贝构造函数中为对象中的每个需要拷贝的成员分配新的内存并复制原对象相应的成员的内容 4.空类的大小  空类的 大小为  1  字节  5.类的继承有几种方式区别是什么  C中类的继承有三种方式 : 公有继承(public)基类的公有成员和保护成员继承后在派生类中保持原有状态基类的私有成员不能直接访问。保护继承(protected)基类的公有成员和保护成员继承后在派生类中变为保护成员。私有继承(private)基类的公有成员和保护成员继承后在派生类中变为私有成员。 六、extern 关键字的作用 主要作用  允许在多个文件中访问同一个全局变量或函数 表明变量和函数的定义存在于其它文件中  七、static关键字的作用  在变量和函数中的作用 1️⃣在函数中声明变量时 static 关键字指定变量只初始化一次并在之后调用该函数时保留其状态。 2️⃣在声明变量时变量具有静态持续时间并且除非您指定另一个值。 3️⃣ 在全局和/或命名空间范围 (在单个文件范围内声明变量或函数时) static 关键字指定变量或函数为内部链接即外部文件无法引用该变量或函数。 4️⃣static 关键字 没有赋值时默认赋值为 0  5️⃣static修饰局部变量时会改变局部变量的存储位置从而使得局部变量的生命周期变长。 static是 C/C中的关键字之一是常见的函数与变量C中还包括类的修饰符它常被用来控制变量的存储方式和作用范围。   在C类中的作用  在类的成员函数前使用静态成员变量属于类本身而不是类的某个实例。换句话说所有实例共享同一个静态成员变量而不是每个实例各自拥有自己的副本。在类的成员函数前使用静态成员函数static member function 的概念。静态成员函数属于类本身而不是某个具体的对象实例。因此你可以直接通过类名来调用静态成员函数而不需要创建类的对象。静态成员函数可以在不创建对象实例的情况下直接调用并且由于没有 this 指针它们只能访问静态成员而不能访问非静态成员。在局部变量前使用表示该变量在函数调用前结束后不会被销毁而是保持其值不变。在全局变量或者函数前使用限制其作用范围仅在定义的文件内对其它文件不可见当类中声明了静态成员函数后静态成员函数的定义必须在类的外部进行无论该函数是否为inline函数静态成员函数可以被覆盖。即派生类可以定义同名的静态成员函数。 八、指针和引用的区别  区别  指针是一个存储变量地址的变量而引用是一个变量的别名。指针可以被重新赋值以指向另一个不同的地址但引用一旦绑定到一个对象就不能改变指向另一个对象指针可以是nullptr或者指向任意地址的值而引用必须进行初始化且不能为nullptr指针使用* 操作符来访问目标变量值引用直接像普通变量一样使用  九、C内存分配方式  分配方式 静态存储编译时分配如全局变量、静态变量自动存储函数内部声明变量如局部变量随着函数调用创建和退出销毁动态存储使用new 和 delete 进行手动分配和释放堆内存 十、结构体对齐原则 原则  第一个成员在与结构体变量偏移量为0的地址处其他成员变量要对齐到某个数字对齐数的整数倍的地址处对齐数 编译器默认的一个对齐数 与 该成员大小的较小值。【VS中默认的值为8、Linux环境默认不设对齐数对齐数是结构体成员自身的大小】结构体总大小为最大对齐数每个成员变量都有一个对齐数的整数倍如果嵌套了结构体的情况嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体的对齐数的整数倍 意义 性能优化内存对齐可以显著提高内存访问速度因为数据对齐可以使处理器更加高效地访问内存。兼容性内存对齐可以确保程序在不同硬件架构上正确运行避免未对齐的数据访问带来的问题。 结论  结构体内存对齐是一种用于优化数据访问的机制通过保证数据在内存中的存储方式符合处理器的要求减少不必要的内存访问次数从而提高程序的性能和可靠性。这也是编译器在处理结构体时自动完成的一项重要优化。  十一、数据结构中有了二叉树为什么要有平衡二叉树 在数据结构中二叉树是一种常用的数据结构具有树的基本特性和二叉结构的灵活性。然而二叉树的结构并不一定总是平衡的这可能导致某些操作的效率较低。为了提高二叉树在各种操作如查找、插入、删除中的性能引入了平衡二叉树的概念。  二叉树的问题 最坏情况普通的二叉树在最坏情况下可能会退化成一条链表例如每次插入的元素都比当前节点大或小导致查找、插入、删除等操作的时间复杂度从理想情况下的 O(log⁡n退化为 O(n)。效率低下当二叉树不平衡时树的高度接近于节点的数量操作效率会大幅降低。 平衡二叉树的优势  保证操作效率平衡二叉树通过特定的规则保持树的平衡例如 AVL 树和红黑树使得树的高度保持在 O(logn) 的水平确保了在最坏情况下查找、插入和删除操作的时间复杂度为 O(log⁡n)。均匀分布平衡二叉树的节点分布更加均匀查找路径较短可以显著提高访问数据的速度。空间利用平衡树的结构避免了普通二叉树中可能出现的节点过度集中在一侧的情况提高了内存的利用率。 什么是二叉树不平衡能用简单的生活例子说明吗  生活中的例子排队买票 假设你去电影院买票那里有两个窗口相当于二叉树的两个子节点。如果所有人都只排在一个窗口前而另一个窗口完全空着这就像是二叉树不平衡的情况。 不平衡的二叉树所有顾客都排在一个窗口前导致这条队伍很长排到最后一个顾客需要很久查找时间变长。平衡的二叉树顾客均匀分布在两个窗口前两个队伍都很短这样大家买票都能更快查找时间缩短。 在计算机中的二叉树如果所有节点都倾向于一侧就像所有顾客都挤在一个窗口前这就导致了“树不平衡”从而使得查找、插入、删除等操作变慢。为了避免这种情况平衡二叉树会自动调整让节点分布得更均匀就像有人引导顾客均匀地排在两个窗口前一样。 平衡二叉树会如何调节让节点分布的更加均匀  平衡二叉树的调节机制 旋转操作 单旋转当插入或删除一个节点导致树不平衡时平衡二叉树可以通过“单旋转”来恢复平衡。单旋转分为“左旋”和“右旋”用于重新调整树的结构。双旋转当单旋转无法恢复平衡时使用双旋转它结合了左旋和右旋进一步调整树的结构。 自平衡算法 AVL树每次插入或删除节点后AVL树会检查每个节点的“平衡因子”左右子树的高度差。如果某个节点的平衡因子超过允许范围通常是 -1、0、1AVL树会通过旋转操作进行调整。红黑树红黑树通过对节点进行“染色”和“旋转”确保在插入或删除节点时树始终保持接近平衡的状态。 生活中的类比重新排队 回到电影院买票的例子如果发现某个窗口前的队伍变得特别长工作人员相当于平衡二叉树的自调节机制可能会引导一些顾客转到另一个较短的队伍中进行队伍重新调整。这种重新排队就类似于平衡二叉树中的旋转操作通过将队伍重新分配使两个队伍保持在合理的长度确保每个人都能更快买到票。 具体例子右旋  假设有三个顾客 A、B、C 按顺序到来他们都排在同一个窗口前这样形成了一个不平衡的队伍类似于一个倾向右侧的链表。  A\B\C此时工作人员平衡二叉树的机制发现了不平衡于是他会把顾客 B 提到前面把 A 放在 B 的左边而 C 保持在 B 的右边。  B/ \A C这个重新排队的过程就是右旋。这样队伍重新平衡所有顾客都可以更快地买票。 总结来说平衡二叉树通过旋转操作和自平衡算法来调整节点的分布就像重新排队以保证每个顾客能更快买到票一样。 有了平衡二叉树为什么要有红黑树  虽然平衡二叉树如 AVL 树已经能很好地保持树的平衡但红黑树作为另一种自平衡二叉查找树具有独特的优势使其在某些情况下更为实用。以下是为什么在有了平衡二叉树之后还需要红黑树的原因。  AVL 树 vs 红黑树 旋转操作的频率 AVL 树每次插入或删除节点后AVL 树都会进行平衡因子的检查如果树不平衡就需要进行旋转操作。由于 AVL 树严格要求每个节点的左右子树高度差不超过 1可能会频繁地进行旋转操作尤其是在频繁插入和删除的情况下。红黑树红黑树通过对节点进行“染色”红色或黑色和较少的旋转操作来维持平衡。红黑树允许树在某种程度上的“不完全平衡”因此在插入和删除操作时通常需要的旋转次数更少。它在保证 O(log⁡n) 操作复杂度的同时减少了旋转操作的开销。 操作性能 AVL 树在查找操作上AVL 树由于更加严格的平衡条件往往具有更快的查找速度。因此对于读操作较多的场景AVL 树可能表现更好。红黑树红黑树由于旋转操作较少因此在插入和删除操作频繁的场景中红黑树的整体性能通常优于 AVL 树。许多标准库如 C 的 std::map 和 std::set都使用红黑树因为它们需要频繁的插入、删除和查找操作。 代码复杂度 AVL 树实现相对复杂尤其是在处理旋转操作和更新平衡因子时。红黑树相对来说红黑树的实现虽然涉及染色和旋转但其逻辑相对统一维护平衡的规则较少使得代码管理相对简单。 现实生活中的类比不同的队伍管理策略 假设你负责管理一个有多个窗口的售票厅每天有大量顾客来买票。 AVL 树严格管理你规定每个窗口前的顾客数必须严格保持平衡。如果有某个窗口的队伍变长或变短你会马上进行调整。这种方式虽然能保证每个顾客排队的时间都差不多但你可能会花很多精力去频繁地调整队伍。红黑树宽松管理你允许某些窗口前的队伍稍微长一点只要不超过一定的限制。当某个窗口的队伍过长或过短时你才进行调整。这样你减少了调整队伍的频率虽然偶尔某些顾客的等待时间会稍长但整体管理更为轻松和高效。 十二、哈希表插入元素一般有那几个重要的步骤 哈希表Hash Table是一种用于实现快速查找的数据结构它通过将键Key映射到数组中的特定位置来实现快速的插入、删除和查找操作。插入元素到哈希表中时一般包括以下几个重要的步骤  1. 计算哈希值Hash Function 步骤首先使用哈希函数Hash Function对给定的键进行计算生成对应的哈希值。哈希函数将键转换为一个整数通常是数组的索引。目的将不同的键映射到哈希表中的不同位置从而实现快速访问。 例子假设你有一组电话号码你可以使用电话号码中的某些数字如最后几位来计算哈希值将每个电话号码映射到哈希表的某个位置。 2. 处理冲突Handling Collisions 步骤由于不同的键可能会生成相同的哈希值即哈希冲突因此需要处理冲突的策略。常见的冲突处理方法有两种 开放地址法Open Addressing如果计算出的哈希值位置已经被占用则查找下一个可用位置进行存储。这种方式包括线性探测Linear Probing、二次探测Quadratic Probing和双重散列Double Hashing等方法。链地址法Chaining每个哈希值位置存储一个链表或其他数据结构当发生冲突时将新的元素附加到链表的末尾。 例子在电话本的例子中如果两个电话号码的最后几位相同就可能映射到同一个位置。你可以选择下一个空位置存储也可以将这些电话号码链在一起。 3. 插入元素Insert Element 步骤在找到适当的位置后将元素键和值对插入到哈希表中。目的将数据实际存储在哈希表中使其可以通过哈希值快速访问。 例子找到空位置后你将电话号码和联系人信息存储在该位置。 4. 检查装载因子Check Load Factor 步骤插入元素后检查哈希表的装载因子Load Factor即哈希表中已经使用的存储空间比例。如果装载因子超过了某个阈值通常为0.75则可能需要重新调整哈希表如扩展哈希表或重新哈希。目的确保哈希表的性能不会因为装载因子过高而下降。 例子如果你的电话本几乎满了就需要准备一个更大的电话本将所有的电话号码重新安排。 总结 在哈希表中插入元素的过程中关键步骤包括计算哈希值、处理冲突、插入元素、检查装载因子以及必要时进行动态扩展。通过这些步骤哈希表能够高效地组织和管理数据实现快速查找和操作。 哈希表什么时候扩容  哈希表通常在其负载因子达到某个阈值时进行扩容。负载因子时当前存储的元素数量与哈希表容量的比值。当插入元素操作使得当前负载因子超过预期设的阈值时哈希表会进行扩容以保持操作的效率。 十三、但是红黑树 和 哈希表有什么区别呢  数据结构类型 红黑树红黑树是一种自平衡的二叉查找树。它通过颜色红色或黑色和旋转操作保持树的平衡使得插入、删除、查找操作的时间复杂度保持在 O(log⁡n)。哈希表哈希表使用哈希函数将键映射到数组中的特定位置。其查找、插入、删除操作的平均时间复杂度为 O(1)但在最坏情况下可能退化为 O(n)例如所有键都映射到同一位置产生大量冲突时。 2. 数据存储方式 红黑树红黑树以树形结构存储数据。节点按照键的顺序排列使得中序遍历树时可以得到有序的数据。哈希表哈希表将数据存储在数组中位置由哈希函数决定。数据在哈希表中是无序的无法通过遍历哈希表直接得到有序数据。 3. 查找时间复杂度 红黑树由于其自平衡特性红黑树的查找操作具有 O(log⁡n) 的时间复杂度。哈希表哈希表的查找操作平均时间复杂度为 O(1)在冲突处理得当的情况下性能非常高。 4. 顺序性 红黑树红黑树是有序的支持按键值顺序遍历。比如在 std::map 中元素按照键的顺序存储并且可以高效地查找某个范围内的元素。哈希表哈希表是无序的在 std::unordered_map 中键值对的存储顺序与插入顺序或键的值无关因此不支持按顺序遍历。 5. 内存使用 红黑树由于树结构的需要红黑树的内存使用较为紧凑没有额外的空间浪费节点只存储与其直接相关的数据如键、值、颜色指示器等。哈希表哈希表通常需要更多的内存因为它们必须预留空间来减少冲突的概率。如果装载因子过高哈希表可能会进行扩展导致内存使用量增加。 6. 插入和删除操作 红黑树插入和删除操作需要维护树的平衡性可能涉及旋转操作时间复杂度为 O(log⁡n)。哈希表插入和删除操作通常非常快时间复杂度为 O(1)。但在最坏情况下如大量冲突或需要重新哈希时插入和删除的时间复杂度可能上升。 7. 适用场景 红黑树适用于需要保持元素有序的场景或需要频繁进行区间查询的场景。例如需要快速查找某个范围内的键的情况。哈希表适用于需要快速查找、插入和删除元素但不关心元素顺序的场景。例如存储和查找大量无序数据的情况。 总结 红黑树如 std::map提供了有序的数据存储适合需要按顺序访问或区间查询的场景但其操作的时间复杂度为 O(log⁡n)。哈希表如 std::unordered_map则提供了快速的查找和插入操作适合需要高效查找的场景但它存储的数据是无序的且在最坏情况下性能可能下降。 十四、不用第三个变量如何交换ab 要在不使用第三个变量的情况下交换两个变量 a 和 b 的值可以使用多种方法下面介绍三种常见的方法使用加减法、使用异或运算符以及使用乘除法。  方法 1: 使用加减法  a a b; b a - b; a a - b;方法 2: 使用异或运算  a a ^ b; b a ^ b; a a ^ b;十五、什么是锁为什么需要锁 在多线程编程中多个线程可以同时访问和操作共享资源比如一个变量、文件、数据库等。当多个线程同时读写这些共享资源时可能会产生数据不一致或冲突的情况这种情况称为竞争条件Race Condition。 锁是一种机制用来确保在同一时刻只有一个线程可以访问共享资源。通过使用锁可以防止多个线程同时修改共享资源从而保证数据的一致性和正确性。 十六、什么时互斥锁  互斥锁Mutex, Mutual Exclusion Lock**是最常用的一种锁。它保证了在任何时刻只有一个线程能够获得锁并访问共享资源其他线程必须等待直到锁被释放。  互斥锁的作用 互斥锁的主要作用是避免多个线程在同一时间访问和修改共享资源从而防止竞争条件确保程序运行的正确性。 为了更好地理解我们可以用生活中的例子来说明锁和互斥锁的概念 例子 1: 使用洗手间 想象一个有多个人的家庭只有一个洗手间。这个洗手间就是共享资源家庭成员就像不同的线程。 锁的作用当一个家庭成员使用洗手间时门会被锁上这样其他人就无法进入必须等待洗手间被释放即前一个人用完并打开门。这就是锁的作用确保在任何时刻只有一个人线程可以使用洗手间共享资源。 互斥锁的作用互斥锁确保家庭成员不会同时使用洗手间避免出现尴尬的情况数据不一致。每次只能有一个人进出洗手间确保所有人都可以正确、安全地使用它。 锁是用来控制对共享资源的访问的机制避免竞争条件。互斥锁是一种特殊的锁确保在任何时刻只有一个线程可以访问共享资源。 十七、互斥锁和自旋锁有什么区别  1. 互斥锁 互斥锁是一种让线程等待直到锁被释放的机制。当一个线程获取到互斥锁后其他线程如果想要获取同一个锁就必须等待期间这些线程会进入睡眠状态直到锁被释放。 生活例子餐厅的独占包厢 想象你和朋友在餐厅里用餐这里有一个独占包厢相当于共享资源。你进入包厢后门就被锁上其他想用这个包厢的人只能在外面等待。互斥锁的机制其他等待的人不会在包厢门外一直站着等忙等待而是坐在大厅的椅子上睡眠状态等你用餐完毕并离开包厢释放锁后下一位客人才会被叫进去。 2. 自旋锁 自旋锁是一种忙等待的锁当一个线程尝试获取锁时如果锁已经被其他线程持有线程不会进入睡眠状态而是持续检查锁的状态直到获取到锁为止。 生活例子洗手间的排队情况 想象你在一个有单独洗手间的办公室洗手间外面没有椅子没有睡眠状态。当你需要上洗手间时发现门被锁了里面有人。你只能站在门外等待不能去做其他事情也不会离开忙等待。自旋锁的机制你在门外一直等着直到里面的人出来释放锁。虽然站在外面等可能有点浪费时间但如果洗手间使用时间很短可能比坐下等待互斥锁更有效率。 主要区别 线程等待方式 互斥锁线程等待时会进入睡眠状态不占用CPU资源。自旋锁线程等待时不会进入睡眠而是持续占用CPU资源进行忙等待。 适用场景 互斥锁适用于锁的持有时间较长的场景因为线程进入睡眠状态后可以释放CPU资源避免浪费。自旋锁适用于锁的持有时间非常短的场景因为忙等待可以避免线程睡眠和唤醒带来的开销提高效率。 十八、什么是死锁怎么构成死锁的  什么是死锁 死锁发生在多个线程或进程在等待对方持有的资源时导致所有线程或进程都无法继续执行。简而言之死锁是指系统中的线程因相互等待而陷入一种僵局导致程序无法继续执行下去。 死锁的构成 死锁通常有以下四个必要条件称为“死锁的四个必要条件” 互斥条件Mutual Exclusion 至少有一个资源被当前线程或进程独占其他线程或进程不能同时访问该资源。 占有且等待Hold and Wait 已经获得资源的线程或进程在等待获得其他资源时不释放自己当前持有的资源。 不剥夺条件No Preemption 资源不能被强行从线程或进程中剥夺只能由持有资源的线程或进程自行释放。 循环等待Circular Wait 存在一个等待资源的循环其中每个线程或进程都在等待下一个线程或进程持有的资源。 避免死锁的策略 资源分配顺序确保所有进程以相同的顺序请求资源资源一次性分配要求进程启动时一次性申请所要的所有资源资源使用限制限制同一时刻请求资源的进程数量或者通过预先分析确定资源的大量需求。使用锁超时进程尝试锁定资源时加入超时机制超时未锁定则释放已占有的资源。 例子 1: 共享停车位 场景 假设有两个停车位资源但每个停车位只能被一个车线程占用。车A和车B同时到达停车场车A已经停在停车位1车B停在停车位2。车A想进入停车位2但停车位2已经被车B占用。车B也想进入停车位1但停车位1被车A占用。 死锁发生 车A和车B都在等待对方的停车位形成了一个等待的循环两个车都无法继续停车。 总结 死锁是指多个线程或进程在等待对方持有的资源导致所有线程或进程都无法继续执行。死锁的四个必要条件包括互斥、占有且等待、不剥夺和循环等待。定位死锁可以通过死锁检测、分析资源分配图、日志分析和使用调试工具来进行。通过生活中的例子如共享停车位和购买商品的排队系统可以帮助理解死锁的概念和构成。 十九、什么是单例模式 单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时为了防止频繁地创建对象使得内存飙升单例模式可以让程序仅在内存中创建一个对象让所有需要调用的地方都共享这一单例对象。 私有化构造函数和析构函数拷贝构造和赋值运算符重载防止外部创建和销毁在类内部提供一个私有的静态指针变量指向唯一实例提供一个公共静态方法用于获取这个唯一的实 单例模式是一个设计模式它确保一个类只有一个实例并提供一个全局访问点来获取这个实例。  单例模式的最重要目的 确保一个类只有一个实例在整个程序的生命周期内某个类只有一个实例并且提供全局访问点来获取这个实例。控制全局资源的访问它确保程序中的某些全局资源如配置管理器、日志记录器、数据库连接等只存在一个实例避免资源冲突或重复创建导致的系统问题。 你可以将 单例模式 类比为整个城市的 交通信号灯控制系统。 类比解释 唯一实例在整个城市中只需要一个交通信号灯控制系统。它负责管理所有交通信号灯的开关确保不同路口的交通流量平稳。而我们不希望在同一个城市中有多个独立的控制系统否则会造成混乱。 全局访问每个路口的交通信号灯都可以访问这个唯一的控制系统并从它那里获取指令。也就是说任何一个地方的信号灯系统都能够通过相同的方式与这个唯一的控制系统交互。 城市交通信号灯控制系统只需要一个实例这样可以确保所有交通灯的一致性和统一管理防止多个控制系统相互冲突。同样单例模式确保在程序中一个类的实例只能有一个并且全局共享。  二十、Vector的底层原理和扩容机制 底层原理  vector 是基于动态数组实现的支持随机访问。在连续的空间中存储元素允许快速访问。 扩容机制  当向 vector 添加元素超过其当前容量时它会创建一个更大的动态数组并将所有现有元素复制到新数组中释放就数组的内存。新容器通常是当前容量的两倍不过这可能因实现而异。  // 扩容void reserve(size_t n){if (n capacity())//判断是否需要扩容{//扩容size_t sz size(); //提前算好增容前的数据个数T* tmp new T[n]; //开辟n个空间if (_start){//数据拷贝也不能去使用memcpy函数for (size_t i 0; i sz; i){tmp[i] _start[i];}delete[]_start; //释放旧空间}_start tmp; //指向新空间_finish _start sz;_end_of_storage _start n;}} 二十一、红黑树 和 avl 的区别插入多个数据选择 avl还是红黑树 主要区别在于 平衡条件 和 平衡后树的高度  1. 平衡条件 AVL 树要求任何节点的两个子树的高度差的绝对值对多为 1 这使得 AVL 树比红黑树更加平衡红黑树通过确保从根到叶子得所有路径上黑色节点得数量相同并且不存在连续的红色节点来进行平衡 2. 高度和操作复杂度 由于严格的平衡条件AVL 树的高度比红黑树更低因此在查找操作中 AVL 可能表现更好红黑树的插入删除操作引起的重新平衡操作比AVL树少因此在频繁进行插入和删除的场景下红黑树可能有更好的性能 选择 如果插入多个数据并且插入操作比查找操作更加频繁选择红黑树更加合适 因为它的插入和删除操作引起的平衡调整较少如果系统更加注重查找效率并且对插入删除操作的效率要求不是很高可与选择AVL树因为它更加平衡查找效率高 二十二、C多态的实现原理 多态的实现主要依赖于虚函数和虚函数表vtable。下面详细解释其原理。  1. 静态绑定 vs 动态绑定 静态绑定函数调用在编译时确定通常用于非虚函数。这种方式比较高效但无法实现运行时多态。动态绑定函数调用在运行时确定通常用于虚函数。基类的指针或引用指向派生类对象时通过动态绑定可以调用派生类的重写函数。 2. 虚函数与虚函数表vtable 虚函数在基类中使用 virtual 关键字声明的函数允许派生类进行重写。虚函数实现动态绑定的基础。 class Base { public:virtual void show() { // 虚函数std::cout Base class show std::endl;} };class Derived : public Base { public:void show() override { // 重写基类的虚函数std::cout Derived class show std::endl;} };虚函数表vtable编译器为包含虚函数的类创建一个虚函数表。虚函数表是一个函数指针的数组存储类的每个虚函数的地址。当类对象被创建时它的虚函数指针vptr指向该类的虚函数表。 对于基类对象vptr 会指向基类的虚函数表对于派生类对象vptr 会指向派生类的虚函数表。当调用虚函数时通过 vptr 在运行时确定调用的函数是基类还是派生类的版本。 3. 多态实现的流程 当基类的指针或引用调用虚函数时 对象的 vptr 会指向相应类的虚函数表。编译器通过 vptr 在虚函数表中查找相应函数的地址。在运行时调用派生类中重写的虚函数而不是基类的版本。 #include iostream using namespace std;class Base { public:virtual void show() { // 虚函数cout Base class show endl;} };class Derived : public Base { public:void show() override { // 重写虚函数cout Derived class show endl;} };int main() {Base* ptr;Base baseObj;Derived derivedObj;ptr baseObj;ptr-show(); // 调用 Base::show输出 Base class showptr derivedObj;ptr-show(); // 调用 Derived::show输出 Derived class showreturn 0; }4. 运行时的表现 当 ptr 指向 baseObj 时调用的是 Base::show因为 ptr 的 vptr 指向 Base 的虚函数表。当 ptr 指向 derivedObj 时调用的是 Derived::show因为 ptr 的 vptr 指向 Derived 的虚函数表。 5. 多态的条件 必须通过基类的指针或引用来调用虚函数。基类中的函数必须被声明为虚函数virtual。派生类中可以选择性地重写override该虚函数。 总结 C 多态的实现依赖于虚函数和虚函数表。编译器通过为每个包含虚函数的类创建虚函数表并在运行时通过虚指针查找相应的函数实现了动态绑定和多态。这使得同一个基类指针或引用可以在运行时根据实际对象的类型调用不同的派生类方法。 二十三、多态分几种类型  C 中的多态主要分为两大类编译时多态 和 运行时多态。每种多态的实现方式和应用场景不同具体分类如下  1. 编译时多态静态多态 编译时多态也称为静态多态是在编译阶段确定函数调用的类型。其主要通过函数重载、运算符重载和模板来实现。 1.1 函数重载 函数重载 是指在同一作用域中多个函数可以有相同的名字但它们的参数列表不同。编译器根据函数调用时提供的参数数量和类型来决定调用哪个函数。 #include iostream using namespace std;void print(int i) {cout Integer: i endl; }void print(double d) {cout Double: d endl; }int main() {print(10); // 调用 print(int)print(5.5); // 调用 print(double)return 0; }1.2 运算符重载 运算符重载 是允许用户定义的类型重新定义内置运算符的行为。通过重载用户可以让自定义对象像内置类型一样使用运算符。 #include iostream using namespace std;class Complex { public:int real, imag;Complex(int r 0, int i 0) : real(r), imag(i) {}// 重载 运算符Complex operator (const Complex other) {return Complex(real other.real, imag other.imag);} };int main() {Complex c1(2, 3), c2(1, 4);Complex c3 c1 c2; // 使用重载的 运算符cout Real: c3.real , Imaginary: c3.imag endl;return 0; }2. 运行时多态动态多态 运行时多态也称为动态多态是在程序运行过程中根据实际的对象类型决定调用哪个函数。它主要通过虚函数和继承来实现。 2.1 虚函数 当一个基类指针或引用指向派生类对象时使用虚函数可以在运行时决定调用派生类的函数。这是通过虚函数表vtable来实现的。 #include iostream using namespace std;class Base { public:virtual void display() {cout Base display endl;} };class Derived : public Base { public:void display() override {cout Derived display endl;} };int main() {Base* ptr;Derived obj;ptr obj;ptr-display(); // 调用 Derived::displayreturn 0; }2.2 抽象类与纯虚函数 纯虚函数是没有实现的虚函数用于在基类中定义接口。包含纯虚函数的类称为抽象类无法实例化必须通过派生类重写其纯虚函数并实现。 #include iostream using namespace std;class Shape { public:// 纯虚函数Shape 是抽象类virtual void draw() 0; };class Circle : public Shape { public:void draw() override {cout Drawing Circle endl;} };int main() {Shape* shape new Circle();shape-draw(); // 调用 Circle::drawdelete shape;return 0; }3. 总结 编译时多态静态多态 函数重载、运算符重载、模板。在编译阶段确定函数调用效率较高。 运行时多态动态多态 虚函数、继承、纯虚函数。在运行时根据对象的实际类型决定调用哪个函数更灵活但需要一些额外的开销。 这两种多态各有优缺点编译时多态效率较高而运行时多态灵活性强适用于复杂的对象模型和扩展性需求。 二十四、多态有那些特点  1. 接口一致性 多态允许使用相同的接口处理不同类型的对象。无论是基类对象还是派生类对象只要它们继承自同一个基类就可以通过基类的接口进行操作。这使得代码在处理不同类型的对象时保持简洁一致。 2. 动态绑定 多态的实现依赖于动态绑定又称为运行时绑定。当基类的指针或引用指向派生类对象时虚函数的调用在运行时根据对象的实际类型确定。这与编译时的静态绑定不同动态绑定提供了更大的灵活性。 动态绑定使得可以在运行时选择适合的函数调用避免在编译期固定函数行为。这也是虚函数在 C 多态中发挥关键作用的原因。 3. 继承和虚函数 多态的实现依赖于继承机制以及虚函数。基类通过虚函数定义一个接口派生类可以根据需要重写这些虚函数提供自己的实现。这种机制允许基类指针或引用在运行时调用派生类的重写函数。 基类的虚函数提供接口派生类的重写函数提供具体实现。 5. 提高代码的复用性 多态通过继承和接口的复用使得代码可以更容易地被重用。相同的基类接口可以应用于多个派生类减少了重复代码。基于多态的设计模式比如工厂模式和策略模式也大大提高了代码的复用性。 6. 抽象性 通过多态性基类通常只定义接口或抽象行为而具体实现由派生类来完成。这种抽象性使得系统的设计更加模块化有助于将功能分离为可重用的组件。 抽象类通过纯虚函数定义接口派生类提供具体实现。 7. 灵活性 多态提高了代码的灵活性特别是在处理不同类型的对象时。程序可以在运行时动态地决定调用哪个类的实现而不需要硬编码在编译时确定。这种特性在需要处理大量不同类型的对象时非常有用。 例如游戏中的角色可以有多种类型玩家、敌人、NPC 等通过多态机制可以为每种类型的角色提供不同的行为但使用相同的接口处理它们。 8. 支持运行时类型识别 虽然多态的重点在于统一的接口但有时我们需要在运行时判断实际的对象类型。在 C 中可以通过 dynamic_cast 进行运行时类型识别RTTI以确认某个基类指针指向的具体对象类型。 Animal* animal new Dog(); if (Dog* dog dynamic_castDog*(animal)) {dog-sound(); // 确认是 Dog 类型 }总结 C 中多态的主要特点包括 接口一致性通过基类指针或引用调用派生类的函数保持代码一致性。动态绑定在运行时根据对象的实际类型进行函数调用。继承和虚函数多态依赖继承和虚函数机制实现。代码的可扩展性无需修改已有代码即可添加新功能。提高代码的复用性减少代码重复通过接口复用。抽象性通过抽象类和接口实现功能的分离和模块化。灵活性支持在运行时处理不同类型的对象提高系统的灵活性。运行时类型识别支持通过 dynamic_cast 识别对象的实际类型。 二十五、什么情况下会导致内存泄漏 内存泄漏是指在程序中动态分配的内存没有被正确释放导致这部分内存无法被再次使用。C 中的内存泄漏问题尤为常见因为它允许手动内存管理。以下是一些常见导致内存泄漏的情况  1. 动态分配的内存未释放 最常见的内存泄漏场景是使用 new 或 malloc() 动态分配的内存没有被及时释放。C 需要手动释放动态分配的内存如果忘记使用 delete 或 free() 释放这块内存程序退出之前这部分内存将无法被使用。 void memoryLeak() {int* arr new int[100]; // 动态分配内存// 忘记释放内存// delete[] arr; // 这行代码没有执行导致内存泄漏 }2. 在异常处理时未释放内存 当程序抛出异常时如果没有适当地处理内存释放可能会导致内存泄漏。特别是在使用 new 动态分配内存的过程中如果在释放内存之前发生了异常分配的内存将无法被释放。 void exceptionLeak() {int* data new int[10]; // 动态分配内存throw std::runtime_error(Some error); // 抛出异常但没有 delete[] data; }3. 没有为类的析构函数释放内存 在类中使用 new 动态分配内存时如果没有在析构函数中正确释放这部分内存会导致每次创建对象时动态分配的内存得不到释放进而造成内存泄漏。 class MyClass { private:int* data; public:MyClass() {data new int[100]; // 动态分配内存}~MyClass() {// 如果析构函数没有释放内存// delete[] data; // 忘记释放内存导致泄漏} };4. 循环引用 在使用智能指针特别是 std::shared_ptr时循环引用是导致内存泄漏的一个常见原因。std::shared_ptr 会使用引用计数来管理内存当引用计数为 0 时内存会被释放。然而如果两个对象通过 std::shared_ptr 互相引用它们的引用计数永远不会变为 0导致内存无法释放。 #include memoryclass B; class A { public:std::shared_ptrB b_ptr; };class B { public:std::shared_ptrA a_ptr; };void circularReference() {std::shared_ptrA a std::make_sharedA();std::shared_ptrB b std::make_sharedB();a-b_ptr b; // A 引用 Bb-a_ptr a; // B 引用 A形成循环引用// 循环引用导致两者的引用计数无法变为 0内存泄漏 }解决办法是使用 std::weak_ptr 解决循环引用问题。std::weak_ptr 不会增加引用计数从而避免循环引用。  class A { public:std::weak_ptrB b_ptr; // 使用 weak_ptr };二十六、什么是智能指针你在一般什么情况下使用智能指针  智能指针Smart Pointer是 C 标准库中提供的一种自动化管理内存的工具。与传统的指针不同智能指针会自动管理所指向的对象并在不再使用时自动释放对象的内存避免了手动调用 delete 来释放内存减少了内存泄漏的风险。  C 标准库中最常用的智能指针有三种 std::unique_ptr独占所有权的智能指针不支持赋值和赋值操作。std::shared_ptr共享所有权的智能指针引用技术机制多个智能指针可共享同一个对象。std::weak_ptr避免循环引用的辅助智能指针不对对象的所有权技术用于解决shared_ptr的循环引用问题。 智能指针的类型和使用场景 1. std::unique_ptr独占所有权 特点std::unique_ptr 拥有独占所有权意味着在同一时间只能有一个 unique_ptr 指向一个对象。不能复制 unique_ptr但可以通过 std::move 转移所有权。应用场景 当你希望明确某个对象的生命周期并且不希望其被多个对象共享时使用 std::unique_ptr。适用于对象的所有权明确归属单个作用域或单一逻辑控制者。 #include memory #include iostreamvoid uniquePtrExample() {std::unique_ptrint ptr1(new int(10)); // 创建 unique_ptrstd::cout Value: *ptr1 std::endl;// std::unique_ptrint ptr2 ptr1; // 错误无法复制 unique_ptrstd::unique_ptrint ptr2 std::move(ptr1); // 转移所有权if (!ptr1) {std::cout ptr1 is null after move std::endl;}std::cout Value: *ptr2 std::endl; }何时使用 对象的生命周期在单个作用域内且不需要多个指针共享该对象。你希望在对象不再使用时自动销毁它避免内存泄漏。 2.std::shared_ptr共享所有权 特点std::shared_ptr 允许多个指针共享同一个对象并通过引用计数来管理对象的生命周期。只有当所有 shared_ptr 都释放对该对象的引用时才会释放内存。应用场景 当你希望多个对象共享同一个资源时使用 std::shared_ptr。适用于需要共享资源或对象生命周期无法明确归属单一对象的场景。 #include memory #include iostreamvoid sharedPtrExample() {std::shared_ptrint ptr1 std::make_sharedint(20); // 创建 shared_ptrstd::shared_ptrint ptr2 ptr1; // 共享所有权std::cout Reference count: ptr1.use_count() std::endl;std::cout Value: *ptr1 std::endl;ptr2.reset(); // ptr2 不再共享该对象std::cout Reference count after reset: ptr1.use_count() std::endl; }何时使用 当需要多个对象或作用域共享同一个对象且该对象在某一特定时刻自动销毁时。对象的生命周期无法由一个所有者单独控制而是依赖于多个共享者的引用计数来决定释放时间。 shared_ptr 通过维护一个 引用计数reference count来管理对象的生命周期。每次创建一个新的 shared_ptr 或拷贝一个已有的 shared_ptr 时引用计数会增加每次销毁或重置一个 shared_ptr 时引用计数会减少。当引用计数归零时shared_ptr 自动释放所管理的对象。 1. 控制块存储引用计数器和指向动态分配的对象的指针 2.智能指针对象包含对控制块的引用 3. std::weak_ptr弱引用 特点std::weak_ptr 不会增加对象的引用计数通常用于打破 std::shared_ptr 之间的循环引用。weak_ptr 不能直接访问对象而是通过 lock() 函数获取一个 shared_ptr 以访问对象。应用场景 用于解决 std::shared_ptr 之间的循环引用问题确保对象可以被正确释放。std::weak_ptr 的引用不会影响对象的生命周期。 #include memory #include iostreamclass Node { public:std::shared_ptrNode next;std::weak_ptrNode prev; // 使用 weak_ptr 避免循环引用~Node() {std::cout Node destroyed std::endl;} };void weakPtrExample() {auto node1 std::make_sharedNode();auto node2 std::make_sharedNode();node1-next node2;node2-prev node1; // 避免循环引用std::cout Both nodes created std::endl; }何时使用 当你需要避免 std::shared_ptr 之间的循环引用时使用 std::weak_ptr。当你需要引用一个对象但不希望影响其生命周期时。 总结 一般情况下使用智能指针的场景 内存安全性智能指针的核心优势是自动释放动态分配的内存避免内存泄漏。尤其是在复杂的对象生命周期管理中智能指针可以避免由于手动管理内存而引入的错误。独占所有权如果某个对象的所有权明确属于单个对象或单个操作使用 std::unique_ptr 来管理该对象的生命周期。共享所有权当对象需要在多个地方使用且多个对象或作用域需要共享该对象时使用 std::shared_ptr。避免循环引用在使用 std::shared_ptr 时如果可能引发循环引用比如在双向链表、树等数据结构中使用 std::weak_ptr 来打破循环引用避免内存泄漏。异常安全智能指针能够在异常发生时自动释放内存避免资源泄露确保程序健壮性。 通过使用智能指针可以大大简化 C 中的内存管理工作提高代码的安全性和可维护性。 二十七、智能指针的本质是什么  智能指针的本质是通过RAII 机制结合指针操作符重载自动管理动态分配的资源避免内存泄漏和手动管理的复杂性。其核心功能包括 自动化内存管理通过构造函数和析构函数自动获取和释放内存。RAII 模式确保资源在对象超出作用域时被正确释放。指针运算符重载使智能指针的使用方式与普通指针类似但更安全。引用计数shared_ptr通过引用计数共享资源确保资源的唯一释放。异常安全智能指针确保即使在异常情况下也能正确释放资源。循环引用的管理weak_ptr防止 shared_ptr 引发的循环引用问题。 通过智能指针程序员可以大大减少内存泄漏和悬空指针的风险提高代码的安全性和可维护性。 二十八、new 和 malloc 的区别  1. new 和 delete 是C中用于动态内存分配和释放的操作符。new在分配内存的同时调用构造函数初始化对象delete 释放内存前调用对象的析构函数 2. malloc 和 free 是C语言中用于动态内存分配和释放的函数。malloc 只分配内存不进行初始化free 只是放内存不调用析构函数。  二十九、什么是线程同步为什么需要线程同步  线程同步是指在多线程编程中通过某种机制来控制线程之间的执行顺序确保多个线程可以正确、有序地访问共享资源从而避免竞态条件Race Condition等问题。线程同步的主要目的是防止数据竞争、死锁等多线程并发问题使得程序在多个线程同时执行时仍然能够保持正确性和一致性 为什么需要线程同步 线程同步的目的是协调多个线程对共享资源的访问防止出现以下问题 竞态条件Race Condition多个线程同时对共享数据进行修改导致数据不一致的情况。例如两个线程同时读取一个变量的值然后进行修改最后将修改结果写回这样可能会丢失一个线程的更新。数据竞争Data Race如果两个线程同时读写同一个变量且至少有一个线程执行写操作可能导致意外的结果。死锁Deadlock多个线程相互等待对方释放资源导致线程永远无法继续执行。 1. 互斥锁Mutex 互斥锁是一种用于防止多个线程同时访问共享资源的同步机制。只有一个线程可以在任意时刻持有互斥锁这确保了共享资源在同一时刻只能被一个线程访问。 5. 信号量Semaphore 信号量用于控制对共享资源的访问。它维护一个计数器表示资源的可用数量。当计数器大于 0 时线程可以获取信号量并访问资源当计数器为 0 时线程将阻塞直到资源可用。可以通过 std::counting_semaphoreC20 引入来实现。 4. 自旋锁Spinlock 自旋锁是一种轻量级的锁机制它让线程在获取锁失败时不停地检查锁是否可用而不是进入睡眠状态。这种方式适用于锁的等待时间较短的情况减少了线程上下文切换的开销。 总结 线程同步是为了避免多线程程序中对共享资源的并发访问导致数据不一致或其他错误。常用的同步机制包括 互斥锁Mutex确保同一时间只有一个线程访问共享资源。条件变量Condition Variable让线程等待特定条件的满足。读写锁Read-Write Lock支持多个线程同时读取数据但只允许一个线程写入数据。自旋锁Spinlock用于等待时间短的场景通过忙等待方式获取锁。信号量Semaphore控制线程对多个资源的访问。原子操作Atomic Operation轻量级的同步方式适用于简单的共享数据访问。 三十、进程的通信有哪些 进程间通信Inter-Process Communication, IPC有很多种方式常见的包括 管道Pipe消息队列Message Queue共享内存Shared Memory信号量Semaphore信号Signal套接字Socket文件File 我最熟悉的一种共享内存Shared Memory 共享内存是一种效率非常高的进程间通信方式因为它允许多个进程直接访问同一块内存区域而不需要像其他方式那样频繁的内核态和用户态切换。共享内存的使用可以大大提高进程间通信的速度适用于需要传递大量数据的场景。 优点 速度快共享内存避免了在用户态和内核态之间频繁切换因此非常适合传输大数据量的场景。节省资源只需一块内存区域多个进程可以共享使用节省内存资源。 缺点 同步问题由于多个进程可能同时访问共享内存所以需要使用同步机制例如信号量来避免数据竞争问题。 在进程间通信中不同方式有各自的优缺点。共享内存适用于高效的大数据量传输但需要处理好进程同步和竞争问题。 1. 管道Pipe 特点单向通信通常用于父子进程之间。优点简单易用适合基本的父子进程通信。缺点数据只能单向传输且只能在具有亲缘关系的进程之间使用。扩展命名管道FIFO可以在无亲缘关系的进程之间通信。 2. 消息队列Message Queue 特点通过消息传递进行通信可以在任意进程之间使用消息以队列形式存储。优点消息可以按照优先级排序允许随机读取队列中的消息。缺点内核管理消息队列系统资源有限。 3. 共享内存Shared Memory 特点多个进程共享同一块内存区域数据直接读写速度最快。优点适合大数据量传输效率高。缺点需要额外的同步机制如信号量来避免数据竞争。 4. 信号量Semaphore 特点主要用于同步多个进程或线程确保多个进程能够有序地访问共享资源。优点可以控制多个进程对共享资源的访问。缺点不适用于传输数据只用于进程同步。 6. 套接字Socket 特点广泛用于网络通信既可以在同一台机器的进程间通信也可以跨网络进行通信。优点灵活性强支持本地和远程进程通信。缺点比管道、共享内存等通信方式复杂通信开销较大。 7. 文件File 特点通过读写同一文件来共享信息。优点简单易实现可以持久化数据。缺点效率较低且需要额外的同步机制。 8. 信号量Semaphore和互斥锁Mutex 特点这两种方式主要用于进程或线程的同步确保对共享资源的有序访问。优点控制并发问题防止资源竞争。 每种通信方式都有其适用的场景。比如管道适合父子进程间的简单通信共享内存适合大数据量的高效传输消息队列和套接字则更加灵活适合更复杂的进程间数据交互。 总结 管道 和 命名管道 适合简单的、同一台机器上的进程通信管道只能单向而命名管道可以实现双向通信。消息队列 支持有序、灵活的消息传递但容量有限适合需要多进程之间进行信息传递的场景。共享内存 是最高效的数据传输方式但需要额外的同步机制来避免数据竞争。信号量 主要用于同步避免进程对共享资源的竞争不适合传输数据。信号 主要用于简单通知或控制进程行为信息量非常有限。套接字 是最灵活的通信方式支持本地和远程通信但效率较低特别是在远程通信场景中。 三十一、多进程和多线程的优缺点  总结 多进程适用于独立性强的任务容错性好但资源消耗大通信复杂适合高容错、分布式场景。多线程适合轻量级并发任务资源消耗小但同步复杂容易出错适合高效并发场景。 三十二、进程和线程的区别  进程和线程的区别主要体现在内存管理、资源使用、执行方式、通信方式等多个方面。以下是它们的详细对比  进程它包含了运行程序的所需代码、数据以及其它系统资源。线程是进程中过的执行流它是CPU调度和执行的最小单位 1. 定义 进程 进程是操作系统中资源分配的基本单位。每个进程有自己独立的内存空间、全局变量、文件句柄等资源并且彼此之间相互独立。线程 线程是CPU调度的基本单位。线程是进程内的一个执行流多个线程共享同一个进程的资源如内存、全局变量等。一个进程可以包含多个线程。 总结 进程 更适合需要高隔离度、独立运行的任务开销较大但容错性好通信复杂。线程 更适合轻量级的并发任务资源消耗小通信方便但需要处理同步和潜在的竞争问题。进程拥有独立的地址空间而同一进程下的线程共享地址空间进程间切换开销大线程间切换开销小。进程间通信IPC方式多样但相对复杂线程间可以直接通过读写共享内存来通信更简单高效 三十三、线程的通信方式有哪些分别有什么不同  线程之间的通信主要通过共享内存和同步机制来实现因为同一进程内的线程共享内存空间所以它们可以直接访问和修改共享的数据。然而为了避免数据竞争和其他并发问题线程通信通常需要一些同步机制来确保数据一致性和线程安全。常见的线程通信方式包括以下几种  总结 共享内存 是线程通信的基础所有其他方式都是为了解决共享内存带来的同步问题。互斥锁 和 条件变量 是最常用的同步机制用于控制线程对共享资源的访问。信号量 和 自旋锁 适用于特定场景前者用于控制并发访问数量后者用于短时间的忙等待。屏障 和 线程局部存储 适用于需要同步执行和独立变量维护的场景。 三十四、 Linux 如何查看进程状态 1. ps 命令 ps 命令用于显示当前系统中运行的进程信息。它可以通过不同的选项来获取特定的进程信息。 ps auxa显示所有终端的进程不仅限于当前用户的。u以用户为主的输出格式显示更多详细信息如 CPU 和内存使用情况。x显示没有控制终端的进程即守护进程。 2. top 命令 top 命令提供了一个动态、实时的进程视图显示系统中最耗资源的进程并可以按资源使用情况进行排序。 top这将显示进程的 CPU、内存使用情况、运行时间、进程状态等。 通过 q 退出 top。 三十五、 linux 如何查看线程状态 1. ps 命令 ps 命令可以显示线程的详细信息。 查看某个进程的线程 ps -T -p PID其中PID 是进程的 ID。这个命令会列出该进程下的所有线程及其状态。  显示所有线程  ps -eLfe显示所有进程。L显示所有线程。f显示详细的格式。 三十六、 如何查看网络连接情况 1. netstat 命令已被 ss 替代但仍然常用 netstat 是一个经典的网络工具可以显示网络连接、路由表、接口状态等信息。 查看所有当前的网络连接包括监听和已建立的连接 netstat -an-a显示所有的连接包括监听和非监听的。-n以数字形式显示地址和端口。 查看 TCP 连接  netstat -tn查看 UDP 连接 netstat -un查看某个进程使用的网络连接  netstat -tp2. ss 命令netstat 的现代替代工具 ssSocket Statistics是查看网络连接的现代工具比 netstat 更快、更高效。 -tTCP。-uUDP。-n以数字形式显示地址。-l仅显示监听套接字。-p显示进程。 三十七、TCP 和 UDP 的区别  TCPTransmission Control Protocol和 UDPUser Datagram Protocol是两种常见的传输层协议。它们的区别主要体现在数据传输方式、可靠性、连接性等方面。下面从几个维度详细对比这两种协议  1. 连接性 TCP面向连接的协议。在传输数据之前TCP 需要通过“三次握手”来建立连接保证通信双方的连接可靠。数据传输完成后双方还需要通过“四次挥手”来断开连接。 三次握手发送方和接收方要进行三个数据包的交换来建立连接。四次挥手双方通过四个数据包来终止连接。UDP无连接的协议。UDP 不需要建立连接数据直接发送给目标省略了连接建立和断开的过程。因此UDP 通信开销较小但没有连接管理的机制。 2. 可靠性 TCP提供可靠的传输。TCP 通过序列号、确认机制和重传策略确保数据包按序到达并且数据不会丢失、重复或损坏。如果数据在传输中丢失TCP 会自动重传丢失的数据包直到对方确认接收。UDP不保证可靠性。UDP 不提供数据包的确认、重传、或顺序保证因此有可能出现数据包丢失、重复或乱序的情况。UDP 适用于对可靠性要求不高的场景。 5. 数据传输效率 TCP由于 TCP 的连接建立、确认、重传、拥塞控制等机制数据传输的开销较大效率较低。它更适合需要高可靠性和数据完整性的应用。UDPUDP 由于没有复杂的连接和控制机制传输效率更高适合需要低延迟、实时传输的场景。 8. 传输单位 TCP基于流传输。TCP 会将数据分片按需组装成数据流。因此TCP 更适合连续的数据传输如文件传输。UDP基于数据报文传输。UDP 以数据报文为单位进行传输每个数据报文是独立的且大小固定适合传输短的消息。 应用建议 如果你的应用需要高可靠性例如文件传输、银行交易、电子邮件等使用 TCP。如果你的应用追求低延迟且容忍一定的数据丢失例如视频直播、在线游戏等使用 UDP。 三十八、 左值和右值引用 在 C 中左值和右值引用是重要的概念特别是在现代 CC11 及之后中这些概念在性能优化和资源管理方面起到了关键作用。  1. 左值 (Lvalue) 与 右值 (Rvalue) 首先我们需要理解什么是左值和右值 左值 (Lvalue)指的是内存中有明确存储位置的对象换句话说左值可以出现在赋值操作符的左边。左值在程序的生命周期内是可以被修改的。常见的左值是变量和可以取地址的对象。 int x 10; // x 是左值因为它有存储位置可以被修改右值 (Rvalue)指的是不具备持久存储位置通常是临时对象或字面量无法取地址。右值通常出现在赋值操作符的右边它们是短暂的、不可修改的。  int y x 5; // x 5 是右值它是临时生成的一个值没有存储位置5. 移动语义和右值引用的应用场景重点 移动构造函数与移动赋值运算符 在移动构造函数和移动赋值运算符中通过右值引用可以高效地“移动”对象的资源而不是进行深拷贝显著提升性能。 class MyClass { public:MyClass(std::string str) : data(std::move(str)) {} // 使用右值引用和移动语义 private:std::string data; };避免临时对象拷贝 右值引用可以让我们避免在传递临时对象时的深拷贝从而减少不必要的性能开销。 总结 左值引用用于绑定左值常用于传递可修改的对象。右值引用用于绑定右值特别用于移动语义减少不必要的对象拷贝。左值持久存在具备存储位置。右值临时存在不具备存储位置。 左值引用是对可寻址、可重复操作使用的对象左值的引用。它使用传统的单个 符号。右值引用是对临时对象右值的引用使用双 符号。右值引用允许实现移动语义和完美转发它可以将资源从一个临时的对象转移到另一个对象提高效率、避免不必要的拷贝。  三十九、虚函数指针是什么时候初始化的  虚函数指针vptr是在 对象构造时初始化的。具体地说当一个包含虚函数的类的对象被构造时编译器会在构造函数中为对象分配一个指向虚函数表vtable的指针即虚函数指针 vptr。这个指针会指向该对象所属类的虚函数表从而实现运行时的多态性。  以下是虚函数指针初始化的几个关键点 在构造函数中初始化在对象的构造过程中每个类的构造函数会负责初始化 vptr。如果类是派生类派生类的构造函数会重新设置 vptr使其指向派生类的虚函数表。 初始化顺序如果有继承关系基类的构造函数会首先运行并设置基类的 vptr然后派生类的构造函数会覆盖基类的 vptr指向派生类的虚函数表。 运行时多态通过 vptr对象在调用虚函数时可以根据实际的对象类型选择相应的虚函数实现。 #include iostream using namespace std;class Base { public:virtual void show() { cout Base class show endl; }Base() { cout Base constructor called endl; } };class Derived : public Base { public:void show() override { cout Derived class show endl; }Derived() { cout Derived constructor called endl; } };int main() {Base* b new Derived();b-show(); // 输出 Derived class showdelete b;return 0; }在这个例子中Derived 类对象构造时基类 Base 的构造函数先被调用初始化基类的 vptr然后派生类 Derived 的构造函数调用时重新设置 vptr使其指向 Derived 的虚函数表。最终通过 b-show() 调用的是 Derived 类的 show 方法实现了多态。  总结 虚函数指针 vptr 在对象的构造过程中初始化且在派生类构造函数中会重新设置为派生类的虚函数表以保证虚函数调用的多态性。 四十、析构函数不是虚函数会有什么问题  如果基类的析构函数不是虚函数而你通过基类指针删除指向派生类对象时不会调用派生类的析构函数这可能导致派生类特有资源无法正确释放从而产生资源泄漏等问题。 问题的根源  当基类的析构函数不是虚函数时C 不会进行动态绑定。在通过基类指针指向派生类对象并调用 delete 时只会调用基类的析构函数而不会调用派生类的析构函数。这意味着派生类中分配的资源如动态内存、文件句柄等无法得到正确释放。  举例说明  #include iostream using namespace std;class Base { public:~Base() { cout Base destructor called endl; } };class Derived : public Base { public:~Derived() { cout Derived destructor called endl; } };int main() {Base* b new Derived();delete b; // 只调用 Base 的析构函数return 0; }Base destructor called在上面的代码中b 是一个基类指针但它实际上指向了 Derived 类的对象。当我们通过 delete b 释放对象时由于 Base 类的析构函数不是虚函数只会调用 Base 的析构函数而不会调用 Derived 的析构函数。 这会导致 Derived 类中的资源没有被正确释放从而可能导致资源泄漏。 如何解决 解决方案是将基类的析构函数声明为虚函数。这样当我们通过基类指针释放派生类对象时编译器会正确调用派生类的析构函数实现动态绑定确保所有资源得到正确释放。 总结 析构函数不是虚函数时的问题通过基类指针删除派生类对象时只会调用基类的析构函数导致派生类特有的资源未释放可能会引发内存泄漏或其他资源管理问题。虚析构函数的必要性为了解决这个问题基类的析构函数应该声明为虚函数以确保正确调用派生类的析构函数从而正确释放资源。 当类含有虚函数时务必将析构函数也声明为虚函数以确保通过基类指针可以正确删除派生类对象避免潜在的资源管理问题。  四十一、map使用find查找和[]查找的区别为啥要有find  1. 为什么需要 find find 的存在是为了提供一种安全的、不改变容器内容的查找方式。如果只依赖 [] 来查找元素那么在有些情况下你可能会无意间修改了容器插入了新键。有些应用场景下你可能只想做一个纯粹的查找操作而不希望修改 map 的内容这时候 find 非常必要。 总结 find仅查找不会改变 map返回一个迭代器适合需要检查键是否存在时使用。[]查找并插入如果键不存在返回值的引用适合在需要访问和修改 map 时使用。 通过这两种方式可以灵活处理不同的查找需求需要安全、不修改数据时用 find需要自动插入和修改时用 []。 四十二、this 指针有什么作用这个指针的值是从那里来的  this 指针是 C 中一个隐含的、指向当前对象的指针作用是指向当前对象自身使得类的成员函数能够访问该对象的成员变量和成员函数。它存在于每一个非静态成员函数中并且在成员函数内部用于区分不同对象。  1.this 指针的作用 访问对象的成员在成员函数中this 指针可以访问该对象的成员变量和成员函数。避免命名冲突当成员函数的形参与成员变量重名时使用 this 来区分。例如this-member 表示访问成员变量 member而不是函数参数 member。返回对象的地址可以使用 return *this 来返回对象的引用常见于链式调用或一些设计模式中。指向当前对象this 指针始终指向当前调用成员函数的对象。 2. this 指针的来源  this 指针在编译时由编译器自动生成。对于每个非静态成员函数编译器会隐含地将当前对象的地址作为第一个参数传递给成员函数因此在成员函数内部this 指针就指向了调用该函数的对象。  3. this 指针的特性 只存在于非静态成员函数中this 指针在静态成员函数中不可用因为静态成员函数与具体对象无关。只读指针this 指针本身是只读的不能对它进行赋值操作但可以通过它修改当前对象的成员。类型this 指针的类型是 T* const其中 T 是类的类型。例如在 class MyClass 中this 的类型为 MyClass* const指向当前对象。 1. 访问成员变量和函数  class MyClass { public:int value;void setValue(int value) {// 使用 this- 来区分成员变量和参数this-value value;}void display() {// 直接通过 this 指针访问成员变量std::cout Value: this-value std::endl;} };2. 返回当前对象的引用链式调用  class MyClass { public:int value;MyClass setValue(int v) {value v;return *this; // 返回当前对象的引用}void display() const {std::cout Value: value std::endl;} };int main() {MyClass obj;obj.setValue(10).display(); // 链式调用return 0; }5. 总结 this 指针的作用是指向调用成员函数的当前对象可以用于访问该对象的成员避免命名冲突或者返回对象本身。this 指针的值是编译器在调用成员函数时自动生成的指向当前对象的地址。编译时编译器会自动将对象的地址作为隐含参数传递给成员函数因此在每次调用成员函数时this 指针就指向了调用该函数的具体对象。 四十三、多线程同步方式。条件变量和信号量的区别 在多线程编程中同步是确保多个线程安全地访问共享资源的关键机制。常见的多线程同步方式包括互斥锁、条件变量、信号量等。下面重点介绍条件变量和信号量的区别以及它们的适用场景。  1. 条件变量 (Condition Variable) 条件变量是一种用于线程间等待某个条件成立的同步机制。它通常与互斥锁mutex一起使用使线程能够在等待某个条件时释放锁并进入休眠直到某个线程通知条件已经满足。 关键特性 条件变量是“触发机制”允许一个线程等待另一个线程发出某个条件成立的信号signal 或 broadcast。与互斥锁结合使用必须配合互斥锁用于保护共享数据同时防止多个线程并发修改该数据。等待条件线程调用 wait() 进入休眠并释放持有的互斥锁直到被另一个线程用 notify_one() 或 notify_all() 唤醒。 使用场景 当你需要一个或多个线程等待某个特定条件满足并且在等待期间需要释放互斥锁时。例如生产者-消费者问题消费者需要等待队列有数据可消费。 2. 信号量 (Semaphore) 信号量是一种计数器用来控制多个线程对共享资源的访问。信号量可以用来限制可以访问资源的线程数量允许多个线程同时访问资源或仅允许一个线程访问。 关键特性 计数机制信号量有一个计数器当线程进入临界区时计数器减 1当线程离开时计数器加 1。如果计数器为 0则新的线程必须等待直到计数器大于 0。可以允许多个线程访问共享资源信号量允许多个线程同时访问某个资源例如可以限制最多 3 个线程同时访问。二元信号量和计数信号量二元信号量类似互斥锁只允许一个线程进入临界区。计数信号量允许多个线程同时进入临界区。 使用场景 用于控制多个线程访问有限资源时例如限制数据库连接池的最大连接数。二元信号量可以用来代替互斥锁而计数信号量则用来控制对多个资源的访问。 4. 总结 条件变量用于线程间的条件同步线程在等待某个条件时可以释放锁并休眠适合生产者-消费者等场景。信号量通过计数器机制控制线程对共享资源的访问允许同时管理多个线程对资源的访问。 选择哪种同步方式取决于具体的使用场景。如果需要线程等待某种条件可以使用条件变量如果要控制线程对资源的并发访问可以使用信号量。 四十四、vector 扩容机制 。vector中删除元素是否会释放内存  std::vector 是 C 标准库中的动态数组当 vector 的容量不足以容纳新元素时会自动扩容。扩容是 vector 的关键特性之一因为它隐藏了底层内存管理的复杂性。  1. 扩容机制原理 std::vector 具有两种容量相关的属性 大小size当前 vector 中实际存储的元素数量。容量capacity当前 vector 为元素分配的内存空间即最多可以容纳多少个元素。 当 vector 的大小超过当前容量时它会触发扩容。扩容一般遵循如下步骤 分配更大的内存通常vector 会按照一定的倍增策略分配更多内存通常是两倍这取决于具体的标准库实现。新分配的容量通常比当前需要的大小大一些以减少频繁的扩容操作。 元素搬移将原来 vector 中的元素从旧的内存位置复制到新的内存空间。 释放旧内存释放旧的内存块指向新的内存区域。 #include iostream #include stdexcepttemplate typename T class MyVector { private:T* data; // 指向动态数组的指针size_t size; // 当前元素数量size_t capacity; // 当前容量public:// 构造函数MyVector() : data(nullptr), size(0), capacity(0) {}// 析构函数~MyVector() {delete[] data; // 释放内存}// 获取当前元素数量size_t getSize() const {return size;}// 获取当前容量size_t getCapacity() const {return capacity;}// 添加元素void push_back(const T value) {if (size capacity) {expandCapacity(); // 当容量不足时扩容}data[size] value;size;}// 访问元素T operator[](size_t index) {if (index size) {throw std::out_of_range(Index out of bounds);}return data[index];}private:// 扩容函数void expandCapacity() {// 如果当前容量为 0则初始化容量为 1否则扩容为当前容量的 2 倍size_t newCapacity (capacity 0) ? 1 : capacity * 2;// 分配新空间T* newData new T[newCapacity];// 将原有数据复制到新空间for (size_t i 0; i size; i) {newData[i] data[i];}// 释放旧空间delete[] data;// 更新指针和容量data newData;capacity newCapacity;} };int main() {MyVectorint vec;// 打印初始容量std::cout 初始容量: vec.getCapacity() std::endl;// 添加元素并观察扩容for (int i 0; i 10; i) {vec.push_back(i);std::cout 添加元素 i 后的容量: vec.getCapacity() std::endl;}// 打印最终的元素std::cout 最终元素: ;for (int i 0; i vec.getSize(); i) {std::cout vec[i] ;}std::cout std::endl;return 0; }初始容量: 0 添加元素 1 后的容量: 1 添加元素 2 后的容量: 2 添加元素 3 后的容量: 4 添加元素 4 后的容量: 4 添加元素 5 后的容量: 8 添加元素 6 后的容量: 8 添加元素 7 后的容量: 8 添加元素 8 后的容量: 8 添加元素 9 后的容量: 16 添加元素 10 后的容量: 16 最终元素: 1 2 3 4 5 6 7 8 9 10 扩容机制std::vector 的容量通常按倍数扩展旧数据会被复制到新内存中旧内存会被释放。删除元素后是否释放内存erase() 和 pop_back() 只改变元素数量内存不会自动释放vector 的 capacity 不会缩小。 四十五、vector在删除时是否会产生迭代器失效 std::vector 在删除元素时可能会导致迭代器失效。不同的删除操作会导致不同类型的迭代器失效问题。下面详细解释迭代器失效的原因和情况  1. 在循环中使用失效迭代器 如果你在一个循环中删除 vector 的元素但继续使用失效的迭代器来访问剩余元素这通常会导致未定义行为、程序崩溃或错误输出。常见的场景是 erase() 操作未正确处理迭代器。 #include iostream #include vectorint main() {std::vectorint v {1, 2, 3, 4, 5};// 在删除元素时继续使用失效的迭代器for (auto it v.begin(); it ! v.end(); it) {if (*it 3) {v.erase(it); // 删除 3 后it 失效}std::cout *it ; // 使用了失效迭代器}return 0; }当使用 erase() 删除元素时迭代器会失效因此应始终使用 erase() 返回的新迭代器来继续操作而不是使用失效的旧迭代器。  注意erase 返回的是删除元素的下一个位置  #include iostream #include vectorint main() {std::vectorint v {1, 2, 3, 4, 5};// 改进使用 erase() 返回的有效迭代器for (auto it v.begin(); it ! v.end(); ) {if (*it 3) {it v.erase(it); // 删除 3 后使用 erase 返回的新迭代器} else {it; // 仅当不删除时才递增迭代器}}// 输出剩余元素for (auto val : v) {std::cout val ; // 输出: 1 2 4 5}return 0; }2. 扩容后迭代器失效 虽然你提到的是删除操作但如果删除后添加元素导致 vector 扩容也会导致迭代器失效。在扩容时所有的迭代器都会失效因为 vector 底层内存会重新分配。 #include iostream #include vectorint main() {std::vectorint v {1, 2, 3, 4, 5};auto it v.begin() 2; // 指向 3v.erase(it); // 删除 3迭代器 it 仍然有效v.push_back(6); // 如果扩容之前的迭代器都会失效std::cout *it std::endl; // 可能导致未定义行为return 0; }push_back(6) 可能会触发 vector 扩容导致所有指向原内存的迭代器失效。 vector迭代器失效有两种 扩容、缩容导致野指针式失效迭代器指向的位置意义变了 系统越界机制检查不一定能够检查到。编译实现检查机制相对比较靠谱。 四十六、使用 unique_ptr 和 裸指针 有什么却别效率有什么区别  std::unique_ptr 和裸指针也就是普通的原生指针在 C 中有不同的用途和特性主要区别体现在内存管理、安全性以及效率等方面。 void useRawPointer() {int* ptr new int(10); // 动态分配内存std::cout *ptr std::endl; // 使用delete ptr; // 手动释放内存避免内存泄漏 }#include memory void useUniquePointer() {std::unique_ptrint ptr std::make_uniqueint(10); // 自动管理内存std::cout *ptr std::endl; // 使用// 离开作用域时内存会自动释放 }总结 内存管理unique_ptr 自动管理内存而裸指针需要手动管理。安全性unique_ptr 更安全避免了裸指针常见的内存泄漏和悬空指针问题。效率裸指针在极少数情况下可能比 unique_ptr 更高效但这种差异通常很小unique_ptr 提供的自动化内存管理功能在大多数情况下是值得的。 四十七、大小端有什么区别分别有什么用 大小端Endianness 是指多字节数据在内存中的存储顺序主要有两种方式大端模式Big-Endian 和 小端模式Little-Endian。它们的区别在于多字节数据的高低位字节如何排列。  1. 大端模式Big-Endian 定义高位字节 --- 存储在 --- 低地址低位字节 -- 存储在 -- 高地址。 示例 假设一个 32 位的整型数 0x12345678在大端模式下内存中存储顺序为 地址: 0x00 0x01 0x02 0x03 数据: 0x12 0x34 0x56 0x78用处 网络协议大端模式广泛应用于网络字节序也称为网络序。网络协议通常使用大端字节序传输数据因为这样可以使得跨平台通信更加一致。人类可读性大端模式更符合人类的阅读习惯。对于一个数字 0x12345678人们通常从左到右读作 12 34 56 78这与大端模式的存储顺序一致。 2. 小端模式Little-Endian 定义低位字节存储在低地址高位字节存储在高地址。 示例 假设一个 32 位的整型数 0x12345678在小端模式下内存中存储顺序为 地址: 0x00 0x01 0x02 0x03 数据: 0x78 0x56 0x34 0x12用处 计算机架构小端模式在大多数计算机架构中被广泛使用特别是在Intel x86 和 x86-64 体系结构中。这是因为在处理器中读取和操作数据时低位字节通常是更频繁操作的部分小端模式可以更有效地处理这种情况。 5. 大小端在实践中的作用 网络通信 网络协议如 IP、TCP、UDP规定数据必须按照大端模式即网络字节序进行传输。为了保证不同架构的设备能够正确地通信发送方和接收方都需要在传输数据时按照大端顺序来发送字节。文件存储格式 一些文件格式如图片、音频文件对字节序有严格要求比如 TIFF 图像格式可以使用大端或小端模式存储数据PNG 格式规定使用大端模式。跨平台应用 大端模式保证了不同平台之间的数据一致性尤其是需要在网络或分布式系统中传输数据时。而小端模式则更适合于在单一平台上的高效处理。 总结 大端模式和小端模式的主要区别在于多字节数据的存储顺序。大端模式高位字节存储在低地址小端模式低位字节存储在低地址。大端模式更适合网络通信和跨平台的数据一致性小端模式则更适合高效的位操作和处理低位字节的数据。了解并使用正确的字节序对于确保跨平台通信、文件存储和系统架构中的正确数据处理非常重要。 int main() {int a 1; // 定义一个整型变量 a值为 1char* p (char*)a; // 将整型变量 a 的地址转换为 char* 指针指向 a 的第一个字节if (*p 1) // 检查最低地址的字节也就是 a 的低位字节{printf(小端\n); // 如果最低字节是 1说明是小端}else{printf(大端\n); // 否则是大端}return 0; }四十八、什么是B树它有什么特点  B树 是一种 自平衡的树 数据结构广泛应用于 数据库系统 和 文件系统 中以高效存储和检索大量数据。它在磁盘存储场景中尤为常见因为它能减少磁盘I/O操作的次数极大提高了查询效率。  B树的结构和特点 每个节点可以有多个子节点 B树的每个内部节点可以拥有多个子节点而不像二叉树那样每个节点只有两个子节点。通常B树的阶order决定了每个节点的子节点数。 叶子节点存储所有数据 与 B 树不同B树的所有数据元素都存储在 叶子节点 中非叶子节点只存储索引信息起到导航作用。 叶子节点形成一个链表 所有的叶子节点通过指针相互连接形成一个有序的链表。这样可以大幅提高区间查询范围查询的效率只需遍历链表即可获取相邻的数据。 节点中的元素是有序的 每个节点中的数据或索引都是从小到大排序的保证了高效的搜索、插入和删除操作。 总结 B树 适合 磁盘存储和范围查询如数据库索引树高度较低查询和插入删除操作均为 O(log n)叶子节点有序且通过链表连接支持高效的顺序和范围查询。 哈希表 适合 快速精确查找查找、插入和删除操作在理想情况下为 O(1)但不支持范围查询也可能因哈希冲突导致性能下降。 红黑树 是一种 自平衡二叉搜索树广泛用于 内存存储如 map 和 set它支持 O(log n) 的查找、插入和删除操作但范围查询性能不如 B树通常用于内存中的小规模数据场景。 四十九、虚函数实现机制 C中的虚函数通过虚函数表来实现。每一个使用虚函数的类都有一个对应的虚函数表其中存储了指向类的虚函数指针。每一个对象实例包含一个指向其类的虚函数表的指针(vptr)当调用一个对象的虚函数时会通过这个指针在虚函数表中查找相应的函数实现进行调用从而实现多态性。这种机制允许在运行时根据对象的实际类型来动态绑定方法而不是在编译时。 五十、构造函数能否声明为虚函数 1. 构造函数 原因构造函数的主要作用是初始化对象而虚函数依赖于对象的完整初始化和虚表的正确配置。在对象构造的过程中虚表尚未准备好因此构造函数不能是虚函数。 2. 内联函数 虽然内联函数和虚函数是可以同时存在的但 内联 与 虚函数 存在一定的矛盾。虚函数是通过虚表vtable来动态绑定的内联函数则是编译期就内联展开的。因此即使将虚函数声明为内联函数编译器也可能不会将其内联。 3. 静态函数 原因静态函数属于类本身而不属于某个具体的对象实例。虚函数需要依赖对象的虚表来进行动态调度而静态函数没有对象的上下文因此不能是虚函数。 4. 友元函数 原因友元函数不是类的成员函数它只是具有访问类私有成员的权限。由于虚函数依赖于对象实例的虚表友元函数没有对象上下文因此不能声明为虚函数。 总结 构造函数 和 静态函数 是由于它们与对象的虚表无关因此不能声明为虚函数。友元函数 和 模板函数 由于其自身性质也不能声明为虚函数。 五十一、const 和 define 的区别 const 定义的是常量有数据类型而 define 是 预处理器指令替换文本没有数据类型 1. const 常量通常存储在程序的数据段中define 只是简单的替换不分配存储空间 2. const 是在编译阶段使用define 发生在预处理阶段编译器还未参与。 3. const 常量通常在调试中可见因为它们是符号化的而 define 替换后代码通常难以调试 五十二、 struct 和 class 的区别 struct 和 class 的主要区别在于 默认的 访问权限 和 继承类型  1. 默认访问权限struct 中的成员默认是公开(public)的而 class 中的成员默认是私有的private 2. 默认继承类型使用 struct 继承 默认是公开的 (public) 继承而使用 class 继承默认是私有继承  五十三、lambda 表达式的用法 1. 什么是 Lambda 表达式 Lambda 表达式是一种匿名函数允许你在需要函数的地方定义和使用内联函数尤其适合用于短小的函数逻辑。语法格式如下 [捕获列表](参数列表) - 返回类型 {函数体 };捕获列表定义 lambda 函数如何访问外部作用域的变量。参数列表传递给 lambda 函数的参数。返回类型可选可以省略编译器会根据返回值推断类型。函数体实际执行的代码。 2. Lambda 表达式的优点 简洁性无需为短小的操作单独写一个函数直接就地定义代码更紧凑。可读性lambda 表达式常常用于回调函数或算法操作使得代码更容易理解。灵活的捕获外部变量通过捕获列表可以灵活访问函数外的变量而不需要额外的传参。匿名特性不需要为临时使用的函数命名非常适合一些一次性的操作。 3. 应用场景 STL 算法在使用 std::sort等 STL 算法时lambda 表达式使得定义自定义操作非常简单。 回调函数处理事件驱动或异步编程中的回调函数时lambda 表达式可以简化代码。函数对象替代lambda 可以作为函数对象的替代尤其适用于简单的逻辑。并发编程在多线程编程中lambda 常被用于线程函数的定义避免了额外的函数声明。 4. 优化性能 内联特性lambda 表达式由于可以直接内联减少了函数调用的开销因此有时比使用普通函数性能更高。 1. 对数组排序的例子 在这个例子中我们将对一个整数数组进行降序排序 #include iostream #include vector #include algorithmint main() {std::vectorint nums {5, 2, 8, 1, 4};// 使用 lambda 表达式进行降序排序std::sort(nums.begin(), nums.end(), [](int a, int b) {return a b; // 降序});// 输出排序后的数组for (int n : nums) {std::cout n ;}std::cout std::endl;return 0; }2. 对字符串数组排序的例子 在这个例子中我们将根据字符串的长度对字符串数组进行升序排序 五十四、什么是内联函数 内联函数inline function是通过在函数定义前加上关键字 inline 来建议编译器将函数代码直接插入到每个调用它的地方而不是通过函数调用机制。这样可以减少函数调用时的开销。内联函数通常用于那些频繁调用、代码短小的函数。  inline int add(int a, int b) {return a b; }int main() {int result add(3, 4); // 编译时add(3, 4) 会直接展开为 3 4return 0; }2. 内联函数的优点 减少函数调用的开销内联函数避免了传统函数调用时压栈、出栈等开销特别是在频繁调用的小函数中效果显著。代码优化编译器在内联展开时可以进行一些额外的优化比如消除无用代码或常量折叠。方便调试内联函数保留了函数的语义便于调试和维护同时也能享受代码简化的好处。 3. 内联函数的限制 不能过于复杂内联函数应保持简短如果函数体过于复杂编译器通常会忽略 inline 建议拒绝内联。递归函数通常不会内联递归函数无法内联因为内联展开会导致无限递归的展开。编译器优化决定权即使使用了 inline 关键字是否实际内联仍然由编译器决定并非强制要求。 五十五、虚函数表存在什么区域 1. 虚函数表的存储区域 虚函数表是存储在 静态内存区域静态存储区 的。这是因为每个类的虚函数表是由编译器在编译期创建的并且该表对于某个类的所有对象是共享的。因此虚函数表被存储在静态内存区域静态数据区而不是在堆或栈中。 静态内存区域包含静态变量、全局变量和常量虚函数表因为其与类相关联而不是与对象相关联所以位于这个区域中。 2. 虚表指针vptr的位置 虽然虚函数表本身存储在静态区域中但每个包含虚函数的对象会有一个隐藏的指针称为 虚表指针即 vptr指向该类的虚函数表。这个指针存储在对象的内存中通常在对象的头部。 五十六、动态连接和静态链接的区别 动态链接和静态链接是程序编译和链接过程中使用的两种方式它们的区别主要在于库的加载和程序的大小。以下是它们的详细对比  1. 静态链接 (Static Linking) 定义: 在编译时将所有需要的库文件与程序的目标文件链接在一起生成一个包含所有代码的可执行文件。库的加载: 库在编译时就嵌入到可执行文件中程序运行时不再需要外部库。优点: 程序可以独立运行不依赖外部库。运行时速度较快因为不需要动态加载库。缺点: 可执行文件较大因为每个程序都要包含库的所有代码。当库发生变化时需要重新编译所有使用该库的程序。 2. 动态链接 (Dynamic Linking) 定义: 在编译时不将库嵌入可执行文件而是在程序运行时动态加载所需的库文件。库的加载: 库文件在程序运行时由操作系统加载。优点: 可执行文件较小因为不需要包含库的代码。库可以被多个程序共享减少内存使用。当库更新时不需要重新编译程序程序可以自动使用新版本的库前提是库的接口保持不变。缺点: 程序依赖于外部库缺少这些库时无法运行。运行时加载库会有一些性能开销。 总结 静态链接更适合那些希望生成独立的、无需依赖外部库的可执行文件的场景比如某些嵌入式系统或需要在环境中分发无需额外依赖的软件。动态链接适合需要减少可执行文件大小、共享库资源、并希望在库更新时自动受益的场景常见于现代桌面操作系统和服务器环境。 五十七、MySQL中什么是索引  在 MySQL 中索引是一种用于快速查找数据库表中记录的数据结构。索引类似于一本书的目录可以加速查询操作但也会增加维护成本。索引在数据库优化中扮演着关键角色特别是在涉及大量数据的查询时。 建立索引的原则是什么  建立索引时需要遵循一些原则以确保索引能够有效提高查询效率同时避免过度使用索引带来的维护开销。以下是一些常见的建立索引的原则  1. 选择性高的列优先创建索引 原则: 对选择性高的列创建索引所谓选择性高指的是该列中的值有较高的唯一性。原因: 选择性高的列能有效过滤更多行减少扫描的数据量从而提高查询效率。例子: 在一个有100万行的表中如果 user_id 列几乎没有重复值例如身份证号那么为 user_id 创建索引可以显著提高查询性能。而如果为性别gender只有 男 和 女创建索引效果会不明显。 2. 经常出现在 WHERE 子句中的列 原则: 为 WHERE 子句中经常出现的列创建索引。原因: 查询时数据库需要通过 WHERE 条件筛选数据。为这些列建立索引可以加速筛选过程避免全表扫描。例子: 对于经常按日期筛选记录的查询可以为日期字段如 created_at建立索引。 3. 在 JOIN 和 GROUP BY 中使用的列 原则: 为 JOIN、ORDER BY、GROUP BY 等操作中使用的列创建索引。原因: 联表查询和分组操作都需要在多表之间匹配或者进行排序、分组操作。创建索引能加速这些操作。例子: 当两个表通过 user_id 进行 JOIN 时给 user_id 建立索引可以显著提高联表查询效率。 4. 避免为频繁更新的列创建索引 原则: 尽量避免为频繁变动的列创建索引。原因: 每次对表进行 INSERT、UPDATE 或 DELETE 操作时索引也需要更新增加了额外的维护开销。因此为经常变化的列创建索引会影响数据库的写入性能。例子: 对于库存系统中的 stock_quantity 列库存数量由于它可能频繁更新不建议为其创建索引。 5. 避免为小表创建过多索引 原则: 小表不应过度使用索引甚至有时可以不创建索引。原因: 对于行数较少的表全表扫描的效率可能与索引扫描相差无几索引的维护成本可能超过其带来的性能提升。例子: 如果一个表只有几百行查询时全表扫描的速度已经很快不需要专门为其创建索引。 五十八、什么是事务  事务Transaction是数据库管理系统DBMS中用于保证数据一致性和完整性的一种机制。事务是一组被看作一个单一逻辑单元的操作这些操作要么全都成功要么全都失败。事务可以确保在并发环境中多个用户或应用程序对数据库的操作不会导致数据不一致。  1. 事务的四大特性ACID 事务有四个关键特性简称为 ACID它们是数据库事务的核心保障。 原子性Atomicity: 定义: 事务是一个不可分割的整体要么完全执行成功要么完全回滚失败不会停留在中间状态。举例: 假设有两个操作一个从账户 A 转账到账户 B另一个是从账户 B 接收金额。事务确保如果任何一个操作失败整个转账操作将回滚保证 A 和 B 的余额不会不一致。 一致性Consistency: 定义: 事务执行前后数据库都必须处于一致的状态。即执行事务不会破坏数据库的完整性规则。举例: 在银行转账中无论事务执行成功与否A 和 B 账户的余额总和在操作前后应该是一样的不会凭空增减。 隔离性Isolation: 定义: 并发事务之间相互隔离互不影响。一个事务的执行过程对其他事务是不可见的直到该事务提交。举例: 在一个用户执行转账的过程中另一个用户查询账户余额时不会看到未提交的转账操作避免脏数据的读取。 持久性Durability: 定义: 一旦事务提交它对数据库所做的变更将永久保存在数据库中即使系统崩溃也不会丢失这些修改。举例: 当用户在网上银行完成一笔转账并提交后即便系统崩溃用户稍后查询到的余额依然会包含这笔转账的变动。 2. 事务的控制语句 在 MySQL 等数据库中常用的事务控制语句包括 START TRANSACTION: 开始一个事务。COMMIT: 提交事务将事务中的所有操作持久化到数据库中。ROLLBACK: 回滚事务撤销事务中的所有操作。SAVEPOINT: 设置事务中的一个保存点可以部分回滚到某个保存点而不是整个事务。SET TRANSACTION: 设置事务的隔离级别。 3. 事务的使用 事务通常用于多个数据库操作需要作为一个整体执行时。例如银行系统中的转账操作涉及多个步骤 从账户 A 扣款。向账户 B 存款。 这两个步骤必须保证要么全部完成要么全部不完成回滚。因此可以将它们作为一个事务来执行 START TRANSACTION;-- 扣款操作 UPDATE accounts SET balance balance - 100 WHERE account_id A;-- 存款操作 UPDATE accounts SET balance balance 100 WHERE account_id B;COMMIT;如果在执行过程中发生错误可以使用 ROLLBACK 来回滚整个事务保证数据一致性  ROLLBACK;4. 事务隔离级别 事务的隔离性决定了并发事务之间的相互影响程度。数据库系统通常提供四种事务隔离级别不同的隔离级别可以防止不同类型的并发问题 读未提交Read Uncommitted: 事务可以读取其他未提交事务的修改可能导致“脏读”问题。读已提交Read Committed: 只能读取其他事务已经提交的数据避免脏读。可重复读Repeatable Read: 在同一个事务中多次读取同一数据时保证读取到的数据一致避免“不可重复读”。序列化Serializable: 最严格的隔离级别事务完全隔离相当于所有事务依次执行避免所有并发问题但性能最低。 最常用的级别是 Repeatable Read 因为它平衡了性能与一致性同时也是MySQL的默认隔离级别 总结 事务是数据库系统中确保数据一致性、完整性和安全性的重要机制。通过 ACID 特性事务能够在并发环境中保护数据并在发生错误时回滚变更避免数据损坏。事务广泛应用于金融系统、订单处理系统等需要保证数据一致性的场景中。 五十九、什么叫慢查询 慢查询是指在数据库中执行时间过长响应速度慢的查询操作。具体的时间阈值可以根据系统的具体需求进行定义。  例如 如果一条查询语句执行时间超过了设定的阈值比如1秒那么就可以将这条查询语句标记为慢查询。 慢查询的原因可能包括 数据量过大、数据库设计不合理、索引问题等。 六十、MySQL 是 主要通过什么数据结构实现为什么用B树 MySQL 索引主要通过 B树数据结构实现的。  原因 1. 高效的读写对于数据库读写操作频繁的场景B树平衡性能较好读写性能稳定。 2. 范围查询优势B树支持范围擦汗寻hash表只支持精确查询而二叉树效率地下。 3. 磁盘读写优化B树的节点大小通常和磁盘扇区大小相同这样可以最大化磁盘I/O的效率 4. 磁盘减少I/O次数B树的分支因子大树的层级较低可以减少在查询过程中磁盘的I/O的次数  六十一、C 中 volatile 的作用 在 C 中volatile 关键字的主要作用是告诉编译器被 volatile 修饰的变量可能会在程序执行的过程中被外部环境如硬件设备、中断、其他线程等修改因此编译器不应对该变量进行优化。  #include stdio.h #include stdlib.h #include pthread.hvolatile int counter 0;void *increment(void *arg) {for (int i 0; i 100000; i) {counter;}return NULL; }int main() {pthread_t thread1, thread2;// 创建两个线程分别执行increment函数pthread_create(thread1, NULL, increment, NULL);pthread_create(thread2, NULL, increment, NULL);// 等待两个线程执行完毕pthread_join(thread1, NULL);pthread_join(thread2, NULL);printf(Counter: %d\n, counter);return 0; } 这个代码中使用了 volatile 关键字来修饰全局变量 counter并且创建了两个线程分别执行 increment() 函数对 counter 进行递增操作。  输出结果的潜在问题 理论上counter 最终应该输出 200000每个线程递增 100000 次。然而由于多个线程同时对 counter 进行非原子操作可能会出现竞态条件race condition导致结果比预期值要小。例如两个线程同时读取了相同的 counter 值结果都将其加一然后写回内存。这种情况下实际的递增只发生了一次而不是两次。 volatile 的作用 volatile 关键字在这个代码中的作用是告诉编译器counter 的值可能会被其他线程修改因此每次都应该从内存中读取 counter而不能缓存这个变量的值。这意味着即使一个线程对 counter 的值进行了修改另一个线程也能正确读取到它的最新值。 但是volatile 只能防止编译器对变量进行优化不能解决竞态条件问题。线程操作 counter 是一个非原子操作包含了多个步骤读取、增加、写回。即使每次都读取到最新的值由于线程之间并没有同步机制多个线程仍然可能同时读取到相同的值导致最终结果不正确。 总结 volatile 关键字可以防止编译器对变量 counter 进行优化确保每次都从内存读取最新的值但这不能解决多线程并发访问时的竞态条件问题。要解决竞态条件问题需要使用同步机制比如使用 mutex 来保护对 counter 的访问使得每次递增操作是一个原子操作。 在编程中volatile 就是用来提醒编译器某些变量可能会被外部因素改变每次都要从内存中读取最新的值不能仅依赖上次的缓存。
http://www.tj-hxxt.cn/news/223995.html

相关文章:

  • wordpress 多站点 主题多媒体制作公司
  • jsp如何做网站界面海南网站建设优化排名
  • 市场上网站开发价格长沙seo推广营销
  • 肇庆 网站建设公司有哪些怎么制作网页步骤
  • 门户网站设计行业他达拉非
  • 网站开发主机的选择网站集群建设解决方案
  • 株洲网站建设费用赣州门户网站建设
  • 网站如何做数据储存的江苏省建设斤网站
  • seo推广方式是什么呢站长工具seo综合查询收费吗
  • 如何制作简单的网站微信支付需要网站备案
  • 网站特效代码html网站建设公司如何做大
  • 登录器显的窗口网站怎么做广州网站建设工作室招聘
  • 网站建设费专票会计分录洛阳seo管理系统推广团队
  • 零食网站怎么做465端口 WordPress
  • 网页制作与网站开发...郑州seo外包公司哪家好
  • 互联网站备案信息佛山网站建设工作室
  • 输入网站查看空间wap页面模板
  • 天津手机版建站系统哪个好东莞微客巴巴做网站
  • 东丽区网站建设公司wordpress登录背景
  • 企业解决方案提供商宜春seo
  • 俄文网站建设 俄文网站设计如何做好一个网站运营
  • 网站制作的差异化定位网络建设公司方案
  • 微网站建设公司首选做的好的办公家具网站
  • 河南做网站哪个平台好如何建立微信群
  • wordpress本地音乐2m杭州seo
  • 网站设计广州哪里有做网站系统的
  • dedecms网站开发环境深圳投资推广署官网
  • 站长工具备案查询wordpress newsletter 插件
  • 奖励软件下载网站电白区住房和城乡建设部门户网站
  • 网站开发 接口还是ajax统一用户管理系统