秋招日寄|嵌入式模拟面试拷打|20240918

1. push_back() 左值和右值的区别是什么?

左值:当传递一个左值给 push_back() 时,push_back 会调用拷贝构造函数,将对象复制到 vector 的最后一个位置。

右值:当传递一个右值给 push_back() 时,如果存在右值引用版本的 push_back(),它会调用移动构造函数,避免不必要的拷贝,从而提高性能。

深入分析:左值和右值的处理方式是 C++ 的核心特性之一,特别是在资源管理和性能优化时,移动语义的引入极大提高了容器类(如 vector)的效率。对资源占用较高的对象使用右值引用和移动构造,可以避免深拷贝。

2. std::move 底层是如何实现的?

std::move 是一个模板函数,它将对象强制转换为右值引用。这并不会真正移动对象的内容,只是通过将左值转换为右值引用,使该对象可以绑定到右值引用参数中。

深入分析:std::move 的核心作用是配合移动语义使用,它不会改变对象的状态或位置,只是将对象的类型转换为右值,以便可以调用相关的移动操作。在资源管理中,std::move 能减少不必要的内存开销。

3. 完美转发的原理是什么?

完美转发允许函数模板将参数原封不动地传递给另一个函数。其核心在于 std::forward,它结合模板参数推导和右值引用,通过判断传入参数是左值还是右值,确保传递时不改变参数的值类别。

深入分析:完美转发的实现依赖于引用折叠规则。通过 std::forward 保持参数的原始类型,避免不必要的拷贝或移动。它是泛型编程中非常有用的工具,能够在编写高效库代码时发挥重要作用。

4. 空类中有什么函数?

空类中默认包含以下函数:

  • 默认构造函数
  • 拷贝构造函数
  • 移动构造函数(C++11 及以上)
  • 拷贝赋值运算符
  • 移动赋值运算符(C++11 及以上)
  • 析构函数

深入分析:即便空类没有显式定义成员函数,编译器仍会为其生成默认的构造函数和赋值操作符。C++11 后引入的移动构造函数和移动赋值运算符极大提高了对象的灵活性。

5. explicit 用在哪里?有什么作用?

explicit 关键字用于构造函数转换运算符,它的作用是防止隐式转换,以避免意外调用构造函数或类型转换。

深入分析:隐式转换可能导致代码中发生未预期的行为。通过显式声明构造函数为 explicit,可以减少这种不必要的错误,特别是在复杂的类层次中防止意外的自动转换。

6. 成员变量初始化的顺序是什么?

成员变量的初始化顺序是按照它们在类定义中的声明顺序进行,而不是按照初始化列表中的顺序。

深入分析:编译器严格遵循声明顺序来初始化成员变量,而不是构造函数的初始化列表顺序。这可能会在成员变量依赖于其他变量的情况下引发问题,因此需要谨慎管理初始化顺序。

7. 指针占用的大小是多少?

指针的大小取决于平台的位数。在 32 位系统上通常为4 字节,在 64 位系统上为8 字节

深入分析:指针大小与系统架构直接相关,在处理跨平台代码时需要注意,尤其在内存对齐和大小计算时。指针大小也会影响到数据结构和程序性能。

8. 什么是野指针和内存泄漏?如何避免?

野指针: 指向已经释放或未分配内存的指针。内存泄漏: 分配的内存没有被释放,导致该内存无法再被使用。

避免方法:

  • 初始化指针
  • 使用智能指针(如 std::unique_ptrstd::shared_ptr
  • 确保在合适的地方释放内存

深入分析:野指针和内存泄漏是 C++ 开发中常见的内存管理问题。智能指针的引入使得内存管理变得更加安全,避免了手动管理的复杂性。

9. mallocnew 的区别是什么?

  • malloc 只分配内存,但不调用构造函数,必须手动指定内存大小。
  • new 不仅分配内存,还会调用构造函数,并且自动计算所需大小。

深入分析:mallocnew 的最大区别在于后者还涉及对象的构造。现代 C++ 中更推荐使用 new 和智能指针,而不再手动使用 malloc,这能避免手动管理对象生命周期的问题。

10. 多线程会发生什么问题?线程同步有哪些手段?

问题:

  • 数据竞争
  • 死锁
  • 饥饿
  • 竞态条件

同步手段:

  • 互斥锁(std::mutex
  • 条件变量(std::condition_variable
  • 原子操作(std::atomic
  • 信号量

深入分析:线程同步是保证多线程环境下数据一致性的关键,合理使用同步机制可以避免数据竞争和死锁等问题。同时,应尽量减少锁的使用频率,以提高程序性能。

11. 什么是 STL?

STL(标准模板库)是 C++ 标准库的一部分,提供了常用的数据结构、算法和迭代器,为开发者提供了一套通用、复用性强的组件。

深入分析:STL 的引入大大提高了 C++ 的开发效率,开发者可以通过 STL 提供的容器、算法等组件快速实现高效的代码,同时保证代码的安全性和可维护性。

12. 对比迭代器和指针的区别

迭代器: 更通用,能够访问不同容器的元素,并提供丰富的操作接口,如 ++--* 等。指针: 是一种特化的迭代器,通常用于数组或原生指针操作,功能相对较单一。

深入分析:迭代器抽象了容器操作,提供统一接口,支持 STL 中所有容器。而指针仅限于操作原始内存块。迭代器的灵活性使得其在泛型编程中不可或缺。

13. 线程有哪些状态?线程锁有哪些?

线程状态:

  • 新建
  • 就绪
  • 运行
  • 阻塞
  • 终止

线程锁:

  • 互斥锁(std::mutex
  • 读写锁(std::shared_mutex
  • 自旋锁

深入分析:多线程编程中,线程的状态和锁的选择直接影响程序的执行效率和数据一致性。自旋锁适合短时间锁定的场景,而读写锁适合读多写少的场景。

14. mapunordered_map 的区别

  • map 是基于红黑树实现的有序关联容器,键按顺序排列。
  • unordered_map 是基于哈希表实现的无序关联容器,通过哈希值访问键值对。

深入分析:map 提供有序性和对数复杂度的查找,而 unordered_map 则提供平均 O(1) 的查找效率。选择哪种容器取决于具体应用中是否需要键的有序性。

15. push_back()emplace_back() 的区别及使用场景

  • push_back():将元素的副本添加到 vector 中。
  • emplace_back():在 vector 尾部直接构造元素,避免不必要的拷贝或移动操作。

深入分析:emplace_back() 更加高效,适合在性能要求较高的场景下使用,尤其是当对象的构造复杂或不希望额外的拷贝时。

#牛客创作赏金赛#
全部评论
佬 tql 我都不会
点赞 回复 分享
发布于 09-18 21:32 辽宁

相关推荐

点赞 5 评论
分享
牛客网
牛客企业服务