讨论/求职面试/百度|客户端|一面 |技术人求职记|20210126|/
百度|客户端|一面 |技术人求职记|20210126|

主题一:C++

t1. int a[10]; 这个数组的数组变量和数组内容都是存在哪里的?

若是全局数组,则会被创建在静态存储区;其他情况会被分配在栈上。

t2.指针数组和数组指针的区别?

指针数组本质上是数组,每个数组元素指向一个int型变量的地址,数组占多少个字节由数组本身决定。

数组指针本质上是指针,是一个指向一个数组的指针变量,32位系统下永远占4个字节。

t3. STL中vector和list的区别?

vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。其能高效的进行随机存取,时间复杂度为o(1);但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。

list由双向链表实现的,因此内存空间是不连续的,且开销较大因为需要额外的指针信息。由于只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);但由于链表的特点,其能O(1)地进行插入和删除。



主题二:操作系统

t1. 介绍一下一个进程的内存管理

  1. 在操作系统中,系统会给每个进程分配虚拟地址,虚拟地址的大小与处理器的位数有关,如32位处理器进程可分配4GB的虚拟内存供程序正常运行。这4GB的虚拟内存,存储单元从地址0开始进行排序,此地址为虚拟地址。

  2. 该虚拟地址可分为五个部分:

    (1)栈区:由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。线程也有自己维护的栈。

    (2)堆区:程序动态申请的空间,由程序释放或其他方式释放,若没有释放,可能导致内存泄露。

    (3)全局区(静态区):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放 。

    (4)文字常量区:常量字符串就是放在这里的,程序结束后由系统释放。

    (5)程序代码区:存放函数体的二进制代码。

t2. 介绍下程序的内存释放

  1. 当一个程序运行完毕之后,它所使用的数据就不再需要。由于内存是有限的,所以它原来占据的内存空间也应该释放给别的程序使用。对于普通变量和数组,在程序结束运行以后,系统会自动将它们的空间回收。然而对于我们自己分配的堆内存空间,大多数系统都不会将它们回收。如果我们不人为地对它们进行回收,只借不还,那么系统资源就会枯竭,电脑的运行速度就会越来越慢,直至整个系统崩溃。我们把这种只申请空间不释放空间的情况称为内存泄露。
  2. 在C++中,确认申请的堆内存空间不再使用后,我们用delete操作符来释放堆内存空间。如果申请的是一个堆内存变量,则delete后的[]可以省略;如果申请的是一个堆内存数组,则该[]不能省略,否则还是会出现内存泄露。另外,我们也不难发现,delete后的指针就是通过new获得的指针,如果该指针的数据被修改或丢失,也可能造成内存泄露。

t3. Linux下的常用命令

  1. 列出目录:ls
  2. 切换目录:cd
  3. 显示当前目录:pwd
  4. 创建一个新目录:mkdir
  5. 删除一个空的目录:rmdir
  6. 复制文件或目录:cp
  7. 移除文件或目录:rm
  8. 移动文件与目录,或修改文件与目录的名称:mv
  9. 查找文件或目录:find
  10. 新增、修改文本文件:vim
  11. 显示进程情况:ps
  12. 终结进程:kill
  13. 改变文件权限:chmod
  14. 打包:tar

t4. 谈一谈同步和异步

  1. 同步和异步关注的是消息通信机制。同步,就是调用某个东西是,调用方得等待这个调用返回结果才能继续往后执行。异步,和同步相反。调用方不会理解得到结果,而是在调用发出后调用者可用继续执行后续操作,被调用者通过状体来通知调用者,或者通过回掉函数来处理这个调用。
  2. 同步:发送方发出数据后,等接收方发回响应以后才发下一个数据包的通讯方式。(当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续执行下去)
  3. 异步:发送方发出数据后,不等接收方发回响应,接着发送下个数据包的通讯方式。(当程序1调用程序2时,程序1径自继续自己的下一个动作,不受程序2的的影响)
  4. 同步异步不能和阻塞非阻塞混为一谈。阻塞和非阻塞强调的是程序在等待调用结果时的状态,同步和异步强调的是消息通信机制。


