SLAM面试笔记(6) — C++面试题

目录

第1章 常见面试题

1 int main(int argc, char ** argv)函数中,参数argc和argv分别代表什么意思?

2 结构体和共用体的区别

3 使用共用体读写成员时需要注意什么?

4 简述C++有几种传值方式,之间的区别是什么?

5 为什么值传递不改变实参的值?

6 全局变量和局部变量的区别

7 全局变量和局部变量如何初始化?

8 请说说原码、反码、补码

9 说说数组与指针

10 说说数组和指针的区别

11 数组指针与指针数组的区别

12 指针函数与函数指针的区别

13 请说说内存分布模型

14 请你说说野指针

15 如何避免野指针

16 请你说说内存泄露

17 说说new和malloc的区别,各自底层实现原理

18 说说使用指针需要注意什么?

19 初始化为0的全局变量在bss还是data

20 知道动态链接与静态链接吗?两者有什么区别

21 说说内联函数和宏函数的区别

22 类(class)与结构(struct)的区别?

第2章 C++基础

1 C++中static静态变量有什么作用,在什么情况下会用?

2 类中的this指针指向哪里?

3 说一下const的作用。

4 std::string类型为啥不能memset?

5 emplace_back( )和push_back( )有什么区别?

6 traits是什么?什么时候用traits?

7 C++中main函数执行完后还执行其他语句吗

8 从代码到可执行文件的过程

9 extern "C" 的作用

10 什么是内联函数?

11 为什么使用内联函数?

12 内联函数使用的条件

13 什么是内存对齐?

14 为什么要字节对齐? 

15 Static作用

16 volatile和mutable

第3章 指针和引用

1 指针和引用的区别

2 为什么引用比指针安全?

3 什么是“野指针”和形成原因?

4 C++函数指针有哪几类?

5 函数指针、lambda、仿函数对象分别是什么?

6 智能指针分哪几种?std::unique_ptr, std::shared_ptr, std::weak_ptr各有何用途?

7 什么是万能引用?

8 万能引用与右值引用的区别

9 智能指针和祼指针之间的差异?为什么要用指针的引用计数?

10 悬挂指针会导致什么问题?如何避免?

12 智能指针如何实现

13 在c++11中auto_ptr被弃用的原因

第4章 C++继承和多态

1 说一下C++多态的实现原理。

 第5章 多线程

1 C++多线程中进程间通信的手段有哪些?

2 如何在c++中创建线程?如何在线程间同步?

3 互斥锁是什么?用途是什么?条件变量又是什么?为什么要用条件变量?

第6章 标准库STL

1 如何利用谓词对给定容器进行自定义排序?

2 std::unorded_map和std::map之间的差异是什么?

3 map和hashmap有什么区别

4 vector中的erase方法与algorithm中的remove有什么区别

第7章 数据结构

1 如果你来设计vector,你会怎么设计?

2 给你一个vector,求第k大的那个元素。

3 写一个Vec2d类,支持加、减、数乘、数除等操作。

第8章 虚函数

1 C++的构造函数可以是虚函数吗?

2 虚函数有什么作用,析构函数为什么定义为虚函数?

3 虚函数、虚表的原理

第9章 企业面试题



 

第1章 常见面试题

1 int main(int argc, char ** argv)函数中,参数argc和argv分别代表什么意思?

第一个参数, int 型的 argc ,为整型,用来统计程序运行时发送给 main 函数的命令行参数的个数。
第二个参数, char* 型的 argv[] ,而argv是一个字符串数组,这个数组用来存储多个字符串,每个字符串就是我们给main函数传的一个参数,argv[0]就是我们给main函数的第一个传参,argv[1]就是传给main的第二个参数,以此类推后面的传参。

2 结构体和共用体的区别

(1)struct和union都是由多个不同的数据类型成员组成。 struct的所有成员都存在;但在任何同一时刻, union中只存放了一个被选中的成员。
(2)在不考虑字节对齐的情况下,struct变量的总长度等于所有成员长度之和。union变量的长度等于最长的成员的长度。
(3)struct的不同成员赋值是互不影响的;而对于union的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了。

3 使用共用体读写成员时需要注意什么?

共用体是共用内存空间,所以每个成员都是读写同一个内存空间,那么内存空间里面的内容不停的被覆盖,而同一时刻,都只能操作一个成员变量。否则会出现读错误。

