1.自我介绍
2.直接开始问项目了
这个是按照时间倒序来问的,先问的机械臂采摘,问了代码语言、代码规模和写代码的时间;你负责的是什么一个模块设计吗,然后有什么模块的设计吗比如,什么类啊模块之间的交互逻辑,我回答的是用rostopic与另一个检测模块进行信息传输;后面问我这个模块是不是制作一个数据处理然后再输出给下一个模块,我回答的是机械臂采摘的流程。
3.八股
C/C++的内存模型,分为多少个区?(这里我答错了,回答的是静态、堆、栈、常量)
-
代码区(Text Segment):存储程序的执行代码。这个区域是只读的,用于保存程序的指令集。
- 常量存储区:存放的是常量字符串,不允许修改,程序运行结束自动释放。
-
堆区(Heap):用于动态分配内存。通过
malloc
、calloc
、realloc
和free
等函数进行内存管理。堆区的内存由程序员手动分配和释放。 -
栈区(Stack):用于存储函数的局部变量、参数、返回地址等。栈区的内存由系统自动分配和释放,栈的管理遵循先进后出(LIFO)的原则。
- 全局/静态存储区:⽣命周期是整个程序运⾏期间;在程序启动时分配,程序结束时释放;全局区存储全局变量和静态变量;
堆跟栈的区别?
栈(Stack)栈内存由系统自动管理,分配和释放速度快。它用于存储函数的局部变量和参数,数据的生命周期与函数调用相关,栈内存较小且有限。如果栈空间用尽会导致栈溢出。栈的管理遵循先进后出(LIFO)原则。
堆(Heap)堆内存由程序员手动管理,分配和释放速度较慢。它用于动态分配内存,数据的生命周期由程序员控制,堆的大小通常较大但可能会面临内存泄漏和碎片化的问题。堆内存支持复杂的数据结构和较长的存储时间。
举个例子程序员是怎么申请堆的,在代码上:
我回答new()和delete(),还有吗?malloc(读作麦咯可)和free();有什么区别?(这个我就想到一个new能自动初始化,malloc需要自己指定,然后嗯嗯了半天他说不纠结,下一个问题)
- malloc、free 是库函数,而new、delete 是关键字。
- new 申请空间时,无需指定分配空间的大小,编译器会根据类型自行计算;malloc 在申请空间时,需要确定所申请空间的大小。
- new 申请空间时,返回的类型是对象的指针类型,无需强制类型转换,是类型安全的操作符;malloc 申请空间时,返回的是 void* 类型,需要进行强制类型的转换,转换为对象类型的指针。
- new 分配失败时,会抛出 bad_alloc 异常,malloc 分配失败时返回空指针。
- 对于自定义的类型,new 首先调用 operator new() 函数申请空间(底层通过 malloc 实现),然后调用构造函数进行初始化,最后返回自定义类型的指针;delete 首先调用析构函数,然后调用 operator delete() 释放空间(底层通过 free 实现)。malloc、free 无法进行自定义类型的对象的构造和析构。
- new 操作符从自由存储区上为对象动态分配内存,而 malloc 函数从堆上动态分配内存。(自由存储区不等于堆)
数组跟链表有什么区别?插入跟删除那个效率更高?(我回答数组连续、没有指针、通过索引来查找,链表不连续、会有指针、顺序查找;链表效率更高,数组多了一个移动后面元素的步骤)
数组 | 链表 | |
---|---|---|
逻辑结构 | 1)数组在内存中连续; (2)使用数组之前,必须事先固定数组长度,不支持动态改变数组大小;(3) 数组元素增加时,有可能会数组越界;(4) 数组元素减少时,会造成内存浪费;(5)数组增删时需要移动其它元素 | (1)链表采用动态内存分配的方式,在内存中不连续 (2)支持动态增加或者删除元素 (3)需要时可以使用malloc或者new来申请内存,不用时使用free或者delete来释放内存 |
内存结构 | 数组从栈上分配内存,使用方便,但是自由度小 | 链表从堆上分配内存,自由度大,但是要注意内存泄漏 |
访问效率 | 数组在内存中顺序存储,可通过下标访问,访问效率高 | 链表访问效率低,如果想要访问某个元素,需要从头遍历 |
越界问题 | 数组的大小是固定的,所以存在访问越界的风险 | 只要可以申请得到链表空间,链表就无越界风险 |
局部性原理,空间局部性和时间局部性
-
空间局部性(Spatial Locality):
- 当一个数据项被访问时,其附近的数据项也很可能在未来被访问。
- 比如,顺序访问数组中的元素,或者沿着链表的方向访问节点。
- 这种模式下,CPU可以预先将附近的数据加载到缓存中,减少内存访问延迟。
-
时间局部性(Temporal Locality):
- 当一个数据项被访问时,它很可能在不久的将来再次被访问。
- 比如,在一个循环中反复访问同一个变量,或者在函数中多次使用同一个局部变量。
- 这种模式下,CPU可以将数据保留在缓存中,减少内存访问次数。
这两种局部性原理对计算机系统的性能有很大影响:
- 当程序遵循局部性原理时,CPU可以更好地利用缓存,减少内存访问延迟和次数,从而提高整体性能。
- 反之,如果程序访问模式不遵循局部性原理,将会导致大量缓存未命中,频繁访问内存,从而拖慢程序运行速度。
因此,在程序设计时,充分利用局部性原理是非常重要的优化手段之一。常见的优化方法包括:
- 顺序访问数组/链表等数据结构
- 重用已经加载到缓存中的数据
- 避免随机访问内存
- 合理安排数据在内存中的布局等
项目中有没有用到多线程?是怎么使用的?(我回答使用的互斥锁lock)再问还有什么?不用lock还有别的方式能替代吗(我回答的是条件变量,反问我c++中有这个用法吗?)
在使用多线程时,为了避免数据竞争和线程安全问题,通常会使用互斥锁(lock)来保护共享资源的访问。除了使用互斥锁外,还有其他一些可以替代的方式,比如条件变量(condition variable)。条件变量可以让线程在满足某个条件时被唤醒,从而避免了一直轮询共享资源的情况,提高了效率。C++中确实有条件变量的用法。在C++标准库中,std::condition_variable
就提供了这个功能。使用条件变量通常需要配合互斥锁一起使用。
除了互斥锁和条件变量,还有一些其他的同步机制可以替代lock,比如:
- 原子操作(Atomic operations): C++11引入了一系列原子操作,可以无锁地进行一些简单的并发操作,如增减、交换等。
- 乐观锁(Optimistic locking): 先假设没有竞争,在提交更新时再检查是否有竞争,如果有则重试。
- 无锁数据结构(Lock-free data structures): 通过无锁算法设计出线程安全的数据结构,避免使用互斥锁。
自己去创建线程还是用线程池?如果让你用线程池,你会怎么定义线程池的数量?线程池和自己创建线程的优劣对比。
线程池的大小设置需要结合具体的应用场景和需求。一般来说,可以使用以下方法来确定线程池的大小:
- CPU 密集型任务: 线程池大小可设置为 CPU 核心数的 1-2 倍。这样可以充分利用 CPU 资源,但不会过度占用系统资源。
- I/O 密集型任务: 线程池大小可设置稍大一些,如 2-4 倍 CPU 核心数。这样可以在 I/O 等待时充分利用 CPU。
- 混合型任务: 可以设置为 CPU 核心数的 2-3 倍。权衡 CPU 密集和 I/O 密集的需求。
- 监控线程池使用情况,适当调整线程池大小以达到最佳性能。
相比自己创建线程,使用线程池有以下优势:
- 避免频繁创建和销毁线程的开销。线程池会重复利用已有的线程。
- 方便管理和控制线程。线程池可以限制并发线程的数量,防止资源耗尽。
- 简化编程模型。使用线程池可以将线程管理的细节封装起来,让开发者专注于业务逻辑。
但线程池也有一些劣势:
- 增加了额外的管理开销。需要维护线程池的状态和调度。
- 不适合短期任务。如果任务执行时间很短,线程池的开销会抵消它的优势。
- 限制了并发度。线程池大小有限,可能会限制应用的并发能力。
因此,在选择使用线程池还是自己创建线程时,需要权衡具体场景下的利弊。对于复杂的、长时间运行的应用,使用线程池通常更加合适和高效。而对于简单的、短暂的任务,自己创建线程可能更合适。
视频、音频、文档哪些用TCP哪些用UDP(我当时答的是视频音频UDP,文档TCP,然后说了点UDP不可靠但效率高,TCP可靠)
视频音频UDP,文档TCP。UDP和TCP的差别:
- 可靠性:TCP 提供可靠性保证,适合文件传输、电子邮件等需要数据完整传输的场合;UDP 不提供可靠性,适合实时应用。
- 开销:TCP 由于需要维护状态、流量控制和拥塞控制,开销较大;UDP 开销小,适合轻量级应用。
- 速度:TCP 的速度受到其可靠性机制的影响,可能较慢;UDP 速度快,但可能会出现数据丢失。
4.手撕代码
删除链表倒数第n个节点,ACM格式,要自己构造链表结构体和用例。(先说让我想一下然后对一下思路,我当时急了因为是简单题我就想直接说,但却没说清楚,后面还是缕了一会才说清楚,这部分应该扣分了)不知道他看不看得到我的代码,他先问你没有输出是因为什么,我说我不会写输入输出和用例,他说好了就让我反问
5.反问
但不能问面试流程相关的。我就问了进去干什么,为什么面向安卓和ios的会招c++的