模拟面试-C++

第一题(开发音视频处理模块)

在开发音视频处理模块时,FFmpeg资源(AVFrame*)需要自动释放。如何用unique_ptr定制删除器替代手动av_frame_free()?写出代码并解释RAII优势。

参考答案:

auto frame_deleter=[](AVFrame* ptr){av_frame_free(&ptr);};
std::unique_ptr<AVFrame,decltypt(frame_deleter)>frame(av_frame_alloc(),frame_deleter);

优势:

异常安全:函数提前退出时自动释放资源

所有权明确:禁止拷贝,转移需用std::move

第二题(日志系统需要创建LogEntry对象)

日志系统需要创建LogEntry对象,其构造函数含std::string初始化。为何用emplace_back替代push_back可提升性能?移动语义在此过程如何生效?

=====================================================================参考答案:

push_back问题:先构造临时对象,再移动/拷贝到容器

emplace_back优化:直接在容器内存构造对象(完美转发参数)

//性能对比
std::vector<LogEntry> logs;
logs.push_back(LogEntry("Error", 404)); 	// 1次构造+1次移动
logs.emplace_back("Error", 404);         // 仅1次构造

移动语义的作用:减少对象传递的开销

移动语义允许对象的资源(如std::string的底层字符数组)在不同对象间转移所有权,而非深拷贝。当使用push_back时:

  • 若传入临时对象(右值),编译器会优先调用移动构造函数,将资源从临时对象转移到容器元素中(O(1)开销)。
  • 若传入左值对象,则仍需调用拷贝构造函数(O(n)开销,n为字符串长度)。