4 简述C++有几种传值方式,之间的区别是什么?

传参方式有这三种:值传递、引用传递、指针传递

  • 值传递:形参即使在函数体内值发生变化,也不会影响实参的值;
  • 引用传递:形参在函数体内值发生变化,会影响实参的值;
  • 指针传递:在指针指向没有发生改变的前提下,形参在函数体内值发生变化,会影响实参的值;

5 为什么值传递不改变实参的值?

因为在函数传参的过程中,函数会为形参申请新的内存空间,并将实参的值复制给形参。形参的改变当然不会影响实参的值。要想影响实参的值,可以使用指针传递。在C++中,可以使用引用传递。

6 全局变量和局部变量的区别

  • 作用域不同:全局变量的作用域为整个程序,而局部变量的作用域为当前函数或循环等
  • 内存存储方式不同:全局变量存储在全局数据区中,局部变量存储在栈区
  • 生命期不同:全局变量的生命期和主程序一样,随程序的销毁而销毁,局部变量在函数内部或循环内部,随函数的退出或循环退出就不存在了
  • 使用方式不同:全局变量在声明后程序的各个部分都可以用到,但是局部变量只能在局部使用。函数内部会优先使用局部变量再使用全局变量。

7 全局变量和局部变量如何初始化?

当局部变量被定义时,系统不会对其初始化,必须自行对其初始化。定义全局变量时,系统会自动初始化为下列值:

数据类型 初始化默认值
char '\0'
float  0
double  0
pointer NULL

8 请说说原码、反码、补码

整型数值在计算机的存储里,最左边的一位代表符号位,0代表正数,1代表负数。

  • 原码:为二进制的数,如:10 原码为0000 1010
  • 反码:正数的反码与原码相同:如:10 原码为0000 1010,反码为0000 1010;负数为原码0变1,1变0,(符号位不变):如:-10 原码为1000 1010,反码为1111 0101
  • 补码:正数的补码与原码相同:如:10 原码为0000 1010,补码为0000 1010;负数的补码为反码加1:如:-10 反码为1111 0101,补码为1111 0110

9 说说数组与指针

数组:数组是相同类型数据的集合。引入数组就不需要在程序中定义大量的变量,大大减少了程序中变量的数量,使程序精炼,而且数组含义清楚,使用方便,明确地反映了数据间的联系。
指针:也是一种变量,但它和普通的变量的区别是,普通的变量存放的是实际的数据,而指针变量包含的是内存中的一块地址,这块地址指向某个变量或者函数。

10 说说数组和指针的区别

赋值:同类型指针变量可以相互赋值;数组不行,只能一个一个元素的赋值或拷贝
存储方式:

  • 数组:数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下标进行访问的,数组的存储空间,不是在静态区就是在栈上。
  • 指针:指针本身就是一个变量,作为局部变量时存储在栈上。

求sizeof

  • 数组所占存储空间的内存大小:sizeof(数组名)/sizeof(数据类型)
  • 在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4,在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。

11 数组指针与指针数组的区别

数组指针:是一个指针变量,指向了一个一维数组, 如 int (*p)[4] , (*p)[4] 就成了一个二维数组,p也称行指针;

指针数组:是一个数组,只不过数组的元素存储的是指针变量, 如 int *p[4] 。

12 指针函数与函数指针的区别

定义不同

  • 指针函数本质是一个函数,其返回值为指针。
  • 函数指针本质是一个指针,其指向一个函数。

写法不同

  • 指针函数:int *fun(int x,int y);
  • 函数指针:int (*fun)(int x,int y);

用法不同

  • 指针函数返回一个指针。
  • 函数指针使用过程中指向一个函数。通常用于函数回调的应用场景。

13 请说说内存分布模型

SLAM面试笔记(6) — C++面试题_第1张图片

如上图,从低地址到高地址,一个程序由代码段、数据段、BSS段、堆栈段组成。

  • 代码段:存放程序执行代码的一块内存区域。只读,不允许修改,代码段的头部还会包含一些只读的常量,如字符串常量字面值(注意:const变量虽然属于常量,但是本质还是变量,不存储于代码段)。
  • 数据段data:存放程序中已初始化的全局变量和静态变量的一块内存区域。
  • BSS 段:存放程序中未初始化的全局变量和静态变量的一块内存区域。
  • 堆区:动态申请内存用。堆从低地址向高地址增长。
  • 栈区:存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。
  • 共享区:最后还有一个文件映射区(共享区),位于堆和栈之间。

