C++面试题准备

文章目录


一、线程

1.什么是进程,线程,彼此有什么区别?

概念:
进程:是并发执行的程序在执行过程中分配和管理资源的基本单位。

线程:处理器任务的基本单位。线程也被称为轻量级进程。

协程:是一种比线程更加轻量级的存在。C++ 20的协程是一个特殊函数。只是这个函数具有挂起和恢复的能力,可以被挂起(挂起后调用代码继续向后执行),而后可以继续恢复其执行。

区别:
地址空间:进程之间是独立的地址空间,线程共享本进程的地址空间。

健壮性:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。

执行过程或者切换时:进程执行开销大。线程执行开销小。

最小单位: 进程是管理资源的基本单位。线程是处理器调度的基本单位

2.多进程、多线程的优缺点

在这里插入图片描述
在这里插入图片描述

3.什么时候用进程,什么时候用线程

进程与线程的选择取决以下几点:

  1. 需要频繁创建销毁的优先使用线程;因为对进程来说创建和销毁一个进程代价是很大的。
  2. 线程的切换速度快,所以在需要大量计算,切换频繁时用线程,还有耗时的操作使用线程可提高应用程序的响应
  3. 因为对CPU系统的效率使用上线程更占优,所以可能要发展到多机分布的用进程,多核分布用线程;
  4. 并行操作时使用线程,如C/S架构的服务器端并发线程响应用户的请求;
  5. 需要更稳定安全时,适合选择进程;需要速度时,选择线程更好。

4.多进程、多线程同步(通讯)的方法

