某私募公司面经

161 阅读12分钟

挑几个记得的问题写一下

计算机网络

1.流量控制和拥塞控制的区别是什么?

流量控制解决的是发送方和接收方速率不匹配的问题。如果发送方发送过快就来不及接收和处理,采用滑动窗口的机制,控制的是发送方的发送速率。在接收方发送的确认报文段中有一个窗口字段,该字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。如果将窗口字段设置为0,则发送方不能发送数据。
在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫做拥塞
拥塞控制就是为了防止过多的数据注入网络中,这样就可以使网络中的路由器或链路不致过载。当出现丢包时,控制发送的速率达到降低网络负载的目的。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。
拥塞控制是一个全局的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是一个端到端的问题。流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。

操作系统

1. 进程和线程的区别?

进程是对运行时程序的封装,是系统进行资源调度和分配的基本单位,实现了操作系统的并发。 线程是进程的子任务,是CPU调度和分配的基本单位,用于保证程序的实时性,实现进程内部的并发
区别:

  1. 一个线程只能属于一个进程,一个进程可以拥有多个线程,并且至少有一个线程。
  2. 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存
  3. 进程是资源分配的最小单位,线程是CPU调度的最小单位。
  4. 进程间是独立的,不会互相影响;一个线程挂掉将导致整个进程挂掉。

2. 什么情况下用多进程/多线程?

我说了个例子:用QQ同时进行语音和发文件就需要用到多线程。
多进程的优点:进程增减容易,进程间独立互不干扰。多进程缺点:频繁进行上下文切换会严重降低整体效率。
多线程的优点:上下文切换问题没那么严重,线程间可以方便地共享数据。 多线程地缺点:共享数据时需要小心、坑多,会增加一定的代码复杂度。
网上查到的答案是:对一个程序,在CPU的使用方面可以分为两类:1. CPU密集型:其主要特点是要进行大量的计算,消耗CPU资源,比如计算圆周率,高清视频解码,这些需要CPU的计算能力。为了最高效地利用CPU,计算密集型任务同时进行地数量应当等于CPU的核心数。 2. I/O密集型:主要涉及到网络、磁盘I/O的任务都是I/O密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都是在等待I/O操作完成(因为I/O的速度远远低于CPU和内存的速度)。对于I/O密集型任务,任务越多,CPU效率越高,但是也有一个限度,常见的大部分任务都是I/O密集型任务,如Web应用。
综上,在处理CPU密集型任务时,应该选择多进程实现,有效地利用多核提升效率;在处理I/O密集型任务时,由于99%的时间都花在I/O上,花在CPU上的时间很少,所以应该用多线程提高效率。

3. 虚拟地址转换为物理地址?

在分页存储管理情况下,首先对将虚拟地址切分为页号和页内偏移量,然后通过查询页表得到具体的物理地址。分段和段页式存储管理也类似,都需要查询段表或页表。

4. 如何找到页表?

在我说完通过查询页表将虚拟地址转换为物理地址后,就被问到了这个问题。
页表是存放在内存中的,并且是连续存储的,所以只要知道了页表的起始地址,就能计算出所要查询的页号存放的位置。而页表的起始地址存放在页表基址寄存器中。

5. 多级页表

由于页表必须连续存放,所以当页表项很多时,可能需要占用很多个连续的页框。所以可以将页表项分组,让一个组的页表项恰好占用一个页面,然后通过顶级页表存储下一级中每一组页表的存放位置。这样看上去可能多级页表所占的内存空间更多,其实不然,多级页表是比单级页表省内存的,原因如下:

  1. 二级页表可以不存在:由于每个进程都有4GB的虚拟内存空间,但是对于大多数程序来说,其使用到的空间远远未达到4GB,所以就不需要去映射那些用不到的空间。也就是说,一级页表覆盖了整个4GB的虚拟地址空间,但是如果某个一级页表的页表项没有用到(说明该页表项对应的二级页表的一组页表项没有被用到),也就不需要创建这个页表项对应的二级页表了,即可以在需要时才创建二级页表。 那为什么不分级的页表就做不到这样节约内存呢?从页表的性质看,保存在内存中的页表所承担的职责是将虚拟地址翻译为物理地址,假如虚拟地址在页表中找不到对应的页表项,计算机系统就不能工作了,所以页表一定要覆盖全部虚拟地址空间,所以不分级的页表就需要将用不到的空间也映射出来。
  2. 二级页表可以不在内存中:其实就是把页表当成页面,通过请求分页存储管理,当需要用到某个页面时,将其从外存调入内存。由于虚拟内存地址存在局部性,那么负责映射虚拟内存地址的页表项也存在局部性,所以可以将二级页表放入磁盘,在需要时再将其调入内存。

C++

1. 常量指针和指针常量?

常量指针const * 表示该指针是指向一个常量的,不能通过该指针去修改指向的对象。
指针常量* const 表示该指针是一个常量,不能修改这个指针,即不能修改指针指向的地址,但是可以修改指针指向的对象