14 请你说说野指针

野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的),野指针不同于空指针,空指针是指一个指针的值为null,而野指针的值并不为null,野指针会指向一段实际的内存,只是它指向哪里我们并不知情,或者是它所指向的内存空间已经被释放,所以在实际使用的过程中,我们并不能通过指针判空去识别一个指针是否为野指针。

出现野指针的情况:

  • 指针变量的值未被初始化: 声明一个指针的时候,没有显示的对其进行初始化,那么该指针所指向的地址空间是乱指一气的。如果指针声明在全局数据区,那么未初始化的指针缺省为空,如果指针声明在栈区,那么该指针会随意指向一个地址空间。
  • 指针所指向的地址空间已经被free或delete:在堆上malloc或者new出来的地址空间,如果已经free或delete,那么此时堆上的内存已经被释放,但是指向该内存的指针如果没有人为的修改过,那么指针还会继续指向这段堆上已经被释放的内存,这时还通过该指针去访问堆上的内存,就会造成不可预知的结果,给程序带来隐患。
  • 指针操作超越了作用域

15 如何避免野指针

  • 初始化置NULL
  • 申请内存后判空:malloc申请内存后需要判空,而在现行C++标准中,如C++11,使用new申请内存后不用判空,因为发生错误将抛出异常。
  • 使用时不要超出指针作用域。
  • 指针释放后置NULL
  • 使用智能指针

16 请你说说内存泄露

简单地说就是申请了一块内存空间,使用完毕后没有释放掉。

  • new和malloc申请资源使用后,没有用delete和free释放;
  • 子类继承父类时,父类析构函数不是虚函数。
  • 比如文件句柄、socket、自定义资源类没有使用对应的资源释放函数。
  • shared_ptr共享指针成环,造成循环引用计数,资源得不到释放。

有以下几种避免方法:

  • 第一:良好的编码习惯,使用了内存分配的函数,一旦使用完毕,要记得使用其相应的函数释放掉。
  • 第二:将分配的内存的指针以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表。
  • 第三:使用智能指针。
  • 第四:一些常见的工具插件可以帮助检测内存泄露,如ccmalloc、Dmalloc、Leaky、Valgrind等。

17 说说new和malloc的区别,各自底层实现原理

  • new是操作符,而malloc是函数。
  • new在调用的时候先分配内存,在调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数。
  • malloc需要给定申请内存的大小,返回的指针需要强转;new会调用构造函数,不用指定内存的大小,返回指针不用强转。
  • new可以被重载;malloc不行
  • new分配内存更直接和安全。
  • new发生错误抛出异常,malloc返回null

18 说说使用指针需要注意什么?

  • 定义指针时,先初始化为NULL。
  • 用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。在现行C++标准中,如C++11,使用new申请内存后不用判空,因为发生错误将抛出异常。
  • 不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
  • 避免数字或指针的下标越界,特别要当心发生“多1”或者“少1”操作
  • 动态内存的申请与释放必须配对,防止内存泄漏
  • 用free或delete释放了内存之后,立即将指针设置为NULL,防止“野指针”

19 初始化为0的全局变量在bss还是data

BSS段通常是指用来存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域。特点是可读写的,在程序执行之前BSS段会自动清0。

20 知道动态链接与静态链接吗?两者有什么区别

  • 静态链接:是在链接的时候就已经把要调用的函数或者过程链接到了生成的可执行文件中,就算你在去把静态库删除也不会影响可执行程序的执行;生成的静态链接库,Windows下以.lib为后缀,Linux下以.a为后缀。
  • 动态链接:是在链接的时候没有把调用的函数代码链接进去,而是在执行的过程中,再去找要链接的函数,生成的可执行文件中没有函数代码,只包含函数的重定位信息,所以当你删除动态库时,可执行程序就不能运行。生成的动态链接库,Windows下以.dll为后缀,Linux下以.so为后缀。 

21 说说内联函数和宏函数的区别

  • 宏定义不是函数,但是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提高了效率;而内联函数本质上是一个函数,内联函数一般用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,并且内联函数本身不能直接调用自身。
  • 宏函数是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换 ;而内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用的开销,提高效率
  • 宏定义是没有类型检查的,无论对还是错都是直接替换;而内联函数在编译的时候会进行类型的检查,内联函数满足函数的性质,比如有返回值、参数列表等