多进程、多线程同步(通讯)的方法
进程间通讯:

  1. 管道( pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系
  2. 有名管道 (named pipeline) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  3. 信号量( semaphore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  4. 消息队列( message queue):消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  5. 信号 ( signal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  6. 共享内存( shared memory):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  7. 套接字(socket ) :套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

线程通讯:

  1. 互斥锁:提供了以排他方式防止数据结构被并发修改的方法。
  2. 读写锁:允许多个线程同时读共享数据,而对写操作是互斥的。
  3. 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
  4. 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量。 信号机制(Signal):类似进程间的信号处理。

5.父进程、子进程的关系以及区别

父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集

fork函数时调用一次,返回两次。在父进程和子进程中各调用一次。子进程中返回值为0,父进程中返回值为子进程的PID。

父子进程间遵循读时共享写时复制的原则。

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始终会负责清理僵尸进程。它产生的所有僵尸进程也跟着消失。
3.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的区别

  1. malloc和free是库函数,而new和delete是C++操作符;

  2. new自己计算需要的空间大小,比如’int * a = new,malloc需要指定大小,例如’int * a = malloc(sizeof(int))’;

  3. new在动态分配内存的时候可以初始化对象,调用其构造函数,delete在释放内存时调用对象的析构函数。而malloc只分配一段给定大小的内存,并返回该内存首地址指针,如果失败,返回NULL。

  4. new是C++操作符,是关键字,而operate new是C++库函数

  5. opeartor new /operator delete可以重载,而malloc不行

  6. new可以调用malloc来实现,但是malloc不能调用new来实现

  7. 对于数据C++定义new[]专门进行动态数组分配,用delete[]进行销毁。new[]会一次分配内存,然后多次调用构造函数;delete[]会先多次调用析构函数,然后一次性释放。
    分配数组不同之处
    int char* pa = new char[100];
    int char* pb = malloc(sizeof(char) * 100);

  8. malloc能够直观地重新分配内存
    使用malloc分配的内存后,如果在使用过程中发现内存不足,可以使用realloc函数进行内存重新分配实现内存的扩充。realloc先判断当前的指针所指内存是否有足够的连续空间,如果有,原地扩大可分配的内存地址,并且返回原来的地址指针;如果空间不够,先按照新指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来的内存区域。
    new没有这样直观的配套设施来扩充内存。

2.malloc的底层实现

malloc()工作机制
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。

3.在1G内存的计算机中能否malloc(1.2G)?为什么?

是有可能申请1.2G的内存的。

回答这个问题前需要知道malloc的作用和原理,应用程序通过malloc函数可以向程序的虚拟空间申请一块虚拟地址空间,与物理内存没有直接关系,得到的是在虚拟地址空间中的地址,之后程序运行所提供的物理内存是由操作系统完成的。

4.指针与引用的相同和区别;如何相互转换?

指针与引用的异同
相同

  1. 都是地址的概念,指针指向某一内存、它的内容是所指内存的地址;引用则是某块内存的别名。
  2. 从内存分配上看:两者都占内存,程序为指针会分配内存,一般是4个字节;而引用的本质是指针常量,指向对象不能变,但指向对象的值可以变。两者都是地址概念,所以本身都会占用内存。

区别

  1. 指针是实体,而引用是别名
  2. 指针和引用的自增(++)运算符意义不同,指针是对内存地址自增,而引用是对值的自增。
  3. 引用使用时无需解引用(*),指针需要解引用;(关于解引用大家可以看看这篇博客,传送门)
  4. 引用只能在定义时被初始化一次,之后不可变;指针可变;
  5. 引用不能为空,指针可以为空
  6. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小,在32位系统指针变量一般占用4字节内存。

5.C语言检索内存情况 内存分配的方式

内存分配方式:

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释 放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大 小的堆空间将会产生堆内碎块。

程序的内存空间:

  1. 栈区(stack):由编译器自动分配释放,存放为运行函数而分配的局部变量、函数参数、返回数据、返回地址。其操作方式类似于数据结构中的栈。
  2. 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
  3. 全局区(静态区)(static):存放全局变量、静态数据、常量。程序结束后由系统释放。
  4. 文字常量区:常量字符串就是放在这里的。程序结束后由系统释放。
  5. 程序代码区:存放函数体(类成员函数和全局函数)的二进制代码。

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的区别 ⭐⭐⭐⭐⭐

  1. 默认的继承访问权。class默认的是private,strcut默认的是public。
  2. 默认访问权限:struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
  3. 3.“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数
  4. 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的用法(定义和用途)⭐⭐⭐⭐⭐

  1. 在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。该变量在全局数据区分配内存;静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;未经初始化的静态全局变量会被程序自动初始化为0
  2. 在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。
    • 该变量在全局数据区分配内存;
    • 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
    • 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
    • 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;
  3. 在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
  4. 静态数据成员
    在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。
    • 静态数据成员是该类的所有对象所共有的
    • 静态数据成员存储在全局数据区。
  5. 与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。

14.const常量和#define的区别(编译阶段、安全性、内存占用等) ⭐⭐⭐⭐

  1. define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
  2. const 定义的常数是变量 也带类型,#define 定义的只是个常数 不带类型。
  3. define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
  4. const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了
  5. define可以用来防止头文件重复引用,而const不能
  6. const不足的地方,是与生俱来的,const不能重定义,而#define可以通过#undef取消某个符号的定义,再重新定义。

15.c/c++中变量的作用域⭐⭐⭐⭐⭐

在这里插入图片描述

16.volatile作用和用法 ⭐⭐⭐⭐⭐

首先volatile修饰的变量,作用在编译阶段,影响编译出的结果,其修饰的变量是随时可能被修改的,volatile告诉编译器,这个变量是重要人物,不要偷懒的去走捷径,每次认认真真的去从内存拿值。
一般说来,volatile用在如下的几个地方:

  1. 中断服务程序中修改的供其它程序检测的变量需要加 volatile;
  2. 多任务环境下各任务间共享的标志应该加 volatile;
  3. 存储器映射的硬件寄存器通常也要加 volatile 说明,因为每次对它的读写都可能由不同意义;

17.有常量指针 指针常量 常量引用 没有 引用常量⭐⭐⭐

  1. 常量指针(const int* p)常量指针本质上是一个指针,是一个指向“常量”的指针,即不能通过指针改变指向对象的值(不能解除引用),但可以更改指向
  2. 常量引用(const int& a)本质上是一个引用,对常量的引用,不能通过引用改变绑定对象的值
  3. 指针常量(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 ,虚函数就成为纯虚函数

  1. 虚函数定义形式:成员函数前添加 virtual 关键字,纯虚函数在虚函数后添加 =0 ;
  2. 含有纯虚函数的类,称为抽象类;只含有虚函数的类,不能称为抽象类。
  3. 虚函数既可以直接使用,也可以被子类重载实现后,以多态的形式调用;而纯虚函数必须被子类重载实现,才能以多态的形式调用,因为纯虚函数在基类中只有声明。
  4. 无论虚函数还是纯虚函数,定义中都不能有 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

18.手撕智能指针shared_ptr

template<class T>
class SmartPtr{
public:
	SmartPtr(T* ptr = NULL): _ptr(ptr), _pcount(new int(1)) {} //构造函数
	SmartPtr(const SmartPtr& s): _ptr(s.ptr), _pcount(s._pcount){  //拷贝构造函数
		*(_pcount)++; //用指针进行count操作能够方便控制不同智能指针对象的计数值
	}
	SmartPtr<T>& operator=(const SmartPtr& s){  //重载 = ,赋值函数
		if(this != &s){ //检测自我赋值
			if(--(*(this->_pcount)) == 0){
				delete this->_ptr;
				delete this->_pcount;
			}
			_ptr = s._ptr;
			_pcount = s._pcount;
			*(_pcount)++;
		}
		return *this;
	}
	T& operator*(){
		return *(this->_ptr);
	}
	T* operator->(){
		return this->_ptr;
	}
	~SmartPtr(){ //析构函数要进行判定,count为0才会delete,delete之后避免野指针要赋值nullptr
		--(*(this->_pcount));
		if(this->_pcount == 0){
			delete _ptr;
			_ptr = nullptr;
			delete _pcount;
			_pcount = nullptr;
		}
	}
private:
	T* _ptr;
	int* _pcount;
};

19.C++ 静态多态和动态多态

在C++中,静态多态和动态多态是实现多态性的两种主要方式,它们各自有不同的特点和使用场景:

静态多态(Static Polymorphism):

实现方式:通常通过模板和函数重载实现。
编译时决定:在编译时确定调用哪个函数或哪个类的实例,因此称为静态。
模板:允许在编译时根据不同的类型生成不同的函数或类。
函数重载:同一个函数名称可以用于不同参数列表的多个函数,编译器会根据调用时传递的参数类型来选择合适的函数。
效率:通常比动态多态更高效,因为函数调用在编译时就已确定,无需运行时查找。
例子:C++模板类和函数,如 std::vector 或重载的函数。

动态多态(Dynamic Polymorphism):

实现方式:通过虚函数(Virtual Function)实现。
运行时决定:对象的实际类型在运行时才确定,因此函数调用也是在运行时通过虚表来解析的。
虚函数:在基类中声明为虚的函数可以在派生类中重写。调用时,基于对象的实际类型来选择适当的函数实现。
性能开销:相比于静态多态,动态多态有一定的性能开销,因为需要运行时处理虚表。
例子:基类指针或引用调用派生类的重写函数。

20.为什么函数重载不能靠返回值实现

这是因为在函数调用时,编译器需要根据传递的参数来确定应该调用哪个函数版本。

21.空类大小,为什么不能是0

在C++中,空类(即没有成员变量和成员函数的类)的大小不能为0字节,这是因为C++标准规定,每个类的实例在内存中至少占用一个字节的空间。这是为了确保每个对象在内存中都有一个唯一的地址,以便在程序中能够区分不同的对象。

22.内存泄漏和内存碎片

内存泄漏(Memory Leak)和内存碎片(Memory Fragmentation)都是与内存管理相关的问题,但它们涉及的方面不同。

内存泄漏(Memory Leak):

内存泄漏是指在程序运行过程中,分配的内存没有被正确释放或回收,导致程序无法再次使用这些内存块,从而造成内存资源的浪费。

内存泄漏通常发生在以下情况下:
忘记使用 free(C语言)或 delete(C++)来释放动态分配的内存。
丢失了对内存块的指针,无法释放它们。
在循环中重复分配内存而没有释放旧的内存。
无法访问到要释放的内存,例如,引用了已经销毁的对象。
内存泄漏可能导致程序占用越来越多的内存,最终耗尽可用内存资源,导致程序崩溃或变得非常缓慢。

内存碎片(Memory Fragmentation):

内存碎片是指在内存中出现了不连续的小块或碎片化的内存空间,这些小块不足以容纳大的内存分配请求。

内存碎片通常分为两种类型:
外部碎片(External Fragmentation):外部碎片是在内存中的未分配区域之间的碎片,由于这些碎片无法用于分配大内存块,因此它们浪费了内存资源。
内部碎片(Internal Fragmentation):内部碎片是已分配的内存块中未被使用的部分,这些部分通常比外部碎片小,但同样浪费了内存。
内存碎片会导致内存资源的浪费,使得分配较大内存块时变得更加困难,因为需要找到足够连续的内存块。这可能导致程序性能下降,甚至因无法满足内存需求而失败。

为了解决内存泄漏和内存碎片问题,程序员需要谨慎地分配和释放内存,确保动态分配的内存得到适当的释放,同时尽量减少内存碎片的产生。在C/C++中,使用 free(C语言)或 delete(C++)来释放动态分配的内存是防止内存泄漏的重要步骤。对于内存碎片问题,可以使用内存池或内存分配策略来优化内存使用。在高级语言中,如Java和C#,垃圾收集器可以帮助自动管理内存,减少内存泄漏的风险。

四、网络编程

1.TCP、UDP的区别 ⭐⭐⭐⭐⭐

在这里插入图片描述

2.TCP、UDP的优缺点⭐⭐⭐

3.TCP UDP适用场景⭐⭐⭐

UDP 常用于以下几个方面:
1.包总量较少的通信(DNS、SNMP等);
2.视频、音频等多媒体通信(即时通信);
3.限定于 LAN 等特定网络中的应用通信;
4.广播通信(广播、多播)

4.TCP为什么是可靠连接⭐⭐⭐⭐

TCP如何实现可靠性传输?
①重传机制:针对数据包丢失或者出现定时器超时

②确认应答:停止等待协议,发送之后等待收到应答。

③序列号:针对数据包到达接收端主机顺序乱掉

④流量控制:针对避免网络拥堵时候;针对高效传输数据包的流动窗口的控制

⑤拥塞控制:针对刚开始启动的时候避免一下子发送大量数据包而导致网络瘫痪的慢启动算法和拥塞控制。

⑥校验和:发送方和接收方计算校验和并进行对比确认未出错

⑦连接管理:三次握手与四次挥手

⑧数据的合理分片和排序

UDP如何实现可靠性传输?
①超时重传(定时器)

②有序接受 (添加包序号)将数据包进行编号,按照包的顺序接收并存储。

③应答确认 (Seq/Ack应答机制)

④滑动窗口流量控制等机制 (滑动窗口协议)

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的区别⭐⭐⭐

1、HTTP 1.1支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理
HTTP 1.0规定浏览器与服务器只保持短暂的连接,浏览器的每次请求都需要与服务器建立一个TCP连接,服务器完成请求处理后立即断开TCP连接,服务器不跟踪每个客户也不记录过去的请求。

HTTP 1.1则支持持久连接Persistent Connection, 并且默认使用persistent connection. 在同一个tcp的连接中可以传送多个HTTP请求和响应. 多个请求和响应可以重叠,多个请求和响应可以同时进行. 更加多的请求头和响应头(比如HTTP1.0没有host的字段).

2.HTTP 1.1增加host字段
在HTTP1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。

3、100(Continue) Status(节约带宽)
HTTP/1.1加入了一个新的状态码100(Continue)。客户端事先发送一个只带头域的请求,如果服务器因为权限拒绝了请求,就回送响应码401(Unauthorized);如果服务器接收此请求就回送响应码100,客户端就可以继续发送带实体的完整请求了。100 (Continue) 状态代码的使用,允许客户端在发request消息body之前先用request header试探一下server,看server要不要接收request body,再决定要不要发request body。

4.缓存处理
在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。

5.错误通知的管理
在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。

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连接。

14.TCP的拥塞控制机制

拥塞控制是计算机网络中的一个重要概念,特别是在TCP(传输控制协议)中。它旨在防止过多的数据注入到网络中,这可能导致网络拥塞,降低网络效率,甚至导致网络瘫痪。拥塞控制机制确保网络中的数据流量保持在可管理的水平,以便数据能够有效地传输。

TCP拥塞控制的主要机制包括:

  1. 慢启动(Slow Start):
    初始时,TCP连接的拥塞窗口(cwnd)大小很小(通常从1个MSS(最大报文段长度)开始)。
    随着每个成功的确认,窗口大小加倍(指数增长),快速增加网络的利用率,直到达到一个阈值(ssthresh)。
  2. 拥塞避免(Congestion Avoidance):
    当cwnd达到或超过ssthresh时,进入拥塞避免阶段。
    在这个阶段,cwnd的增长转为线性增长,即每个RTT(往返时间)增加一个MSS,以避免过快增加流量导致的拥塞。
  3. 快重传(Fast Retransmit):
    当发送方收到三个重复的确认(Duplicate ACKs)时,立即重传丢失的报文段,而不是等待超时发生。
    这有助于快速恢复丢失的报文段。
  4. 快恢复(Fast Recovery):
    快重传后,TCP不立即减少cwnd到1个MSS,而是减半其值,并重新进入拥塞避免阶段。
    这有助于在丢包后快速恢复传输速度。

超时后的处理:

如果出现超时,TCP将ssthresh设置为当前cwnd的一半,并将cwnd重置为1个MSS,重新开始慢启动过程。
这些机制结合起来,使得TCP能够在不同的网络条件下动态调整其发送速率,以有效地响应网络拥塞的情况,保证网络的稳定性和效率。这也是TCP被广泛认为是一种可靠的传输协议的原因之一。

15.滑动窗口

滑动窗口的原理基于发送方和接收方之间维护一个窗口大小,该窗口内的数据包可以被传输或接收,窗口以一定的速率向前滑动。这个机制可以用来实现流量控制、可靠传输和拥塞控制。

下面是滑动窗口的工作原理的简要说明:

发送窗口(Sender Window): 发送方维护一个发送窗口,其中包含可以发送但尚未得到确认的数据包。发送窗口的大小通常由接收方通告,或者根据网络条件进行动态调整。发送方只能发送位于发送窗口内的数据包。

接收窗口(Receiver Window): 接收方维护一个接收窗口,其中包含可以接收但尚未确认的数据包。接收窗口的大小也可以根据网络条件进行动态调整。接收方只能接收位于接收窗口内的数据包。

发送和接收操作: 发送方在发送窗口内的数据包进行发送,而接收方在接收窗口内的数据包进行接收。接收方会发送确认(ACK)通知,通知发送方哪些数据包已成功接收。

滑动窗口: 随着数据包的发送和确认,发送窗口和接收窗口会向前滑动。发送方在收到确认后可以移动发送窗口的起始位置,接收方在接收到数据包后可以移动接收窗口的起始位置。

流量控制: 发送方根据接收方的接收窗口大小来控制发送速率。如果接收窗口变小,发送方需要降低发送速率,以防止数据包过多堆积在接收方。如果接收窗口变大,发送方可以增加发送速率。

可靠传输: 发送方需要等待接收方的确认,确保数据包已成功接收。如果某个数据包没有收到确认,发送方可以重传该数据包,以确保它最终被接收。

拥塞控制: 滑动窗口也可用于拥塞控制,通过动态调整发送窗口大小以应对网络拥塞。当网络拥塞时,发送窗口可以缩小,减少发送速率,以减轻网络负担。

16怎么减小或避免内存碎片

内存碎片是指内存空间被分割成许多小的碎片,无法满足大块内存分配需求的情况。内存碎片会影响系统的性能和稳定性,以下是一些减少内存碎片的方法:

使用内存池技术
内存池是一种预先分配一定数量内存的技术,可以避免频繁申请和释放内存带来的性能损失和内存碎片问题。

统一内存分配
在内存分配时尽量使用相同大小的内存块,避免使用不同大小的内存块导致内存碎片。

避免频繁内存分配和释放
尽量避免频繁申请和释放内存,可以使用对象池或内存池等技术来管理内存。

压缩内存
在内存碎片比较严重时,可以使用内存压缩技术来整理内存空间,减少内存碎片。

使用智能指针
智能指针可以自动管理内存,避免内存泄漏和内存碎片的问题。

五、常见算法

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 RightSubArray
    vector<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]);
    else
        left++;
    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异同⭐⭐⭐⭐⭐

  1. vector底层实现是数组;list是双向链表。
  2. vector支持随机访问,list不支持。
  3. vector是顺序内存,list不是。
  4. vector在中间节点进行插入删除会导致内存拷贝,list不会。
  5. vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。
  6. vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。

11. vector内存是怎么增长的vector的底层实现⭐⭐⭐⭐

vector 容器是 C++ 标准库中的一种动态数组容器,它可以自动调整大小以适应元素的添加和删除。当 vector 容器需要扩大容量时,会采用半倍扩容的方式。

半倍扩容的原理如下:

  1. 当 vector 容器的当前容量不足以存储新添加的元素时,需要进行扩容。
  2. 首先,计算出新的容量大小(new_capacity):将当前容量(current_capacity)乘以 2。
  3. 然后,检查 new_capacity 是否小于默认容量(即初始容量),如果是,则将 new_capacity 设置为默认容量。
  4. 分配一个新的内存空间,大小为 new_capacity。
  5. 将原来的元素逐个拷贝到新的内存空间中。
  6. 释放原来的内存空间。
  7. 更新当前容量为 new_capacity。

在这里插入图片描述

12.几种模板插入的时间复杂度 ⭐⭐⭐⭐⭐

在这里插入图片描述

13. vector和deque的比较⭐⭐⭐⭐

  1. 两端都能快速安插和删除元素,这些操作可以在分期摊还的常数时间(amortized constant time)内完成。

  2. 元素的存取和迭代器的动作比vector稍慢。

  3. 迭代器需要在不同区块间跳转,所以它非一般指针。

  4. 因为deque使用不止一块内存(而vector必须使用一块连续内存),所以deque的max_size()可能更大。

  5. 不支持对容量和内存重新分配时机的控制。不过deque的内存重分配优于vector,因为其内部结构显示,deque不必在内存重分配时复制所有元素。

  6. 除了头尾两端,在任何地方安插或删除元素,都将导致指向deque元素的所有pointers、references、iterators失效。

  7. deque的内存区块不再被使用时,会自动被释放。deque的内存大小是可自动缩减的。

  8. deque与vector组织内存的方式不一样。在底层,deque按“页”(page)或“块”(chunk)来分配存储器,每页包含固定数目的元素。而vector只分配一块连续的内存。例如,一个10M字节的vector使用的是一整块10M字节的内存,而deque可以使用一串更小的内存块,比如10块1M的内存。所以不能将deque的地址(如&deque[0])传递给传统的C API,因为deque内部所使用的内存不一定会连续。

deque的下述特性与vector差不多:

  1. 在中部安插、删除元素的速度较慢。

  2. 迭代器属于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。

六、Linux操作系统

1. Linux内核的组成⭐⭐

Linux内核主要由五个子系统组成:

进程调度,内存管理,虚拟文件系统,网络接口,进程间通信。

Linux内核是Linux操作系统的核心部分,负责管理系统的硬件资源,提供系统服务,以及允许用户空间程序的执行。Linux内核的主要组成部分包括:

进程调度(Process Scheduler):
负责处理CPU资源的分配,确保各个进程都能公平且高效地使用CPU。
包括时间片轮转、优先级调度等算法。

内存管理(Memory Management):
管理系统的物理和虚拟内存。
包括页面置换算法、虚拟内存管理、物理内存分配和回收等。

文件系统(File System):
提供对不同类型存储设备的文件访问和管理。
支持多种文件系统,如ext4、NFS、FAT等。

网络栈(Networking):
实现TCP/IP协议栈,处理数据包的传输和接收。
提供网络通信功能,包括数据路由、端口管理等。

设备驱动(Device Drivers):
提供与外部设备交互的接口。
包括显示器、硬盘、USB设备等硬件的驱动程序。

系统调用接口(System Call Interface):
为用户空间的应用程序提供访问内核服务的接口。
如文件操作、进程控制、网络通信等系统调用。

安全(Security):
负责系统的安全机制,包括访问控制、安全审计等。
包括SELinux、AppArmor等安全框架。

中断处理(Interrupt Handling):
处理硬件或软件中断信号。
包括中断服务程序(ISR)的管理。

进程间通信(Interprocess Communication, IPC):
提供进程间数据交换的机制。
包括信号量、消息队列、共享内存等。

模块管理(Module Management):
允许内核功能的动态加载和卸载。
方便了设备驱动和系统服务的管理。

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内核 根文件的关系⭐⭐⭐⭐

1. bootloader
BootLoader就是在操作系统运行之前运行的一段小程序。通过这段小程序,可以初始化硬件设备,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统做好准备。
2.kernel
kernel的启动依赖于bootLoader,因此需要用u-boot来引导和加载我们的内核镜像。使用u-boot的boot相关命令能够启动linux kernel。当kernel启动完毕的时候,u-boot就完成了它的任务,把命令行让给linux内核。
3.rootfs
kernel的正常运行要求有一个根文件系统rootfs,这个根文件系统存放了linux系统的一些重要文件、库、命令等,内核启动后需要对其进行挂载。

7. Bootloader多数有两个阶段的启动过程:⭐⭐⭐

bootloader一般都是分为两个阶段的,第一个阶段使用汇编来实现,它完成一些依赖于CPU体系结构的初始化,并调用第二阶段的代码;第二阶段则通常使用C语言来实现,这样可以实现更复杂的功能,而且代码会有更好的可读性和移植性。

两个阶段如下:

一、第一阶段功能

(1)硬件设备初始化;

(2)为加载bootloader的第二个阶段代码准备RAM空间。

(3)复制bootloader的第二个阶段代码到RAM中;

(4)设置好栈。

(5)跳转到第二阶段代码的C入口点。

二、第二个阶段的功能

(1)初始化本阶段要使用到的硬件设备。

(2)检测系统内存映射。

(3)讲内核映像和根文件系统从Flash上读到RAM空间中。

(4)为内核设置启动参数。

(5)调用内核。

8. linux的内核是由bootloader装载到内存中的?⭐⭐⭐

linux的内核的确是由bootloader装载到内存中的。 linux的bootloader有2个部分组成:bootstrap和uboot。

9.为什么需要BootLoader⭐⭐⭐⭐

1、Bootloader的作用

简单的说,BootLoader就是在操作系统运行之前运行的一段小程序。通过这段小程序,可以初始化硬件设备,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统做好准备。对于Bootloader的启动过程又分为两个阶段stage1和stage2。

stage1全部由汇编编写,它的主要工作是(1)初始化硬件设备、(2)为加载Bootlodader的stage2准备RAM空间(3)拷贝Bootloader的stage2到RAM空间(4)设置好堆栈段为stager2的C语言环境做准备。

stage2全部由C语言编写,其的主要工作是(1)初始化本阶段要使用到的硬件设备(2)将内核映像和根文件系统映像从 flash 上读到RAM (3)调用内核

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下检查内存状态的命令⭐⭐⭐

  1. 使用 free 命令
  2. 查看 /proc/meminfo
  3. 使用 top 命令

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 内存 虚拟内存 磁盘/硬盘 的关系⭐⭐⭐

  1. CPU从内存或缓存中取出指令,放入指令寄存器,并对指令译码分解成 系统指令的执行。

  2. 内存(即物理 内存,是相对于硬盘这个“外存”而言)作为硬盘和CPU的“中转站”,对电脑运行速度有较大影响。

  3. 当运行数据超出物理内存容纳限度的时候,部分数据就会自行“溢出”,虚拟内存运行的程序或 不使用的数据存放到这部分空间之中,等待需要的时候方便及时调用。

  4. 由于内存是带电存储的(一旦断电数据就会消失),而且容量有限,有了硬盘

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,当然了传输的时候还要加上起始位和结束位,没有这两位接收方就不知道什么时候开始接收数据什么时候结束了。

同步通信,是指数据传送是以数据块(一组字符)为单位,字符与字符之间、字符内部的位与位之间都同步。同步通信时,通信双方共用一个时钟,这是同步通信区分于异步通信的最显著的特点。同步串行通信的特点可以概括为:

以数据块为单位传送信息。
在一个数据块(信息帧)内,字符与字符间无间隔。
因为一次传输的数据块中包含的数据较多,所以接收时钟与发送进钟严格同步,通常要有同步时钟。

12 I2C时序图⭐⭐⭐⭐⭐

在这里插入图片描述

webserver项目

1、文件描述符与IO模型

文件描述符:当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
  IO多路复用是一种同步IO模型,实现一个进程可以监视多个文件句柄(socket、文件或者管道等等),一旦某个文件句柄就绪,就能够通知程序进行相应的读写操作。
在这里插入图片描述

IO多路复用相比于多线程的优势在于系统的开销小,系统不必创建和维护进程或线程,免去了线程或进程的切换带来的开销。而操作系统支持IO多路复用的系统调用有select,poll和epoll。

2、端口和地址复用

在默认的情况下,如果一个网络应用程序的一个套接字绑定了一个端口( 8080),这时候,别的套接字就无法使用这个端口( 8080 )。

但是端口复用允许在一个应用程序可以把多个套接字绑在一个端口上而不出错。通过设置socket的SO_REUSEADDR选项,即可实现端口复用:

int opt = 1;  
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 
		  (const void *)&opt, sizeof(opt));  

【为什么要有这个端口复用呢】 ?

因为在服务端结束后,也就是第三次挥手的时候会有个等待释放时间(time_wait),这个时间段大概是1-4分钟(2MSL), 在这个时间内,端口不会迅速的被释放,所以可通过端口复用的方法来解决这个问题。

SO_REUSEADDR允许单个进程绑定相同的端口到多个socket上,但每个socket绑定的ip地址不同。

【为什么一个端口可以建立多个连接】 ?

一个TCP连接需要由四元组来形成,即(src_ip,src_port,dst_ip,dst_port)。
假设有客户端建立了连接(src_ip1,src_port1,dst_ip1,dst_port1),那么,如果我们还有listen在(src_ip1,src_port1),那么当(dst_ip1,dst_port1)发送消息过来,系统应该把消息给谁?所以就说明了客户端占用了某一端口时,该端口就不能被其它进程listen了。

作为一个服务器监控一个端口,比如80端口,它为什么可以建立上百万个连接?首先要明白一点,当accept出来后的新socket,它所占用的本地端口依然是80端口,很多新手都以为是一个新的随机端口。由四元组就很容易分析到了,同一个(src_ip,src_port),它所对应的(dst_ip,dst_port)可以无穷变化,这样就可以建立很多个客户端的请求了。

3、select/poll/epoll的区别

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, 
			struct timeval *timeout);