2. 动态链接库和静态链接库有什么区别?

程序的编译链接.png 静态链接库:静态链接库在程序编译时被链接到目标代码中参与编译,链接时将完整地拷贝至可执行文件中,当多次使用该库时,就会有多份冗余拷贝。当生成可执行程序后,静态链接库由于已经在文件当中了,所以就不需要了。 特点: 1. 对函数库地链接是在编译时期完成的。 2. 程序在运行时与函数库无瓜葛,方便移植。 3. 浪费空间和资源。 4. 如果静态库更新,则应用该库的所有程序都需要重新编译。 动态链接库:在程序运行时,由系统动态加载动态库到内存,供程序调用,系统只加载一次,供多个程序共用,节省内存。 特点: 1. 把一些库函数的链接载入推迟到程序的运行时期。 2. 可以实现进程之间的资源共享。(动态库也叫共享库) 3. 使一些程序的升级变得简单,直接修改动态库即可。 4. 可以做到链接载入由程序员在程序代码中控制(显示调用)。
区别:

  1. 静态库在编译时连接,在链接时拷贝。 动态库在运行时连接。
  2. 静态库在使用时,将会全部连接进可执行程序,造成资源浪费。 而动态库在使用时可以直接访问动态库中的函数,节省资源。
  3. 静态库更新时,每个使用静态库的程序都需要更新,不易于更新升级。动态库仅更新自身,易于更新升级。
  4. 静态链接库不能再包含其他动态链接库。动态链接库可以包含其他动态链接库。

3. 重写和重载有什么区别?

重载:是在同一个访问区内被声明的几个具有不同参数列(参数的个数,类型,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心返回类型。
重写(覆盖):一般是在派生类中重写基类中的虚函数,即在派生类中重新定义函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有花括号内的函数体不同。

4. 什么成员函数不能是虚函数?

  1. 只有类的成员函数才能被声明为虚函数。
  2. 友元函数不属于类的成员函数,不能被声明为虚函数。
  3. 构造函数不能是虚函数。因为构造函数是不能够被继承的。假如子类可以继承基类的构造函数,那么子类对象的构造将使用基类的构造函数,但是基类构造函数并不知道子类有什么成员,这是不符合语义的。(我们只能在子类构造函数中调用基类的构造函数,而不是继承)
  4. 内联函数不能是虚函数。内联函数为了减少函数调用花费的代价,是直接在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。
  5. 静态成员函数不能是虚函数。静态成员函数同样是在编译时确定的,无法动态绑定,不支持多态。 注:析构函数一般写成虚函数。 虚析构函数是为了解决这样一个问题:用基类指针指向派生类对象,并用基类的指针删除派生类对象。如果此时析构函数是非虚函数,那么在delete时只会调用基类的析构函数,而不会调用派生类的析构函数。这样,在派生类中申请的资源就不会得到释放,就会造成内存泄漏,这是相当危险的:如果系统中有大量的派生类对象被这样创建和销毁,就会有内存不断的泄漏,久而久之,系统就会因为缺少内存而崩溃。

5. 对虚函数重写和对非虚函数重写有什么区别(通过父类指针调用时)?

在派生类中对基类中的虚函数重写后,通过父类指针指向派生类然后调用该函数时,会调用派生类中重写后的版本(多态)。而如果一个函数在基类中并不是虚函数,是一个普通函数的话, 在派生类中对它重写后,会将基类中的同名函数隐藏。此时通过父类指针指向派生类对象,然后调用该同名函数,会调用父类中的函数。因为该函数并不是虚函数,而父类指针默认指向的是父类对象,在调用时,自然会调用父类中的函数,而不会调用派生类中的函数,只有该函数是虚函数时,才会导致多态。

重载、重写、隐藏之间的区别是什么?

重载:是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
隐藏:是指派生类的函数屏蔽了与其同名的基类函数,注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏
重写(覆盖):是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。只有函数体不同(花括号内)。派生类调用时会调用派生类的重写函数,不会调用被重写函数。基类中被重写的函数必须有virtual修饰(即应该重写基类中的虚函数)。
如果想要使用隐藏的成员的话,可以通过作用域运算符来实现。

数据结构

1. 设计一个set,插入、删除、随机取出一个数的时间复杂度均为O(1),空间复杂度不限。

需要同时使用两种数据结构,数组哈希表(我说的好像是直接用哈希函数算下标,这样的话相当于将元素离散存储,会浪费很多空间)。 通过数组保存所有元素,HashMap中key为元素,value为元素在数组中的下标。
插入:
将元素插入到数组的最后,并将元素和它的下标存储在HashMap中。
删除:

  1. 通过HashMap找到要删除元素的下标
  2. 删除HashMap中的对应项
  3. 将数组中最后一个位置的元素拷贝到要删除的元素位置
  4. 删除数组中最后一个元素
  5. 更新被拷贝元素在HashMap中的value 这样的操作的好处是元素会保持连续存储,不会造成空间的浪费。 随机取出一个数:
    通过随机函数在数组中随机取一个值即可。