22 类(class)与结构(struct)的区别?

struct(结构体)和class(类)都是用于定义自定义数据类型的关键字。它们的作用和区别如下:

  • struct(结构体):struct是C语言中的概念,C++中也保留了它。struct可以包含数据成员,在C语言里结构体是一种复合数据类型只能存放数据,不过可以定义一个指向函数的指针进行使用。
  • class(类):class是C++中引入的概念,用于面向对象编程。class可以包含数据成员和函数成员。

区别:

(1)默认的继承访问权限:struct是public的,可以直接访问;class是private的,只能通过成员函数来访问

(2)class是引用类型,struct是值类型;

(3)class可以继承类、接口和被继承,struct只能继承接口,不能被继承;

(4)class有默认的无参构造函数,有析构函数,struct没有默认的无参构造函数,且只能声明有参的构造函数,没有析构函数;

(5)class可以使用abstract和sealed,有protected修饰符,struct不可以用abstract和sealed,没有protected修饰符;

(6)class必须使用new初始化,结构可以不用new初始化;

(7)class实例由垃圾回收机制来保证内存的回收处理,而struct变量使用完后立即自动解除内存分配。

第2章 C++基础

1 C++中static静态变量有什么作用,在什么情况下会用?

在C++中,static静态变量有以下几个作用:

  1. 保持变量的持久性:静态变量在程序执行期间一直存在,不会随着函数的调用结束而销毁。这使得静态变量可以在函数调用之间保持其值,以供后续调用使用。

  2. 共享数据:静态变量可以在多个函数之间共享数据。例如,如果有多个函数需要访问同一个计数器变量,可以将该变量声明为静态变量,这样每个函数都可以访问和修改同一个计数器。

  3. 控制作用域:静态变量的作用域仅限于声明它的函数内部。这意味着其他函数无法直接访问该变量,从而提供了一定的封装性。

  4. 初始化一次:静态变量在程序运行时只会被初始化一次。这意味着在函数多次调用时,静态变量的值会保持不变,直到程序结束。

在以下情况下,可以使用静态变量:

  1. 需要在函数调用之间保持数据的状态:如果某个函数需要在多次调用之间保持某个变量的值,可以将该变量声明为静态变量。

  2. 需要共享数据:如果多个函数需要访问和修改同一个变量,可以将该变量声明为静态变量,以便在函数之间共享数据。

  3. 控制作用域:如果需要限制变量的可见性,使其仅在声明它的函数内部可见,可以将该变量声明为静态变量。

需要注意的是,静态变量的生命周期与程序的运行周期相同,因此需要谨慎使用,避免滥用静态变量导致程序的可维护性和可扩展性降低。

2 类中的this指针指向哪里?

3 说一下const的作用。

C语言中,const关键字用于声明只读变量,表示该变量的值不能被修改,C语言的const表示“只读”的语义。

而在C++中,const关键字不仅可以用于声明只读变量,还可以用于声明只读成员函数、只读参数、只读引用等。而C++的const既表示“只读”的语义,又承担了“常量”的属性。

C和C++的const几点不同之处:

(1)C++中,const关键字可以用于声明只读变量,尝试修改只读变量将导致编译错误。

(2)C++中,const成员函数的声明后面会有一个const修饰符,用于表示该成员函数不会修改该类的成员变量。

(3)C++中,可以使用const引用来引用常量对象,这样可以保证无法通过该引用修改对象的状态。

(4)C++中,常量成员变量的值不能在对象创建后被修改。

参考文章:C++ const的用法详解_c++ const用法-CSDN博客

4 std::string类型为啥不能memset?

5 emplace_back( )和push_back( )有什么区别?

6 traits是什么?什么时候用traits?

7 C++中main函数执行完后还执行其他语句吗

在main()函数退出之后,一般不会有语句继续执行,但是如果我们使用atexit()函数,事先注册一个无返回值、无参数的函数,就可以在main()函数退出之后,继续执行我们注册的函数中的任务,用来保证一些资源的使用或清理等操作,就类似C++中的析构函数一般,不一样的是atexit()是在程序结束后,进行执行的。

8 从代码到可执行文件的过程

