首页 > 字节跳动 C++面经总结及解析 第一期
头像
Demo!!
编辑于 2020-09-19 20:37
+ 关注

字节跳动 C++面经总结及解析 第一期

我们的公众号:扶摇就业   更多面经
希望大家来我们的公众号来投稿内推资源,谢谢!!

面试题目

1.进程和线程的区别,有了进程为什么还要有线程

2.进程的资源、线程的资源

3.多线程怎么同步,多线程竞争怎么解决

4.进程通信

5.线程之间的通信方式

6.一次完整的http请求过程

7.http和https区别,哪个更快

8.https加密过程

9.HTTP和TCP的区别

10.DNS域名解析过程,用到了什么协议(UDP),最后获得IP地址就能怎样呢(就可以识别在网络中的机器)

11.TCP和UDP区别以及各自使用场景,UDP不可靠的话那为什么咱们现在视频这么流畅,是依靠什么

12.TCP拥塞控制

13.udp可以实现的功能

14.如何使UDP达到和TCP相同的效果

15.new malloc区别

16.new/delete,malloc/free,delete/delete[]

17.static关键字

18.static应用场景

19.虚函数的作用和底层实现(实现多态,虚表 虚表指针)

20.哪些函数不能是虚函数,构造函数为什么不行

题目解析

1.

区别:

1. 进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)
2. 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
3. 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
4. 但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

为什么:

进程属于在CPU和系统资源等方面提供的抽象,能够有效提高CPU的利用率。

线程是在进程这个层次上提供的一层并发的抽象:

(1)能够使系统在同一时间能够做多件事情;

(2)当进程遇到阻塞时,例如等待输入,线程能够使不依赖输入数据的工作继续执行

(3)可以有效地利用多处理器和多核计算机,在没有线程之前,多核并不能让一个进程的执行速度提高


2.

一、堆与栈

堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

二、其他

线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。
进程拥有这许多共性的同时,还拥有自己的个性。有了这些个性,线程才能实现并发性。这些个性包括:
1.线程ID
每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标识线程。
2.寄存器组的值
由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线程切换到另一个线程上时,必须将原有的线程的寄存器集合的状态保存,以便将来该线程在被重新切换到时能得以恢复。
3.线程的堆栈
堆栈是保证线程独立运行所必须的。线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影响。
4.错误返回码
由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用
后设置了errno值,而在该线程还没有处理这个错误,另外一个线程就在此时
被调度器投入运行,这样错误值就有可能被修改。
所以,不同的线程应该拥有自己的错误返回码变量。
5.线程的信号屏蔽码
由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自
己管理。但所有的线程都共享同样的信号处理器。
6.线程的优先级
由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参
数,这个参数就是线程的优先级。


3.

C++线程同步的四种方式:互斥锁,条件变量,读写锁,信号量


4.

5.

1. 使用全局变量
主要由于多个线程可能更改全局变量,因此全局变量最好声明为volatile

2. 使用消息实现通信
在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。
1)定义消息#define WM_THREAD_SENDMSG=WM_USER+20;
2)添加消息函数声明afx_msg int OnTSendmsg();
3)添加消息映射ON_MESSAGE(WM_THREAD_SENDMSG,OnTSM)
4)添加OnTSM()的实现函数;
5)在线程函数中添加PostMessage消息Post函数

3. 使用事件CEvent类实现线程间通信
Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。
1)创建一个CEvent类的对象:CEvent threadStart;它默认处在未通信状态;
2)threadStart.SetEvent();使其处于通信状态;
3)调用WaitForSingleObject()来监视CEvent对象


6.

一个完整的HTTP过程包括建立连接、数据传输、断开连接等七个步骤。

1. TCP建立连接

HTTP协议是基于TCP协议来实现的,因此首先就是要通过TCP三次握手与服务器端建立连接,一般HTTP默认的端口号为80;

2. 浏览器发送请求命令

在与服务器建立连接后,Web浏览器会想服务器发送请求命令

3. 浏览器发送请求头消息

在浏览器发送请求命令后,还会发送一些其它信息,最后以一行空白内容告知服务器已经完成头信息的发送;

4. 服务器应答