select的底层是一个fd_set的数据结构,本质上是一个long类型的数组,数组中每一个元素都对应于一个文件描述符,通过轮询所有的文件描述符来检查是否有事件发生。

【优点】:

可移植性好;

连接数少并且连接都十分活跃的情况下,效率也不错。

【缺点】:

可以监听的最大文件描述符数量为1024(因为内核写定了)。

检查是否有事件发生是采用轮询遍历的方式,当文件描述符很多时开销很大。
poll

int poll(struct pollfd* fds, unsigned int nfds, int timeout);

poll与select差不多,但poll的文件描述符没有最大数量的限制,但是依然采用轮询遍历的方式检查是否有事件发生。
epoll

// 返回epoll文件描述符,size表示要监听的数目 (这个返回的fd要记得close)
int epoll_create(int size); 
// epoll事件注册函数
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 
// 等待事件发生,events是返回的事件链表
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);  
  1. epoll是一种更加高效的IO多路复用的方式,它可以监视的文件描述符数量突破了1024的限制(十万),同时不需要通过轮询遍历的方式去检查文件描述符上是否有事件发生,因为epoll_wait返回的就是有事件发生的文件描述符。本质上是事件驱动。
  2. 是通过红黑树和就绪链表实现的,红黑树存储所有的文件描述符,就绪链表存储有事件发生的文件描述符;
  3. epoll_ctl可以对文件描述符结点进行增、删、改、查,并且告知内核注册回调函数(事件)。
  4. 一旦文件描述符上有事件发生时,那么内核将该文件描述符节点插入到就绪链表里面
  5. 这时候epoll_wait将会接收到消息,并且将数据拷贝到用户空间。