预编译 -> 编译 -> 汇编 -> 链接
预编译:这个过程主要的处理操作如下:
(1) 将所有的#define删除,并且展开所有的宏定义
(2) 处理所有的条件预编译指令,如#if、#ifdef
(3) 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
(4) 过滤所有的注释,如//、/**/
(5) 添加行号和文件名标识。
编译:这个过程主要的处理操作如下:
(1) 词法分析:将源代码的字符序列分割成一系列的记号。
(2) 语法分析:对记号进行语法分析,产生语法树。
(3) 语义分析:判断表达式是否有意义。
(4) 代码优化:
(5) 目标代码生成:生成汇编代码。
(6) 目标代码优化:
汇编:这个过程主要是将汇编代码转变成机器可以执行的指令。
链接:将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序。

  • 静态链接,是在链接的时候就已经把要调用的函数或者过程链接到了生成的可执行文件中,就算你在去把静态库删除也不会影响可执行程序的执行。生成的静态链接库,Windows下以.lib为后缀,Linux下以.a为后缀。
  • 动态链接,是在链接的时候没有把调用的函数代码链接进去,而是在执行的过程中,再去找要链接的函数,生成的可执行文件中没有函数代码,只包含函数的重定位信息,所以当你删除动态库时,可执行程序就不能运行。生成的动态链接库,Windows下以.dll为后缀,Linux下以.so为后缀。

9 extern "C" 的作用

extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。
由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。

10 什么是内联函数?

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline。

11 为什么使用内联函数?

函数调用是有调用开销的,执行速度要慢很多,调用函数要先保存寄存器,返回时再恢复,复制实参等等。如果本身函数体很简单,那么函数调用的开销将远大于函数体执行的开销。为了减少这种开销,我们才使用内联函数。

12 内联函数使用的条件

内联是以代码复制为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。以下情况不宜使用内联:

(1)如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。

(2)如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

内联不是什么时候都能展开的,一个好的编译器将会根据函数的定义体,自动地取消不符合要求的内联。

13 什么是内存对齐?

在C语言中,结构体是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构体、联合体等)的数据单元。在结构体中,编译器为结构体的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构体的地址相同。
为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的“对齐”,比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除,也即“对齐”跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称做自然对齐。

14 为什么要字节对齐? 

字节对齐是为了优化内存的使用和访问效率。它确保数据在内存中按照特定的规则对齐,以便于处理器能够高效地读取和写入数据。

15 Static作用

Static作用

  • 定义静态函数或全局变量:当我们同时编译多个文件时,在函数返回类型或全局变量前加上static关键字,函数或全局变量即被定义为静态函数或静态全局变量。静态函数或静态全局变量只能在本源文件中使用。
  • static 的第二个作用是保持变量内容的持久:在变量前面加上static关键字。初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存。直到程序结束,静态变量始终会维持前值。只不过全局静态变量和局部静态变量的作用域不一样。
  • static 的第三个作用是默认初始化为 0:在静态数据区,内存中所有的字节默认值都是 0x00,某些时候这一特点可以减少程序员的工作量。

Static使用

  • 类中的静态成员变量
    类中的static静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据成员都共享这一块静态存储空间,这就为所有对象提供了相互通信的方法。派生类对象与基类对象共享基类的静态数据成员。静态数据成员一个地方被修改,其他所有对象的该静态数据成员都同样发生改变。
  • 类中的静态成员函数
    与静态成员变量类似,类里面同样可以定义静态成员函数。只需要在函数前加上关键字static即可。如: static int volume() 和静态成员变量一样,静态成员函数也是类的一部分,而不是对象的一部分。

16 volatile和mutable

  • mutable是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中,甚至结构体变量或者类对象为const,其mutable成员也可以被修改。mutable在类中只能够修饰非静态数据成员。
  • 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器每次会从内存里重新读取这个变量的值,而不是从寄存器里读取。特别是多线程编程中,变量的值在内存中可能已经被修改,而编译器优化优先从寄存器里读值,读取的并不是最新值。这就是volatile的作用了。

第3章 指针和引用

1 指针和引用的区别

指针和引用都可以作为函数参数,改变实参的值。

(1) 两者的定义和性质不同

  • 指针是一个变量,存储的是一个地址,指向内存的一个存储单元;
  • 引用是原变量的一个别名,跟原来的变量实质上是同一个东西。
