分享C++程序员面试八股文(三)

以下是更多 C++ 常见八股文内容:

一、智能指针相关

  1. 请解释一下 C++ 中的智能指针

    • 智能指针是一种用于管理动态分配对象的资源的类。它的主要目的是自动管理对象的生命周期,防止内存泄漏、悬空指针等问题。
    • 在 C++ 中,主要有std::unique_ptrstd::shared_ptrstd::weak_ptr三种智能指针。
    • std::unique_ptr

      • 独占式拥有对象。一个对象只能被一个std::unique_ptr所指向。
      • std::unique_ptr被销毁时(例如离开作用域),它所指向的对象会被自动删除。
      • 例如:
        std::unique_ptr<int> up = std::make_unique<int>(5);
        // 不需要手动释放内存,当up离开作用域时,所指向的int对象会被自动删除

      • std::shared_ptr

        • 采用引用计数的方式来管理对象。多个std::shared_ptr可以指向同一个对象。
        • 当最后一个指向对象的std::shared_ptr被销毁时,对象才会被删除。
        • 例如:
          std::shared_ptr<int> sp1 = std::make_shared<int>(10);
          std::shared_ptr<int> sp2 = sp1;
          // 此时有两个shared_ptr指向同一个int对象,引用计数为2
          // 当sp1和sp2都离开作用域后,int对象才会被删除

        • std::weak_ptr

          • 是一种弱引用,它不控制对象的生命周期。主要用于解决std::shared_ptr循环引用的问题。
          • 例如,在一个双向链表结构中,如果两个节点都用std::shared_ptr相互指向对方,会导致引用计数永远不为 0,造成内存泄漏。使用std::weak_ptr可以打破这种循环引用。
          • 可以通过std::weak_ptrlock方法获取对应的std::shared_ptr,如果对象已经被销毁,lock返回nullptr
        • std::shared_ptr的循环引用是怎么回事?如何解决?

          • 循环引用问题
            • 当有两个或多个对象通过std::shared_ptr相互引用时,就可能出现循环引用的情况。
            • 例如,考虑如下类定义:
class A;
class B;

class A {
public:
    std::shared_ptr<B> b;
    A() {}
    ~A() {}
};

class B {
public:
    std::shared_ptr<A> a;
    B() {}
    ~B() {}
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->b = b;
    b->a = a;
    // 此时a和b相互引用,它们的引用计数永远不会降为0,导致内存泄漏
    return 0;
}
  • 解决方法
    • 使用std::weak_ptr来打破循环引用。将其中一个std::shared_ptr改为std::weak_ptr
    • 修改后的代码如下:
class A;
class B;

class A {
public:
    std::weak_ptr<B> b;
    A() {}
    ~A() {}
};

class B {
public:
    std::shared_ptr<A> a;
    B() {}
    ~B() {}
};

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    std::shared_ptr<B> b = std::make_shared<B>();
    a->b = b;
    b->a = a;
    // 此时不会出现内存泄漏,因为std::weak_ptr不增加引用计数
    return 0;
}

 

二、异常处理相关

  1. 请简述 C++ 中的异常处理机制

    • C++ 中的异常处理通过try - catch语句块实现。
    • try块中放置可能抛出异常的代码。例如:
      try {
          int num1 = 10;
          int num2 = 0;
          if (num2 == 0) {
              throw std::runtime_error("除数不能为0");
          }
          int result = num1 / num2;
      } catch (const std::runtime_error& e) {
          std::cout << "捕获到异常: " << e.what() << std::endl;
      }

    • try块中的代码抛出异常时,程序会立即停止try块中当前代码的执行,并开始在try块后面的catch块中查找匹配的异常类型。
    • 如果找到匹配的catch块(异常类型匹配或者是异常类型的基类),则执行catch块中的代码来处理异常。
    • 还可以有多个catch块来捕获不同类型的异常。例如:
      try {
          // 可能抛出不同类型异常的代码
      } catch (const std::runtime_error& e) {
          // 处理runtime_error类型的异常
      } catch (const std::logic_error& e) {
          // 处理logic_error类型的异常
      } catch (...) {
          // 捕获所有其他类型的异常
      }

    • 在函数中抛出异常时需要注意什么?

      • 函数的异常声明:在 C++ 中,可以在函数声明中使用noexcept关键字表示函数不会抛出异常,或者列出函数可能抛出的异常类型。例如:
        void func1() noexcept;
        void func2() throw(std::runtime_error, std::logic_error);

      • 资源管理:如果函数在抛出异常前分配了资源(如动态内存、文件句柄等),需要确保在异常抛出时这些资源能够被正确释放。一种常见的方法是使用 RAII(Resource Acquisition Is Initialization)技术,例如通过智能指针或类的构造函数和析构函数来管理资源。
      • 异常安全:函数应该具有一定的异常安全性。例如,基本的异常安全保证是如果函数在执行过程中抛出异常,程序的状态不会被破坏(例如不会出现内存泄漏、数据结构处于不一致的状态等)。更高级的异常安全保证还包括强异常安全(操作如果失败,程序状态会回滚到操作之前的状态)等。

