目录
前言
这篇总结一下之前参加过的面试题。岗位:C++软件工程师。
面试题总结
智能指针(出现最多)
智能指针最重要的是特性是不必使用 delete 和 delete[] 运算符释放内存,只要不再需要智能指针,它们就会自动释放。这样就可以避免多次释放、分配/释放不匹配、指针悬挂和内存泄露的可能性。
智能指针内部原理:计数。(某自动驾驶公司面试题)
unique_ptr
唯一指向一个对象。这表明了不能同时有两个此类型的指针指向同一对象。
shared_ptr
shared_ptr 对象指向T类型的指针,但与 unique_ptr 不同,可以有任意多个 shared_ptr 对象包含或共享相同的地址。
weak_ptr
weak_ptr 被链接到 shared_ptr 上,包含相同的地址。创建 weak_ptr 不会递增所链接的 shared_ptr 对象的引用计数,所以不能防止它指向的对象被释放。引用它的最后一个 shared_ptr 对象释放或重置为指向另一个地址时,即使链接的 weak_ptr 对象仍然存在,其内存也会释放。即使这种情况发生了,weak_ptr 也不会包含悬挂指针。编译器首先会强制从 weak_ptr 创建一个 shared_ptr 对象来引用相同的地址。
auto_ptr(强调缺点)
如果多个auto_ptr指向了同一对象,那么在程序编译期间不会报错,报错将发生在程序运行阶段,这将会造成不可预知的后果。因此一般不再使用这种指针,普遍替换成unique_ptr。因为unique_ptr指针如果指向同一对象,会在程序编译期间报错,安全性更高。
指针和引用区别(出现次数很多)
- 指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元,即指针是一个实体;而引用跟原来的变量实质上是一个东西,只不过是原变量的一个别名而已。
- 可以有const指针,但是没有const引用。const引用其实是有的,但是和const指针完全是两个概念(防止在编写函数时不小心修改了参数)。
- 指针可以有多级,但是引用只能是一级。
- 指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化。
- 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了,从一而终。
- sizeof引用得到的是所指向的变量(对象)的大小,而sizeof指针得到的是指针本身的大小。
const int* 和 int* const 区别
const int* 指针常量 = 指针是常量
int* const 常量指针 = 常量的指针
const int* 和 int const* 是一样的。
详细:更详细的描述
new、malloc区别(出现次数很多)
delete会调用对象的析构函数,和new对应free只会释放内存,new调用构造函数。malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
编译的四个阶段(百度、商汤)
预处理阶段
预处理器(cpp)根据以字符#开头的命令,修改原始的C程序。比如hello.c中第一行的#include<stdio.h>命令告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中,结果就得到了另一个C程序,通常是以.i作为文件扩展名。
编译阶段
编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切的描述了一条低级机器语言指令。
汇编阶段
汇编器(as)将hello.s翻译成机器语言指令,把这些指令打包成一种可重定位目标程序的格式,并将结果保存在目标文件hello.o中。hello.o文件是一个二进制文件,它的字节编码是机器语言指令而不是字符,如果我们在文本文件中打开hello.o文件,看到的将是一堆乱码。
链接阶段
链接器(ld)负责处理合并目标代码,生成一个可执行目标文件,可以被加载到内存中,由系统执行。
动态链接静态链接区别和优缺点
一两句话说不明白。。放上个不错的文章链接吧。
传送门
宏定义相关(滴滴)
宏定义和全局变量区别
- 宏会在预处理阶段被替换,而全局变量是在运行时;
- 宏定义不分配内存,全局变量定义需要分配内存;
- 宏不区分数据类型,它本质上是一段字符,在预处理的时候被替换到引用的位置,而全局变量区分数据类型;
- 宏定义之后值是不能改变的,全局变量的值是可以改变的;
- 宏定义只有在定义所在文件,或引用所在文件的其它文件中使用。 而全局变量可以在工程所有文件中使用,只需在使用前加一个声明。
宏函数特点及优缺点
-
宏函数会在编译预处理时展开,只占编译时间,函数调用则会占用运行时间(分配单元,保存现场,值传递,返回),每次执行都要载入,所以执行相对宏会较慢。
-
在函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符替换。
-
函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
-
对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
-
宏函数的定义也有其缺点,它很容易会产生二义性
C++类相关问题(字节)
内联函数
内容比较多,也是附上一个链接
内联函数
单例模式实现方式
static作用
- 函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
- 在模块内的static全局变量和函数可以被模块内的函数访问,但不能被模块外其它函数访问;
- 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
- 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
算法题
一共前前后后面过十次左右,算法题的难度均为Leecode简单题。没遇见过路径规划等题目。
数组
出现了大概三次,均为Leecode上简单的算法应用题。
链表
只遇见过一次反转链表的题(快手)
开根号算法
这个居然遇见了两次,分别为百度和商汤。可以用牛顿法和逼近法求解,要会保留几位小数的算法。
C++实例:传送门
树
已知中序遍历和后序遍历求前序遍历或者是类似的题。
经验总结
把牛客上的基础题(剑指offer等)都刷到,再刷Leecode上的应用题。简单刷差不多了再去尝试中等题。简单题刷多了一般也就够用了。熟练掌握STL真的很重要,尤其是unordered_map,vector基本上用的多自然就熟练了,像这种用的少的会有奇效。
其他问题(答案不全)
- git的作用和用法:当时问我的时候只说了git clone 应该是想听一些版本控制之类的答案。(商汤)
- shell编程:不会(问的比较多)
- Linux递归删除命令:rm -rf(快手)
- C++编译器报错说到底是谁在报错:系统内核(百度)
- C++这种语言出现之前是用的什么编程:百度考的,不知道答案是啥,可能是内核编程,或者是系统编程(极度不确定)
- 浮点数如何用二进制表示:这个搜一下吧,讲起来也比较复杂(滴滴)
- CPU如何处理浮点数精度损失的问题:不会(滴滴)
- A*算法启发函数
- 向量机