int a = 996;
int *p = &a; // p是指针, &在此是求地址运算,p 的值是变量 a 的地址
int &r = a; // r是引用, &在此起标识作用,是 a 的一个别名

SLAM面试笔记(6) — C++面试题_第2张图片

(2) 指针可以有多级,引用只能是一级

int **p; // 合法
int &&a; // 不合法

(3) 指针可以在定义的时候不初始化,引用必须在定义的时候初始化

int *p; // 合法
int &r; // 不合法
int a = 996;
int &r = a; // 合法

(4) 指针可以指向NULL,引用不可以为NULL

int *p = NULL; // 合法
int &r = NULL; // 不合法

(5) 指针初始化之后可以再改变,引用不可以

int a = 996;
int *p = &a; // 初始化, p 是 a 的地址
int &r = a; // 初始化, r 是 a 的引用

int b = 885;
p = &b;	// 合法, p 更改为 b 的地址
r = b; 	// 不合法, r 不可以再变

 (6) sizeof 的运算结果不同

int a = 996;
int *p = &a;
int &r = a;

cout << sizeof(p); // 返回 int* 类型的大小
cout << sizeof(r); // 返回 int 类型的大小

在64位机器上,int* 类型的大小为8个字节,int类型的大小为4个字节。

(7) 自增运算意义不同

如下图所示,p++之后指向a后面的内存,r++相当于a++。

int a = 996;
int *p = &a;
int &r = a;

p++;  //p指向后面的内存
r++;  //相当于a++ 

(8) 指针和引用作为函数参数时,指针需要检查是否为空,引用不需要

void fun_p(int *p)
{
    // 需要检查P是否为空
    if (p == NULL) 
    {
        // do something
    }
}

2 为什么引用比指针安全?

  • 由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。
  • 对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为ULL,所以不安全。const指针仍然存在空指针,并且有可能产生野指针。

3 什么是“野指针”和形成原因?

野指针:“野指针”不是NULL指针,而是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用f语句很容易判断。但是“野指针”是很危险的,f语句对它不起作用。

野指针的成因主要有两种:

  • 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的默认值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
  • 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。

4 C++函数指针有哪几类?

C++函数指针有成员函数指针、静态函数指针、全局函数指针。

5 函数指针、lambda、仿函数对象分别是什么?

函数指针:指针指向普通的函数对象,函数指针是指针,不是函数,指针只是一个变量。

lambda: C++11后支持的匿名函数对象,可捕获函数域外的变量。

仿函数对象:仿函数是STL的6大组件之一(Allocator, Algorithm, Adapter, Container, Functor(function objects), Iterator)。  仿函数(functor),就是使一个类的使用看上去像一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类。该类重载了operator()运算符,调用仿函数的时候实际上就是通过类对象调用重载后的operator操作符,重载operator()和重载普通的函数效果相同,当参数类型不同时会执行不同的代码逻辑,让类型可以模仿函数调用的行为。

6 智能指针分哪几种?std::unique_ptr, std::shared_ptr, std::weak_ptr各有何用途?

c++里面有四个智能指针: auto_ptr(已经被c++11弃用), shared_ptr, weak_ptr, unique_ptr.
智能指针就是一个类,当超过类的作用域时,类会自动调用析构函数,自动释放资源.

unique_ptr:保证同一时间只有一个智能指针可以指向该对象

unique_ptr p3 (new string ("auto"));   
unique_ptr p4;                       
p4 = p3;//此时会报错!!

shared_ptr:个智能指针可以指向相同对象,该对象和其相关资源会在 "最后一个引用被销毁" 时释放
weak_ptr:是一种不控制对象生命周期的智能指针,weak_ptr只是提供了对管理对象的一个访问手段

7 什么是万能引用?

万能引用(Universal Reference)是指模板参数使用 && 时,能够接受任何类型的引用,包括左值引用和右值引用。右值引用是指绑定到右值的引用,可以实现移动语义和完美转发。 

8 万能引用与右值引用的区别

  • 万能引用是模板参数的一种表达方式,而右值引用是一种变量类型
  • 万能引用可以接受任何类型的引用,包括左值引用和右值引用,而右值引用只能接受右值引用
  • 万能引用在模板函数中用于实现完美转发,而右值引用主要用于实现移动语义
  • 万能引用的声明方式为 T&&,而右值引用的声明方式为 X&&,其中 T 和 X 都表示类型。