三、模板相关

  1. 请解释函数模板和类模板的实例化过程

    • 函数模板实例化

      • 函数模板是一种通用的函数定义,可以用于生成针对不同类型的函数实例。
      • 例如,有如下函数模板:
        template <typename T>
        T add(T a, T b) {
            return a + b;
        }

      • 当调用add(1, 2)时,编译器会根据实参的类型(这里是int)自动实例化一个int版本的add函数,就好像有一个如下定义的函数:
        int add(int a, int b) {
            return a + b;
        }

      • 如果调用add(1.0, 2.0),则会实例化一个double版本的add函数。
      • 类模板实例化

        • 类模板定义了一个通用的类结构,在使用类模板时需要实例化。
        • 例如,有类模板:
          template <typename T, int N>
          class Array {
          public:
              T data[N];
          };

        • 当使用Array<int, 5>时,编译器会实例化一个特定的类,其中T被替换为intN被替换为5。这个实例化后的类就像一个普通的类一样,可以创建对象,如Array<int, 5> arr;
        • 什么是模板特化?为什么要使用模板特化?

          • 定义
            • 模板特化是指为特定的类型或一组类型定制模板的实现。
            • 例如,对于一个通用的compare函数模板:
              template <typename T>
              bool compare(T a, T b) {
                  return a < b;
              }

            • 如果要为char*类型提供特殊的比较逻辑(比较字符串内容而不是指针地址),可以进行模板特化:
              template <>
              bool compare<char*>(char* a, char* b) {
                  return strcmp(a, b) < 0;
              }

            • 使用原因

              • 当通用的模板实现对于某些特定类型不能提供合适的行为时,就需要模板特化。
              • 例如,在处理容器类型时,对于vectorlist可能需要不同的算法实现,尽管它们都是容器,但内部结构不同(vector是连续存储,list是链表结构),此时可以通过模板特化来针对vectorlist分别提供高效的算法实现。

四、STL(标准模板库)相关

  1. vectorlist在插入和删除操作上有什么区别?

    • 插入操作

      • vector
        • vector的末尾插入元素(使用push_back)通常是一个很快的操作,时间复杂度为 (平均情况,不考虑动态扩容)。
        • 但是在vector的中间或开头插入元素(使用insert)可能会比较慢,因为需要移动插入位置之后的所有元素来为新元素腾出空间,时间复杂度为 ,其中  是vector的大小。
      • list
        • list中任何位置插入元素(使用insert或者push_frontpush_back)的时间复杂度都是 ,因为list是双向链表结构,插入操作只需要修改几个指针即可。
    • 删除操作

      • vector
        • vector的末尾删除元素(使用pop_back)时间复杂度为 。
        • vector中间或开头删除元素(使用erase)需要移动删除位置之后的所有元素,时间复杂度为 。
      • list
        • list中删除任何位置的元素(使用erase或者pop_frontpop_back)的时间复杂度都是 ,同样是因为只需要修改几个指针。
  2. mapunordered_map的区别是什么?

    • 数据结构

      • map
        • map是基于红黑树(一种自平衡二叉查找树)实现的关联容器。
        • 它的元素是按照键值自动排序的,在插入和查找操作时,时间复杂度为 ,其中  是容器中的元素个数。
      • unordered_map
        • unordered_map是基于哈希表实现的关联容器。
        • 它不保证元素的顺序,插入和查找操作在平均情况下时间复杂度为 ,但在最坏情况下(哈希冲突严重时)可能会退化为 。
    • 迭代器遍历

      • map
        • 由于map中的元素是有序的,通过迭代器遍历map时,元素是按照键值的顺序访问的。
      • unordered_map
        • 遍历unordered_map时,元素的访问顺序是无序的,取决于哈希表中元素的存储位置。
    • 键值要求

      • map
        • 对于map,键值类型必须定义了小于运算符(<),因为红黑树的操作依赖于元素的顺序比较。
      • unordered_map
        • 对于unordered_map,键值类型需要定义哈希函数和相等比较运算符(==),以便在哈希表中进行元素的存储和查找操作。

 

 喜欢的同学可以点点关注!咱们下期再分享更干货的知识!

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值