表面上看epoll的性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能比epoll好,毕竟epoll的通知机制需要很多函数回调。

4、epoll两种触发模式——LT/ET

epoll的两种触发模式会在epoll_wait()函数处对读取缓冲区有不用的处理方式。
1.LT水平触发(默认):当缓存区的数据没有被一次性读取完,那么epoll_wait()函数会非阻塞的进行再次读取,直至读写缓存区的数据被读取完成。
2.ET边沿触发 :每当进行一次读取操作后,epoll_wait()函数就会堵塞,直至下一次缓存区数据的写入,才会在此的触发读取操作。

对于水平模式,只要 socket 上有未读完的数据,就会一直产生 EPOLLIN 事件;而对于边缘模式,socket 上每新来一次数据就会触发一次,如果上一次触发后,未将 socket 上的数据读完,也不会再触发,除非再新来一次数据。对于 socket 写事件,如果 socket 的 TCP 窗口一直不饱和,会一直触发 EPOLLOUT 事件;而对于边缘模式,只会触发一次,除非 TCP 窗口由不饱和变成饱和再一次变成不饱和,才会再次触发 EPOLLOUT 事件。

区别:对于LT模式,保证了对数据的完整性读取,但是对一些不重要的不必要的数据来说,会加大内核对epoll_wait()函数的调用,加大开销。对于ET模式,可以在程序中设定读取的关键信息段,从而来减轻内核的开销,效率而言相比LT来说较高。但是值得注意的是读取缓存区需要及时将多余的信息清理掉,保证下一次读取的可靠性。