9 智能指针和祼指针之间的差异?为什么要用指针的引用计数?

10 悬挂指针会导致什么问题?如何避免?

12 智能指针如何实现

  • 每次创建类的新对象时,初始化指针并将引用计数置为1:
  • 当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数:
  • 对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数减至0,则删除对象),并增加右操作数所指对象的引用计数:
  • 调用析构函数时,减少引用计数(如果引用计数减至0,则删除基础对象):
  • 重载“>”以及“*”操作符,使得智能指针有类似于普通指针的操作。

13 在c++11中auto_ptr被弃用的原因

auto_ptr是C++98标准库中的一个智能指针,用于管理动态分配的内存,在C++11中被弃用,原因如下:

拷贝行为产生不可控结果:auto_ptr的拷贝行为是通过移动语义实现的,即将内存所有权从一个auto_ptr对象转移到另一个auto_ptr对象。由于auto_ptr没有提供复制构造函数和拷贝赋值运算符,因此它们会被编译器默认为移动构造函数和移动赋值运算符。这意味着当我们将一个auto_ptr对象赋值给另一个auto_ptr对象时,源对象将失去对内存的控制权。这种行为可能会导致程序出现不可预期的行为或者内存泄漏。

std::auto_ptr p1(new int(42));
std::auto_ptr p2(p1);

在这个例子中,我们将p1赋值给p2,由于auto_ptr的拷贝行为是移动语义,因此p1将失去对内存的控制权。这可能会导致p1悬空指针的问题。

不支持数组:auto_ptr只能管理单个对象的动态内存分配,不能管理数组类型的动态内存分配。如果我们使用auto_ptr来管理数组类型的动态内存分配,可能会导致内存泄漏或其他问题。

std::auto_ptr p(new int[10]); // 错误,auto_ptr不支持数组

与STL容器不兼容:由于auto_ptr的拷贝行为是移动语义,它不能与STL容器(例如vector、map等)一起使用,否则可能会导致内存泄漏或其他问题。

std::vector> v; // 错误,auto_ptr不能与STL容器一起使用

因此,建议使用unique_ptr、shared_ptr、weak_ptr替代auto_ptr
 

第4章 C++继承和多态

1 说一下C++多态的实现原理。

 第5章 多线程

1 C++多线程中进程间通信的手段有哪些?

2 如何在c++中创建线程?如何在线程间同步?

3 互斥锁是什么?用途是什么?条件变量又是什么?为什么要用条件变量?

第6章 标准库STL

1 如何利用谓词对给定容器进行自定义排序?

2 std::unorded_map和std::map之间的差异是什么?

3 map和hashmap有什么区别

  • 底层数据结构不同,map是红黑树,hashmap是哈希表。
  • map的优点在于元素可以自动按照键值排序,而hashmap的优点在于它的各项操作的平均时间复杂度接近常数。
  • map属于标准的一部分,而hashmap则不是。

4 vector中的erase方法与algorithm中的remove有什么区别

  • vector中erase是真正删除了元素,迭代器不能访问了。
  • 而algorithm中的remove只是简单地把要remove的元素移到了容器最后面,迭代器还是可以访问到的。这是因为algorithm通过迭代器操作,不知道容器的内部结构,所以无法做到真正删除。

第7章 数据结构

1 如果你来设计vector,你会怎么设计?

2 给你一个vector,求第k大的那个元素。

3 写一个Vec2d类,支持加、减、数乘、数除等操作。

第8章 虚函数

1 C++的构造函数可以是虚函数吗?

2 虚函数有什么作用,析构函数为什么定义为虚函数?

3 虚函数、虚表的原理

第9章 企业面试题

参考书籍:《C++面试秘籍》

参考文章:

(1)『干货』SLAM工程师需要掌握哪些C++知识点_vector_什么_std

(2)SLAM 面试时问到的C++问题_slam c++_每天都在努力学习SLAM的小黑的博客-CSDN博客

(3)在c++11中auto_ptr被弃用的原因_autoptr放弃原因_十方光明的博客-CSDN博客 

(4) C++中指针与引用的区别 - 知乎

(5)main()函数结束之后会执行其他语句吗?_JunJie_1107的博客-CSDN博客

你可能感兴趣的:(C++,SLAM面试宝典,c++,面试,slam)