C++ 八股文(性能优化)

1. 如何优化 C++ 中的算法复杂度?

1. 选择合适的数据结构: 根据操作频率选择容器,频繁查找用unordered_map时间O(1)而不是map的O(log n),频繁插入删除用list而不是vector,需要排序用set或priority_queue,空间换时间用哈希表缓存计算结果。

2. 算法优化技巧: 避免嵌套循环降低时间复杂度,使用双指针、滑动窗口减少遍历次数,二分查找将O(n)降到O(log n),动态规划避免重复计算,分治算法降低复杂度如归并排序。

3. 提前终止和剪枝: 找到结果立即返回不继续遍历,使用短路求值&&和||,循环中使用break减少无效迭代,递归中剪枝避免无效分支,缓存中间结果避免重复计算。

4. 空间时间权衡: 使用额外空间换取时间如哈希表,预计算结果存储查找表,使用位运算优化空间和时间,lazy evaluation延迟计算,批量处理减少函数调用开销。

// 优化前:O(n²)
for(int i = 0; i < n; i++)
    for(int j = 0; j < n; j++)
        if(arr[i] + arr[j] == target) return true;

// 优化后:O(n)使用哈希表
std::unordered_set<int> seen;
for(int x : arr) {
    if(seen.count(target - x)) return true;
    seen.insert(x);
}

2. C++ 中如何优化循环和内存访问模式?

1. 缓存友好访问: 按行优先顺序访问二维数组利用空间局部性,连续内存访问提高缓存命中率,避免跨步访问stride access,数组优于链表因为内存连续,结构体成员按访问频率排列热数据放一起。

2. 循环优化技巧: 循环不变量外提减少重复计算,循环展开减少分支判断开销,循环融合合并多个循环减少遍历次数,循环交换改变嵌套顺序优化缓存,使用迭代器而不是索引访问。

3. 减少分支预测失败: 避免循环内的条件判断,使用位运算代替if-else,数据排序后处理减少分支,使用查找表代替复杂条件,编译器likely/unlikely提示优化分支。

4. 向量化和并行: 使用SIMD指令并行处理,编译器自动向量化需要满足条件,OpenMP并行化循环,使用std::transform等算法库,避免循环依赖使能并行。

// 缓存不友好:按列访问
for(int j = 0; j < cols; j++)
    for(int i = 0; i < rows; i++)
        sum += matrix[i][j];

// 缓存友好:按行访问
for(int i = 0; i < rows; i++)
    for(int j = 0; j < cols; j++)
        sum += matrix[i][j];

// 循环展开
for(int i = 0; i < n; i += 4) {
    sum += arr[i] + arr[i+1] + arr[i+2] + arr[i+3];
}

3. 如何使用缓存优化提高 C++ 程序性能?

1. 缓存层次理解: L1缓存最快但最小约32KB,L2缓存较大约256KB,L3缓存更大约8MB但较慢,主内存最慢,缓存行通常64字节,理解缓存工作原理才能有效优化。

2. 数据局部性优化: 时间局部性短时间内重复访问同一数据,空间局部性访问相邻地址的数据,数组优于链表因为连续存储,结构体数组SoA优于数组结构体AoS提高向量化,热数据集中存储冷数据分离。

3. 避免false sharing: 多线程访问不同变量但在同一缓存行导致缓存失效,使用alignas(64)对齐到缓存行,在结构体中添加padding分隔热数据,使用thread_local避免共享,每个线程独立的数据结构。

4. 预取和预分配: 使用__builtin_prefetch预取数据到缓存,容器使用reserve预分配避免重新分配,批量处理数据提高缓存利用率,循环分块tiling优化缓存使用,减少工作集大小适应缓存。

// 避免false sharing
struct alignas(64) ThreadData {  // 缓存行对齐
    int counter;
    char padding[60];  // 填充到64字节
};

