文章目录
- 一、线程
- 1.什么是进程,线程,彼此有什么区别?
- 2.多进程、多线程的优缺点
- 3.什么时候用进程,什么时候用线程
- 4.多进程、多线程同步(通讯)的方法
- 5.父进程、子进程的关系以及区别
- 6.什么是进程上下文、中断上下文
- 7.一个进程可以创建多少线程,和什么有关
- 8.什么是线程同步和互斥
- 9.进程的空间模型
- 10.进程线程的状态转换图 什么时候阻塞,什么时候就绪
- 11.线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?
- 12.并发,同步,异步,互斥,阻塞,非阻塞的理解
- 13.孤儿进程、僵尸进程、守护进程的概念
- 14.正确处理僵尸进程的方法
- 15.如何创建守护进程
- 二、C/C++高频面试题
- 1.new和malloc的区别
- 2.malloc的底层实现
- 3.在1G内存的计算机中能否malloc(1.2G)?为什么?
- 4.指针与引用的相同和区别;如何相互转换?
- 5.C语言检索内存情况 内存分配的方式
- 6. extern”C” 的作用⭐⭐⭐
- 7.头文件声明时加extern定义时不要加 因为extern可以多次声明,但只有一个定义⭐⭐⭐⭐
- 8.函数参数压栈顺序,即关于__stdcall和__cdecl调用方式的理解⭐⭐⭐
- 9.重写memcpy()函数需要注意哪些问题⭐⭐
- 10.数组到底存放在哪里⭐⭐⭐
- 11.struct和class的区别 ⭐⭐⭐⭐⭐
- 12. char和int之间的转换;⭐⭐⭐
- 13. static的用法(定义和用途)⭐⭐⭐⭐⭐
- 14.const常量和#define的区别(编译阶段、安全性、内存占用等) ⭐⭐⭐⭐
- 15.c/c++中变量的作用域⭐⭐⭐⭐⭐
- 16.volatile作用和用法 ⭐⭐⭐⭐⭐
- 17.有常量指针 指针常量 常量引用 没有 引用常量⭐⭐⭐
- 18.c++中类型转换机制?各适用什么环境?dynamic_cast转换失败时,会出现什么情况?⭐⭐⭐
- 19.char、short、int、long、float、double、struct的大小
- 三、继承、多态相关面试题
- 1.继承和虚继承 ⭐⭐⭐⭐⭐
- 2.多态的类,内存布局是怎么样的 ⭐⭐⭐⭐⭐
- 3.被隐藏的基类函数如何调用或者子类调用父类的同名函数和父类成员变量 ⭐⭐⭐⭐⭐
- 4.多态实现的三个条件、实现的原理 ⭐⭐⭐⭐⭐
- 5.对拷贝构造函数 深浅拷贝 的理解 拷贝构造函数作用及用途?什么时候需要自定义拷贝构造函数?⭐⭐⭐
- 6.析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?⭐⭐⭐
- 7.什么情况下会调用拷贝构造函数(三种情况)⭐⭐⭐
- 8.析构函数一般写成虚函数的原因⭐⭐⭐⭐⭐
- 9.构造函数为什么一般不定义为虚函数⭐⭐⭐⭐⭐
- 10.什么是纯虚函数⭐⭐⭐⭐⭐
- 11.静态绑定和动态绑定的介绍⭐⭐⭐⭐
- 12.C++所有的构造函数 ⭐⭐⭐
- 13.重写、重载、覆盖的区别⭐⭐⭐⭐⭐
- 14.成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?⭐⭐⭐⭐
- 15.如何避免编译器进行的隐式类型转换;(explicit)⭐⭐⭐⭐
- 16.程序详细编译过程(预处理、编译、汇编、链接)
- 17.c++:四大类型转换
- 四、网络编程
- 1.TCP、UDP的区别 ⭐⭐⭐⭐⭐
- 2.TCP、UDP的优缺点⭐⭐⭐
- 3.TCP UDP适用场景⭐⭐⭐
- 4.TCP为什么是可靠连接⭐⭐⭐⭐
- 5.典型网络模型,简单说说有哪些;⭐⭐⭐
- 6.Http1.1和Http1.0的区别⭐⭐⭐
- 7.URI(统一资源标识符)和URL(统一资源定位符)之间的区别⭐⭐
- 8.什么是三次握手⭐⭐⭐⭐⭐
- 9.为什么三次握手中客户端还要发送一次确认呢?可以二次握手吗?⭐⭐⭐⭐
- 10.为什么服务端易受到SYN攻击?⭐⭐⭐⭐
- 11.什么是四次挥手⭐⭐⭐⭐⭐
- 12.为什么客户端最后还要等待2MSL?⭐⭐⭐⭐
- 13.为什么建立连接是三次握手,关闭连接确是四次挥手呢?⭐⭐⭐⭐
- 五、常见算法
- 1.各种排序算法的时间空间复杂度、稳定性⭐⭐⭐⭐⭐
- 2.各种排序算法什么时候有最好情况、最坏情况(尤其是快排) ⭐⭐⭐⭐
- 3.冒泡排序⭐⭐⭐⭐
- 4.选择排序⭐⭐⭐⭐
- 5.插入排序⭐⭐⭐⭐
- 6.希尔排序⭐⭐⭐⭐
- 7.归并排序⭐⭐⭐⭐
- 8.快速排序⭐⭐⭐⭐⭐
- 9.快排的partition函数与归并的Merge函数⭐⭐⭐
- 10. vector list异同⭐⭐⭐⭐⭐
- 11. vector内存是怎么增长的vector的底层实现⭐⭐⭐⭐
- 12.几种模板插入的时间复杂度 ⭐⭐⭐⭐⭐
- 13. vector和deque的比较⭐⭐⭐⭐
- 14.为什么stl里面有sort函数list里面还要再定义一个sort⭐⭐⭐
- 15. STL底层数据结构实现⭐⭐⭐⭐
- 16.利用迭代器删除元素会发生什么?⭐⭐⭐⭐
- 17. map是如何实现的,查找效率是多少⭐⭐⭐⭐⭐
- 六、Linux操作系统
- 1. Linux内核的组成⭐⭐
- 2.用户空间与内核通信方式有哪些?⭐⭐⭐⭐⭐
- 3.系统调用read()/write(),内核具体做了哪些事情⭐⭐
- 4.系统调用的作用⭐⭐⭐⭐⭐
- 5.内核态,用户态的区别⭐⭐⭐⭐⭐
- 6. bootloader内核 根文件的关系⭐⭐⭐⭐
- 7. Bootloader多数有两个阶段的启动过程:⭐⭐⭐
- 8. linux的内核是由bootloader装载到内存中的?⭐⭐⭐
- 9.为什么需要BootLoader⭐⭐⭐⭐
- 10. Linux内核同步方式总结⭐⭐⭐⭐
- 11.为什么自旋锁不能睡眠 而在拥有信号量时就可以?⭐⭐⭐⭐
- 12. linux下检查内存状态的命令⭐⭐⭐
- 13.大小端的区别以及各自的优点,哪种时候用⭐⭐⭐⭐⭐
- 14. 一个程序从开始运行到结束的完整过程(四个过程)⭐⭐⭐⭐⭐
- 15.什么是堆,栈,内存泄漏和内存溢出?⭐⭐⭐⭐
- 16.硬链接与软链接的区别;⭐⭐⭐⭐⭐
- 17.虚拟内存,虚拟地址与物理地址的转换⭐⭐⭐⭐
- 18.计算机中,32bit与64bit有什么区别⭐⭐⭐
- 19.中断和异常的区别⭐⭐⭐⭐⭐
- 20.中断怎么发生,中断处理大概流程⭐⭐⭐⭐
- 21. Linux 操作系统挂起、休眠、关机相关命令⭐⭐
- 22.数据库为什么要建立索引,以及索引的缺点⭐⭐
- 23.堆和栈的区别⭐⭐⭐⭐⭐
- 24.死锁的原因、条件 创建一个死锁,以及如何预防⭐⭐⭐⭐⭐
- 单片机
- 1 CPU 内存 虚拟内存 磁盘/硬盘 的关系⭐⭐⭐
- 2 CPU内部结构⭐⭐⭐⭐
- 3 ARM结构处理器简析 ⭐⭐
- 4波特率是什么,为什么双方波特率要相同,高低波特率有什么区别;⭐⭐⭐⭐
- 5.arm和dsp有什么区别⭐⭐
- 6 ROM RAM的概念浅析⭐⭐⭐
- 7 IO口工作方式:上拉输入 下拉输入 推挽输出 开漏输出⭐⭐⭐⭐
- 8扇区 块 页 簇的概念⭐⭐⭐⭐
- 9简述处理器在读内存的过程中,CPU核、cache、MMU如何协同工作?画出CPU核、cache、MMU、内存之间的关系示意图加以说明⭐⭐
- 10请说明总线接口USRT、I2C、USB的异同点(串/并、速度、全/半双工、总线拓扑等)⭐⭐⭐⭐⭐
- 11什么是异步串口和同步串口⭐⭐⭐⭐⭐
- 12 I2C时序图⭐⭐⭐⭐⭐
一、线程
1.什么是进程,线程,彼此有什么区别?
概念:
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位。
线程:处理器任务的基本单位。线程也被称为轻量级进程。
协程:是一种比线程更加轻量级的存在。C++ 20的协程是一个特殊函数。只是这个函数具有挂起和恢复的能力,可以被挂起(挂起后调用代码继续向后执行),而后可以继续恢复其执行。
区别:
地址空间:进程之间是独立的地址空间,线程共享本进程的地址空间。
健壮性:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。
执行过程或者切换时:进程执行开销大。线程执行开销小。
最小单位: 进程是管理资源的基本单位。线程是处理器调度的基本单位
2.多进程、多线程的优缺点
3.什么时候用进程,什么时候用线程
进程与线程的选择取决以下几点:
- 需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。
- 线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应
- 因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;
- 并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;
- 需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。
4.多进程、多线程同步(通讯)的方法
多进程、多线程同步(通讯)的方法
进程间通讯:
- 管道( pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系
- 有名管道 (named pipeline) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
- 信号量( semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列( message queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号 ( signal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 共享内存( shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
- 套接字(socket ) :套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
线程通讯:
- 互斥锁:提供了以排他方式防止数据结构被并发修改的方法。
- 读写锁:允许多个线程同时读共享数据,而对写操作是互斥的。
- 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
- 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量。 信号机制(Signal):类似进程间的信号处理。
5.父进程、子进程的关系以及区别
父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
父子进程间遵循读时共享写时复制的原则。
6.什么是进程上下文、中断上下文
处理器总处于以下三种状态之一:
1、内核态,运行于进程上下文,内核代表进程运行于内核空间;
2、内核态,运行于中断上下文,内核代表硬件运行于内核空间;
3、用户态,运行于用户空间。
什么是中断上下文?
当执行一个中断处理函数时,内核处于中断上下文。
所谓的“ 中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。
中断上下文:
(1)中断上文:硬件通过中断触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。中断上文可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被中断的进程环境。
(2)中断下文:执行在内核空间的中断服务程序。
什么是进程上下文?
用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递 很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存 器值、变量等。所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。
进程上下文:
(1)进程上文:其是指进程由用户态切换到内核态是需要保存用户态时cpu寄存器中的值,进程状态以及堆栈上的内容,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换时的状态,继续执行。
(2)进程下文:其是指切换到内核态后执行的程序,即进程运行在内核空间的部分。
7.一个进程可以创建多少线程,和什么有关
理论上,一个进程可用虚拟空间是2G,默认情况下,线程的栈的大小是1MB,所以理论上最多只能创建2048个线程。如果要创建多于2048的话,必须修改编译器的设置。
一个进程可以创建的线程数由可用虚拟空间和线程的栈的大小共同决定,只要虚拟空间足够,那么新线程的建立就会成功。如果需要创建超过2K以上的线程,减小你线程栈的大小就可以实现了。
8.什么是线程同步和互斥
线程同步:每个线程之间按预定的先后次序进行运行,协同、协助、互相配合。可以理解成“你说完,我再做”。有了线程同步,每个线程才不是自己做自己的事情,而是协同完成某件大事。
线程互斥:当有若干个线程访问同一块资源时,规定同一时间只有一个线程可以得到访问权,其它线程需要等占用资源者释放该资源才可以申请访问。线程互斥可以看成是一种特殊的线程同步。
9.进程的空间模型
1、常量区
2、全局变量区
3、静态变量区
4、代码区
两个动态区域,这就是
堆和栈。
10.进程线程的状态转换图 什么时候阻塞,什么时候就绪
创建态、终止态、就绪态、运行态、阻塞态。
11.线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?
同步是个过程,阻塞是线程的一种状态。多个线程操作共享变量时可能会出现竞争。这时需要同步来防止两个以上的线程同时进入临界区,在这个过程中,后进入临界区的线程将阻塞,等待先进入的线程走出临界区。
线程同步不一定发生阻塞,线程同步的时候,需要协调推进速度,互相等待和互相唤醒会发生阻塞。
12.并发,同步,异步,互斥,阻塞,非阻塞的理解
并发(concurrency):在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥。
1.互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
2.同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。
异步(asynchronous):异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。
阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式。阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值。
一般来说IO模型可以分为:同步阻塞,同步非阻塞,异步阻塞,异步非阻塞。
同步阻塞IO :用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。
同步非阻塞IO :用户进程发起一个IO操作以后可返回做其它事情, 但是用户进程需要时不时的询问 IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。应用发起一个IO操作以后,使用阻塞select系统调用 来等待 I/O可用的通知。 select 调用非常有趣的是它可以用来为多个IO描述符提供通知,而不仅仅为一个描述符提供通知。
异步阻塞IO :应用发起一个IO操作以后,不等待内核IO操作的完成, 等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别同步必须等待或者主动的去询问IO是否完成。
异步非阻塞IO :用户进程只需要发起一个IO操作然后立即返回, 等IO操作真正的完成以后,应用程序会得到IO操作完成的通知, 此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作因为真正的IO读取或者写入操作已经由内核完成了。
同步与异步是对应的,它们是线程之间的关系,两个线程之间要么是同步的,要么是异步的。
阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞。
阻塞是使用同步机制的结果,非阻塞则是使用异步机制的结果。
13.孤儿进程、僵尸进程、守护进程的概念
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
Linux Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
14.正确处理僵尸进程的方法
1.子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。
2.把父进程杀掉。父进程死后,僵尸进程成为"孤儿进程",过继给进程init,init始终会负责清理僵尸进程。它产生的所有僵尸进程也跟着消失。
fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。
两次fork的原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。
15.如何创建守护进程
1、fork()创建子进程,父进程exit()退出;
2、在子进程调用setsid()创建新会话;
3、再次 fork() 一个子进程,父进程exit退出;
4、在子进程中调用chdir()让根目录“/”成为子进程的工作目录;
5、在子进程中调用umask()重设文件权限掩码为0;
6、在子进程中close()不需要的文件描述符;
7、守护进程退出处理
二、C/C++高频面试题
1.new和malloc的区别
-
malloc和free是库函数,而new和delete是C++操作符;
-
new自己计算需要的空间大小,比如’int * a = new,malloc需要指定大小,例如’int * a = malloc(sizeof(int))’;
-
new在动态分配内存的时候可以初始化对象,调用其构造函数,delete在释放内存时调用对象的析构函数。而malloc只分配一段给定大小的内存,并返回该内存首地址指针,如果失败,返回NULL。
-
new是C++操作符,是关键字,而operate new是C++库函数
-
opeartor new /operator delete可以重载,而malloc不行
-
new可以调用malloc来实现,但是malloc不能调用new来实现
-
对于数据C++定义new[]专门进行动态数组分配,用delete[]进行销毁。new[]会一次分配内存,然后多次调用构造函数;delete[]会先多次调用析构函数,然后一次性释放。
分配数组不同之处
int char* pa = new char[100];
int char* pb = malloc(sizeof(char) * 100); -
malloc能够直观地重新分配内存
使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。
new没有这样直观的配套设施来扩充内存。
2.malloc的底层实现
malloc()工作机制
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。
3.在1G内存的计算机中能否malloc(1.2G)?为什么?
是有可能申请1.2G的内存的。
回答这个问题前需要知道malloc的作用和原理,应用程序通过malloc函数可以向程序的虚拟空间申请一块虚拟地址空间,与物理内存没有直接关系,得到的是在虚拟地址空间中的地址,之后程序运行所提供的物理内存是由操作系统完成的。
4.指针与引用的相同和区别;如何相互转换?
- 都是地址的概念,指针指向某一内存、它的内容是所指内存的地址;引用则是某块内存的别名。
- 从内存分配上看:两者都占内存,程序为指针会分配内存,一般是4个字节;而引用的本质是指针常量,指向对象不能变,但指向对象的值可以变。两者都是地址概念,所以本身都会占用内存。
- 指针转引用:把指针用*就可以转换成对象,可以用在引用参数当中。
- 引用转指针:把引用类型的对象用&取地址就获得指针了。
5.C语言检索内存情况 内存分配的方式
内存分配方式:
- 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
- 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释 放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大 小的堆空间将会产生堆内碎块。
程序的内存空间:
- 栈区(stack):由编译器自动分配释放,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址。其操作方式类似于数据结构中的栈。
- 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
- 全局区(静态区)(static):存放全局变量、静态数据、常量。程序结束后由系统释放。
- 文字常量区:常量字符串就是放在这里的。程序结束后由系统释放。
- 程序代码区:存放函数体(类成员函数和全局函数)的二进制代码。
6. extern”C” 的作用⭐⭐⭐
extern “C”的作用是告诉C++编译器用C规则编译指定的代码(除函数重载外,extern “C”不影响C++其他特性)。
7.头文件声明时加extern定义时不要加 因为extern可以多次声明,但只有一个定义⭐⭐⭐⭐
8.函数参数压栈顺序,即关于__stdcall和__cdecl调用方式的理解⭐⭐⭐
__stdcall和__cdecl都是函数调用约定关键字。
__stdcall:参数由右向左压入堆栈;堆栈由函数本身清理。
__cdecl:参数也是由右向左压入堆栈;但堆栈由调用者清理
9.重写memcpy()函数需要注意哪些问题⭐⭐
10.数组到底存放在哪里⭐⭐⭐
1.数组就是一片地址连续且空间大小一致的存储空间 (但是每个空间存的还是其他数据的地址)
2.数组存在于堆内存中,但凡在堆中存储的数据都称之为 对象(但凡在堆内存中创建的对象都会有默认初始值)
11.struct和class的区别 ⭐⭐⭐⭐⭐
- 默认的继承访问权。class默认的是private,strcut默认的是public。
- 默认访问权限:struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
- 3.“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数
- class和struct在使用大括号{ }上的区别
关于使用大括号初始化
1.)class和struct如果定义了构造函数的话,都不能用大括号进行初始化
2.)如果没有定义构造函数,struct可以用大括号初始化。
3.)如果没有定义构造函数,且所有成员变量全是public的话,class可以用大括号初始化
12. char和int之间的转换;⭐⭐⭐
1.1、char转int:
char a = '1';
int b = a - '0'; // b = 1;
1.2、int转char:
int a = 1;
char b = a + '0'; // b = '1';
13. static的用法(定义和用途)⭐⭐⭐⭐⭐
- 在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。该变量在全局数据区分配内存;静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;未经初始化的静态全局变量会被程序自动初始化为0
- 在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。
• 该变量在全局数据区分配内存;
• 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
• 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
• 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束; - 在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
- 静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。
• 静态数据成员是该类的所有对象所共有的
• 静态数据成员存储在全局数据区。 - 与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。
14.const常量和#define的区别(编译阶段、安全性、内存占用等) ⭐⭐⭐⭐
- define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
- const 定义的常数是变量 也带类型,#define 定义的只是个常数 不带类型。
- define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
- const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了
- define可以用来防止头文件重复引用,而const不能
- const不足的地方,是与生俱来的,const不能重定义,而#define可以通过#undef取消某个符号的定义,再重新定义。
15.c/c++中变量的作用域⭐⭐⭐⭐⭐
16.volatile作用和用法 ⭐⭐⭐⭐⭐
首先volatile修饰的变量,作用在编译阶段,影响编译出的结果,其修饰的变量是随时可能被修改的,volatile告诉编译器,这个变量是重要人物,不要偷懒的去走捷径,每次认认真真的去从内存拿值。
一般说来,volatile用在如下的几个地方:
- 中断服务程序中修改的供其它程序检测的变量需要加 volatile;
- 多任务环境下各任务间共享的标志应该加 volatile;
- 存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能由不同意义;
17.有常量指针 指针常量 常量引用 没有 引用常量⭐⭐⭐
- 常量指针(const int* p)常量指针本质上是一个指针,是一个指向“常量”的指针,即不能通过指针改变指向对象的值(不能解除引用),但可以更改指向
- 常量引用(const int& a)本质上是一个引用,对常量的引用,不能通过引用改变绑定对象的值
- 指针常量(int* const p)指针常量本质上是一个常量,const是修饰p的,即指针的值自身是一个常量,不可改变,始终指向一个地址,在创建的同时必须进行初始化
18.c++中类型转换机制?各适用什么环境?dynamic_cast转换失败时,会出现什么情况?⭐⭐⭐
http://t.csdn.cn/wBwuS
若对指针进行dynamic_cast,失败返回nullptr,成功返回正常cast后的对象指针
19.char、short、int、long、float、double、struct的大小
结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。
偏移量指的是结构体变量中成员的地址和结构体变量地址的差。
1、结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
2、结构体大小必须是所有成员大小的整数倍。
三、继承、多态相关面试题
1.继承和虚继承 ⭐⭐⭐⭐⭐
虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
2.多态的类,内存布局是怎么样的 ⭐⭐⭐⭐⭐
https://blog.csdn.net/yuupengsun/article/details/104136210
和类中的数据有关,和函数无关,若是有虚函数,内存加上虚指针的大小
3.被隐藏的基类函数如何调用或者子类调用父类的同名函数和父类成员变量 ⭐⭐⭐⭐⭐
4.多态实现的三个条件、实现的原理 ⭐⭐⭐⭐⭐
1.继承,2.虚函数重写,3.父类指针或引用指向子类对象
5.对拷贝构造函数 深浅拷贝 的理解 拷贝构造函数作用及用途?什么时候需要自定义拷贝构造函数?⭐⭐⭐
拷贝构造函数一般用于以下三种情况:
1.当用类的一个对象去初始化该类的另外一个对象时。
2.如果函数的形参是类的对象,调用函数时,是值传递。(引用传递并不调用拷贝构造函数)
3.如果函数的返回值是类的对象,函数执行完成时会返回调用者时。
如果是浅拷贝,那么只会拷贝指针b=a,它们都指向D内存区域。如果是深拷贝,不仅拷贝指针b=a,还会申请一块新的内存空间E,原来的a指向D,新的b指向E。
当类存在指针类成员变量时,默认拷贝构造函数是浅拷贝,会导致二次析构问题,所以要自定义拷贝构造函数。
6.析构函数可以抛出异常吗?为什么不能抛出异常?除了资源泄露,还有其他需考虑的因素吗?⭐⭐⭐
1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
2)通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
7.什么情况下会调用拷贝构造函数(三种情况)⭐⭐⭐
1.当用类的一个对象去初始化该类的另外一个对象时。
2.如果函数的形参是类的对象,调用函数时,是值传递。(引用传递并不调用拷贝构造函数)
3.如果函数的返回值是类的对象,函数执行完成时会返回调用者时。
8.析构函数一般写成虚函数的原因⭐⭐⭐⭐⭐
在派生类中申请的资源就不会得到释放,就会造成内存泄漏
9.构造函数为什么一般不定义为虚函数⭐⭐⭐⭐⭐
存储空间角度:虚函数对应一个vtable,vtable存储于对象的内存空间
若构造函数是虚的,则需要通过 vtable来调用,若对象还未实例化,即内存空间还没有,无法找到vtable
使用角度:虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。
构造函数本身就是要初始化实例,那使用虚函数就没有实际意义
从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有太大的必要成为虚函数
10.什么是纯虚函数⭐⭐⭐⭐⭐
纯虚函数只有函数的名字而不具备函数的功能,不能被调用。在虚函数后面添加 =0 ,虚函数就成为纯虚函数
- 虚函数定义形式:成员函数前添加 virtual 关键字,纯虚函数在虚函数后添加 =0 ;
- 含有纯虚函数的类,称为抽象类;只含有虚函数的类,不能称为抽象类。
- 虚函数既可以直接使用,也可以被子类重载实现后,以多态的形式调用;而纯虚函数必须被子类重载实现,才能以多态的形式调用,因为纯虚函数在基类中只有声明。
- 无论虚函数还是纯虚函数,定义中都不能有 static 关键字。因为 static关键字修饰的内容在编译前就要确定,而虚函数、纯虚函数是在运行时动态绑定的。
11.静态绑定和动态绑定的介绍⭐⭐⭐⭐
静态类型:对象在声明时采用的类型,在编译期既已确定;
动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的;
静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期;
动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期;
12.C++所有的构造函数 ⭐⭐⭐
C++中的构造函数可以分为4类:
(1)默认构造函数。以Student类为例,默认构造函数的原型为
Student();//没有参数
(2)初始化构造函数
Student(int num,int age);//有参数
(3)复制(拷贝)构造函数
Student(Student&);//形参是本类对象的引用
(4)转换构造函数
Student(int r) ;//形参时其他类型变量,且只有一个形参
13.重写、重载、覆盖的区别⭐⭐⭐⭐⭐
Overload(重载):重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型(参数列表不同)。调用的时候根据函数的参数来区别不同的函数。有以下特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无;
Override(覆盖):是指派生类函数覆盖基类函数,有以下特征:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字
Overwrite(重写):是值在派生类中重新对基类中的虚函数重新实现。即函数名和参数都一样,只是函数的实现体不一样
覆盖就是重写
14.成员初始化列表的概念,为什么用成员初始化列表会快一些(性能优势)?⭐⭐⭐⭐
如果有些成员是类,那么在进入构造函数之前,会先调用一次默认构造函数,进入构造函数后所做的事其实是一次赋值操作(对象已存在),所以如果是在构造函数体内进行赋值的话,等于是一次默认构造加一次赋值,而初始化列表只做一次赋值操作。
15.如何避免编译器进行的隐式类型转换;(explicit)⭐⭐⭐⭐
将构造函数声明为explicit,来防止隐式类型转换。
explicit关键字只能用于类内部的构造函数声明上,而不能用在类外部的函数定义上。
16.程序详细编译过程(预处理、编译、汇编、链接)
预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)、链接(Linking)。
编译过程可分为6步:扫描(词法分析)、语法分析、语义分析、源代码优化、代码生成、目标代码优化。
词法分析:扫描器(Scanner)将源代的字符序列分割成一系列的记号(Token)。lex工具可实现词法扫描。
语法分析:语法分析器将记号(Token)产生语法树(Syntax Tree)。yacc工具可实现语法分析(yacc: Yet Another Compiler Compiler)。
语义分析:静态语义(在编译器可以确定的语义)、动态语义(只能在运行期才能确定的语义)。
源代码优化:源代码优化器(Source Code Optimizer),将整个语法书转化为中间代码(Intermediate Code)(中间代码是与目标机器和运行环境无关的)。中间代码使得编译器被分为前端和后端。编译器前端负责产生机器无关的中间代码;编译器后端将中间代码转化为目标机器代码。
目标代码生成:代码生成器(Code Generator).
目标代码优化:目标代码优化器(Target Code Optimizer)。
17.c++:四大类型转换
- dynamic_cast 用于多态类型的转换
- static_cast 用于非多态类型的转换
- const_cast 用于删除const,volatile 和 __unaligned 属性
- reinterpret_cast 用于位的简单重新解释
http://t.csdnimg.cn/iFtb8
四、网络编程
1.TCP、UDP的区别 ⭐⭐⭐⭐⭐
2.TCP、UDP的优缺点⭐⭐⭐
3.TCP UDP适用场景⭐⭐⭐
UDP 常用于以下几个方面:
1.包总量较少的通信(DNS、SNMP等);
2.视频、音频等多媒体通信(即时通信);
3.限定于 LAN 等特定网络中的应用通信;
4.广播通信(广播、多播)
4.TCP为什么是可靠连接⭐⭐⭐⭐
- 检验和:通过检验和的方式,接收端可以检测出来数据是否有差错和异常,假如有差错就会直接丢弃TCP 段,重新发送。
- 序列号/确认应答机制过程如下:
发送端每次发送数据时,TCP就给每个数据包分配一个序列号并且在一个特定的时间内等待接收端对分配的这个序列号进行确认,如果发送端在一个特定时间内没有收到接收端的确认,则发送端会重传此数据包。
接收端利用序列号对接收的数据进行确认,以便检测对方发送的数据是否有丢失或者乱序等,接收端一旦收到已经顺序化的数据,它就将这些数据按正确的顺序重组成数据流并传递到高层进行处理。 - 超时重传原理
TCP协议要求在发送端每发送一个报文段,就启动一个定时器并等待确认信息;接收端成功接收新数据后返回确认信息。若在定时器超时前数据未能被确认,TCP就认为报文段中的数据已丢失或损坏,需要对报文段中的数据重新组织和重传。
5.典型网络模型,简单说说有哪些;⭐⭐⭐
网络模型一般是指OSI七层参考模型和TCP/IP四层参考模型。
OSI分层(7层):物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
TCP/IP分层(4层):网络接口层、网际层、运输层、应用层。
网络层:IP协议、ICMP协议、ARP协议、RARP协议。
传输层:UDP协议、TCP协议。
应用层:FTP(文件传送协议)、Telnet(远程登录协议)、DNS(域名解析协议)、SMTP(邮件传送协议),POP3协议(邮局协议),HTTP协议。
6.Http1.1和Http1.0的区别⭐⭐⭐
7.URI(统一资源标识符)和URL(统一资源定位符)之间的区别⭐⭐
8.什么是三次握手⭐⭐⭐⭐⭐
三次握手的目的是建立可靠的通信通道,说到通信,简单来说就是数据的发生与接收,而三次握手最主要的目的就是双方确认自己与对方的发送与接收是否正常
第一次握手:Client什么都不能确认,Server确认了对方发送正常,自己接收正常
第二次握手:Client确认了:自己发送、接收正常,对方发送正常、接收正常;Server确认了:对方发送正常,自己接收正常
第三次握手:Client确认了:自己发送、接收正常,对方发送正常接收正常;Server 确认了:对方发送正常,接收正常,自己发送正常,接收正常。
9.为什么三次握手中客户端还要发送一次确认呢?可以二次握手吗?⭐⭐⭐⭐
下面解释明明两次就可以建立连接的为什么还要加第三次的确认。
如果发送两次就可以建立连接话,那么只要客户端发送一个连接请求,服务端接收到并发送了确认,就会建立一个连接。
可能出现的问题:如果一个连接请求在网络中跑的慢,超时了,这时客户端会从发请求,但是这个跑的慢的请求最后还是跑到了,然后服务端就接收了两个连接请求,然后全部回应就会创建两个连接,浪费资源!
如果加了第三次客户端确认,客户端在接受到一个服务端连接确认请求后,后面再接收到的连接确认请求就可以抛弃不管了。
10.为什么服务端易受到SYN攻击?⭐⭐⭐⭐
服务端的资源分配是在二次握手时分配的,而客户端的资源是在三次握手时分配的。
SYN攻击,即客户端在短时间内伪造大量不存在的IP地址,并向SERVER端不断的发送SYN包,SERVER收到请求即回复确认,并等待客户端的确认,由于源地址不存在,因此SERVER需要不断的重发直到超时。
这些伪造的SYN包长时间占用未连接队列,导致正常的SYN请求因为队列满而丢弃,因为网络拥塞。
一般服务端会重试5次。
分类: 嵌入式100题
11.什么是四次挥手⭐⭐⭐⭐⭐
第一次挥手: Client端发起挥手请求,向Server端发送标志位是FIN报文段,设置序列号seq,此时,Client端进入FIN_WAIT_1状态,这表示Client端没有数据要发送给Server端了。
第二次分手:Server端收到了Client端发送的FIN报文段,向Client端返回一个标志位是ACK的报文段,ack设为seq加1,Client端进入FIN_WAIT_2状态,Server端告诉Client端,我确认并同意你的关闭请求。
第三次分手: Server端向Client端发送标志位是FIN的报文段,请求关闭连接,同时Client端进入LAST_ACK状态。
第四次分手 : Client端收到Server端发送的FIN报文段,向Server端发送标志位是ACK的报文段,然后Client端进入TIME_WAIT状态。Server端收到Client端的ACK报文段以后,就关闭连接。此时,Client端等待2MSL的时间后依然没有收到回复,则证明Server端已正常关闭,那好,Client端也可以关闭连接了
12.为什么客户端最后还要等待2MSL?⭐⭐⭐⭐
TIME_WAIT状态的时长设置为2MSL的主要原因:
确保被动关闭TCP连接的一端能收到第四次挥手的ACK
避免上一次TCP连接的数据包影响到下一次的TCP连接。
13.为什么建立连接是三次握手,关闭连接确是四次挥手呢?⭐⭐⭐⭐
建立连接时因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。所以建立连接只需要三次握手。
由于TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议,TCP是全双工模式。这就意味着,关闭连接时,当Client端发出FIN报文段时,只是表示Client端告诉Server端数据已经发送完毕了。当Server端收到FIN报文并返回ACK报文段,表示它已经知道Client端没有数据发送了,但是Server端还是可以发送数据到Client端的,所以Server很可能并不会立即关闭SOCKET,直到Server端把数据也发送完毕。当Server端也发送了FIN报文段时,这个时候就表示Server端也没有数据要发送了,就会告诉Client端,我也没有数据要发送了,之后彼此就 会愉快的中断这次TCP连接。
五、常见算法
1.各种排序算法的时间空间复杂度、稳定性⭐⭐⭐⭐⭐
2.各种排序算法什么时候有最好情况、最坏情况(尤其是快排) ⭐⭐⭐⭐
3.冒泡排序⭐⭐⭐⭐
https://www.runoob.com/w3cnote/bubble-sort.html
4.选择排序⭐⭐⭐⭐
5.插入排序⭐⭐⭐⭐
6.希尔排序⭐⭐⭐⭐
7.归并排序⭐⭐⭐⭐
8.快速排序⭐⭐⭐⭐⭐
9.快排的partition函数与归并的Merge函数⭐⭐⭐
void Merge(vector<int> &Array, int front, int mid, int end) {// preconditions:// Array[front...mid] is sorted// Array[mid+1 ... end] is sorted// Copy Array[front ... mid] to LeftSubArray// Copy Array[mid+1 ... end] to RightSubArrayvector<int> LeftSubArray(Array.begin() + front, Array.begin() + mid + 1);vector<int> RightSubArray(Array.begin() + mid + 1, Array.begin() + end + 1);int idxLeft = 0, idxRight = 0;LeftSubArray.insert(LeftSubArray.end(), numeric_limits<int>::max());RightSubArray.insert(RightSubArray.end(), numeric_limits<int>::max());// Pick min of LeftSubArray[idxLeft] and RightSubArray[idxRight], and put into Array[i]for (int i = front; i <= end; i++) {if (LeftSubArray[idxLeft] < RightSubArray[idxRight]) {Array[i] = LeftSubArray[idxLeft];idxLeft++;} else {Array[i] = RightSubArray[idxRight];idxRight++;}}
}void MergeSort(vector<int> &Array, int front, int end) {if (front >= end)return;int mid = (front + end) / 2;MergeSort(Array, front, mid);MergeSort(Array, mid + 1, end);Merge(Array, front, mid, end);
}
template <typename T>
void quick_sort_recursive(T arr[], int start, int end) {if (start >= end)return;T mid = arr[end];int left = start, right = end - 1;while (left < right) { //在整个范围内搜寻比枢纽元值小或大的元素,然后将左侧元素与右侧元素交换while (arr[left] < mid && left < right) //试图在左侧找到一个比枢纽元更大的元素left++;while (arr[right] >= mid && left < right) //试图在右侧找到一个比枢纽元更小的元素right--;std::swap(arr[left], arr[right]); //交换元素}if (arr[left] >= arr[end])std::swap(arr[left], arr[end]);elseleft++;quick_sort_recursive(arr, start, left - 1);quick_sort_recursive(arr, left + 1, end);
}
template <typename T> //整數或浮點數皆可使用,若要使用物件(class)時必須設定"小於"(<)、"大於"(>)、"不小於"(>=)的運算子功能
void quick_sort(T arr[], int len) {quick_sort_recursive(arr, 0, len - 1);
}
10. vector list异同⭐⭐⭐⭐⭐
- vector底层实现是数组;list是双向链表。
- vector支持随机访问,list不支持。
- vector是顺序内存,list不是。
- vector在中间节点进行插入删除会导致内存拷贝,list不会。
- vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。
- vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。
11. vector内存是怎么增长的vector的底层实现⭐⭐⭐⭐
vector 容器是 C++ 标准库中的一种动态数组容器,它可以自动调整大小以适应元素的添加和删除。当 vector 容器需要扩大容量时,会采用半倍扩容的方式。
半倍扩容的原理如下:
- 当 vector 容器的当前容量不足以存储新添加的元素时,需要进行扩容。
- 首先,计算出新的容量大小(new_capacity):将当前容量(current_capacity)乘以 2。
- 然后,检查 new_capacity 是否小于默认容量(即初始容量),如果是,则将 new_capacity 设置为默认容量。
- 分配一个新的内存空间,大小为 new_capacity。
- 将原来的元素逐个拷贝到新的内存空间中。
- 释放原来的内存空间。
- 更新当前容量为 new_capacity。
12.几种模板插入的时间复杂度 ⭐⭐⭐⭐⭐
13. vector和deque的比较⭐⭐⭐⭐
-
两端都能快速安插和删除元素,这些操作可以在分期摊还的常数时间(amortized constant time)内完成。
-
元素的存取和迭代器的动作比vector稍慢。
-
迭代器需要在不同区块间跳转,所以它非一般指针。
-
因为deque使用不止一块内存(而vector必须使用一块连续内存),所以deque的max_size()可能更大。
-
不支持对容量和内存重新分配时机的控制。不过deque的内存重分配优于vector,因为其内部结构显示,deque不必在内存重分配时复制所有元素。
-
除了头尾两端,在任何地方安插或删除元素,都将导致指向deque元素的所有pointers、references、iterators失效。
-
deque的内存区块不再被使用时,会自动被释放。deque的内存大小是可自动缩减的。
-
deque与vector组织内存的方式不一样。在底层,deque按“页”(page)或“块”(chunk)来分配存储器,每页包含固定数目的元素。而vector只分配一块连续的内存。例如,一个10M字节的vector使用的是一整块10M字节的内存,而deque可以使用一串更小的内存块,比如10块1M的内存。所以不能将deque的地址(如&deque[0])传递给传统的C API,因为deque内部所使用的内存不一定会连续。
deque的下述特性与vector差不多:
-
在中部安插、删除元素的速度较慢。
-
迭代器属于random access iterator(随机存取迭代器)。
14.为什么stl里面有sort函数list里面还要再定义一个sort⭐⭐⭐
虽然STL中提供了标准的sort函数,但是它只适用于随机存取的容器,而显然list并不是这样的容器,因此list提供了专用的链表排序函数,虽然同样名为sort,但是这个sort函数是list类的成员函数。
15. STL底层数据结构实现⭐⭐⭐⭐
C++ STL 的实现:
1.vector 底层数据结构为数组 ,支持快速随机访问
2.list 底层数据结构为双向链表,支持快速增删
3.deque 底层数据结构为一个中央控制器和多个缓冲区,详细见STL源码剖析P146,支持首尾(中间不能)快速增删,也支持随机访问
deque是一个双端队列(double-ended queue),也是在堆中保存内容的.它的保存形式如下:
[堆1] --> [堆2] -->[堆3] --> …
每个堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品.
4.stack 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
5.queue 底层一般用list或deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时
(stack和queue其实是适配器,而不叫容器,因为是对容器的再封装)
6.priority_queue 的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
7.set 底层数据结构为红黑树,有序,不重复
8.multiset 底层数据结构为红黑树,有序,可重复
9.map 底层数据结构为红黑树,有序,不重复
10.multimap 底层数据结构为红黑树,有序,可重复
11.hash_set 底层数据结构为hash表,无序,不重复
12.hash_multiset 底层数据结构为hash表,无序,可重复
13.hash_map 底层数据结构为hash表,无序,不重复
14.hash_multimap 底层数据结构为hash表,无序,可重复
16.利用迭代器删除元素会发生什么?⭐⭐⭐⭐
(1)对于关联容器(如map,set,multimap,multiset),删除当前的iterator,仅仅会使当前的iterator失效,只要在erase时,递增当前的iterator即可。这是因为map之类的容器,使用了红黑树来实现,插入,删除一个结点不会对其他结点造成影响。
(2)对于序列式容器(如vector,deque,list等),删除当前的iterator会使后面所有元素的iterator都失效。这是因为vector,deque使用了连续分配的内存,删除一个元素导致后面所有的元素会向前移动一个位置。不过erase方法可以返回下一个有效的iterator。
17. map是如何实现的,查找效率是多少⭐⭐⭐⭐⭐
C++ STL中的map用红黑树实现,搜索效率是O(lgN)
六、Linux操作系统
1. Linux内核的组成⭐⭐
Linux内核主要由五个子系统组成:
进程调度,内存管理,虚拟文件系统,网络接口,进程间通信。
2.用户空间与内核通信方式有哪些?⭐⭐⭐⭐⭐
系统调用,提供特定的用户空间与内核空间的信息传递。
信号,内核空间出现一些异常时候会发送信号给进程,如SIGSEGV、SIGILL、SIGPIPE等。
/proc,proc可以读取内核空间的信息并且设置部分属性的值,需要循环检测。
文件,可以通过指定文件的读写操作来实现通信,但是流程不够实时,需要循环检测来实现。
netlink,类似socket通信方式,可以读写大量的数据,实现稍微复杂。
ioctl,可以实现数据量比较少时候的通信。
3.系统调用read()/write(),内核具体做了哪些事情⭐⭐
用户空间read()–>内核空间sys_read()–>scull_fops.read–>scull_read();
该过程分为两个部分:用户空间的处理和核心空间的处理。在用户空间中通过 0x80 中断的方式将控制权交给内核处理,内核接管后,经过6个层次的处理最后将请求交给磁盘,由磁盘完成最终的数据拷贝操作。在这个过程中,调用了一系列的内核函数。
4.系统调用的作用⭐⭐⭐⭐⭐
(1) 系统调用可以为用户空间提供访问硬件资源的统一接口,以至于应用程序不必去关 注具体的硬件访问操作。
比如,读写文件时,应用程序不用去管磁盘类型,甚至于不用关心是哪种文件系统。
(2) 系统调用可以对系统进行保护,保证系统的稳定和安全。系统调用的存在规定了用 户进程进入内核的具体方式,
换句话说,用户访问内核的路径是事先规定好的,只能从规定位置进入内核,而不准许肆意跳入内核。有了这样的进
入内核的统一访问路径限制才能保证 内核的安全。
5.内核态,用户态的区别⭐⭐⭐⭐⭐
用户空间就是用户进程所在的内存区域,相对的,系统空间就是操作系统占据的内存区域。用户进程和系统进程的所有数据都在内存中。
开机加电,系统启动后,就对物理内存进行了划分。
当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。 此时处理器处于特权级最高的(0级)内核代码中执行。 当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。 每个进程都有自己的内核栈。 当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。
6. bootloader内核 根文件的关系⭐⭐⭐⭐
7. Bootloader多数有两个阶段的启动过程:⭐⭐⭐
8. linux的内核是由bootloader装载到内存中的?⭐⭐⭐
linux的内核的确是由bootloader装载到内存中的。 linux的bootloader有2个部分组成:bootstrap和uboot。
9.为什么需要BootLoader⭐⭐⭐⭐
10. Linux内核同步方式总结⭐⭐⭐⭐
原子操作
信号量(semaphore)
读写信号量(rw_semaphore)
Spinlock
Mutex
BKL(Big Kernel Lock,只包含在2.4内核中,不讲)
Rwlock
brlock(只包含在2.4内核中,不讲)
RCU(只包含在2.6内核及以后的版本中)
seqlock(只包含在2.6内核及以后的版本中)
11.为什么自旋锁不能睡眠 而在拥有信号量时就可以?⭐⭐⭐⭐
12. linux下检查内存状态的命令⭐⭐⭐
13.大小端的区别以及各自的优点,哪种时候用⭐⭐⭐⭐⭐
大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。这种排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。
小端序(Little-Endian),将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序。小端序与人类的阅读习惯相反,但更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地址向高地址方向进行读取的。
小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。
大端模式 :符号位的判定固定为第一个字节,容易判断正负。
14. 一个程序从开始运行到结束的完整过程(四个过程)⭐⭐⭐⭐⭐
预处理:.i文件。在预编译的过程中,主要处理源代码中的预处理指令,引入头文件,去除注释,处理所有的条件编译指令,宏的替换,添加行号,保留所有的编译器指令。当进行预编译以后的文件中将不再存在宏,所有的宏都已经被替代。
编译:.s文件。在预处理结束后,进行的是编译。编译过程所进行的是对预处理后的文件进行语法分析,词法分析,语义分析,符号汇总,然后生成汇编代码。
汇编:.o文件。汇编过程将汇编代码转成二进制文件,二进制文件就可以让机器来读取。每一条汇编语句都会产生一句机器语言。这个阶段会形成符号表,并给这些符号分配虚拟地址。
链接:链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体
15.什么是堆,栈,内存泄漏和内存溢出?⭐⭐⭐⭐
堆(heap):是由malloc之类函数分配的空间所在地。地址是由低向高增长的。
栈(stack):是自动分配变量,以及函数调用的时候所使用的一些空间。地址是由高向低减少的。
内存溢出(out of memory):通俗理解就是内存不够,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。
内存泄漏(Memory Leak):是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
16.硬链接与软链接的区别;⭐⭐⭐⭐⭐
1.软链接是存放另一个文件的路径的形式存在。
2.软链接可以 跨文件系统 ,硬链接不可以。
3.软链接可以对一个不存在的文件名进行链接,硬链接必须要有源文件。
4.软链接可以对目录进行链接。
17.虚拟内存,虚拟地址与物理地址的转换⭐⭐⭐⭐
虚拟内存,借助于地址转换,操作系统可以给应用程序一种假象,独占整个计算机内存,可以使用超过实际物理大小的内存,应用程序之间互不干扰。
进程隔离,地址转换可以用来构建沙盒(SandBoxes)技术,让第三方代码在沙盒中运行,限制其对内存的访问,从而避免操作系统内核和应用程序受到病毒或者恶意代码的攻击。
进程间通信,地址转换可以将不同进程空间的地址映射到同一段物理内存,从而实现进程间通信。
共享代码段,动态加载so库可以在多个程序实例之间进行共享。
程序初始化,使用地址转换技术可以让应用程序只加载部分代码和数据就可以运行(内核在后台继续加载剩余部分),若执行到未加载部分,则发生中断挂起应用程序,待内核将剩余部分加载到内存后再恢复应用程序的执行。
缓存管理,内核通过合理安排程序所在的内存位置来提高缓存效率。
内存映射文件,将文件内容映射到应用程序地址空间,这样一来,文件的内容就可以被应用程序直接访问。
内存的两种视角
虚拟地址,进程看到的内存地址称为虚拟地址,他们不对应任何物理实体,每个进程有自己的地址空间。
物理地址,内存系统看到的地址称为物理地址,他们用实际的地址去查找和存储内容。
虚拟寻址的方法。CPU生成一个虚拟地址(VA),然后MMU(Memory Management Unit 内存管理单元) 将虚拟地址翻译成实际的物理地址,然后再进行物理寻址。
虚拟内存是计算机系统中的一种技术,它通过将物理内存和磁盘空间结合起来,为每个进程提供了一个统一的连续地址空间。在虚拟内存中,使用虚拟地址来访问内存,而不是直接使用物理地址。虚拟地址需要通过地址转换机制转换为对应的物理地址,以实现内存的访问。
18.计算机中,32bit与64bit有什么区别⭐⭐⭐
对于电脑CPU来说,64位和32位就是CPU一次出来数据的能力,32位就是32bit即一次可以处理4个字节的数据;64位就是64bit即一次可以处理8个字节。
32位的系统许多支持4G的内存,而64位则可以支持上百G的内存。
19.中断和异常的区别⭐⭐⭐⭐⭐
中断和异常
相同点:都是CPU对系统发生的某个事情做出的一种反应。
区别:中断由外因引起,异常由CPU本身原因引起。
把中断分为外中断和内中断。
外中断——就是我们指的中断——是指由于外部设备事件所引起的中断,如通常的磁盘中断、打印机中断等;
内中断——就是异常——是指由于 CPU 内部事件所引起的中断,如程序出错(非法指令、地址越界)。内中断(trap)也被译为“捕获”或“陷入”。
异常是由于执行了现行指令所引起的。由于系统调用引起的中断属于异常。
中断则是由于系统中某事件引起的,该事件与现行指令无关。
20.中断怎么发生,中断处理大概流程⭐⭐⭐⭐
21. Linux 操作系统挂起、休眠、关机相关命令⭐⭐
Linux 操作系统挂起、休眠、关机相关命令
立刻关机:
shutdown -h now
shutdown -h 0
定时/延时关机:
shutdown -h 10:00
shutdown -h +30 //单位为分钟
重启:
reboot
待机/挂起:
sudo pm-suspend
sudo pm-suspend-hybrid
echo “mem” > /sys/power/state
sudo hibernate-ram
休眠:
sudo pm-hibernate
echo “disk” > /sys/power/state
sudo hibernate-disk
22.数据库为什么要建立索引,以及索引的缺点⭐⭐
23.堆和栈的区别⭐⭐⭐⭐⭐
(1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
(2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;
(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
(4)分配方式不同。堆都是动态分配的。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。
(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。
(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。
从以上可以看到,堆和栈相比,由于大量malloc()/free()或new/delete的使用,容易造成大量的内存碎片,并且可能引发用户态和核心态的切换,效率较低。栈相比于堆,在程序中应用较为广泛,最常见的是函数的调用过程由栈来实现,函数返回地址、EBP、实参和局部变量都采用栈的方式存放。虽然栈有众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,主要还是用堆。
无论是堆还是栈,在内存使用时都要防止非法越界,越界导致的非法内存访问可能会摧毁程序的堆、栈数据,轻则导致程序运行处于不确定状态,获取不到预期结果,重则导致程序异常崩溃,这些都是我们编程时与内存打交道时应该注意的问题。
24.死锁的原因、条件 创建一个死锁,以及如何预防⭐⭐⭐⭐⭐
产生死锁的原因(两个):
由竞争资源引起死锁:多个进程,共享资源,资源不足,竞争资源。
进程推进顺序不当引起死锁:进程运行过程中,请求和释放资源的顺序不当,而导致进程死锁。
产生死锁的四个必要条件:
① 互斥条件——进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占有。
② 请求和保持条件——当进程因请求资源而阻塞时,对已获得的资源保持不放。
③ 不剥夺条件——进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
④ 环路等待条件——在发生死锁时必然存在一个“进程—资源”的环形链。
解决死锁的基本方法(四种):
① 预防死锁——通过设置某些限制条件,以破坏产生死锁的四个必要条件中的一个或几个,来防止发生死锁。
② 避免死锁——在资源的动态分配过程中,使用某种方法去防止系统进入不安全状态,从而避免了死锁的发生。
③ 检测死锁——检测死锁方法允许系统运行过程中发生死锁。但通过系统所设置的检测机构,可以及时检测出死锁的发生,并精确地确定与死锁有关的进程和资源,然后采取适当措施,从系统中消除所发生的死锁。
④ 解除死锁——解除死锁是与检测死锁相配套的一种设施,用于将进程从死锁状态下解脱出来。常用的方法是撤销或者挂起一些进程,以便于释放出一些资源,再将它分配给已经处于阻塞的进程,使其转换为就绪状态可以继续运行。解决死锁的基本方法(四种):
① 预防死锁——通过设置某些限制条件,以破坏产生死锁的四个必要条件中的一个或几个,来防止发生死锁。
② 避免死锁——在资源的动态分配过程中,使用某种方法去防止系统进入不安全状态,从而避免了死锁的发生。
③ 检测死锁——检测死锁方法允许系统运行过程中发生死锁。但通过系统所设置的检测机构,可以及时检测出死锁的发生,并精确地确定与死锁有关的进程和资源,然后采取适当措施,从系统中消除所发生的死锁。
④ 解除死锁——解除死锁是与检测死锁相配套的一种设施,用于将进程从死锁状态下解脱出来。常用的方法是撤销或者挂起一些进程,以便于释放出一些资源,再将它分配给已经处于阻塞的进程,使其转换为就绪状态可以继续运行。
单片机
1 CPU 内存 虚拟内存 磁盘/硬盘 的关系⭐⭐⭐
-
CPU从内存或缓存中取出指令,放入指令寄存器,并对指令译码分解成 系统指令的执行。
-
内存(即物理 内存,是相对于硬盘这个“外存”而言)作为硬盘和CPU的“中转站”,对电脑运行速度有较大影响。
-
当运行数据超出物理内存容纳限度的时候,部分数据就会自行“溢出”,虚拟内存运行的程序或 不使用的数据存放到这部分空间之中,等待需要的时候方便及时调用。
-
由于内存是带电存储的(一旦断电数据就会消失),而且容量有限,有了硬盘
2 CPU内部结构⭐⭐⭐⭐
3 ARM结构处理器简析 ⭐⭐
4波特率是什么,为什么双方波特率要相同,高低波特率有什么区别;⭐⭐⭐⭐
波特率是指数据传送时,每秒传送数据二进制代码的位数,它的单位是位/秒(b/s)。1波特就是一位每秒。
5.arm和dsp有什么区别⭐⭐
ARM具有比较强的事务管理功能,可以用来跑界面以及应用程序等,其优势主要体现在控制方面,它的速度和数据处理能力一般,但是外围接口比较丰富,标准化和通用性做的很好,而且在功耗等方面做得也比较好,所以适合用在一些消费电子品方面;
而DSP主要是用来计算的,比如进行加密解密、调制解调等,优势是强大的数据处理能力和较高的运行速度。由于其在控制算法等方面很擅长,所以适合用在对控制要求比较高的场合,比如军用导航、电机伺服驱动等方面。
如果只是着眼于嵌入式应用的话,嵌入式CPU和DSP的区别应该只在于一个偏重控制一个偏重运算了。
DSP的优势主要是速度,它可以在一个指令周期中同时完成一次乘法和一次加法,这非常适合快速傅立叶变换的需求。DSP有专门的指令集,主要是专门针对通讯和多媒体处理的;而ARM使用的是RISC指令集(当然ARM的E系列也支持DSP指令集)是通用处理用的。
6 ROM RAM的概念浅析⭐⭐⭐
- 只读存储器(Read Only Memory,ROM)。ROM所存数据,一般是装入整机前事先写好的,整机工作过程中只能读出,而不像随机存储器那样能快速地、方便地加以改写。ROM所存数据稳定,断电后所存数据也不会改变。
- 随机存取存储器(Random Access Memory,RAM)又称作“随机存储器”,是与CPU直接交换数据的内部存储器,也叫主存(内存)。它可以随时读写,而且速度很快,通常作为操作系统或其他正在运行中的程序的临时数据存储媒介。当电源关闭时RAM不能保留数据。如果需要保存数据,就必须把它们写入一个长期的存储设备中(例如硬盘)。RAM和ROM相比,两者的最大区别是RAM在断电以后保存在上面的数据会自动消失,而ROM不会自动消失,可以长时间断电保存。
7 IO口工作方式:上拉输入 下拉输入 推挽输出 开漏输出⭐⭐⭐⭐
-
浮空输入 :当IO口没有接输入的时候,此时的电平会是一个不确定的值,也就是我们所说的浮空。电平会处于一个跳变的状态,一会高,一会低。只有输入了一个高/低电平才会确定下来。
-
上拉输入 :在没有信号输入的时候,此时的电平就是VDD的电平,此时读取到的电平就是高电平。如果输入了一个高电平,VDD和O点(最上面的图中的O点)之间就几乎没有电势差,此时O点的电平就仍然是高电平,读取到的电平就是高电平。当输入信号是一个低电平的时候,此时O点的电平的电平就会变成低电平,那么VDD和O点之间形成了电势差,但是因为上拉电阻的存在,所以不会出现一个大电流。此时单片机读取到的一个电平就是一个低电平。上拉输入的好处就是输入的电平不会上下浮动而导致输入信号不稳定,在没有信号输入的情况下可以稳定在高电平。
-
下拉输入 : 在没有信号输入的时候,根据电路知识,电平就是VSS的电平,此时读取到的电平就是低电平。此时输入的电平如果是一个低电平,就没有办法和之前的情况进行区分。但如果输入的是一个高电平,O点和VSS之间同样形成了电势差,O点的电平会变成外部的高电平,那么单片机得到的就是一个高电平信号。
下拉输入的好处就是输入的电平不会上下浮动而导致输入信号不稳定,在没有信号输入的情况下可以稳定在低电平。 -
模拟输入 : 模拟输入需要走的路径如图所示。首先得知道,模拟输出走的这一条路径,是我们需要对一个模拟信号进行读取。
在我们使用单片机的时候,我们有时候需要用AD采集到IO口上面的真实电压。这就有了我们所需要的模拟输入。为了让外部的电压真实的读取到单片机的AD模块,我们既不能闭合上拉和下拉的开关,也不能让信号经过施密特触发器。
优势:可以让AD读取电压。还可以在低功耗模式下运行,实现省电的作用。 -
开漏输出
我们可以把这一个MOS管当成一个三极管,对于图中所示的这种三极管我们可以简单的理解成一个水龙头,左侧就是一个水龙头开关,当给一个高电平的时候, O点和GND就会导通。(O点的输出就是一种反向器的输出,也就是O点的电平会和左侧MOS的栅极(三极管的基极)相反)
所以说,开漏输出就很好理解了。当我们给一个低电平的时候,MOS管关闭,此时输出的电压就是一个浮空,即不确定的电压。如果给一个高电平,那么MOS管导通,相当于IO口与VSS相连,此处就输出了一个低电平电压。
优势①
虽然我们可以看到开漏输出是没有办法在内部输出一个高电平,但是这一个看似是缺点。其实实际上是一种优点。我们可以得到,当给一个低电平的时候,MOS管没有导通,此时电压不确定导致无法输出高电平,但是一旦我们在外部增加一个上拉,那么这一个缺点就会被有效避免。并且,因为是我们自己设计一个上拉,这个上拉的电压是由我们自己确定,这样我们就可以根据外部电路需要多少V的高电平来给这一个上拉的电压,可以更好的适应更多情况。如下图,我们可以给定任意的VDD电压,来适应我们实际所需要的情况。
优势②
开漏输出的实质其实就是一个OD门(OD:漏极输出(Open Drain))。而在数电中,OD门有一个非常重要的特性就是可以实现线与的功能,简单来说,就是在像IIC这样的总线协议中,只要有一个给低电平,那么总线都会被拉低。 -
推挽输出 :推挽输出就是可以需要利用两个不同的MOS管来实现输出。P-MOS和N-MOS是不同的控制方式,当给一个高电平的时候,N-MOS不导通,P-MOS导通,此时IO口接通在VDD,此时输出的是高电平。当给一个低电平的时候,N-MOS导通,P-MOS不导通,此时IO口接通在VSS 电源上面,此时输出的是低电平。
一定要把这个MOS管理解成开关控制的水龙头!!!
优势
带载能力强。
8扇区 块 页 簇的概念⭐⭐⭐⭐
扇区是磁盘中最小的物理存储单位。通常情况下每个扇区的大小是512字节。
块是操作系统中最小的逻辑存储单位。操作系统与磁盘打交道的最小单位是磁盘块。
与内存操作,是虚拟一个页的概念来作为最小单位。与硬盘打交道,就是以块为最小单位。吧
9简述处理器在读内存的过程中,CPU核、cache、MMU如何协同工作?画出CPU核、cache、MMU、内存之间的关系示意图加以说明⭐⭐
10请说明总线接口USRT、I2C、USB的异同点(串/并、速度、全/半双工、总线拓扑等)⭐⭐⭐⭐⭐
UART:通用异步串行口,速率不快,可全双工,结构上一般由波特率产生器、UART发送器、UART接收器组成,硬件上两线,一收一发。
I2C:双向、两线、串行、多主控接口标准。速率不快,半双工,同步接口,具有总线仲裁机制,非常适合器件间近距离经常性数据通信,可实现设备组网。
SPI:高速同步串行口,高速,可全双工,收发独立,同步接口,可实现多个SPI设备互联,硬件3~4线。
USB:通用串行总线,高速,半双工,由主机、hub、设备组成。设备可以与下级hub相连构成星型结构。
11什么是异步串口和同步串口⭐⭐⭐⭐⭐
串行通信进行数据传送时是将要传送的数据按二进制位,依据一定的顺序逐位发送到接收方。其有两种通信方式:异步通信和同步通信。
异步通信,是指数据传送以字符为单位,字符与字符间的传送是完全异步的,位与位之间的传送基本上是同步的。异步通信采用固定的通信格式,数据以相同的帧格式传送。每一帧由起始位、数据位、奇偶校验位和停止位组成。异步串行通信的特点可以概括为:
以字符为单位传送信息。
相邻两字符间的间隔是任意长。
因为一个字符中的比特位长度有限,所以需要的接收时钟和发送时钟只要相近就可以。
异步方式特点简单的说就是:字符间异步,字符内部各位同步。
异步位系统是面向字符来传输信息的,也就是我们一般情况下的一个字符,8位,1bit,当然了传输的时候还要加上起始位和结束位,没有这两位接收方就不知道什么时候开始接收数据什么时候结束了。
同步通信,是指数据传送是以数据块(一组字符)为单位,字符与字符之间、字符内部的位与位之间都同步。同步通信时,通信双方共用一个时钟,这是同步通信区分于异步通信的最显著的特点。同步串行通信的特点可以概括为:
以数据块为单位传送信息。
在一个数据块(信息帧)内,字符与字符间无间隔。
因为一次传输的数据块中包含的数据较多,所以接收时钟与发送进钟严格同步,通常要有同步时钟。