在收到浏览器发送的请求后,服务器会对其进行回应,应答的第一部分是协议的版本号和应答状态码;

5. 服务器回应头信息

与浏览器端同理,服务器端也会将自身的信息发送一份至浏览器

6. 服务器发送数据

在完成所有应答后,会以Content-Type应答头信息所描述的格式发送用户所需求的数据信息

7. 断开TCP连接

在完成此次数据通信后,服务器会通过TCP四次挥手主动断开连接。但若此次连接为长连接,那么浏览器或服务器的头信息会加入keep-alive的信息,会保持此连接状态,在有其它数据发送时,可以节省建立连接的时间。


7.

Http更快。


8.

https证书加密的第一步是认证服务器。

一些主流浏览器会内置一个受信任的CA机构列表,并会保存相关CA机构的https证书。当用户在访问部署了https证书的网站时,服务器会提供经CA机构颁发的https证书,如果认证该服务器证书的CA机构是存在于浏览器的受信任CA机构列表当中,并且该https证书中的所有信息均与当前证在访问的网站所有信息一致,那么浏览器就会认为服务端是可信的,并从https证书中取得公钥(也就是CSR文件),用于后面的流程。

https证书加密的第二步是协商会话秘钥。

在服务器认证完获取公钥之后,利用公钥与服务器进行加密通信,协商出两个会话秘钥,用于加密客户端和加密服务端互发数据时的会话秘钥。这个秘钥是随机生成的,每一次协商产生的结果都不一样,所以安全性也是比较高的。

https证书加密的第三步是加密传输。

当客户端和服务器端都拥有了协商的会话密钥之后,进行数据传输时,都是以密文的方式进行传输。这样的传输方式,保证了数据的私密性和完整性,再也不用担心数据在传输过程中被第三者窃取和篡改了。

https证书加密完成这三步,就表示很完美的进行了一次数据加密传输。只要你申请的https证书是由受信任的CA机构(比如Symantec、Geotrust、Comodo以及RapidSSL等)颁发的,都可以保障网络的基本安全。


9.

TCP是传输层协议,定义数据传输和连接方式的规范。握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。

HTTP 超文本传送协议(Hypertext Transfer Protocol )是应用层协议,定义的是传输数据的内容的规范。

HTTP协议中的数据是利用TCP协议传输的,特点是客户端发送的每次请求都需要服务器回送响应,它是TCP协议族中的一种,默认使用 TCP 80端口。


10.

UDP

就可以识别在网络中的机器


11.

1、面向连接VS无连接
TCP建立一个连接需要3次握手IP数据包,断开连接需要4次握手。另外断开连接时发起方可能进入TIME_WAIT状态长达数分钟(视系统设置,windows一般为120秒),在此状态下连接(端口)无法被释放。
UDP不需要建立连接,可以直接发起。

2、可靠VS不可靠
TCP利用握手、ACK和重传机制,udp没有。
(1)校验和(校验数据是否损坏);
(2)定时器(分组丢失则重传);
(3)序列号(用于检测丢失的分组和重复的分组);
(4)确认应答ACK(接收方告知发送方正确接收分组以及期望的下一个分组);
(5)否定确认(接收方通知发送方未被正确接收的分组);
(6)窗口和流水线(用于增加信道的吞吐量)。(窗口大小:无需等待确认应答而可以继续发送数据的最大值)

3、有序性
TCP利用seq序列号对包进行排序,udp没有。

4、面向字节流vs面向报文

(1)面向报文
面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这也就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。(一个upd的最大报文长度2^16-1-20-8,20是ip报文头,8是udp报文头)

(2)面向字节流
面向字节流的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。

5、tcp有流量控制,udp没有

6、tcp的头部比20bytes,udp8byres

TCP应用场景:
效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。举几个例子:文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。

UDP应用场景:
效率要求相对高,对准确性要求相对低的场景。举几个例子:QQ聊天、在线视频、网络语音电话(即时通讯,速度要求高,但是出现偶尔断续不是太大问题,并且此处完全不可以使用重发机制)、广播通信(广播、多播)。

UDP 要保证可靠,就依赖于重传


12.

1.慢开始

2.拥塞控制

3.快重传

4.快恢复


13.

简单的聊天功能、在线视频、网络语音电话