// 循环分块优化缓存
const int BLOCK = 64;
for(int ii = 0; ii < n; ii += BLOCK)
    for(int jj = 0; jj < n; jj += BLOCK)
        for(int i = ii; i < ii+BLOCK; i++)
            for(int j = jj; j < jj+BLOCK; j++)
                C[i][j] += A[i][j] * B[i][j];

4. C++ 中如何避免不必要的内存拷贝?

1. 使用移动语义: 返回值使用std::move转移所有权,函数参数使用右值引用接收临时对象,容器插入使用emplace_back而不是push_back,std::move转移大对象避免拷贝,移动构造和移动赋值声明noexcept。

2. 引用传递: 函数参数使用const引用传递大对象,返回引用而不是值避免拷贝,使用std::ref包装引用传递给线程或bind,注意引用生命周期避免悬空引用,小对象按值传递大对象按引用。

3. 原地构造: 使用emplace系列函数原地构造对象,避免先构造再拷贝的两步操作,容器使用reserve预分配避免扩容拷贝,使用placement new在指定位置构造,std::optional和std::variant原地构造。

4. 返回值优化: 编译器RVO返回值优化自动消除拷贝,NRVO命名返回值优化,返回局部对象不要使用std::move阻止RVO,C++17保证拷贝消除,返回大对象时信任编译器优化。

// 避免拷贝:使用移动
std::vector<int> create() {
    std::vector<int> v(1000);
    return v;  // RVO或移动,不拷贝
}

// 避免拷贝:引用传递
void process(const std::string& str) {}  // 不拷贝

// 避免拷贝:原地构造
vec.emplace

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

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

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

全部评论
点赞 回复 分享
发布于 昨天 09:12 上海

相关推荐

1.&nbsp;什么是软件测试?核心目标是什么?2.&nbsp;黑盒测试和白盒测试有什么区别?3.&nbsp;什么是冒烟测试?什么时候执行?4.&nbsp;回归测试的目的是什么?如何确定回归范围?5.&nbsp;兼容性测试一般从哪些方面考虑?6.&nbsp;如何设计一个注册功能的测试用例?7.&nbsp;常用的测试用例设计方法有哪些?请举例说明。8.&nbsp;测试流程包含哪些关键阶段?9.&nbsp;什么是测试用例评审?为什么重要?10.&nbsp;测试计划通常包括哪些内容?11.&nbsp;Bug&nbsp;的生命周期是怎样的?12.&nbsp;提交一个缺陷报告需要包含哪些信息?13.&nbsp;开发不认可你提交的&nbsp;Bug,你会怎么处理?14.&nbsp;你用过哪些缺陷管理工具?比如&nbsp;Jira&nbsp;或禅道?15.&nbsp;接口测试一般关注哪些点?16.&nbsp;你用过&nbsp;Postman&nbsp;吗?如何做接口自动化或参数化?17.&nbsp;如何判断一个接口是否返回正确?18.&nbsp;是否有自动化测试经验?使用过哪些框架或语言?19.&nbsp;自动化测试适合用在哪些场景?哪些不适合?20.&nbsp;如何用&nbsp;SQL&nbsp;查询订单表中金额大于1000的用户订单?21.&nbsp;常用的&nbsp;Linux&nbsp;命令有哪些?如何实时查看日志?22.&nbsp;如何查看某个端口是否被占用?23.&nbsp;你在项目中遇到印象最深的&nbsp;Bug&nbsp;是什么?怎么定位的?24.&nbsp;如果上线前发现一个高危&nbsp;Bug,但&nbsp;deadline&nbsp;不允许延期,你会怎么做?25.&nbsp;你怎么理解“测试左移”和“测试右移”?26.&nbsp;为什么选择做软件测试?你的职业规划是什么?27.&nbsp;你了解持续集成(CI)吗?测试在其中起什么作用?28.&nbsp;如何保证测试覆盖率?29.&nbsp;Web&nbsp;测试和&nbsp;App&nbsp;测试的主要区别是什么?30.&nbsp;如何做弱网测试?有哪些工具?
查看30道真题和解析
点赞 评论 收藏
分享
评论
1
7
分享

创作者周榜

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