1min和3min各准备一个,随机应变。
https://www.zhihu.com/question/19603341
我叫xxx,来自xxxx大学xxxxxxx专业,非常高兴参加咱们xx公司的面试,我想用3个关键词来介绍自己:
第一个是xxxx,主要讲学历背景
第二个是xxxx,主要讲项目经验
最后一个是xxxx,主要讲个人技能及亮点
我对xxxxxx很感兴趣, 同时咱们公司是这一行业的龙头企业,非常感谢有机会参加这次面试。 谢谢!
消费级产品、影像系统、行业应用、教育产品,车载布局
Linux相关
Linux内核的组成部分
Linux内核主要由五个子系统组成:进程调度,内存管理,虚拟文件系统,网络接口,进程间通信。
Linux虚拟文件系统
虚拟文件系统(Virtual File System,简称VFS)是Linux内核的子系统之一,它为用户程序提供文件和文件系统操作的统一接口,屏蔽不同文件系统的差异和操作细节。借助VFS可以直接使用
open()
、
read()
、
write()
这样的系统调用操作文件,而无须考虑具体的文件系统和实际的存储介质。
通过VFS系统,Linux提供了通用的系统调用,可以跨越不同文件系统和介质之间执行,极大简化了用户访问不同文件系统的过程。另一方面,新的文件系统、新类型的存储介质,可以无须编译的情况下,动态加载到Linux中。
"一切皆文件"是Linux的基本哲学之一,不仅是普通的文件,包括目录、字符设备、块设备、套接字等,都可以以文件的方式被对待。实现这一行为的基础,正是Linux的虚拟文件系统机制。
https://zhuanlan.zhihu.com/p/69289429
Linux系统的组成部分
Linux系统一般有4个主要部分:内核、shell、文件系统和应用程序。
Linux的管道原理
管道是一种在两个进程间进行单向通信的机制。因为管道传递数据的单向性,管道又称为半双工管道。数据只能由一个进程流向另一个进程(其中一个读管道,一个写管道);如果要进行双工通信,需要建立两个管道。
管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。
管道的一端连接一个进程的输出。这个进程会向管道中放入信息。
管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。
一个缓冲区不需要很大一般为4K大小,它被设计成为环形的数据结构,以便管道可以被循环利用。
当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。
当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。
当两个进程都终结的时候,管道也自动消失。
https://segmentfault.com/a/1190000009528245
https://blog.csdn.net/jmppok/article/details/17451261
https://www.cnblogs.com/sallyliu/p/6385806.html
用户态与内核态
内核态,操作系统在内核态运行——运行操作系统程序
用户态,应用程序只能在用户态运行——运行用户程序
当一个进程在执行用户自己的代码时处于用户运行态(用户态),此时特权级最低,为3级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。3级状态不能访问0级的地址空间,包括代码和数据;当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),此时特权级最高,为0级。执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。
用户空间与内核通信方式有哪些?
1)系统调用。用户空间进程通过系统调用进入内核空间,访问指定的内核空间数据;
2)驱动程序。用户空间进程可以使用封装后的系统调用接口访问驱动设备节点,以和运行在内核空间的驱动程序通信;
3)共享内存mmap。在代码中调用接口,实现内核空间与用户空间的地址映射,在实时性要求很高的项目中为首选,省去拷贝数据的时间等资源,但缺点是不好控制;
4)copy_to_user()、copy_from_user(),是在驱动程序中调用接口,实现用户空间与内核空间的数据拷贝操作,应用于实时性要求不高的项目中。
段错误如何定位?
段错误是指访问的内存超出了系统给这个程序所设定的内存空间,例如访问了不存在的内存地址、访问了系统保护的内存地址、访问了只读的内存地址、栈溢出等等情况。
段错误的调试方法:使用printf输出信息、使用gcc和gdb、使用core文件和gdb、使用objdump、使用catchsegv
https://blog.csdn.net/jyr6316521/article/details/74390052
bootloader、内核 、根文件的关系
启动顺序:bootloader->linux kernel->rootfile->app
Bootloader全名为启动引导程序,是第一段代码,它主要用来初始化处理器及外设,然后调用Linux内核。
Linux内核在完成系统的初始化之后需要挂载某个文件系统作为根文件系统(RootFilesystem),然后加载必要的内核模块,启动应用程序。
(一个嵌入式Linux系统从软件角度看可以分为四个部分:
引导加载程序(Bootloader),Linux内核,文件系统,应用程序
。)
Bootloader启动的两个阶段
Stage1:汇编语言
1)基本的硬件初始化(关闭看门狗和中断,MMU(带操作系统),CACHE。配置系统工作时钟)
2)为加载stage2准备RAM空间
3)拷贝内核映像和文件系统映像到RAM中 4)设置堆栈指针sp 5)跳到stage2的入口点
Stage2:C语言
1)初始化本阶段要使用到的硬件设备(UART等)
2)检测系统的内存映射
3)加载内核映像和文件系统映像
4)设置内核的启动参数
嵌入式系统中广泛采用的非易失性存储器通常是Flash,而Bootloader就位于该存储器的最前端,所以系统上电或复位后执行的第一段程序便是Bootloader。
https://blog.csdn.net/a2145565/article/details/114222971
Linux软件性能调优
1.系统硬件资源:CPU,多核与超线程
消耗CPU的业务:动态WEB服务,Mail服务器
2.内存:物理内存与swap的取舍,64操作系统
消耗内存的业务:内存数据库(Redis、hbase、mongodb)
3.磁盘:RAID技术(RAID0/1/5/10),SSD
消耗磁盘的业务:数据库服务器
4.网络带宽:网卡/交换机/双网卡绑定
消耗带宽的业务:hadoop平台,视频业务平台
https://blog.csdn.net/yayaayaya123/article/details/85247850
单片机相关
STM32的一些芯片的配置
CPU主频:F103系列72MHz,F4系列168MHz;
C8T6:FLASH 64K,RAM 20K;RCT6:FLASH 256K,RAM 48K;ZET6:FLASH 512K,RAM 64K
https://www.cnblogs.com/cai-zi/p/14383210.html
你在stm32上都做过哪些开发?几个单片机项目
你开发过哪些驱动吗?屏幕驱动、无线模块的驱动、GUI开发
SPI、I2C、UART总线
https://www.cnblogs.com/cai-zi/p/14682280.html
你了解整个SPI的通信过程吗?SPI有几根线,分别是什么?你使用SPI的时候速率配置的是多少?
SPI是
同步全双工
串行通信协议。 SPI通信有4种不同的模式,不同的从设备在出厂就已配置为某种模式,这是不能改变的;但我们的通信双方必须是工作在同一模式下,所以需要对主设备的SPI模式进行配置,通过CPOL(时钟极性)和CPHA(时钟相位)来控制我们主设备的通信模式 。
SPI定义了4根信号线:SCK:时钟线,主机提供;MISO:主入从出;MOSI:主出从入;SS:片选。
SPI2设置8分频36MHz/8=4.5MHz
SPI的4种模式
SPI通过
时钟极性CPOL
和
时钟相位CPHA
定义了4种通信模式:
CPOL(时钟极性)位:控制在没有数据传输时时钟的空闲状态电平,此位对主模式和从模式下的设备都有效。
CPHA(时钟相位):用来配置数据采样是在第几个边沿。
如果CPOL被清’0’, SCK引脚在空闲状态保持低电平;
如果CPOL被置’1’, SCK引脚在空闲状态保持高电平。
如果CPHA(时钟相位)位被置’1’, SCK时钟的第二个边沿(
CPOL位为0时就是下降沿, CPOL位为’1’时就是上升沿
)进行数据位的采样,数据在第二个时钟边沿被锁存。
如果CPHA位被清’0’,SCK时钟的第一个边沿(
CPOL位为’0’时就是下降沿, CPOL位为’1’时就是上升沿
)进行数据位采样,数据在第一个时钟边沿被锁存。
I2C总线
开始:SCL为高电平时,SDA有下降沿。
数据传输:数据传输以字节为单位,第一个字节表示从机地址+读写方向,后续数据格式由器件自己定义。数据传输中,SDA只能在SCL低电平时变化,并在SCL上升沿进行数据采样。
应答:每发送一个字节后,接收方必须回复应答信号ACK,但发送最后一个字节后,回复非应答信号NACK。
停止:SCL为高电平时,SDA有上升沿。
说一下SPI和I2C和UART的各自的工作方式优缺点。
SPI同步全双工,I2C同步半双工,UART异步全双工
SPI速度快 协议简单 但线多; I2C的速度比SPI慢一点,协议比SPI复杂一点,但是连线也比标准的SPI要少;UART距离远但是只能1对1
直接内存存取技术( Direct Memory Access ), DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。当CPU初始化这个传输动作,传输动作本身是由
DMA控制器
来实现和完成的。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场过程,而是通过硬件为RAM和IO设备开辟一条直接传输数据的通道,使得CPU的效率大大提高。
在实现DMA传输时,是由DMA控制器直接掌管总线,因此,存在着一个总线控制权转移问题。即DMA传输前,CPU要把总线控制权交给DMA控制器,而在结束DMA传输后,DMA控制器应立即把总线控制权再交回给CPU。
一个完整的DMA传输过程必须经过
DMA请求、DMA响应、DMA传输、DMA结束
4个步骤。
中断处理流程
请求中断→响应中断→关闭中断→保留断点→中断源识别→保护现场→中断服务子程序→恢复现场→中断返回。
大型的软件开发
https://www.bilibili.com/read/cv12937847
经典的软件开发模型—
瀑布模型
这个模型把市场与项目紧紧的联系在一起了,如果一个项目的市场前景很清楚,软件需求十分明确,那么软件大多就是采用这种模型进行开发。但是这个开发过程是逐步向下的,那么就会导致软件后期的维护不方便、软件的开发周期也很长。
保证项目精准性的模型—
V模型
该模型最大的特点就是将测试环节贯穿了整个软件的各个阶段,由此达到一种反馈的目的,使得项目的精准性得到很大提高。
快速开发上市的模型—
敏捷开发模型
这个模型简化了开发人员的开发流程、实现快速迭代、循序渐进!简化开发人员的开发流程体现在不注重各种标准文档的书写,注重人的沟通。而快速迭代、循环渐进指的是产品的迭代要快速、分阶段进行开发。
以用户为基础的模型—
原型
原型会先快速构造一个功能模型,演示给用户看,待用户确认后再继续开发。在完善的过程中不断的演示与用户沟通。这样一来就不会出现产品与用户要求不符合的情况了!这种开发模型最优越的地方就是可以快速定位用户的需求。
项目分层
驱动层、中间层和应用层
。
底层一般是直接访问硬件的接口,以串口而言如寄存器操作函数;中间层一般是在底层与上层之间进行数据及信息的转换,以串口而言如封包/拆包/消息产生/消息响应;应用层就是在很少考虑硬件实现的前提下以通用的方式实现所需的功能,以串口而言如printf。
优点: 开发的快速性、 系统安全性与程序可读性 、代码可移植性
嵌入式实时操作系统
FreeRTOS,UCOS,RT-thread,Vxworks等实时系统
容器的本质,就是
一组受到资源限制,彼此间相互隔离的进程
。容器是没有自己的OS的,直接共享宿主机的内核,也没有hypervisor这一层进行资源隔离和限制,所有对于容器进程的限制都是基于操作系统本身的能力来进行的,由此容器获得了一个很大的优势:轻量化,对应用友好,又具备了一定的隔离性。
https://www.jianshu.com/p/517e757d6d17
中间件是一种独立的系统软件服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源,中间件位于客户机服务器的操作系统之上,管理计算资源和网络通信。从这个意义上可以用一个等式来表示中间件:中间件=平台+通信,这也就限定了只有用于分布式系统中才能叫中间件,同时也把它与支撑软件和实用软件区分开来。
各线程间通信
(1)信号量(2)读写锁(3)条件变量(4)互斥锁(5)自旋锁
linux中的线程调度
在Linux中,线程是由进程来实现,线程就是轻量级进程( lightweight process ),因此在Linux中,线程的调度是按照进程的调度方式来进行调度的,也就是说线程是调度单元。Linux这样实现的线程的好处的之一是:线程调度直接使用进程调度就可以了,没必要再搞一个进程内的线程调度器。在Linux中,调度器是基于线程的调度策略(scheduling policy)和静态调度优先级(static scheduling priority)来决定那个线程来运行。
https://blog.csdn.net/MaximusZhou/article/details/42042161
单核多线程的系统调度?
一般有这样两种模式:分时调度和抢占式调度。
分时调度就是按照顺序平均分配;
抢占式调度就是按照优先级来进行分配。
如果现在多个线程,怎么确定哪一个会先被执行?
首先是看优先级,其次看是否处于就绪状态
进程间的通信方式
管道/匿名管道
(Pipes) :⽤于具有亲缘关系的⽗⼦进程间或者兄弟进程之间的通信。
有名管道
(Names Pipes) : 匿名管道由于没有名字,只能⽤于亲缘关系的进程间通信。为了克服
这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁
盘⽂件的⽅式存在,可以实现本机任意两个进程通信。
信号
(Signal) :信号是⼀种⽐较复杂的通信⽅式,⽤于通知接收进程某个事件已经发⽣;
消息队列
(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息
队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(⽆名管道:只存在
于内存中的⽂件;命名管道:存在于实际的磁盘介质或者⽂件系统)不同的是消息队列存放在内
核中,只有在内核重启(即,操作系统重启)或者显示地删除⼀个消息队列时,该消息队列才会被
真正的删除。消息队列可以实现消息的随机查询,消息不⼀定要以先进先出的次序读取,也可以按
消息的类型读取.⽐ FIFO 更有优势。 消息队列克服了信号承载信息量少,管道只能承载⽆格式
字 节流以及缓冲区⼤⼩受限等缺。
信号量
(Semaphores) :信号量是⼀个计数器,⽤于多进程对共享数据的访问,信号量的意图在
于进程间同步。这种通信⽅式主要⽤于解决与同步相关的问题并避免竞争条件。
共享内存
(Shared memory) :使得多个进程可以访问同⼀块内存空间,不同进程可以及时看到对
⽅进程中对共享内存中数据的更新。这种⽅式需要依靠某种同步操作,如互斥锁和信号量等。可
以说这是最有⽤的进程间通信⽅式。
套接字
(Sockets) : 此⽅法主要⽤于在客户端和服务器之间通过⽹络进⾏通信。套接字是⽀持
TCP/IP 的⽹络通信的基本操作单元,可以看做是不同主机之间的进程进⾏双向通信的端点,简
单的说就是通信的两⽅的⼀种约定,⽤套接字中的相关函数来完成通信过程。
https://www.jianshu.com/p/c1015f5ffa74
优先级反转问题
14.进程通信有使用过一些锁和同步的东西吗?
15.多个进程在获取不到锁的时候会进入什么状态?
A:阻塞。
16.假设一个低优先的进程A获取先到锁,高优先级的进程B获取不到锁,那高优先级进程B的进行也会阻塞吗?
A:还是会阻塞。
17.那如果还有一个任务C,优先级介于A和B之间,那么C任务会去抢占CPU资源吗?
A:会的.
18.那这样导致B等待资源的时候会越来越长,这样的问题有方法可以解决吗?因为B的优先级才最高。
A:先把A优先级提高,等A释放后再给B。
19.用什么方式提高A的优先级?
RTOS有函数可以提高。
20.那么A的优先级需要提高到多少?
进程调度算法
(1)先来先服务算法(first-come first-service,FCFS): 只考虑每个作业的等待时间长短
(2)短作业优先调度算法( Shortest Job First,SJF): 只考虑执行时间的长短
(3)高响应比优先调度算法: 对FCFS方式和SJF方式的一种综合平衡
(4)时间片轮转法: 让每个进程在就绪队列中的等待时间与享受服务的时间成正比例
(5)优先权调度算法(分非抢占和抢占式)
(6)多级反馈队列调度算法: 轮转算法和优先级算法的综合和发展
C语言基础
程序编译过程(四个过程)
预处理(Pre-Processing)、编译(Compiling)、汇编(Assembling)、链接(Linking)
读取C语言源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。
伪指令主要包括以下四个方面:
(1)宏定义指令
(2)条件编译指令,如#ifdef, #ifndef, #else, #elif, #endif
(3)头文件包含指令,如#include “FileName” 或者 #include 等。
(4)特殊符号,预编译程序可以识别一些特殊的符号。
预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输出而被翻译成为机器指令。
经过预编译得到的输出文件中,将只有常量,如数字、字符串、变量的定义,以及C语言的关键字,如main, if, else, for, while, {, }, +, -, *, , 等等。预编译程序所要做的工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。
优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来。
https://blog.csdn.net/weixin_40756041/article/details/88052207
程序的内存分段
(1)栈(stack):由编译器自动分配释放,存放函数的参数值、局部变量的值、返回地址等,其操作方式类似于数据结构中的栈。
栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{ }”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
(2)堆(heap):一般由程序员动态分配(调用malloc函数)和释放(调用free函数),若程序员不释放,程序结束时可能由操作系统回收。
堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
(3)数据段(data):存放的是常量、常变量(const 变量)、静态变量、全局变量等。根据存放的数据,数据段又可以分为普通数据段(包括可读可写/只读数据段,存放静态初始化的全局变量或常量)、BSS数据段(存放未初始化的全局变量)。在采用段式内存管理的架构中,BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。 程序结束后由系统释放
(4)常量存储区:常量占用内存,只读状态,决不可修改,常量字符串就是放在这里的。
(5)代码段(code):用于存放程序代码。代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
int a = 0; //全局初始化区
char *p1; //全局未初始化区
main()
int b; //栈中
char s[] = "abc"; //栈中
char *p2; //栈中
char *p3 = "123456"; //123456\0在常量区,p3在栈上
static int c =0; //全局(静态)初始化区
//以下分配得到的10和20字节的区域就在堆区
p1 = (char *)malloc(10);
p2 = new char[20];//(char *)malloc(20);
strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
什么是函数指针和指针函数?
指针函数本质是一个函数,其返回值为指针。
函数指针本质是一个指针,其指向一个函数。
指针函数:int* fun(int x,int y);
函数指针:int (*fun)(int x,int y);
https://blog.csdn.net/luoyayun361/article/details/80428882
如何判断栈溢出?
1、用一个变量记录栈大小
2、重新设置堆栈指针,指向新的堆栈,并设置堆栈两端页面为保护页面,一旦堆栈溢出,就会产生保护异常
解决的方法:
1、使用虚拟空间
vector存储数据时,会分配一个存储空间,如果继续存储,该分配的空间已满,就会分配一块更大的内存,把原来的数据复制过来,继续存储。
2、动态分配:就是不要静态分配,用new动态创建,是从堆中分配的,堆的空间足够大,不过记得写析构函数,delete你申请的堆空间。类结束的时候会自动调用析构函数释放空间。
一般情况:因为栈一般默认为1-2m,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容量超过1m而导致溢出。
方法:用栈把递归转换成非递归
堆栈指针(SP)
堆栈指针总是指向栈顶位置。一般堆栈的栈底不能动,数据入栈前要先修改堆栈指针,使它指向新的空余空间然后再把数据存进去,出栈的时候相反。
最先入栈的数据要到最后才能出栈,而最后入栈的数据最先出栈。
数据流的Top K问题,所用到的数据结构,以及操作的复杂度
首先以给定数据中的前 K 个数建立小堆,然后从 K+1 个数据开始与堆顶元素作比较,小于或等于堆顶元素时,保持建立的小堆不变;大于堆顶元素时,将该数据与堆顶元素交换,此时有可能破坏了小堆的有序性,需要从堆顶元素开始来一次向下调整使堆恢复成小堆。 基于堆数据结构下的 Top K 问题时间复杂度为:
https://blog.csdn.net/z_x_m_m_q/article/details/82389347
如何在main函数之前进行控制台输出,在C++中如何实现,在C中如何实现
定义在main( )函数之前的全局对象、静态对象的构造函数在main( )函数之前执行。
可以用main调用main实现在main前执行一段代码
#include<stdio.h>
#include<stdbool.h>
int main(int argc, char **argv)
static _Bool firstTime = true;
if(firstTime) {
firstTime = false;
printf("BEFORE MAIN\n");
return main(argc, argv);
printf("main\n");
return 0;
C++11中可以用lambda expression
// C++11
#include <iostream>
int a = []() {
std::cout << "a";
return 0;
int main()
std::cout << "b";
return 0;
指针和引用的区别
引用是别名,所以自身没有空间,那么必须与一个合法的存储单元关联,所以定义必须初始化,所以引用不能为空,没有null引用。
指针是类型,有空间,可以为null,适用于动态分配内存。
(1)指针是实体,引用是别名,没有空间。
(2)引用定义时必须初始化,指针不用。
(3)指针可以改,引用不可以。
(4)引用不能为空,指针可以。
(5)Sizeof(引用)计算的是它引用的对象的大小,而sizeof(指针)计算的是指针本身的大小。
(6)不能有NULL引用,引用必须与一块合法的存储单元关联。
(7)给引用赋值修改的是该引用与对象所关联的值,而不是与引用关联的对象。
(8)如果返回的是动态分配的内存或对象,必须使用指针,使用引用会产生内存泄漏。
(9)对引用的操作即是对变量本身的操作。**
https://www.cnblogs.com/yuanqiangfei/category/1392382.html
构造循环链表
构造:把尾结点的next指向头结点。
判断是否是循环链表时,也设置两个指针,慢指针和快指针,让快指针比慢指针每次移动快两次。如果快指针追赶上慢指针,则为循环链表,否则不是循环链表,如果快指针或者慢指针指向NULL,则不是循环链表。
祝大家都能拿到自己心仪的offer!!!