主题三:计算机网络

t1. 介绍下TCP三次握手、四次挥手

  1. 三次握手过程理解

    (1)第一次握手:建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认。

    (2)第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。

    (3)第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手

  2. 四次挥手过程理解

    (1)第一次挥手:客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u,此时,客户端进入FIN-WAIT-1(终止等待1)状态。

    (2)第二次挥手:服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

    (3)第三次挥手:服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。

    (4)第四次挥手:客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB(传输控制块)后,才进入CLOSED状态。而服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

t2. 浏览器输入URL到显示网页,发生了什么?

  1. 输入网址
  2. DNS解析URL对应的IP
  3. 建立TCP连接
  4. 浏览器向WEB服务器发起Http请求
  5. 服务器处理请求,浏览器接收HTTP响应
  6. 关闭TCP连接
  7. 浏览器解析资源,渲染布局

t3. 提到了DNS查询,本地有存放域名和IP地址的映射吗,在哪里?

  1. 有,在本地硬盘的hosts的文件里。

  2. hosts文件:由操作系统操作的IP和域名的本地映射文件,可以视为DNS server的重写,一旦查到了指定的域名,就不会继续查找DNS server,所以可以节省时间。但是hosts设置的IP地址是静态的,如果web app的宿主机地址发生改变,对应的hosts也要改写。

  3. DNS解析查询的详细过程

    (1)首先浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用这个IP地址映射,完成域名解析。

    (2)如果没找到则会查找本地DNS解析器缓存,如果查找到则返回。如果还是没有找到则会查找本地DNS服务器,如果查找到则返回。此过程是递归查询。

    (3)如果没有,本地DNS服务器还要向DNS根服务器进行查询。此过程是迭代查询,按根域服务器->顶级域(.com)->第二层域(baidu.com)->子域。

t4. HTTP1.1 和 HTTP2.0 的区别

  1. 多路复用:HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。
  2. 头部数据压缩:HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。
  3. 服务器推送: 为了改善延迟,HTTP2.0引入了server push,它允许服务端在浏览器明确地请求之前推送资源给浏览器,免得客户端再次创建连接发送请求到服务器端获取。这样客户端可以直接从本地加载这些资源,不用再通过网络。


主题四:算法

t1.在不借助临时变量的前提下,交换两个变量的值?

一般而言,我们的代码是这样的:

void swap(int& a, int& b) {
    int tmp = a;
    a = b;
    b = tmp;
}

这是因为在 a=ba = b 语句执行中,只能进行这一次值的转移,转移过后变量 aa 原来的值就丢失了。所以需要一个辅助变量 tmptmp 暂存 aa 的值。同理,我们还可以利用临时变量暂存 a+ba + b 的值,并在一次值转移后通过暂存的两数之和以及已经改变了存储值的一个数相减得到另一个数:

void swap2(int& a, int& b) {
	int tmp = a + b;
	a = b;
	b = tmp - a;
}

我们可以进一步地利用 bb 代替 tmptmp 实现这一功能:

void swap3(int& a, int& b) {
	b = a + b; 
	// 现在 b = sum{a, b}

	a = b - a;
	// a = sum{a, b} - a = b
	// 此时 a 中存放的是原来 b 的值

	b = b - a; 
	// b = sum{a, b} - b = a
	// 此时 b 中存放的是原来 a 的值
}
16
共 7 个回复

交换两数的方法可以改进一下,因为相加操作可能导致溢出,最好改用异或。
如果是c++或某些语言还能这样写:

a ^= b ^= a ^= b;
3

大佬非常严谨

2

异或如果a==b就不行了

看不懂。

学到了

不是大佬,而且不是特别严谨,注意一个情况:

比如交换a和a(相同的两数),这种方法没法用。

a ^= a; // a -> 0 
a ^= a; // a -> 1
a ^= a; // a -> 0

无法达到交换的结果,应该这样写:

void swap(int& a, int& b) {
    if (&a == &b) return; //若地址相同则为同一个数
    a ^= b;
    b ^= a;
    a ^= b;
}

好评!!!学习了