LogEntry(LogEntry&& other) noexcept: message(std::move(other.message)), // 转移string资源,而非拷贝code(other.code) {}

    第三题(两个类Device和Controller互相持有shared_ptr)

    两个类Device和Controller互相持有shared_ptr,导致内存泄漏。如何用weak_ptr打破循环引用?画出引用计数变化图。

    =====================================================================

    参考答案:

    class Controller {
    public:std::shared_ptr<Device> dev;  // 强引用
    };class Device {
    public:std::weak_ptr<Controller> ctrl;  // 弱引用不增加计数
    };

    弱智能指针:专门用来解决循环引用问题的一个智能指针
    weak_ptr 虽然有引用计数,但是被 weak_ptr 指向的地址,仅仅会被 weak_ptr 观察并记录引用计数的值,并不会增加

    weak_ptr 的特点
    weak_ptr 是专门、也只能用来解决 shared_ptr 循环引用的问题
    所以 weak_ptr 是没有任何针对指针的操作
    说人话就是:weak_ptr 没有重载 operator* 和 operator-> 函数

    使用时候,将weak_ptr 临时转换成 shared_ptr
    使用 weak_ptr 里面一个叫做 lock的函数
    通过lokc函数转换出来的shared_ptr也是一个临时的shared_ptr,用完就会被释放,不影响引用计数

    weak_ptr<int> p2=p1;//弱智能指针可以直接指向共享智能指针,并获取该共享智能指针的引用计数,但是不会+1;
    cout<<*p2.lock()<<endl;

    第四题(游戏引擎需频繁创建/销毁Enemy对象)

    游戏引擎需频繁创建/销毁Enemy对象,直接new/delete导致内存碎片。如何用placement new和内存池优化?说明避免malloc次数统计的方法。

    =====================================================================

    参考答案:

    核心步骤:
    01.预分配大块内存:char* pool = new char[POOL_SIZE]
    02.对象构造:Enemy* e = new (pool + offset) Enemy()
    03.手动析构:e->~Enemy()
    04.统计技巧:重载类专属operator new/delete

    一、内存池核心设计:预分配+内存块管理

    1. 预分配大块内存
    const size_t POOL_SIZE = 1024 * 1024; // 1MB内存池
    const size_t OBJECT_SIZE = sizeof(Enemy);
    char* memory_pool = new char[POOL_SIZE]; // 预分配连续内存
    char* free_ptr = memory_pool; // 空闲内存指针
    2. 空闲块管理(链表法)
    struct FreeBlock { FreeBlock* next; };
    FreeBlock* free_list = reinterpret_cast<FreeBlock*>(memory_pool);// 初始化空闲链表(将内存池分割为OBJECT_SIZE大小的块)
    for (size_t i = 0; i < POOL_SIZE / OBJECT_SIZE - 1; ++i) {free_list[i].next = &free_list[i + 1];
    }
    free_list[POOL_SIZE / OBJECT_SIZE - 1].next = nullptr;

    二、placement new:在指定内存地址构造对象

    1. 分配对象(从内存池获取内存)
    Enemy* create_enemy() {if (free_list == nullptr) {// 内存池耗尽,可扩容或返回nullptrreturn nullptr;}// 1. 获取空闲块地址void* mem = free_list;free_list = free_list->next;// 2. placement new构造对象(不分配内存,仅调用构造函数)return new (mem) Enemy(); // 在mem地址上构造Enemy
    }
    2. 销毁对象(手动析构+归还内存块)
    void destroy_enemy(Enemy* enemy) {if (!enemy) return;// 1. 手动调用析构函数enemy->~Enemy();// 2. 将内存块归还到空闲链表FreeBlock* block = reinterpret_cast<FreeBlock*>(enemy);block->next = free_list;free_list = block;
    }

    三、避免malloc次数统计的关键:重载operator new/delete

    通过类专属内存分配器,确保Enemy对象只能从内存池分配,从而绕过系统malloc

    class Enemy {
    public:// 重载operator new:从内存池分配void* operator new(size_t size) {// 直接调用内存池分配函数return create_enemy();}// 重载operator delete:归还到内存池void operator delete(void* ptr) {destroy_enemy(static_cast<Enemy*>(ptr));}// ... 其他成员 ...
    };

    第五题(实现高性能网络服务器)

    在实现高性能网络服务器时,接收到的数据包Packet对象需要传递给处理线程。已知Packet包含大量数据(如std::vector<uint8_t> payload)。
    问:
    如何通过移动语义避免数据复制?请说明push_back时std::move的作用原理,并解释为何移动构造函数应声明为noexcept。

    // 使用场景
    std::queue<Packet> packet_queue;
    Packet new_packet = receive_packet();
    packet_queue.push_back(std::move(new_packet));  // 避免payload深拷贝

    关键点:

    std::move将左值转为右值,触发移动构造

    noexcept保证容器重分配时不回退到拷贝构造

    移动后源对象处于有效但未定义状态(不应再使用)

    AI答案:

    一、移动语义避免数据复制的核心实现

    1. 定义Packet类的移动构造函数
    class Packet {
    public:// 移动构造函数:接管资源而非复制Packet(Packet&& other) noexcept: payload(std::move(other.payload))  // 转移vector内存所有权, size(other.size) {other.size = 0;  // 源对象状态重置}private:std::vector<uint8_t> payload;  // 大量数据存储size_t size;
    };
    2. 使用std::move触发移动构造
    std::queue<Packet> packet_queue;
    Packet new_packet = receive_packet();  // 接收数据包(左值)// 关键:std::move将左值转为右值引用,触发移动构造
    packet_queue.push_back(std::move(new_packet));
    // new_packet此时处于“有效但未定义状态”,不应再使用

    二、std::movepush_back中的作用原理

    1. 左值转右值

      • new_packet是左值(有名称的对象),std::move(new_packet)将其转换为右值引用Packet&&)。
      • 这告诉编译器:“允许接管此对象的资源,无需保留其原始状态”。
    2. 容器的构造选择

      • queue::push_back接收右值时,会优先调用Packet移动构造函数(而非拷贝构造函数)。
      • 对于std::vector<uint8_t> payload,移动构造仅转移指针和大小(O(1)操作),避免深拷贝数据(O(n)操作)。

    三、移动构造函数声明noexcept的必要性

    1. 确保容器内存重分配时的性能

    当容器(如vectorqueue)需要扩容时,若元素类型的移动构造函数标记为noexcept

    // 伪代码:容器扩容逻辑
    if (std::is_nothrow_move_constructible_v<Packet>) {move_elements_to_new_buffer();  // 高效移动
    } else {copy_elements_to_new_buffer();  // 安全但低效的拷贝
    }
    • noexcept:容器无法保证移动操作的异常安全性,会退回到拷贝构造,导致性能退化。
    • noexcept:容器可安全使用移动构造,确保资源高效转移。

    自己的理解:

    int main(int argc,const char** argv){Stu zs;Stu ls = zs;// 拷贝构造 = 创建新对象ls,并且将zs里面的值赋值给lsStu ww = Stu();// 这里是一次普通构造,最终结果是,Stu()构建的临时对象,他内部所有数据的最终所有权,被编译器优化,变成了ww,即ww 的地址和 = 右侧 Stu() 临时对象地址是一样的
    )return 0;
    }

    什么样的高效操作:临时对象管理的内存直接移交给 = 左侧的长生命周期对象进行管理

    Stu zs; 普通构造
    Stu ls = move(zs); 移动构造    move(zs)会创建一个新的临时对象,并让ls接管该对象
    Stu ww = zs; 拷贝构造 ww会创建新的对象,并且去拷贝zs里面的所有数据所以从描述上来说,一定是移动构造效率更高

    第六题(匿名函数)

    在算法中使用自定义比较或操作逻辑时,定义完整的命名函数或仿函数 (functor) 类有时显得繁琐,尤其当逻辑很简单且只使用一次时。我们需要一种更简洁、更灵活的方式在调用点就地定义匿名函数。

    参考答案:

    =====================================================================

    1:C++11 Lambda 表达式的基本语法结构是什么?[capture-list] (parameters) -> return-type { body } 各部分的作用是什么?(特别强调 capture-list)

    • [capture-list](捕获列表):指定外部变量如何被Lambda访问(按值/按引用),为空则不捕获任何变量。
    • (parameters):参数列表,与普通函数参数相同(可省略,若无参数)。
    • -> return-type:返回类型(可省略,编译器会根据return语句自动推断)。
    • { body }:函数体,包含具体逻辑。
    int x = 10;
    auto add = [x](int y) -> int { return x + y; };
    // 捕获x,接收y,返回int,函数体返回x+y

    2:捕获列表 (capture-list) 有几种主要捕获方式?解释 按值捕获 [=]、按引用捕获 [&]、捕获特定变量 [x, &y] 的含义和行为。按值捕获的变量在 Lambda 体内能被修改吗?如何使其可修改?(mutable)

    (1)按值捕获 [=]
    • 含义:捕获所有外部变量的副本(值传递)。
    • 行为:Lambda体内使用的是变量的快照,外部变量后续修改不影响Lambda内的值。

    int a = 1, b = 2;
    auto func = [=]() { return a + b; };
    a = 3; // 外部修改不影响Lambda内的a(仍为1)
    (2)按引用捕获 [&]

    • 含义:捕获所有外部变量的引用(引用传递)。
    • 行为:Lambda体内直接访问外部变量,修改会影响原值。
    int a = 1;
    auto func = [&]() { a++; };
    func(); // a变为2
    (3)捕获特定变量 [x, &y]

    • 含义:显式指定捕获变量,x按值捕获,y按引用捕获。
    • 行为:仅捕获列表中的变量可被访问,未列出的变量无法使用。
    int x = 1, y = 2, z = 3;
    auto func = [x, &y]() { return x + y; };
    // 可访问x(值)和y(引用),无法访问z
    (4)按值捕获的变量修改问题

    • 默认行为:按值捕获的变量在Lambda体内不可修改(被视为const)。
    • 修改方法:使用mutable关键字解除常量性:
    int x = 1;
    auto func = [x]() mutable { x++; return x; };
    func(); // x变为2(仅修改副本,外部x仍为1)

      3:Lambda 表达式在底层是如何实现的?(通常被编译器转换为一个匿名的函数对象类 (仿函数))。

      // Lambda被转换为类似这样的匿名类
      class AnonymousFunctor {
      private:// 捕获的变量作为成员(按值捕获为副本,按引用捕获为引用)int captured_x;    // 按值捕获的xint& captured_y;   // 按引用捕获的y
      public:// 构造函数初始化捕获的变量AnonymousFunctor(int x, int& y) : captured_x(x), captured_y(y) {}// 重载operator(),对应Lambda的函数体int operator()(int param) const {  // 若有mutable,则去掉constreturn captured_x + captured_y + param;}
      };
      • 调用Lambda 等价于创建该匿名类的对象并调用operator()
      • 捕获列表 决定了匿名类的成员变量类型(值或引用)。
      • mutable 关键字会移除operator()const修饰,允许修改按值捕获的成员变量。

      4:以下代码中,Lambda 捕获了局部变量 factor 和 threshold。当 processData 函数返回后,存储了该 Lambda 的函数指针 func 还能被安全调用吗?为什么?

      #include <functional>
      std::function<void(int&)> processData(int threshold){int factor=2;auto lambda=[factor,threshold](int& value) mutable{value=value*factor;if(value>threshold) value=threshold;};return lambda;
      }
      auto func=processData(100);
      int num=50;//// 危险!processData返回后,factor和threshold已销毁
      func(num);//安全吗?????
      结论:不能安全调用,原因如下:
      1. 局部变量生命周期factorthresholdprocessData函数的局部变量,函数返回后会被销毁。
      2. 引用捕获的风险:Lambda按引用捕获了这两个变量,存储的Lambda对象(或函数指针)持有的是悬垂引用
      3. 未定义行为:调用func时访问已销毁的变量,会导致内存访问错误(未定义行为)。

      技术考察点:

      掌握 Lambda 的基本语法和核心组成部分。

      深入理解捕获列表:这是 Lambda 的核心难点和易错点。清楚区分按值和按引用捕获的语义、生命周期影响以及 mutable 的作用。

      理解 Lambda 的底层实现原理(仿函数),知道它是如何携带状态的(捕获的变量成为匿名类的成员)。

      考察对 变量生命周期 和 悬空引用 问题的敏感度(按引用捕获局部变量后,局部变量销毁会导致 Lambda 内引用无效)。

      第七题(自动推导变量类型

      C++ 类型系统强大但有时类型名非常冗长(如迭代器、模板实例化结果、复杂表达式结果),手动写出完整类型既繁琐又容易出错。我们需要编译器帮助我们自动推导变量类型。

      参考答案:

      =====================================================================

      1:C++11 中 auto 关键字的主要用途是什么?

      2:auto 的类型推导规则通常遵循什么原则?(与模板参数推导规则类似)

      3:以下代码片段的推导结果是什么?为什么?

      4:使用 auto 时,哪些情况可能导致意外或不直观的类型推导结果?(例如推导出 std::initializer_list 或代理对象类型如 vector<bool>::reference)

      auto a=42;//a的类型是?
      const int ci=10;
      auto b=ci;//b的类型是什么?const属性还在吗?
      auto c=ci;//c的类型是?
      auto d=&ci;//d的类型是什么?
      std::vector<int> vec;
      auto it=vec.begin();//it的类型是什么?(迭代器类型通常很冗长)
      1. auto a = 42;

        • a的类型int
        • 原因:整数字面量42的默认类型为intauto直接推导为该类型。
      2. const int ci = 10; auto b = ci;

        • b的类型int
        • const属性不在
        • 原因auto推导时会忽略顶层const(即变量本身的const),仅保留底层const(如指针/引用指向的对象是const)。
      3. auto c = ci;

        • c的类型int(与b完全相同,推导规则一致)
      4. auto d = &ci;

        • d的类型const int*(指向const int的指针)
        • 原因&ciconst int的地址,auto推导为const int*,此时const是底层const(指针指向的对象不可修改),会被保留。
      5. std::vector<int> vec; auto it = vec.begin();

        • it的类型std::vector<int>::iterator
        • 原因vec.begin()返回的迭代器类型为容器定义的iterator类型,auto推导时会精确匹配该类型,避免了冗长的手动声明(如std::vector<int>::iterator it = vec.begin();)。

      核心规则auto推导时会忽略顶层const,但保留底层const;对于表达式类型会进行精确匹配,特别适合简化复杂类型(如迭代器、函数指针等)的声明。

      技术考察点:

      理解 auto 的核心价值:简化代码、避免冗长类型名、提高可维护性、防止隐式类型转换错误。

      掌握 auto 推导的基本规则:忽略顶层 const 和引用(除非显式指定 auto& 或 const auto&),推导表达式结果的类型。

      理解 auto 与引用、指针、const 结合时的具体推导结果。

      了解 auto 使用的潜在陷阱(如代理对象、initializer_list 推导),知道何时需要小心或显式指定类型。

      第八题(遍历容器(如数组、vector、list、map)元素是常见操作

      遍历容器(如数组、vector、list、map)元素是常见操作,使用传统的迭代器或下标循环代码相对冗长且容易出错(如迭代器失效、下标越界)。我们需要一种更简洁、更安全的遍历语法。

      考察点:

      =====================================================================

      1:C++11 范围 for 循环 (for (elem : container),自动迭代法) 的基本语法和优势是什么?

      • 简化写法:可省略元素类型,用 auto 自动推导:for (auto elem : container)
      • 引用写法:如需修改元素或避免拷贝,使用引用:for (auto& elem : container)

      核心优势

      • 代码简洁:无需手动调用 begin()/end() 或管理迭代器,减少模板代码(如 std::vector<int>::iterator)。
      • 可读性高:直接表达“遍历容器中所有元素”的意图,逻辑更清晰。
      • 安全性强:自动处理容器边界,避免迭代器越界风险(如 it != container.end() 的漏写)。
      • 通用性好:支持所有符合“范围概念”的对象(如标准容器、原生数组、自定义容器)。

      2:范围 for 循环在底层是如何实现的?(通常被编译器转换为基于迭代器的传统循环)

      编译器会将范围 for 循环转换为基于迭代器的传统循环,等价逻辑如下:

      // 伪代码:编译器对 for (auto elem : container) 的转换
      auto&& __range = container; // 保持容器生命周期(右值引用避免临时对象销毁)
      auto __begin = std::begin(__range); // 调用容器的 begin()
      auto __end = std::end(__range);     // 调用容器的 end()
      for (; __begin != __end; ++__begin) {auto elem = *__begin; // 解引用迭代器获取元素// 循环体
      }

      3:以下两种写法在遍历 std::vector<int> 时有何本质区别?哪种方式修改元素会影响原容器?哪种方式效率更高?

      //写法1
      for(auto value:vec){/*...*/}
      //写法2
      for(auto& value:vec){/*...*/}// 写法1:按值捕获元素
      for (int elem : vec) { ... }// 写法2:按引用捕获元素
      for (int& elem : vec) { ... }

      写法1(按值):修改 value 不会影响原容器。 示例:

      std::vector<int> vec = {1, 2, 3};
      for (auto value : vec) {value *= 2; // 仅修改副本,原容器元素不变
      }
      // vec 仍为 {1, 2, 3}

      写法2(按引用):修改 value 会直接影响原容器。 示例:

      std::vector<int> vec = {1, 2, 3};
      for (auto& value : vec) {value *= 2; // 直接修改原容器元素
      }
      // vec 变为 {2, 4, 6}

      按值拷贝value 是容器元素的副本 | 按引用绑定value 是容器元素的别名 |

      内存操作 | 每次迭代创建新副本(独立内存空间) | 直接引用原容器内存(无额外内存分配) 

      4. 最佳实践建议

      • 只读场景:使用 const auto&(避免拷贝且防止误修改):
      for (const auto& value : vec) { /* 只读访问 */ }
      • 修改场景:使用 auto&(直接修改原容器)。
      • 副本修改场景:使用 auto(明确需要独立副本时)。

      (这题好像有问题,各位可以自己测试一下)

      4:在范围 for 循环体内修改容器本身(如添加或删除元素)通常会发生什么?为什么?(引出 迭代器失效 问题)

      一、现象:未定义行为(Undefined Behavior)

      • 可能表现:程序崩溃、数据错乱、循环提前结束或无限循环。
      • 典型案例:对 std::vector<int> 使用范围 for 循环时执行 push_back,可能触发内存重分配,导致原迭代器指向已释放的内存。

      三、迭代器失效的具体场景

      1. 添加元素(如 push_backinsert

      连续内存容器(vectorstring

      • 若添加元素后容器容量不足,会触发内存重分配(新地址分配+元素拷贝),原 __begin 和 __end 迭代器指向已释放的旧内存,导致失效。
      • 即使容量足够,__end 仍指向原结束位置,新增元素无法被循环遍历(循环范围已固定)。

      链表容器(listforward_list

      • 添加元素不会导致迭代器失效,但 __end 仍为初始值,新增元素无法被遍历。
      2. 删除元素(如 erasepop_back

      • 连续内存容器:删除元素后,后续元素会前移,__begin 可能指向被删除元素的下一个位置(但 __end 未更新),导致循环访问到错误元素。
      • 链表容器:删除当前元素会导致指向该元素的迭代器失效,若 __begin 恰好指向被删除元素,会触发未定义行为。

      五、为何范围 for 循环无法处理迭代器失效?

      • 迭代器固定__begin 和 __end 在循环开始时确定,无法动态更新。
      • 无迭代器调整机制:传统 for 循环可通过 erase 返回的新迭代器调整循环变量(如 it = vec.erase(it)),而范围 for 循环无此接口。

      六、正确做法

      1.避免在范围 for 循环中修改容器,改用传统 for 循环并处理迭代器失效:

      for (auto it = vec.begin(); it != vec.end(); ) { if (*it == 2) { it = vec.erase(it); // 用返回的新迭代器更新} else { ++it; } 
      }
      1. 使用支持安全修改的容器(如 std::list 的迭代器在插入时不失效),但仍需注意 __end 固定的问题。

        参考答案:

        =====================================================================

        技术考察点:

        理解范围 for 的核心优势:简洁、安全(避免手动管理迭代器/下标)、语义清晰。

        了解其底层实现依赖于容器的 begin() 和 end() 成员函数或自由函数(ADL)。

        深刻理解按值遍历 (auto elem) 与 按引用遍历 (auto& elem 或 const auto& elem) 的区别:前者是元素副本,后者是元素别名。按引用遍历才能修改原容器元素,且避免拷贝开销(对大型对象或容器重要)。

        理解在循环体内修改容器结构(增删元素)极易导致迭代器失效,进而引发未定义行为 (UB)。知道范围 for 循环对此不提供额外保护。

        第九题(空指针字面量 NULL 为什么修改为nullptr??

        C++ 中传统的空指针字面量 NULL 通常被定义为 0 或 (void*)0。这可能导致函数重载解析时的歧义(整型 0 vs 指针类型)和类型安全问题。

        1:C++11 引入 nullptr 的主要动机是什么?它解决了 NULL 的什么问题?

        1. 核心动机

        解决传统 NULL 作为空指针表示时的类型二义性问题,提供更安全、明确的空指针语义。

        2. NULL 的缺陷

        在 C++98/03 中,NULL 通常被宏定义为整数 0

        #define NULL 0 // 常见实现

        NULL的不安全性:

        重载歧义:无法区分传递的是空指针还是整数 0

        类型不安全:NULL 可被隐式转换为任何整数类型,可能意外赋值给非指针变量(如 int x = NULL;)。

        nullptr的优势:

        明确的空指针类型nullptr 是独立的空指针字面量,类型为 std::nullptr_t

        消除重载歧义:编译器能正确匹配指针重载版本:

        类型安全:nullptr 仅可隐式转换为指针类型(包括普通指针、函数指针、成员指针)和智能指针,不能转换为整数类型,避免意外赋值。

        2:nullptr 是什么类型?(std::nullptr_t,可以隐式转换为任何指针类型和成员指针类型,但不能转换为整数类型)

        在C++11中,nullptr的类型是std::nullptr_t。这是一种特殊的空指针类型,它可以隐式转换为任何指针类型(包括对象指针、函数指针)和成员指针类型,但不能转换为整数类型。这种设计解决了NULL作为空指针表示时的二义性问题,因为NULL通常被定义为整数0,可能导致重载解析错误。而std::nullptr_t类型的nullptr能更精确地表示空指针语义,提高代码的类型安全性。

        3:给出一个例子说明 NULL 可能导致重载解析歧义,而 nullptr 可以避免。

        void func(int);
        void func(char*);
        func(NULL);//可能调用哪个???(通常是func(int),不符合预期)
        func(nullptr);//可以明确调用哪个???

        参考答案:

        =====================================================================

        技术考察点:

        理解 NULL 的历史问题(本质是整数 0 而非真正的指针类型)。

        理解 nullptr 的核心优势:具有明确的指针类型 (std::nullptr_t),消除了重载歧义,提高了类型安全。

        知道 nullptr 应该成为表示空指针的首选方式。

        第十题(利用多核处理器提高性能或响应性

        现代程序需要利用多核处理器提高性能或响应性,C++11 之前缺乏标准化的线程库,需要依赖平台特定 API (如 pthreads, Win32 Threads),代码可移植性差。

        1:C++11 如何创建一个新线程?基本步骤是什么?(#include <thread>, std::thread t(func, args...))

        1. 包含头文件:首先需要包含C++11线程库的头文件 <thread>
        2. 定义线程函数:准备一个需要在新线程中执行的函数(可以是普通函数、Lambda表达式或函数对象)。
        3. 创建线程对象:使用 std::thread 类创建线程对象,构造时传入函数名和参数(如果有的话),语法为 std::thread t(func, args...)
        4. 管理线程生命周期:通过 t.join() 等待线程执行完毕(阻塞当前线程),或 t.detach() 将线程与主线程分离(线程后台运行)。注意:必须调用其中一个,否则程序会在 std::thread 析构时崩溃。
        #include <iostream>
        #include <thread>// 线程函数
        void printMessage(const std::string& msg) {std::cout << msg << std::endl;
        }int main() {// 创建线程并传入函数和参数std::thread t(printMessage, "Hello from new thread!");// 等待线程执行完毕t.join();return 0;
        }

        2:为什么在多线程环境下访问共享数据通常需要同步?常见的同步原语有哪些?(引出 std::mutex)

        1. 互斥锁(std::mutex)

        最基础的同步原语,通过独占访问保护共享数据。线程必须先获取锁才能访问资源,访问结束后释放锁。

        #include <mutex>
        #include <thread>std::mutex mtx; // 全局互斥锁
        int shared_data = 0;void increment() {mtx.lock(); // 获取锁shared_data++; // 临界区:安全访问共享数据mtx.unlock(); // 释放锁(需确保异常时也能释放,推荐使用RAII封装)
        }

        最佳实践:使用std::lock_guard(RAII)自动管理锁的生命周期,避免忘记释放锁导致死锁:

        void safe_increment() {std::lock_guard<std::mutex> lock(mtx); // 构造时加锁,析构时自动解锁shared_data++;
        }
        2. 条件变量(std::condition_variable)

        用于线程间的等待-通知机制,允许线程阻塞等待某个条件成立(如数据就绪)。常与互斥锁配合使用。

        #include <condition_variable>std::condition_variable cv;
        bool data_ready = false;void producer() {// 生产数据...{ std::lock_guard<std::mutex> lock(mtx);data_ready = true;}cv.notify_one(); // 通知等待的消费者
        }void consumer() {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, []{ return data_ready; }); // 等待条件成立// 消费数据...
        }
        3. 原子变量(std::atomic)

        用于无锁同步,适用于简单数据类型(如计数器)。操作通过硬件支持保证原子性,无需显式加锁。

        #include <atomic>
        std::atomic<int> atomic_counter(0); // 原子变量void atomic_increment() {atomic_counter++;
        } // 无需手动加锁,操作本身是原子的

        3:如何使用 std::mutex 保护共享数据的访问?基本代码模式是怎样的?

        std::mutex mtx;
        int shared_data;
        void safe_increment(){std::lock_guard<std::mutex> lock(mtx);//RAII 锁++shared_data;
        }

        4:std::lock_guard 的作用是什么?

        std::lock_guard 是 C++11 引入的RAII(资源获取即初始化)风格的互斥锁封装工具,其核心作用是自动管理互斥锁(std::mutex)的生命周期,确保锁在任何情况下(包括异常抛出时)都能被正确释放,从而避免死锁。

        主要作用:

        1. 自动加锁与解锁

          • 构造 std::lock_guard 对象时,自动调用 std::mutex::lock() 获取锁。
          • 当 std::lock_guard 对象超出作用域(如函数返回、异常抛出)时,其析构函数自动调用 std::mutex::unlock() 释放锁。
        2. 防止死锁风险: 避免手动调用 lock() 和 unlock() 可能导致的遗漏解锁问题(例如在复杂逻辑或异常分支中忘记解锁)

        参考答案:

        =====================================================================

        技术考察点 (初级要求):

        知道 C++11 提供了标准化的线程库 <thread>。

        理解创建线程的基本方法 (std::thread)。

        理解 数据竞争 (Data Race) 的概念和危害。

        知道最基本的同步机制是 互斥锁 (std::mutex)。

        掌握 std::lock_guard 的 RAII 用法:这是初级开发者必须掌握的安全加锁模式,确保锁在作用域结束时自动释放,避免忘记解锁导致死锁。理解 RAII 在此处的应用。

        第11题(override关键字作用,运行时多态计算薪资)

        公司需要管理不同类型员工(普通员工Employee、经理Manager)的薪资计算。普通员工按月薪计算,经理有基本工资+奖金。要求统一接口计算薪资。

        1:如何设计基类和派生类实现运行时多态计算薪资?

        可以通过基类定义纯虚函数,派生类重写实现的方式实现多态薪资计算:

        // 基类:员工
        class Employee {
        public:virtual double calculateSalary() const = 0; // 纯虚函数,定义接口virtual ~Employee() = default; // 虚析构函数,确保派生类析构正确调用
        };// 派生类:普通员工(月薪)
        class RegularEmployee : public Employee {
        private:double monthlySalary;
        public:RegularEmployee(double salary) : monthlySalary(salary) {}double calculateSalary() const override { // 重写基类方法return monthlySalary;}
        };// 派生类:经理(基本工资+奖金)
        class Manager : public Employee {
        private:double baseSalary;double bonus;
        public:Manager(double base, double b) : baseSalary(base), bonus(b) {}double calculateSalary() const override { // 重写基类方法return baseSalary + bonus;}
        };

        2:若不将基类的calculateSalary()声明为虚函数会怎样?

        若不将基类的calculateSalary()声明为虚函数,将无法实现多态行为,导致派生类的重写函数无法被正确调用

        3:C++11的override关键字有什么作用?

        C++11引入的override关键字主要用于显式标记派生类中重写基类虚函数的方法,其核心作用是提升代码安全性和可读性。

        4:以下代码输出什么?为什么?

        Employee* emp=new Manager("Alice",5000,2000);
        std::cout<<emp->calculateSalary();//基类方法未声明virtual
        delete emp;

        01.此时 emp->calculateSalary() 会调用 Employee::calculateSalary(),输出 0(基类默认值),而非预期的 7000(5000+2000)。

        02.C++ 对非虚函数采用 静态绑定(编译期决议),函数调用根据指针声明类型(Employee*)而非实际指向的对象类型(Manager)决定。

        03.额外风险:析构函数未声明virtual导致内存泄漏

        参考答案:

        =====================================================================

        override作用:

        显式标记重写虚函数

        编译器检查签名是否匹配

        避免隐藏(hide)错误

        第11题(安全数组容器

        需要实现安全数组容器,支持不同类型数据,提供边界检查。

        1:如何用类模板实现通用数组?

        #include <stdexcept> // 用于异常处理template <typename T>
        class SafeArray {
        private:T* data;       // 动态数组存储元素size_t size;   // 数组大小public:// 1. 构造函数:初始化数组explicit SafeArray(size_t size) : size(size) {if (size == 0) {throw std::invalid_argument("Array size must be positive");}data = new T[size]; // 分配内存}// 2. 析构函数:释放内存~SafeArray() {delete[] data; // 释放动态数组}// 3. 拷贝构造函数:防止浅拷贝SafeArray(const SafeArray& other) : size(other.size) {data = new T[size];for (size_t i = 0; i < size; ++i) {data[i] = other.data[i]; // 深拷贝元素}}// 4. 拷贝赋值运算符:防止浅拷贝SafeArray& operator=(const SafeArray& other) {if (this != &other) { // 避免自赋值delete[] data; // 释放原有内存size = other.size;data = new T[size];for (size_t i = 0; i < size; ++i) {data[i] = other.data[i]; // 深拷贝元素}}return *this;}// 5. 访问元素:带边界检查T& operator[](size_t index) {if (index >= size) {throw std::out_of_range("Index out of bounds"); // 抛出越界异常}return data[index];}// 6. const版本访问元素(只读)const T& operator[](size_t index) const {if (index >= size) {throw std::out_of_range("Index out of bounds");}return data[index];}// 7. 获取数组大小size_t getSize() const { return size; }
        };

        关键特性说明

        1. 模板参数化:通过template <typename T>使数组支持任意类型(int/double/自定义类型等)
        2. 边界检查:在operator[]中验证index < size,越界时抛出std::out_of_range异常
        3. 内存安全
          • 动态内存管理(new[]/delete[]
          • 实现拷贝构造和拷贝赋值(深拷贝),避免浅拷贝导致的二次释放
        4. 使用便捷性:重载operator[]使访问语法与原生数组一致

        2:成员函数在类外定义时要注意什么?

        1. 类模板成员函数:必须指定模板参数列表

        类模板的成员函数在类外定义时,需通过<T>显式指定模板参数,并在作用域前添加template <typename T>

        // 类内声明template <typename T>class SafeArray {public:T& operator[](size_t index); // 类内声明};// 类外定义(必须带模板参数)template <typename T>T& SafeArray<T>::operator[](size_t index) {if (index >= size) {throw std::out_of_range("Index out of bounds");}return data[index];}

        2. 作用域限定符:必须使用类名::

        无论是否为模板类,类外定义时必须通过类名::函数名指明作用域:

        // 非模板类示例
        class MyClass {
        public:void func(); // 类内声明
        };// 类外定义必须带作用域
        void MyClass::func() {// 实现
        }

        3. 函数签名:必须与类内声明完全一致

        • 返回值类型、参数列表、const限定符、引用限定符必须完全匹配
        • 模板参数名可不同,但位置和数量必须一致

        4. 访问权限:类外定义不改变成员的访问级别

        类外定义仅影响实现位置,不改变成员的public/private/protected属性:

        总结:类外定义检查清单
        ✅ 模板类成员函数是否带template <typename T>和类名<T>::
        ✅ 函数签名是否与类内声明完全一致(返回值、参数、const等)
        ✅ 是否使用了正确的作用域限定符
        ✅ 模板类成员函数是否在头文件中定义(除非使用显式实例化)
        ✅ 静态成员函数是否正确声明(无需this指针,可通过类名::直接调用)

        3:C++11的using别名相比typedef有何优势?

        1. 语法更直观,可读性更强

        using采用更自然的"别名 = 类型"语法,符合人类阅读习惯,尤其是对于复杂类型(如函数指针、数组等):

        2. 原生支持模板别名

        这是using最核心的优势。typedef无法直接为模板定义别名,必须通过额外的模板结构体包装(如上面的Vec示例),而using可以直接创建模板别名:

        3. 与模板参数结合更灵活

        在类模板中定义成员别名时,using可以直接使用类的模板参数,而typedef需要配合typename关键字才能正确解析依赖类型:

        4. 便于模板特化和偏特化

        using定义的模板别名可以直接参与模板特化,而typedef需要通过模板结构体间接实现特化:

        5. 统一风格优势

        using语法与C++11后的现代特性(如auto推导类型、decltype类型获取等配合时,风格更统一,代码更具一致性:

        4:模板实例化过程是怎样的?

        一、模板实例化的触发条件

        编译器仅在模板被实际使用时才会触发实例化(延迟实例化机制),具体触发场景包括:

        1. 对象创建TemplateClass<int> obj;
        2. 成员函数调用obj.method();(仅调用的成员会被实例化)
        3. 显式实例化指令template class TemplateClass<double>;
        4. 取模板地址&TemplateClass<char>::method

        参考答案:

        =====================================================================

        using优势:

        更清晰直观(类似变量赋值)

        支持模板别名

        //typedef旧语法
        typedef SafeArray<int,10> IntArray;//C++11 using
        using IntArray=SafeArray<int,10>;//模板别名(typedef无法实现)
        template<typename T>
        using Vec=std::vector<T>;

        第12题(函数模板实现)

        需要实现获取两个值中最大值的通用函数。

        1:如何用函数模板实现?

        template <typename T>
        T max(T a, T b) {return (a > b) ? a : b;
        }

        2:调用时类型如何推导?

        一、基本类型推导规则

        1. 单一模板参数情况

        对于只有一个模板参数的函数模板:

        template <typename T>
        T max(T a, T b);  // 两个参数类型相同

        编译器会比较所有实参类型,必须完全匹配才能推导成功:

        max(10, 20);       // 成功推导 T = int
        max(3.14, 2.71);   // 成功推导 T = double
        max('a', 'b');     // 成功推导 T = char
        // max(10, 3.14);  // 编译错误:无法推导 T(int 和 double 不匹配)
        2. 多个模板参数情况

        对于有多个模板参数的函数模板:

        template <typename T1, typename T2>
        auto max(T1 a, T2 b) -> decltype(a > b ? a : b);

        编译器会独立推导每个参数类型:

        max(10, 3.14);     // T1 = int, T2 = double
        max('a', 100L);    // T1 = char, T2 = long

        类型推导的核心原则是根据实参类型自动确定模板参数,主要分为:

        • 单一参数推导:要求所有实参类型完全匹配
        • 多参数推导:独立推导每个参数类型
        • 显式指定:无法自动推导时手动指定类型
        • 特殊类型处理:引用、const、万能引用有特殊推导规则

        3:C++11的decltype和尾返回类型有什么用?

        C++11引入的decltype和尾返回类型(trailing return type)是解决类型推导问题的重要特性,尤其在泛型编程中发挥关键作用。

        总结
        - **`decltype`**:编译时类型查询工具,用于获取表达式的精确类型,是实现
        类型感知代码的基础
        - **尾返回类型**:解决了返回类型依赖参数的语法难题,提升了复杂函数的可
        读性和可维护性
        - **组合使用**:`auto func(...) -> decltype(...)`是C++11泛型编程的标
        配,使编写类型无关的通用代码成为可能

        4:以下代码问题在哪?

        template<typename T,typename U>
        auto max(T a,U b){return a>b?a:b;}
        auto result=max(3,4.5);//返回值类型是什么??

        类型推导过程:

        1. 模板参数推导

          • T被推导为int(实参3的类型)
          • U被推导为double(实参4.5的类型)
        2. 条件表达式类型规则: 函数返回a > b ? a : b,其中:

          • aint类型,bdouble类型
          • 根据C++隐式类型转换规则,int会被提升为double进行比较
          • 条件表达式的结果类型为提升后的公共类型double
        3. auto返回类型推导: 在C++14及以上标准中,auto会根据return语句的表达式类型(double)推导返回类型

        • 若使用C++11标准,此代码无法通过编译,因为C++11要求auto返回类型必须配合尾返回类型(-> decltype(a > b ? a : b)
        • 类型提升遵循"算术转换"规则:小范围类型向大范围类型转换(int → double

        因此,最终返回值类型为double

        decltype与尾返回类型 语法如下:

        template<typename T,typename U>
        auto max(T a,U b)->decltype(a>b?a:b){return a>b?a:b;
        }

        解决返回类型依赖参数的问题

        保持类型推导能力

        第13题(多态机制对调试)

        理解多态机制对调试和性能优化至关重要。

        1:虚函数表(vtable)是什么?

        核心作用

        • 实现动态绑定:确保运行时根据对象实际类型调用正确的虚函数版本
        • 存储虚函数地址:每个包含虚函数的类拥有一个唯一的vtable,存储该类所有虚函数的内存地址

        实现机制

        1. 类层面

          • 编译器为每个包含虚函数的类生成一个vtable(静态数组)
          • vtable中按声明顺序存储虚函数指针,若派生类重写基类虚函数,则替换对应位置的函数指针
          • 类中会隐含一个指向vtable的指针成员(vptr),通常在对象内存布局的最开始位置
        2. 对象层面

          • 对象创建时自动初始化vptr,指向其所属类的vtable
          • 通过基类指针/引用调用虚函数时,CPU通过vptr找到vtable,再定位到具体函数地址

        2:动态绑定如何实现?

        一、动态绑定的核心条件

        1. 基类声明虚函数:使用virtual关键字
        2. 派生类重写虚函数:函数签名(返回类型、参数列表)必须与基类完全一致(C++11可使用override显式标记)
        3. 通过基类指针/引用调用:只有通过基类指针或引用访问虚函数时才触发动态绑定

        3:C++11的final关键字有什么用?

        一、修饰类:禁止继承

        final用于类声明时,表示该类不能被任何类继承

        二、修饰虚函数:禁止重写

        final用于虚函数声明时,表示该函数不能被派生类重写

        4:+以下代码内存布局是怎样的

        class Base{virtual void foo(){}int x;
        };
        class Derived:public Base{void foo() override{}int y;
        }

        final作用:

        禁止重写虚函数:virtual void foo() final;

        禁止类被继承:class Base final {};

        第14题(模板全特化)

        通用打印函数需要对字符串类型特殊处理。

        1:如何实现模板全特化?

        1. 基本模板定义

        首先定义通用的模板函数:

        #include <iostream>
        #include <string>// 主模板定义
        template <typename T>
        void print(const T& value) {std::cout << "Generic print: " << value << std::endl;
        }
        2. 对字符串类型进行全特化

        const char*std::string类型提供特化实现:

        // ... existing code ...// 对const char*类型全特化
        template <>
        void print<const char*>(const char* const& str) {std::cout << "String print: [" << str << "]" << std::endl;
        }// 对std::string类型全特化
        template <>
        void print<std::string>(const std::string& str) {std::cout << "String print: [" << str << "]" << std::endl;
        }
        全特化关键点说明
        1. 语法格式template <>开头,明确指定特化类型
        2. 函数签名:必须与主模板完全匹配,包括参数类型和const限定
        3. 实现位置:类模板特化通常放在头文件,函数模板特化可放在源文件
        4. 调用规则:编译器会优先选择最匹配的特化版本

        2:部分特化在类模板中如何使用?

        // 主模板
        template <typename T, typename U>
        class MyClass {// 通用实现
        };// 部分特化:当第二个参数为int时
        template <typename T>
        class MyClass<T, int> {// 针对T, int的特化实现
        };// 部分特化:当两个参数都是指针时
        template <typename T, typename U>
        class MyClass<T*, U*> {// 针对指针类型的特化实现
        };
        三、关键要点
        1. 特化程度:部分特化必须比主模板更具体,但不需要特化所有参数
        2. 优先级规则:编译器会优先选择最匹配的特化版本
        3. 局限性
          • 函数模板不支持部分特化(可通过函数重载替代)
          • 特化版本需要重新定义整个类结构

        3:以下代码输出什么?

        template<typename T>
        void print(T value){std::out<<"Generic:"<<value<<std::endl;
        }template<>
        void print<const char*>(const char* str){std::out<<"String:"<<str<<std::endl;
        }print(42);
        print("Hello");

        输出结果:

        Generic:42
        String:Hello

        模拟面试IO

        第1题(文件 I/O 和标准 I/O 的区别

        “在开发订单系统时,内存中的交易数据需实时写入文件防止丢失。请解释文件 I/O 和标准 I/O 的区别,以及为何标准 I/O 更适合高频写入?”

        参考答案:

        =====================================================================

        文件 I/O:直接使用 Linux 系统调用(如 open/read/write),无缓冲区,每次操作触发内核切换,适合低延迟场景。
        标准 I/O:C 库函数(如 fopen/fprintf)自带缓冲区,减少系统调用次数。例如全缓冲模式填满缓冲区才写入磁盘,显著降低高频写入的磁盘压力。
        选择依据:订单系统需频繁写小数据,标准 I/O 的缓冲机制可合并多次写入,避免频繁内核切换,提升吞吐量。

        第2题(文件 I/O 和标准 I/O 的区别

        “用 lseek 跳过 1GB 后写入 1 字节,实际磁盘占用仅 4KB。请解释空洞文件的原理及应用场景。

        参考答案:

        =====================================================================

        原理:文件偏移量超越物理存储时,内核记录“空洞”,实际磁盘块仅分配写入区域。du 命令显示逻辑大小,ls 显示物理占用。

        应用:数据库预分配大文件避免碎片,下载工具创建占位文件。

        第3题(守护进程

        “如何将 Web 服务转为守护进程?关键步骤为何要两次 fork?”

        参考答案:

        =====================================================================

        首次 fork 后父进程退出,子进程成为孤儿进程(脱离终端控制)。

        调用 setsid() 创建新会话,脱离终端关联。

        二次 fork 避免子进程重新获取终端(非会话首领进程无法打开终端)。

        关闭文件描述符、重定向标准流到 /dev/null

        AI答案

        守护进程化的完整关键步骤:

        首次fork:父进程退出,子进程成为孤儿进程并被init进程收养,脱离终端控制

        调用setsid():创建新会话,子进程成为会话首领,彻底脱离原终端

        二次fork:

        避免子进程成为会话首领,防止其重新打开控制终端

        确保进程不是会话首领,符合守护进程安全标准

        文件处理:

        关闭所有不必要的文件描述符(0,1,2等)

        将标准输入/输出/错误重定向到/dev/null(修正您回答中的/dev/null15笔误)

        环境清理:重置umask权限掩码,设置工作目录为根目录

        两次fork的核心原因:

        第一次fork:打破与父进程的关联,使进程成为后台进程

        第二次fork:防止进程获得控制终端(会话首领会优先获取终端),确保严格的后台运行状态

        这一标准流程遵循System V守护进程规范,可通过daemon(3)函数简化实现(部分系统提供)。

        第4题(守护进程

        “fork() 后子进程是否复制父进程的 100MB 堆内存?写时复制(Copy-on-Write)如何解决此问题?”

        参考答案:

        =====================================================================

        COW 机制:fork() 后子进程共享父进程内存页,仅当修改内存时触发缺页中断复制新页。避免立即复制大内存,提升创建效率18。

        例外:线程栈、文件描述符表等需独立复制

        第5题(多线程统计接口调用次数

        “多线程统计接口调用次数时,count++ 为何结果错误?如何用原子操作解决?,为什么能解决(工作原理)”??

        参考答案:

        =====================================================================

        问题:count++ 非原子操作(包含读-改-写三步),多线程竞争导致计数丢失更新。

        解决:

        互斥锁或者信号量

        第6题(用条件变量和互斥锁实现

        “异步日志系统中,生产者线程写日志到队列,消费者线程刷盘(写入操作)。如何用条件变量和互斥锁实现?”

        参考答案:

        =====================================================================

        答:描述清楚生产者消费者模式

        消费者循环读取队列中的数据,如果队列中数据为空,则使用条件变量wait阻塞

        当生产者将数据写入队列中后,使用signal唤醒消费者

        关键点:条件变量避免忙等待,互斥锁保护共享队列

        第7题(共享内存比管道更合适

        “视频编辑进程需向编码进程发送 100MB 帧数据。为何共享内存比管道更合适?如何同步访问?”

        参考答案:

        =====================================================================

        优势:共享内存直接映射到进程地址空间,避免管道的数据拷贝(内核-用户态切换)

        同步:

        信号量(如 sem_init)协调读写顺序。

        第8题(微服务间频繁跨进程调用

        “微服务间频繁跨进程调用,Binder 为何用线程池处理请求?对比传统同步 IPC 的优势。”

        参考答案:

        =====================================================================

        线程池:服务端预创建线程,并行处理多个请求,避免同步 IPC 的串行阻塞。

        优势:

        高并发:多请求同时处理。

        资源复用:避免频繁创建/销毁线程

        第9题(微服务间频繁跨进程调用

        “在金融交易系统中,日志必须确保崩溃后不丢失。调用 fwrite() 后立刻掉电,数据会丢失吗?如何用 fsync() 解决?代价是什么?”

        参考答案:

        =====================================================================

        问题:fwrite() 写入标准 I/O 缓冲区,未刷盘时掉电导致丢失。

        解决:定期调用 fsync(fd) 强制内核缓冲区落盘(同步磁盘写入)。

        fflush():

        作用于 用户空间缓冲区(C标准库的FILE*流缓冲区)。

        将用户缓冲区中的数据刷新到操作系统内核的页面缓存(Page Cache)。

        不保证数据写入物理磁盘。

        fsync():

        作用于 内核空间缓冲区(操作系统的页面缓存)。

        将内核缓存中的数据强制写入物理存储设备(如磁盘)。

        确保数据持久化到硬件。

        学生如果回答fflush的话,需要跟他讲fsync和fflush的区别

        代价:磁盘 I/O 阻塞线程,吞吐量下降(需权衡持久化级别与性能)。

        第10题(sendfile()

        用 read() 和 write() 拷贝 10GB 文件,为何性能不如 sendfile()?sendfile() 如何减少数据拷贝次数?”

        参考答案:

        =====================================================================

        传统方式:read()(内核缓冲区→用户缓冲区) + write()(用户缓冲区→内核缓冲区),2 次拷贝 + 4 次上下文切换。

        sendfile():内核直接在内核空间完成文件到套接字的拷贝(零拷贝技术),仅 2 次上下文切换,适合静态文件服务器。

        第11题(为何要设置 SA_RESTART)

        “父进程未调用 wait() 的子进程会变成僵尸。如何用信号处理 + waitpid() 自动回收?为何要设置 SA_RESTART?”

        参考答案:

        =====================================================================

        void sigchld_handler(int sig){while(waitpid(-1,NULL,WNOHANG)>0);//非阻塞回收所有僵尸进程
        }
        int main(){struct sigaction sa={.sa_handler=sigchld_handler,.sa_flags=SA_RESTART//避免系统调用被信号中断};sigaction(SIGCHLD,&sa,NULL);//...后续fork逻辑
        }

        第12题(保证日志不交叉混乱

        “两个进程同时打开同一个文件并追加写入,如何保证日志不交叉混乱?O_APPEND 标志如何解决?”

        参考答案:

        =====================================================================

        问题:无同步时,进程 A 写入中途可能被进程 B 覆盖。

        解决:open(file, O_WRONLY | O_APPEND) 确保每次 write() 前自动将偏移量移到文件末尾,内核保证原子性。

        第13题(如何避免死锁)

        “线程 A 先锁 M1 再锁 M2,线程 B 先锁 M2 再锁 M1,导致死锁。如何用锁顺序协议(Lock Ordering)解决?哪些工具可检测死锁?”

        参考答案:

        =====================================================================

        锁顺序协议:所有线程按固定顺序(如 M1→M2)申请锁,破坏环路等待条件。

        检测工具:

        gdb + thread apply all bt 查看线程栈

        Valgrind 的 Helgrind 模块

        Clang 的 -Wthread-safety 静态检查

        第14题(epoll模型

        “监控进程需实时感知 10 个工作进程的状态变化。为何用信号量不如用事件文件描述符 + epoll?请给出实现框架。”

        参考答案:

        =====================================================================

        信号量缺点:仅传递计数,无法区分事件来源。

        epoll+事件描述符模型

        int efd=epoll_create1(0);
        for(int i=0;i<10;i++){int event_fd=eventfd(0,EFD_NONBLOCK);//创建事件fd//工作进程状态变化时写 event_fdepoll_ctl(efd,EPOLL_CTL_ADD,event_fd,&(struct epoll_event){.events=EPOLLIN});
        }
        while(1){epoll_wait(efd,events,10,-1);//阻塞等待事件for(每个触发的事件fd) read(fd,&val,sizeof(val));//清空事件//根据fd定位到具体进程并处理
        }

        模拟面试网编

        第1题(为什么需要设置 SO_REUSEADDR 选项

        "当实现TCP服务端时,客户端频繁重连会出现 Address already in use 错误。请解释为什么需要设置 SO_REUSEADDR 选项?这与TCP的 TIME_WAIT 状态有什么关系?"

        参考答案:

        =====================================================================

        原因:服务端关闭连接后进入 TIME_WAIT 状态(默认2MSL,约60秒),此期间端口被占用。

        解决:setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) 允许重用 TIME_WAIT 状态的端口。

        风险:可能接收旧连接的残留数据(需序列号校验防护)。

        第2题(TCP三次握手和四次挥手的流程

        "客户端调用 connect() 后卡住,抓包显示SYN报文无响应。请描述TCP三次握手流程,并分析哪些情况会导致SYN丢失?"

        参考答案:

        =====================================================================

        握手流程:

        Client → SYN(序列号x)→ Server

        Server → SYN+ACK(序列号y,确认号x+1)→ Client

        Client → ACK(确认号y+1)→ Server

        SYN丢失原因:

        防火墙拦截

        服务端未监听端口

        网络拥塞(超过重传次数后返回 ETIMEDOUT)

        第3题(select/poll/epoll 的性能比较

        "使用 select() 管理2000个并发连接时CPU占用率飙升。请对比 select/poll/epoll 的性能差异,为什么 epoll 更适合万级连接?"

        参考答案:

        =====================================================================

         select 、 O(n) 、 固定大小fd_set 、 FD_SETSIZE(1024) 、

         poll 、 O(n) 、 动态数组 、 系统文件描述符上限 、

        、epoll 、 O(1) 、 红黑树+就绪链表 、 十万级 、

        epoll 优势:

        仅返回就绪的fd,避免遍历所有连接

        边缘触发(ET)模式减少事件触发次

        第4题( epoll 的边缘触发模式

        在 epoll 的边缘触发模式下,为什么读取数据时必须循环调用 recv() 直到返回 EAGAIN?水平触发模式会有何不同?"

        参考答案:

        =====================================================================

        ET模式:仅在fd状态变化时通知一次,必须一次性读完所有数据(否则剩余数据不会触发新事件)

        LT模式:只要缓冲区有数据就持续通知,可多次读取

        第5题( TCP/IP协议栈中MTU和MSS的关系

        发送2000字节数据时,抓包显示被拆成2个IP分片。请说明TCP/IP协议栈中MTU和MSS的关系,如何避免IP分片?"

        参考答案:

        =====================================================================

        MTU:网络层最大传输单元(以太网默认1500字节)

        MSS:传输层最大段大小(MTU - IP头 - TCP头 = 1460字节)

        避免分片:

        设置 setsockopt(fd, IPPROTO_TCP, TCP_MAXSEG, &size) 限制MSS

        或由TCP自动分片(推荐)

        第6题( 选择UDP而非TCP

        "实时游戏服务需要低延迟通信,为什么选择UDP而非TCP?如何在UDP上实现可靠传输?"

        参考答案:

        =====================================================================

        UDP优势:

        无连接建立开销(0-RTT)

        无拥塞控制,可自定义重传策略

        可靠传输方案:

        添加序列号+确认机制(类似QUIC协议)

        前向纠错(FEC)减少重传

        可以适当解释什么是 FEC 技术,工作原理

        模拟面试QT

        第1题( 信号槽的本质

        在开发聊天软件时,需实现“发送消息按钮点击后自动刷新消息列表”。如何解耦按钮与消息列表的逻辑?

        1:信号槽的本质是什么?与回调函数有何区别?410

        2:connect的第五个参数(Qt::ConnectionType)有哪些?跨线程通信应选哪种?38:

        3:信号槽的缺点是什么?如何优化高频信号场景(如实时绘图)?

        参考答案:

        =====================================================================

        本质与区别:

        信号槽是基于元对象系统(Meta-Object System)的回调机制,通过moc预编译器生成中间代码,将信号与槽的索引存储在staticMetaObject中,通过映射表(Map)实现动态连接

        对比回调函数:信号槽支持类型安全检测(参数类型/数量必须匹配)和松散耦合(发送者无需知道接收者),但比直接回调慢约10倍(需遍历连接、参数编组/解组)

        连接方式与选择

        Qt::DirectConnection:槽函数在发送者线程同步执行(单线程常用)。

        Qt::QueuedConnection:槽函数在接收者线程异步执行(跨线程安全)

        优化策略:

        避免高频信号:如实时数据流改用定时器聚合刷新(QTimer)。

        减少连接数:用Qt::UniqueConnection防止重复连接。

        批处理数据:自定义信号传递数据容器(如QVector而非单条数据)

        第2题( 继承QThread

        视频转码工具需后台线程处理文件转换,防止UI卡死。

        1:继承QThread重写run() vs. QObject::moveToThread(),哪种是Qt官方推荐?为什么?910

        2:子线程中为何不能直接操作UI控件?如何安全更新UI?

        参考答案:

        =====================================================================

        推荐moveToThread:

        原因:继承QThread会混淆线程生命周期和任务逻辑;moveToThread将业务对象移至线程,通过信号槽通信更符合Qt设计哲学

        线程安全规则:

        禁止直接操作UI:GUI操作仅限主线程(如修改QLabel文本)。

        安全更新方式:通过信号槽(自动使用QueuedConnection)传递数据,由主线程执行UI更新

        第3题( 局域网内文件传输功能

        引子:实现局域网内文件传输功能,需稳定传输大文件。

        1:描述Qt下TCP服务器与客户端的通信流程,关键类有哪些?310

        2:如何检测网络断开?如何处理粘包问题?

        3:QTcpSocket::readyRead()信号为何可能多次触发?如何保证完整读取数据?

        参考答案:

        =====================================================================

        断网检测:

        处理QTcpSocket::errorOccurred信号,常见错误QAbstractSocket::RemoteHostClosedError。

        第4题(表格控件(QTableView)添加单元格悬浮提示)

        为表格控件(QTableView)添加单元格悬浮提示。

        1. 如何不继承QTableView实现悬浮事件监听?
        2. QStandardItemModel与QAbstractItemModel的区别?如何自定义模型?

        参考答案:

        =====================================================================

        使用事件过滤器监听悬浮事件:

        bool Widget::eventFilter(QObject *obj,QEvent *event){if(obj==tableView && event->type()==QEvent::HoverMove){QHoverEvent *he=static_cast<QHoverEvent*>(event);QModelIndex index=tableView->indexAt(he->pos());showTooltip(index);//显示提示return true;//拦截事件}return QObject::eventFilter(obj,event);
        }

        回答逻辑:

        事件过滤器过滤出悬浮事件,判断悬浮事件坐标是否在tabWidget上面,如果在,判断在哪个 cell里面,然后做出对应提示

        QStandardItemModel:

        简单易用,内存存储数据,适合小型数据集。

        自定义模型:继承QAbstractItemModel,重写rowCount()、data()等方法,支持懒加载/大数据集

        第5题(整个窗口实现快捷键保存功能)

        为整个窗口实现快捷键保存功能(Ctrl+S),无论焦点在哪个控件上。

        使用事件过滤器:

        1. 如何在不修改子控件代码的情况下全局捕获快捷键?
        2. 事件过滤器与重写event()函数有何区别?

        参考答案:

        =====================================================================

        //1.安装事件过滤器到主窗口
        bool MainWindow::eventFilter(QObject *obj,QEvent *event)
        {if(event->type()==QEvent::KeyPress){QKeyEvent *KeyEvent=static_cast<OKeyEvent*>(event);if(keyEvent->modifiers()==Qt::ControlModifier && keyEvent->key()==Qt::Key_s){saveFile();//触发保存return true;//拦截事件}}return QObject::eventFilter(obj,event);//其他事件继续传递
        }
        //安装:qApp->installEventFilter(this);(qApp指向全局QApplication对象)

        回答逻辑:过滤出键盘事件后,判断是否按了 ctrl + s,如果是 则触发保存函数=

        区别:

        事件过滤器:外部拦截(可监听任何对象的事件)

        重写event():内部处理(仅当前对象有效)

        第6题(根据网络状态切换不同处理函数)

        根据网络状态切换不同处理函数(connected()/disconnected())。

        参考答案:

        =====================================================================

        01.如何将信号关联到重载函数?

        connect(socket,static_cast<void(QTcpSocket::*)
        (QAbstractSocket::SocketError)>(&QTcpSocket::
        errorOccurred),
        this,&MyClass::handleError);
        

        02.Lambda捕获this指针有何风险?

        //2.Lambda中捕获this需注意生命周期
        connect(socket,&QTcpSocket::connected,[this](){//若this已销毁->程序崩溃//解决方案,用QPointer或disconnect
        });

        第7题(动态创建的子窗口关闭后未被释放)

        动态创建的子窗口关闭后未被释放。

        1. 如何利用Qt机制自动释放内存?
        2. QPointer与普通指针的区别?

        参考答案:

        =====================================================================

        设置父子组件,父组件析构自动调用子组件析构

        QPointer析构的时候,会自动置空该指针,防止野指针。

          QPointer<QLabel> label=new QLabel;
          delete label;
          if(label){/*不会进入,label自动变为nullptr*/}

            第8题(软件运行时切换中英文界面) 

            软件运行时切换中英文界面。

            1. 如何实现tr()文本的动态刷新?
            2. 哪些文本需要特殊处理?

            参考答案:

            =====================================================================

            重写 changeEvent:

            当控件的状态属性发生改变时(如启用/禁用、焦点变化、语言切换、样式更新等),Qt 会自动调用该函数。常见场景包括:

            //1.重写changeEvent()
            void Widget::changeEvent(QEvent *event){if(event->type()==QEvent::LanguageChange){ui->retranslateUi(this);//更新UI文本setWindowTitle(tr("Main Window"));}
            }QWidget::changeEvent(event);
            }

            动态文本需用tr()包裹:
            错误示例:ui->label->setText("用户名");
            正确示例:ui->label->setText(tr("Username"));

            第9题(多次点击按钮导致槽函数重复执行) 

            多次点击按钮导致槽函数重复执行

            如何确保信号只连接一次?

            参考答案:

            =====================================================================

            //方案1:使用Qt::UniqueConnection(推荐)
            connect(btn,&QPushButton::clicked,this,&MyClass::onClick,Qt::UniqueConnection);//自动防重复
            //方案2:手动断开旧连接
            disconnect(btn,SIGNAL(clicked()),this,SLOT(onClick()));
            connect(btn,SIGNAL(clicked()),this,SLOT(onClick()));
            

            第10题(多次点击按钮导致槽函数重复执行) 

            保存窗口大小、位置等配置到INI文件

            1. 如何读写INI文件?
            2. 配置项不存在时如何设置默认值?

            参考答案:

            =====================================================================

            QSettings settings("config.ini",QSettings::IniFormat);
            //读取配置(带默认值)
            int width=settings.value("Window/width",800).toInt();//默认800
            //写入配置
            settings.setValue("Window/height",600);

            第11题(多次点击按钮导致槽函数重复执行) 

            程序点击按钮后崩溃

            1.如何快速定位崩溃代码行?

            2.常见崩溃原因有哪些?

            参考答案:

            =====================================================================

            调试步骤:

            1.在Debug模式运行程序

            2.崩溃时查看调用堆栈(Call Stack)

            3.定位到最顶层的用户代码

            常见崩溃原因:

            1.空指针访问:if (!obj) return;

            2.数组越界:for(int i=0; i<list.size(); ++i)

            3.野指针:connect后对象被提前删除

            模拟面试_UART总线

            第1题(UART总线的特性?)

            UART总线的特性?

            参考答案:

            =====================================================================

            全双工、异步、串行

            第2题(UART总线协议格式??)

            描述一下UART总线协议格式?解释起始位、停止位的含义?

            参考答案:

            =====================================================================

            描述一下UART总线协议格式?解释起始位、停止位的含义?

            协议格式

            起始位:产生下降沿信号,标志着数据传输的开始

            停止位:产生上升沿信号,标志着数据传输的结束

            数据位:5~9位的数据位

            奇偶校验位:占用一位数据位,用于标识这段数据的高电平数是否符合奇数或者偶数,可以不启用

            起始位:低电平脉冲,宣告数据传输开始,是接收方同步的触发信号。
            停止位:高电平脉冲,标志帧结束,保证线路回归空闲状态并为下一帧准备。

            第3题(为什么使用UART总线通信时需要设置波特率,通信双方波特率不一致会出现什么情况?)

            为什么使用UART总线通信时需要设置波特率,通信双方波特率不一致会出现什么情况?

            参考答案:

            =====================================================================

            设置波特率:
            01.UART是异步通信,波特率用于替代时钟同步,同步两端的通信时序。
            02.发送端按此刻度输出数据,接收端依此采样信号,确保数据位对齐
            03.抗干扰与稳定性保障:(波特率与传输距离、噪声环境强相关)
            04.较低波特率(如9600 bps)在长距离或高噪声环境中更稳定,因每比特持续时间长,抗干扰能力强;高速波特率(如115200 bps)则需短距离低干扰环境.

            波特率不一致:

            传输数据错位导致数据丢失。
            严重错位导致通信断连。UART允许的波特率误差通常需 ≤3%。

            第4题(你常用的串口协议格式是什么)

            你常用的串口协议格式是什么??

            参考答案:

            =====================================================================

            常用的串口协议格式:8N1协议 + 9600

            第5题(请写出你了解的串行接口 )

            请写出你了解的串行接口 ??

            参考答案:

            =====================================================================

            UART总线、IIC总线、SPI总线、USB总线

            模拟面试_IIC总线

            第1题(IIC总线 )

            你使用过IIC总线吗?使用在哪里?对IIC总线的了解有哪些?

            参考答案:

            =====================================================================

            IIC总线一般用于
            01.数据采集设备,比如温湿度传感器,压力传感器等
            02.显示设备控制,OLED、LCD屏幕驱动
            03.双MCU之间的通信

            了解自由发挥
            01.半双工,同步,串口通信总线
            02.多主多从,一般一主多从
            03.低成本,低功耗
            04.双线设计,硬件电路简单

            第2题(IIC总线的硬件连接?硬件连接的特点?)

            IIC总线的硬件连接?硬件连接的特点?

            参考答案:

            =====================================================================

            IIC总线是双线设计,一根SCL串行时钟线,一根SDA串行数据线
            特点:开漏设计,双线均连接上拉电阻,空闲时保持高电平(利于传输的稳定性)

            第3题(IIC总线的时序是什么样子?)

            IIC总线的时序是什么样子?描述一下IIC总线的数据传输信号的时序?

            参考答案:

            =====================================================================

            时序框架:

            空闲保持高电平

            起始信号,SCL处于高电平时,SDA产生下降沿信号

            停止信号,SCL处于高电平时,SDA产生上升沿信号

            数据传输:

            SCL处于高电平时,电平不允许变化,此时读取数据

            SCL处于低电平时,允许电平变化,此时写入数据

            每次传输8字节数据,必然跟随一个对端的应答信号(ACK/NACK)

            第4题(使用IIC总线接收数据时,需要注意哪些问题??)

            使用IIC总线接收数据时,需要注意哪些问题?

            参考答案:

            =====================================================================

            01.接收数据时,先保证发出发送数据信号,告诉从机需要接收数据的起始地址,
            02.再发出接收数据信号,接收数据,每次接收数据回复ACK包
            03.在结束数据接收时,确保发出NACK包,这样从机才会结束发送数据

            第5题(使用IIC读取数据时,为什么需要读写转换?为什么需要发送NACK非应答信号???)

            使用IIC读取数据时,为什么需要读写转换?为什么需要发送NACK非应答信号?

            01.读取数据前,需要向从机发送读取时的起始地址,方便后续读取时从该地址开始读取数据
            02.读取数据结束后,确保主机发送从机NACK信号,以便结束从机的数据发送
            03.不进行该操作会导致从机数据继续发送,干扰后续的数据传输操作(导致数据错乱)

            本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
            如若转载,请注明出处:http://www.pswp.cn/diannao/95198.shtml
            繁体地址,请注明出处:http://hk.pswp.cn/diannao/95198.shtml
            英文地址,请注明出处:http://en.pswp.cn/diannao/95198.shtml

            如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

            相关文章

            分享一款基于STC8H8K32U-45I-LQFP48单片机的4路数字量输入输出模块

            4路数字量输入输出模块产品说明产品特性输入部分&#xff1a; 4路光耦隔离数字量输入通道支持NPN和PNP两种输入方式&#xff0c;可通过拨码开关切换输入电压范围&#xff1a;10-30VDC典型应用&#xff1a;可连接按钮开关、接近开关、光电传感器等数字信号设备输出部分&#xff…

            redis常见的性能问题

            Redis 的性能问题通常源于配置不当、数据结构误用、资源瓶颈或架构缺陷。以下是 Redis 常见的性能问题及优化方案&#xff0c;结合线上经验整理&#xff1a;&#x1f9e0; ​一、内存相关问题​​1. 内存不足&#xff08;OOM&#xff09;​​​现象​&#xff1a;OOM errors、响…

            Blender 基础操作

            基础操作 一、视角控制 ①旋转视角 &#xff1a; 拖动鼠标中键 ②平移视角 &#xff1a; shift 鼠标中键 ③放大\缩小 &#xff1a;鼠标滚轮 二、物体控制 1、重要 ① 移动物体 : G ② 旋转物体 : R ③ 缩放物体 : S 2、不重要 ④ 新建物体 : shift A ⑤ 复制物体 : shift D…

            Go 语言三大核心数据结构深度解析:数组、切片(Slice)与映射(Map)

            &#x1f680;Go 语言三大核心数据结构深度解析&#xff1a;数组、切片&#xff08;Slice&#xff09;与映射&#xff08;Map&#xff09; 在 Go 语言的开发领域&#xff0c;数组、切片与映射 这三大核心数据结构犹如构建程序的基石&#xff0c;支撑着各类数据的存储与处理。它…

            《Webpack与Vite热模块替换机制深度剖析与策略抉择》

            从早期简单的文件合并工具,到如今功能强大、高度自动化的Webpack和Vite,它们重塑了前端开发的流程与效率。而热模块替换(HMR, Hot Module Replacement)机制,作为其中关键的一环,更是成为开发者提升开发体验、加速项目迭代的秘密武器。Webpack,作为前端构建领域的先驱者,…

            虚拟乐队“天鹅绒落日”:AI生成音乐引发的行业风暴

            引言近日&#xff0c;音乐行业掀起了一阵关于一支名为“The Velvet Sundown”&#xff08;天鹅绒落日&#xff09;乐队的新闻热潮。原因何在&#xff1f;这支乐队很可能并非真正的乐队&#xff0c;其音乐也或许是由人工智能生成的。事实上&#xff0c;越来越多的共识认为&#…

            c++ final override 关键字

            1.finalfinal 防止子类继承&#xff0c;用于类或虚函数&#xff0c;限制继承或重写class Base final {}; // Base类不能被继承class Base { public:virtual void foo() final; // 禁止子类重写foo() };2.overrideoverride 子类中重写父类中函数&#xff0c;&#xff0c;仅用于…

            剑桥大学最新研究:基于大语言模型(LLM)的分子动力学模拟框架,是MD的GPT时刻还是概念包装?

            近期&#xff0c;剑桥大学 Michele Vendruscolo 团队在预印本平台上发布了一项最新研究&#xff0c;提出了一个名为 MD-LLM 的框架&#xff0c;旨在为高效研究蛋白质动态提供一种全新的思路。简单来说&#xff0c;他们希望借助大语言模型&#xff08;LLM&#xff09;&#xff0…

            MySQL梳理:其他

            MySQL数据库技术知识合集&#xff0c;涵盖InnoDB存储引擎的区管理机制、缓冲池机制等核心技术要点。本文档将持续补充MySQL相关的重要技术知识点。 &#x1f4cb; 目录 模块一&#xff1a;InnoDB区状态管理机制 1.1 核心设计思想1.2 四种区状态详解1.3 渐进式空间分配策略1.4…

            影刀 —— 飞书电子表格

            以获取列上第一个可用行为例我们需要获取的分别是 凭证 和 表格唯一标识首先来看如何获取凭证在飞书开发者后台创建应用然后添加权限发版拿App ID 和 App Secret下面来创建电子表格&#xff01;&#xff01;&#xff01;注意这个表格一定不要创建到知识库里面如果创建到知识库里…

            1.二维图像处理(完整版)

            目录 1.变换矩阵 2.在矩阵的基础上添加各种变换形式 3.开始变换 4.计算变换矩阵参数 新算子 二、阈值分割 新算子 三、blob分析案例 1.焊点 2.石头 3.木材 4.车牌 5.骰子 新算子 四、傅里叶变换频域分析 问题一 五、滤波处理 1.均值滤波 2.中值滤波 3.高斯…

            【linux基础】Linux 文本处理核心命令指南

            Linux 文本处理核心命令指南 文本处理是 Linux 系统管理的核心能力&#xff0c;约 80% 的配置文件操作都依赖于文本处理技术。本指南详细讲解 echo、重定向、cat、grep、wc 和 vim 等关键命令&#xff0c;涵盖从基础操作到高级技巧的完整知识体系&#xff0c;并配有实用案例演示…

            基于深度学习YOLOv12的草莓成熟度检测系统(YOLOv12+YOLO数据集+UI界面+登录注册界面+Python项目源码+模型)https://www.bilibili.com/video/BV1

            一、项目介绍 本项目构建了一套基于深度学习 YOLOv12 的草莓成熟度识别检测系统&#xff0c;旨在实现对草莓在不同成熟阶段的高精度、实时检测与分类。系统采用 YOLO 格式数据集&#xff0c;将草莓分为 3 个类别&#xff1a;生&#xff08;raw&#xff09;、半熟&#xff08;tu…

            深入理解Android Kotlin Flow:响应式编程的现代实践

            引言在现代Android开发中&#xff0c;处理异步数据流是一个核心需求。Kotlin Flow作为协程库的一部分&#xff0c;提供了一种声明式的、可组合的异步数据流处理方式。本文将深入探讨Flow的设计理念、核心组件、高级用法以及在实际项目中的最佳实践。一、Flow基础概念1.1 什么是…

            功能测试详解

            &#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 一、测试项目启动与研读需求文档&#xff08;一&#xff09; 组建测试团队1、测试团队中的角色2、测试团队的基本责任尽早地发现软件程序、系统或产品中所有的问题…

            算法73. 矩阵置零

            给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用原地算法。 示例 1&#xff1a;输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]] 示例2&#xff1a; 输入&#xf…

            【力扣22】括号生成

            数字n代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且有效的括号组合。 源代码&#xff1a; class Solution { public:int n;vector<string> ans;string path;vector<string> generateParenthesis(int n) {this->n n;d…

            ELK分布式日志采集系统

            * 系统架构&#xff1a;filebeat 采集各服务器日志&#xff1b;Logstash-docker 过滤整理日志&#xff1b; Elasticsearch-docker 存储和索引数据&#xff1b; Kibana-docker 提供可视化展示和操作。* FileBeat简介&#xff1a;Filebeat是本地文件的日志数据采集器。* Kafka简介…

            Python生产环境部署指南:专业级应用启动方案

            在生产环境中部署Python应用需要考虑稳定性、性能和安全性。本文将详细介绍多种专业部署方案,助你构建可靠的生产环境。 一、核心部署架构 标准Python生产环境包含三个核心组件: 应用服务器:运行Python代码(Gunicorn/uWSGI/Uvicorn) 进程管理器:保障服务持续运行(Supe…

            C语言:结构体、共用体与枚举详解

            在 C 语言编程中&#xff0c;结构体&#xff08;struct&#xff09;、共用体&#xff08;union&#xff09;与枚举&#xff08;enum&#xff09;是三种非常重要的用户自定义数据类型。它们能帮助我们更好地组织、管理和表达复杂的数据结构。本文将结合实例&#xff0c;深入介绍…