面试3——准备
面试3——准备
1 面经
- 自我介绍
- 自我定位
- 行业理解
- 项目难点
- 软件架构设计的思路
- Qt使用的模块
- 为什么选择我们单位
- 为什么选择上海/广州
- 优点/缺点
- 最难的项目,碰到的问题,收获
- 与岗位匹配的技能,自己的优势和劣势
- 职业规划
- 加班怎么看
- 两个月任务,时间砍半怎么办
- 能力强,举例
- 兴趣爱好
- 三句话总结自己
- 一直在钻研的事情
- 反问环节
- 技术面:岗位工作
- 技术二面:部门目前工作方向
- 主管面:需要完成的工作和期望 相关方向的研发阶段
- HR面:新人培养过程,进入一个公司,如何快速地融入到公司
反问自己有什么问题
2 知识点
2.1 语言/C++
2.1.1 特性
继承、多态、封装
- 继承
代码复用、类耦合- 多态
运行时多态 虚函数
编译期多态 模板、宏定义
代码复用、可维护扩展- 封装
隐藏属性和实现细节,仅对外提供接口和方法
定义要为变量分配内存空间;而声明不需要为变量分配内存空间。
2.1.2 语法
【关键字】
- static 限制文件内,限制函数内,静态类成员
- final
C++11的关键字final有两个用途。第一,它阻止了从类继承;第二,阻止一个虚函数的重载。- override & overload & overwrite
overload 重载 相同作用域、相同函数名、参数不同(类型、顺序)
override 覆盖 派生类覆盖基类函数,只针对虚函数,在virtual基础上,还要用override是为防止重写错误、提高代码可读性,可以替代virtual
overwrite 派生类函数屏蔽基类同名函数,两种情况:参数不同,无论有无virtual,基类函数被屏蔽;参数相同,且无virtual,基类函数被屏蔽- volatile
volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
为了安全起见,只要是等待别的程序修改某个变量的话,就加上volatile关键字- union 大小端问题
- const 成员函数
在设计类的时候,一个原则就是对于不改变数据成员的成员函数都要在后面加 const,而对于改变数据成员的成员函数不能加 const。
const对象只能调用const成员函数、不能调用非const成员函数,非const对象可以调用const成员函数
const成员函数可以被对应的具有相同形参列表的非const成员函数重载
const成员函数的写法有两种
1、void fun(int a,int b) const{}
2、void const fun(int a,int b){}
这两种写法的本质是:void fun (const 类 *this, int a,int b);- 继承的public、private、protected
任何继承方式下,派生类不可访问基类private,但有继承
public继承下,基类的public和protected在派生类不变
protected继承下,基类的public和protected在派生类中为protected,作用是对派生类可用,对外不可用
private继承下,基类的public和protected在派生类中为private- typeid
typeid(表达式).name() 获取一个表达式是类型,返回表达式的类型,表达式可以是类型名称、变量名、数字、字符串、指针、结构体等- strlen&sizeof
strlen 计算字符串的长度,以\0’为字符串结束
sizeof 计算的则是分配内存空间的大小,不受里面存储的内容影响- const
普通变量,指针,传参,返回值,类成员- extern
extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
对于变量,区分定义和声明,不加extern是定义,加了也不一定是声明(分配内存),全局变量声明一般不在头文件(重复包含),在源文件中的全局变量引用其他源文件。
对于函数,函数的声明和实现不同(函数体),因此有无extern都行,编译时会去其他地方找实现。
extern “C” 按照C风格编译函数,C++有重载,函数名被改动- 引用和指针
引用:别名
指针:地址
指针 | 地址 | 初始化后,可重新赋值为另一地址 | 可为空 | |
引用 | 别名 | 初始化后,不可重新引用其他对象 | 不可为空 |
【面向对象】虚函数、继承
动态绑定 运行时多态
- 虚函数,指向基类的指针在操作派生类对象时,会根据不同对象,调用相应虚函数
- 为什么用基类的指针指向派生类
为了避免同名隐藏,使用基类指针变成调用虚函数变成从上往下找;
编译器查找函数时,名字查找优先于类型检查- 纯虚函数怎么执行
一定要在派生类实现该纯虚函数- 虚函数表
1 每个包含虚函数的类,都有一个虚函数表和一个指向虚表的指针__vptr,在编译阶段构造
1 一个类的每个对象使用虚表指针__vptr指向同一个虚函数表
2 有虚函数的类被继承后,继承类也有自己的虚函数表
2 虚表中的指针会指向其继承的最近的一个类的虚函数
3 基类指针指向派生类对象,因为虚表指针的存在,该指针依然指向派生类的的虚表- 析构函数是虚函数
在实现多态时,当用基类指针指向派生类对象时,在析构时防止只析构基类而不析构派生类的状况发生
构造函数不能是虚函数,原因如下:- 虚函数对应一个虚指针,虚指针其实是存储在对象的内存空间的。如果构造函数是虚函数,就需要通过虚函数表中对应的虚函数指针(编译期间生成属于类)来调用,可对象目前还没有实例化,也即是还没有内存空间,何来的虚指针,所以构造函数不能是虚函数;
虚函数的作用在于通过父类的指针或者引用来调用它的成员函数的时候,能够根据动态类型来调用子类相应的成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,所以构造函数不能是虚函数;
【堆和栈】
- 栈,编译器分配,存储局部变量、函数传参等
- 堆,程序控制,动态分配、静态变量、常量、全局变量等,分为静态存储区、常量存储区、自由存储区
char *p=“helloworld\n” p在栈上,常量字符串在堆上,p[0]不能修改
char p[]=“helloworld\n”; p在栈上,常量字符串在堆上,因为拷贝了一份到数组,因此p[0]能修改- 自由存储区
由malloc等分配的内存块,用free来结束自己的生命
当开辟的空间小于 128K 时,调用 brk()函数,数据段(.data)的最高地址指针 _edata 往高地址推
当开辟的空间大于 128K 时,调用mmap(),在虚拟地址空间中(堆和栈中间,称为“文件映射区域”的地方)找一块空间来开辟
两种方式分配的都是虚拟内存,没有分配物理内存。在第一次访问已分配的虚拟地址空间的时候,发生缺页中断,操作系统负责分配物理内存,然后建立虚拟内存和物理内存之间的映射关系。
【map】
存储 | 空间复杂度 | 查找、插入 | key需要定义 | |||
---|---|---|---|---|---|---|
map | 二叉搜索树 有序 | 低 | 中 | 重载operator< | ||
unordered_map | hash值 无序 | 高 | 低 | hash_value函数、重载operator== | ||
c++ map是有序还是无序的_C++STL : unordered_map详解 |
【内存对齐】
- 结构体、共同体、类
- 优点:将数据尽量存储在一个步长内,只需要一次寻址,避免跨步长地存储,执行效率和访问范围提升
【函数指针】
- 函数的地址就是函数名,要将函数作为参数进行传递,必须传递函数名
- 声明指针时,必须指定指针指向的数据类型,同样,声明指向函数的指针时,必须指定指针指向的函数类型,这意味着声明应当指定函数的返回类型以及函数的参数列表。
//定义
double cal(int); // prototype
double (*pf)(int); // 指针pf指向的函数, 输入参数为int,返回值为double
//函数传参
void estimate(int lines, double (*pf)(int)); // 函数指针作为参数传递
//调用
double y = (*pf)(5); // 通过指针调用 推荐的写法
//函数指针数组
double (*parray[3])(int) = {f1, f2, f3}; // 声明一个指针数组,存储三个函数的地址
【宏定义】
- 只是替换,在预编译阶段完成
- 方便程序修改,提高运行效率(函数调用/返回耗时
- define中的三个特殊符号:#加双引号,##连接,#@加单引号
【智能指针】
- 使用原因:内存泄露、野指针、异常下的内存泄露
- shared_ptr 多个共享指针指向同一对象
- unique_ptr 同一时间仅一个指针指向该对象
template<class T>
class SmartPtr
{
public:
SmartPtr(T *p);
~SmartPtr();
SmartPtr(const SmartPtr<T> &orig); // 浅拷贝
SmartPtr<T>& operator=(const SmartPtr<T> &rhs); // 浅拷贝
private:
T *ptr;
// 将use_count声明成指针是为了方便对其的递增或递减操作
int *use_count;
};
template<class T>
SmartPtr<T>::SmartPtr(T *p) : ptr(p)
{
use_count = new int(1);
}
template<class T>
SmartPtr<T>::~SmartPtr()
{
// 只在最后一个对象引用ptr时才释放内存
if (--(*use_count) == 0)
{
delete ptr;
delete use_count;
ptr = nullptr;
use_count = nullptr;
cout << "Destructor is called!" << endl;
}
}
template<class T>
SmartPtr<T>::SmartPtr(const SmartPtr<T> &orig)
{
ptr = orig.ptr;
use_count = orig.use_count;
++(*use_count);
}
template<class T>
SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T> &rhs)
{
// 从而防止自身赋值”而导致的提早释放内存
++(*rhs.use_count);
// 将左操作数对象的使用计数减1,若该对象的使用计数减至0,则删除该对象
if (--(*use_count) == 0)
{
delete ptr;
delete use_count;
cout << "Left side object is deleted!" << endl;
}
ptr = rhs.ptr;
use_count = rhs.use_count;
return *this;
}
【C++11】
智能指针、基于范围的for循环、关键字auto/size_t/、列表初始化、作用域内枚举(同作用域定义同枚举量)
string、algorithm、vector、list、map、stack、queue、deque、
更多参见http://www.cplusplus.com/reference/
- vector emplace_back()
push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素;优先选择移动构造
emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
- 容器
容器 | 查找时间复杂度 | 插入时间复杂度 | ||
---|---|---|---|---|
vector | n | n/1 | ||
list | n | 1 | 频繁添加/删除元素 | |
deque | n | n/1 | ||
set | logn | logn | 红黑树 | |
map | logn | logn | 红黑树 | |
unordered_map | 1 | 1 | 以空间换时间 |
- 迭代器
- 函数对象/仿函数
- 算法
- 适配器
- 内存分配器…
2.2 数据结构&算法
- 数据结构基础知识(只看基础,具体应用看算法部分)
- 图的实现 邻接表(稀疏) 邻接矩阵
排序/查找/幷查集/动态规划/…
2.3 线程
线程与进程、线程锁、线程池、线程通信
- 线程
join,detach,sleep- 线程锁
mutex lock try lock与unlock,std::lock_guardstd::mutex lg(mtx),std::unique_lockstd::mutex ul(mtx)- 线程池
充分利用线程资源,避免调度开销,任务队列,唤醒线程notify_one()与notify_all()- 线程通信
互斥量 锁、条件变量可以让等待共享数据条件的线程进入休眠,并在条件达成时唤醒等待线程condition_variable、信号量semaphore- 进程
进程是系统进行任务调度和资源分配的最小单位,进程之间可以并发执行。
线程是程序执行流的最小单元,一个进程可以有多个线程,进程内的线程在其他进程不可见。
线程切换快于进程切换。
任务调度:时间片轮转,每个任务执行一个时间片的时间长度,时间片结束后,强制暂停,执行另一个时间片的程序,等到该任务的时间片再次到来。- 进程通信
管道 父进程生成管道,管道有进有出,fork出子进程,关闭父子的某个进或者出的口
信号 signal.h
共享内存(sys/shm.h,依靠信号量sys/sem.h同步,效率高,但没有同步机制)
信号量 原子操作
套接字
消息队列 独立于发送和接收进程而存在,没有同步、阻塞的问题
多线程、线程之间使用共享内存、线程之间不会互相阻塞
这里的无锁,指线程之间不会互相阻塞,未必是互斥量导致的阻塞,比如线程调度
Lock—free并非是无阻塞的,而是不使用互斥来达到“锁”的目的
2.4 网络
2.4.1 TCP
- 三次握手与四次挥手
TIME_WAIT是主动关闭的一方产生的,一般是client
TCP连接中,主动关闭的一方在关闭后的一段时间内保持一个TIME_WAIT的状态。
状态维持时间一般是2MSL(Maximum Segment Lifetime,最大分段寿命),作用有两点:
(1)保证连接正常终止
(2)保证网络中未接收的数据包正常过期
CLOSE_WAIT是被动关闭的一方产生的,一般是server
接收到关闭连接时,在发出ACK的同时,告知上层,并等待上层处理完后的通知,在此期间保持CLOSE_WAIT状态,在接收到上层通知后,发送FIN,并进入LAST_ACK状态。
若大量出现CLOSE_WAIT,可能是程序响应慢、上层没有close连接等原因。
- 与UDP差异
面向数据流/数据包,是否需要建立连接,可靠性
- TCP粘包
发送方Nagle 一起发送多个包,接收方缓存未及时读取
发送方关闭Nagle ,接收方循环读取缓存全部
UDP没有该问题,因为UDP是基于数据包的,有边界的,TCP是基于数据流的
- TCP重传机制
- 丢包
数据、应答- 超时重传
RTT 发送数据到接收应答的时间 随网络变化 Linux采样计算加权平均和波动范围
RTO 超时重发时间,略大于RTT 动态变化 计算式- 快速重传
1 ACK 收到三个相同ACK
1 重传一个还是重传所有(从第一个丢失的包开始所有)
2 SACK/D-SACK 选择性确认/重复接收
正常 ACK 需要的
丢包 需要的+已接收到的,id大于ACK
ACK丢包(超时触发) 需要的+已接收到的,id小于ACK 重复
网络延迟(SACK触发) 需要的+已接收到的,id小于ACK 重复
2.4.2 七层网络模型
Open System Interconnection Reference Model,缩写为 OSI
- 应用:HTTP
- 表示:图片视频编解码
- 会话:验证,会话管理,断点续传
- 传输:TCP/UDP/socket
- 网络:路由器/IP/防火墙
- 数据链路:网卡/网桥
- 物理:网线/集线器
2.4.3 PING
(Packet Internet Grope)因特网包探索器
ping是一种计算机网络工具,用来测试数据包能否透过IP协议到达特定主机。
ping的运作原理是向目标主机传出一个ICMP echo@要求数据包,并等待接收echo回应数据包。
程序会按时间和成功响应的次数估算丢失数据包率(丢包率)和数据包往返时间(网络时延,Round-trip delay time)。
- ping 127.1.1.1 TCP/IP协议栈
- ping 192.168.1.10 本机网络适配器
- ping 192.168.1.38 内部局域网
- ping 192.168.1.254 路由器
- //ping 202.114.38.62 一个可达的远程IP,确认该主机已开机并且其防火墙未禁止ping
- //ping localhost 本机hosts文件是否可用
- ping www.baidu.com 的DNS服务是否可用
2.4.4 socket
对TCP/UDP的封装接口,根据协议组织数据
2.5 Git
2.6 ROS
- 通信
话题、服务、参数服务器、动作(目标服务、反馈、结果服务) - 实时性
ROS本身是基于Linux,而Linux并非是实时操作系统,因此某些需要在指定时间完成活执行的任务无法得到保证,从而不能满足实时控制的需求。
ROS本身对进程是随机调度的,这导致特别紧急的进程无非最快被调度运行,所以ros需要增加调度策略,让紧急任务更具有实时性。
ROS2实时性:DDS。DDS的底层通信机制基于UDP协议或者共享内存机制
2.7 自动驾驶
2.7.1 传感器
- 激光雷达
原理:三角测距、TOF、相位、调频连续被FMCW
分类:功能(测距/测速/成像/)、线数、扫描方式(机械旋转/MEMS 反射镜固定光束/Flash/相控阵 实验室)
问题:鬼影(高反板)、噪点(远处高反板)、雨雪天气/结冰…
测评:测距和反射强度(渐变反射板)的精度和准度,有效感知距离,高反板- RTK
协议:NTRIP RTCM
时钟相锁:NMEA填充PPS GPRMC传输延迟,PPS脉冲更精确,接收到的NMEA样本时间与NMEA接收时间的offset/PPS整秒的纳秒与0的差,PPS的offset和NMEA的offset的差值过大,导致失锁- V2X
asn.1:用抽象语法描述数据的结构形式,与具体的编码格式无关,同时也不涉及这些数据结构在计算机内如何存放。
2.7.2 规划