八股文-0828

知识点列表

  1. shared_ptr和weak_ptr的区别与联系
  2. 引用作为函数参数的好处
  3. 模版在什么时候进行实例化
  4. 为什么模板类一般都是放在一个 h 文件中
  5. define 宏定义和 const,static 的区别
  6. new 和 malloc 申请的是哪里的内存?如何减少内存碎片?
  7. 实际情况中想用 new 分配内存但又不确定大小怎么做?

1.shared_ptr和weak_ptr的区别与联系

shared_ptr能够增加引用计数,而weak_ptr不能增加引用计数,所以shared_ptr能够控制资源的生命周期,而weak_ptr不能。

shared_ptr由于循环引用会造成内存泄漏的问题,而weak_ptr可以打破循环引用,防止内存泄漏

比如:Student 类有一个 std::vector<std::shared_ptr<Course>> 来存储学生注册的课程。

Course 类有一个 std::vector<std::weak_ptr<Student>> 来存储选修该课程的学生

如果 Course 也使用 std::shared_ptr<Student>,那么 Student 和 Course 之间会形成循环引用,导致它们无法被正确释放。

2.引用作为函数参数的好处

函数参数避免了参数的直接拷贝;能够直接修改对象;传递引用和直接使用对象在语法上保持一致;对于大对象,使用引用的传递效率更高;避免了空指针的问题,提高了代码的安全性;在使用基类指针或引用时,引用可以用于实现多态性。通过引用,可以在运行时调用派生类的实现,而不需要知道具体的对象类型。

3.模版在什么时候进行实例化

模板一般会被编译两次。第一次是检查基础的语法;第二次是在被显式调用时实例化。例如,当调用模板函数或创建模板类的实例时,编译器会生成相应的代码,这一过程被称为模板的实例化。

因此,模板的实例化也可以被称为第二次编译,它生成了拥有具体类型的代码。

4.为什么模板类一般都是放在一个 .h或.hpp 文件中

这个问题是说为什么要将模板的声明和实现都放在一个.h或.hpp文件中。这是在对一个存在模版的文件进行编译的时候,对于模版的实例化,编译器需要知道模版的定义和实现;如果模版的定义和实现分开,编译器可能无法在链接的时候找到实现

对于非模版的函数,编译器会在编译阶段将.cpp文件生成.o(.obj)文件,每一个源文件会被编译成一个独立的目标文件,编译器在这个阶段会:

  • 解析函数和类的声明。
  • 确保所有函数和类的使用都能找到相应的声明。

然而,在这个阶段,编译器并不会检查函数的实现是否存在,它只关注声明。

在链接阶段,链接器负责将所有目标文件和库文件组合成一个可执行文件或共享库。链接器的工作流程如下:

  • 符号解析:链接器会查找所有目标文件中声明的符号(例如函数和变量)。每个目标文件都包含符号表,其中列出了该文件中定义的所有符号及其地址。
  • 符号匹配:链接器会将不同目标文件中的符号进行匹配。例如,如果某个目标文件中调用了一个函数,而该函数在另一个目标文件中被定义,链接器会找到这个定义并将其链接到调用处。
  • 处理未定义符号:如果链接器在所有目标文件中找不到某个符号的定义,它会报告一个链接错误,提示该符号未定义。

5.define 宏定义和 const,static 的区别

define宏定义是通过预处理器在编译时进行简单的文本替换,缺乏类型检查,所以会导致无法设置断点从而调试困难,全局有效

const用于定义一个常量,本质上还是一个变量,只作用于作用域中,编译器视作为常量,所以无法被修改

static是静态存储的变量,生命周期贯穿整个程序,可以进行类型检查和设置断点

6.new 和 malloc 申请的是哪里的内存?如何减少内存碎片?

new和malloc申请的都是堆区的内存,malloc不会调用构造函数,需要手动初始化,而new会调用构造函数进行初始化,

new返回指定类型的指针,而malloc返回的是void*类型,new会在分配失败时抛出异常,malloc分配失败时返回NULL

减少内存碎片的方法:使用内存池,按需分配大块内存,避免频繁的分配和释放操作,合并相邻的空闲块

7.实际情况中想用 new 分配内存但又不确定大小怎么做?

1)确定最大可能的大小:预先确定一个最大可能的大小,然后用 new 分配内存。 (2)动态调整大小:使用 std::vector 或 std::unique_ptr 等动态容器,根据需要动态调整内存 大小。 (3)使用类型擦除和多态:使用基类指针指向派生类对象,动态确定对象的实际类型和大小。