补充

1.gdb常用指令

在这里插入图片描述
在这里插入图片描述

2.Linux指令大全

ls cd find touch vi sudo shutdown man ifconfig ping mkdir kill cp mv pwd rm wget
http://t.csdnimg.cn/bsBw6

3.移动构造函数和复制构造函数的区别

4.如何避免浅拷贝

写拷贝构造函数

5.如何创建一个线程需要什么参数

  1. 传递一个 pthread_t 类型的指针变量
  2. 用于手动设置新建线程的属性
  3. 以函数指针的方式指明新建线程需要执行的函数
  4. 以函数指针的方式指明新建线程需要执行的函数

6.c++中创建一个类默认生成的函数

default构造函数(无参构造函数)
拷贝构造函数
=重载操作符函数
析构函数
移动构造函数

7、网络模型有哪几层

在这里插入图片描述

8.多线程 顺序执行

join()
加锁
信息量

9. TCP协议、IP协议、HTTP协议分别在哪一层吗?

运输层,网络层,应用层。

10. MAC地址是什么

物理地址与ip地址区分

11.全局变量的内存位置在哪个段

静态存储区

12. IP地址的分类及范围

A类地址的第一组数字为1~126。其中0代表任何地址,127为回环测试地址,注意,数字0和 127不作为A类地址,数字127保留给内部回送函数,而数字0则表示该地址是本地宿主机,不能传送。B类地址的第一组数字为128~191。C类地址的第一组数字为192~223。

A类地址
A类地址的表示范围为:0.0.0.0~126.255.255.255,默认网络掩码为:255.0.0.0;A类地址分配给规模特别大的网络使用。A类网络用第一组数字表示网络本身的地址,后面三组数字作为连接于网络上的主机的地址。分配给具有大量主机(直接个人用户)而局域网络个数较少的大型网络。例如IBM公司的网络。

B类地址
B类地址的表示范围为:128.0.0.0~191.255.255.255,默认网络掩码为:255.255.0.0;B类地址分配给一般的中型网络。B类网络用第一、二组数字表示网络的地址,后面两组数字代表网络上的主机地址。

C类地址
C类地址的表示范围为:192.0.0.0~223.255.255.255,默认网络掩码为:255.255.255.0;C类地址分配给小型网络,如一般的局域网和校园网,它可连接的主机数量是最少的,采用把所属的用户分为若干的网段进行管理。C类网络用前三组数字表示网络的地址,最后一组数字作为网络上的主机地址。

D类地址和E类地址
用途比较特殊,D类地址称为广播地址,供特殊协议向选定的节点发送信息时用,E类地址保留给将来使用。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值