14.

UDP要达到TCP的功能就必须实现拥塞控制的功能


15.

1、malloc与free是c++/c语言的标准函数,new/delete是C++的运算符。

2、他们都可用于申请动态内存和释放内存。new/delete比malloc/free更加智能,其实底层也是执行的malloc/free。为啥说new/delete更加的智能?因为new和delete在对象创建的时候自动执行构造函数,对象消亡之前会自动执行析构函数。

既然new/delete的功能完全覆盖了malloc和free,为什么C++中不把malloc/free淘汰出局呢?因为c++程序经常要调用c函数,而c程序智能用malloc/free管理动态内存。

3、new返回指定类型的指针,并且可以自动计算出所需要的大小。如 :

int *p; p = new int; //返回类型为int*类型,大小为sizeof(int);

int *pa; pa = new int[50];//返回类型为int *,大小为sizeof(int) * 100;

malloc必须用户指定大小,并且默然返回类型为void*,必须强行转换为实际类型的指针。


16.

1、new/delete是C++的操作符,而malloc/free是C中的函数。

2、new做两件事,一是分配内存,二是调用类的构造函数;同样,delete会调用类的析构函数和释放内存。而malloc和free只是分配和释放内存。

3、new建立的是一个对象,而malloc分配的是一块内存;new建立的对象可以用成员函数访问,不要直接访问它的地址空间;malloc分配的是一块内存区域,用指针访问,可以在里面移动指针;new出来的指针是带有类型信息的,而malloc返回的是void指针。

4、new/delete是保留字,不需要头文件支持;malloc/free需要头文件库函数支持。


17.

1.先来介绍它的第一条也是最重要的一条:隐藏。(static函数,static变量均可)

当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。

2.static的第二个作用是保持变量内容的持久。(static变量中的记忆功能和全局生存期)

存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。

3.static的第三个作用是默认初始化为0(static变量)

其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。

4.static的第四个作用:C++中的类成员声明static(有些地方与以上作用重叠)

在类中声明static变量或者函数时,初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:

(1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数。

(2)不能将静态成员函数定义为虚函数。

(3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊 ,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。

(4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就 产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X W indow系统结合,同时也成功的应用于线程函数身上。 (这条没遇见过)

(5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问 时间,节省了子类的内存空间。

(6)静态数据成员在<定义或说明>时前面加关键字static。

(7)静态数据成员是静态存储的,所以必须对它进行初始化。 (程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)

(8)静态成员初始化与一般数据成员初始化不同:

初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
初始化时不加该成员的访问权限控制符private,public等;
初始化时使用作用域运算符来标明它所属类;
所以我们得出静态数据成员初始化的格式:
<数据类型><类名>::<静态数据成员名>=<值>

(9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。


18.

1. 出现在类体外的函数定义不能指定关键字static;//static 是申明时候用的

2. 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;

非静态成员函数可以任意地访问静态成员函数和静态数据成员;

3. 静态成员函数不能访问非静态成员函数和非静态数据成员;

4. 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;

5. 调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:

<类名>::<静态成员函数名>(<参数表>)

6. 调用类的静态成员函数。


19.

C++中的虚函数的作用主要是实现了多态的机制。基类定义虚函数,子类可以重写该函数;在派生类中对基类定义的虚函数进行重写时,需要再派生类中声明该方法为虚方法。

C++虚函数的底层实现原理

参考:https://blog.csdn.net/u011000290/article/details/50498683


20.

常见的不能声明为虚函数的有:普通函数(非成员函数)、静态成员函数、内联成员函数、构造函数、友元函数。

为什么:

1.调用虚函数需要访问虚表,构造函数在调用前根本不存在虚表,这带来了先有鸡还是先有蛋的问题;
2.多态是根据指针指向的具体对象类型调用对应的方法,前提当然是这个对象是已经构建出来了;
3.构造函数是定义一个对象时自动调用的,用户不能自己调用构造函数,所以没必要是虚函数;

更多模拟面试

全部评论

(0) 回帖
加载中...
话题 回帖

相关热帖

近期热帖

历年真题 真题热练榜 24小时
技术(软件)/信息技术类
查看全部

近期精华帖

热门推荐