C++八股

1.在main执行之前和之后执行的代码可能是什么

  • main函数执行之前,主要就是初始化系统相关资源

    • 设置栈指针
    • 初始化静态static变量和global全局变量
    • 调用构造函数
  • main函数执行之后:

    • 全局对象的析构函数

2.指针和引用的区别

  • 指针是一个变量,存储的是一个地址,引用是原变量的别名

  • 指针可以为空,引用不能为NULL且在定义时必须初始化

3.在传递函数参数时,什么时候该使用指针,什么时候该使用引用呢?

  • 需要返回函数内局部变量的内存的时候用指针。指针作为函数参数,需要在堆上动态分配内存,在函数外部仍然可以访问这块内存。完要记得释放指针,不然会内存泄漏。
  • 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小
  • 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式

4.堆和栈的区别

  • 申请方式不同。

    • 栈由系统自动分配。
    • 堆是自己申请和释放的。
  • 申请大小限制不同。

    • 栈顶和栈底是之前预设好的,栈是向栈底扩展(从高地址向低地址扩展),大小固定

      注:栈空间耗尽(例如递归调用过深),会导致 栈溢出

    • 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。

  • 申请效率不同。

    • 栈由系统分配,速度快,不会有碎片。
    • 堆由程序员分配,速度慢,且会有碎片。

5.new / delete 与 malloc / free的异同

相同点:

  • 都可用于内存的动态申请和释放

不同点:

  • 前者是C++运算符,后者是C/C++语言标准库函数。
  • new自动计算要分配的空间大小,malloc需要手工计算。
  • new 在分配内存后会调用对象的构造函数初始化对象,在使用 delete 时则会先调用对象的析构函数再释放内存。后者均没有相关调用。
  • new是类型安全的,malloc不是。

  • new是封装了malloc,直接free不会报错,但是这只是释放内存,而不会析构对象

  • malloc和free返回的是void类型指针(void*)(必须进行类型转换),new和delete返回的是具体类型指针。

    注:虽然 newmalloc 都能用于内存的动态分配,但由于C++中面向对象特性的引入,new 提供了更高级、更安全的内存管理机制,包括自动计算内存大小、类型安全性和自动调用构造函数与析构函数等特性。

new/delete:

1
2
3
4
5
6
// 分配一个整型对象
int* pInt = new int;
*pInt = 10;

// 释放该对象
delete pInt;

malloc/free:

1
2
3
4
5
6
// 分配空间用于存储一个整型值,并将其初始化为10
int* pInt = (int*)malloc(sizeof(int));
*pInt = 10;

// 释放该对象
free(pInt);

6.new和delete是如何实现的?

new:

  • 分配内存:调用名为operator new的标准库函数
  • 初始化构造对象:调用构造函数
  • 返回指针:返回指向新分配并构造后的的对象的指针

delete:

  • 调用析构函数
  • 释放所用内存:调用名为operator delete的标准库函数释放所用内存

7.被free回收的内存是立即返还给操作系统吗?

​ 不是的,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并避免过多的内存碎片。

8.宏定义和函数有何区别?

  • 宏在编译时完成替换执行起来更快;函数调用在运行时需要跳转到具体调用函数。
  • 宏定义属于在结构中插入代码,没有返回值;函数调用具有返回值。
  • 宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。

9.宏定义和typedef区别?

  • typedef主要用于定义类型别名。

  • 宏替换发生在编译阶段之前(预处理阶段),属于文本插入替换;typedef是编译的一部分。

  • 宏不检查类型;typedef会检查数据类型。

10.strlen和sizeof区别?

  • sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得strlen是字符处理的库函数。

  • sizeof参数可以是任何数据类型或数据;strlen的参数只能是字符指针且结尾是’\0’的字符串

  • 因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。
1
2
3
4
5
6
7
8
int main(int argc, char const *argv[]){

const char* str = "name";

sizeof(str); // 取的是指针str的长度,是8
strlen(str); // 取的是这个字符串的长度,不包含结尾的 \0。大小是4
return 0;
}

11.常量指针和指针常量区别

  • 指针常量,如int const *pconst int *p,是一个指针指向一个只读变量。
  • 常量指针是一个不能给改变指向的指针,int *const p

12.数组名 a和取地址操作符 &a有什么区别?

1
2
3
4
C++
假设数组
int a[10];
int (*p)[10] = &a;

数组名 a:

  • 作为首元素的指针:指向数组第一个元素
  • 加法运算a + 1,它会根据数组元素的数据类型(在这个例子中是 int)来增加地址,a + 1 将会指向下一个 int 的位置

取地址操作符 &a:

  • 指向整个数组的指针&a + 1,它会跳过整个数组的长度(即10个 int 的大小),指向数组最后一个元素之后的位置。

13.C++中struct和class的区别

相同点:

  • 两者都拥有成员函数、公有和私有部分
  • 任何可以使用class完成的工作,同样可以使用struct完成

不同点:

  • 两者中如果不对成员不指定公私有,struct默认是公有的class则默认是私有的
  • class默认是private继承,而struct模式是public继承
  • struct一般用于描述一个数据结构的集合,而class是对一个对象数据的封装

14.C++代码生成可执行文件的过程有几个阶段?

  • 预处理: 预处理器会处理源代码中的预处理指令,如#include#define等。
  • 编译: 经过预处理后的代码翻译成特定于目标机器的汇编语言代码,这个过程进行了语法检查,每个源文件都会被单独编译成一个对象文件(.obj或.o文件)。。
  • 汇编: 汇编器将编译阶段生成的汇编代码转换为目标机器的二进制指令集
  • 链接: 链接器负责将编译阶段产生的多个对象文件合并成一个最终的可执行文件。