2021-01-05 腾讯PCG一面

1.数组、vector、slice的区别?

在这里插入图片描述
golang中数组和切片的区别

切片时指针类型,数组是值类型
数组的长度是固定的,而切片不是(切片是动态的数组)
切片比数组多一个属性:容量(cap)
切片的底层是数组


2.C++关键字final的作用?

c++中final关键的作用最重要就是两个:
1.禁止虚函数被重写
2.禁止基类被继承

override标识符可以让编译器帮忙确认派生类中声明的重写函数与基类的虚函数是否有相同的签名,同时也明确表明将会重写基类的虚函数,可以防止因疏忽把本来的想重写的基类的虚函数在派生类中声明为重载,还可以防止在派生类中重写的虚函数声明漏掉virtual关键字

class A{
    
    
    virtual void foo1() final;
    virtual void foo2();
    void bar() final;//错误,只能修饰虚函数
};
 
class B final:public A
{
    
    
    virtual void foo2();
    void foo1();//错误,final修饰的虚函数不能重写
};
 
class C:B//错误,final修饰的类不能被继承
{
    
    
    void foo2();
};
 
class Base
{
    
    
    virtual void foo1(int a,double b, bool c){
    
    };
    virtual void foo2(int a,double b, bool c){
    
    };
};
 
class Derived:public Base
{
    
    
    virtual void foo1(int x,int y,bool z) override{
    
    };//错误,签名不一致
    void foo2(int x,int y,bool z) override{
    
    };//错误,缺少virtual关键字声明
};
 
void test()
{
    
    
    C c;
    Derived d;
}

3.C++初始化列表的初始顺序?

变量的初始化顺序就应该是:
1 基类的静态变量或全局变量
2 派生类的静态变量或全局变量
3 基类的成员变量
4 派生类的成员变量

1、成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。因为成员变量的初始化次序是根据变量在内存中次序有关,而内存中的排列顺序早在编译期就根据变量的定义次序决定了。这点在EffectiveC++中有详细介绍。

2、如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。

3、注意:类成员在定义时,是不能初始化的

4、注意:类中const成员常量必须在构造函数初始化列表中初始化。

5、注意:类中static成员变量,必须在类外初始化。

6、静态变量进行初始化顺序是基类的静态变量先初始化,然后是它的派生类。直到所有的静态变量都被初始化。这里需要注意全局变量和静态变量的初始化是不分次序的。这也不难理解,其实静态变量和全局变量都被放在公共内存区。可以把静态变量理解为带有“作用域”的全局变量。在一切初始化工作结束后,main函数会被调用,如果某个类的构造函数被执行,那么首先基类的成员变量会被初始化。

#include <iostream>

using namespace std;

class A
{
    
    
private:
	int n1;
	int n2;

public:
	A() :n2(0), n1(n2 + 2) {
    
    }

	void Print() {
    
    
		cout << "n1:" << n1 << ", n2: " << n2 << endl;
	}
};

int main()
{
    
    

	A a;
	a.Print();

	return 1;
}

在这里插入图片描述

https://www.jb51.net/article/106481.htm


4.C++ 11的移动语义、智能指针、lamuda表达式?右值引用提出的目的是什么?

在移动构造函数中,我们做了什么呢,我们只是获取了被移动对象的资源(这里是内存)的所有权,同时把被移动对象的成员指针置为空(以避免移动过来的内存被析构),这个过程中没有新内存的申请和分配,在大量对象的系统中,移动构造相对与拷贝构造可以显著提高性能!

移动构造函数和移动赋值函数的书写要诀

  1. 偷梁换柱直接“浅拷贝”右值引用的对象的成员;
  2. 需要把原先右值引用的指针成员置为 nullptr,以避免右值在析构的时候把我们浅拷贝的资源给释放了;
  3. 移动构造函数需要先检查一下是否是自赋值,然后才能先delet自己的成员内存再浅拷贝右值的成员,始终记住第2条。

左值引用
左值引用的基本语法:type &引用名 = 左值表达式;

右值引用
右值引用的基本语法type &&引用名 = 右值表达式;

右值引用允许从程序中其他地方无法引用的临时对象转移资源

#include <iostream>
using namespace std;
 
int main()
{
    
    
	cout << "-------引用左值--------" << endl;
	int a = 5;
	int &add_a(a);
 
	cout << " a =" << a <<" "<<" &a = "<<&a<< endl;
	cout << "add_a =" << add_a<<" "<< "&add_a = " << &add_a << endl;
	cout << "-----------------------" << endl;
 
	cout << "-------引用右值--------" << endl;
	int b = 10;
	int &&add_b(b + 1);
	cout << " b =" << b << " " << " &b = " << &b << endl;
	cout << "add_b =" << add_b << " " << "&add_b = " << &add_b << endl;
	add_b++;
	cout << "add_b++ =" << add_b << " " << "&add_b++ = " << &add_b << endl;
	cout << "-----------------------" << endl;
 
	system("pause");
	return 0;
}

在这里插入图片描述
其实对于左值还是很好理解的,主要是对于右值是不好理解的,特别是代码的16行处:右值的例子。C++之所以设计出右值引用的语法,主要是因为对于类似b+1;这样的运算是发生在CPU寄存器上的,就不能对其取地址、赋值等操作,所以这类运算只能放在等号的右边,将其赋给其他的变量。若等号右边出现:&b,这样的操作是,也是右值,因为取地址符的操作也是在寄存器中完成的,所以不能作为左值

在C++11中,区别表达式是左值或右值可以做这样的总结:当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。左值有持久的状态,而右值要么是字面常量,要么是在表达式求值过程中创建的对象,即左值持久,右值短暂

智能指针https://www.nowcoder.com/tutorial/93/a34ed23d58b84da3a707c70371f59c21

使用右值引用的代码可以自由的接管所引用对象的内容。


5.C++ 的虚继承?析构函数采用虚函数的作用?

C++析构函数为什么要为虚函数
原因是因为多态的存在。当派生类对象经由一个基类指针被删除,而该基类的析构函数非虚,其结果未有定义:实际执行时通常发生的是对象的派生成分没被销毁,而派生类的析构函数未能执行起来,积累成分会被销毁,会导致内存泄漏,资源泄漏,败坏的数据结构等情况,给基类一个虚析构函数,可以防止析构的时候只析构基类而不析构派生类的状况发生,用基类的指针去操作继承类的成员,释放指针的过程中释放了继承类的资源,再调用基类的析构函数。

C++的虚继承与虚基类http://c.biancheng.net/view/2280.html


6.MySQL的MVCC如何实现?

MVCC提供了 时间一致性的 处理思路,在MVCC下读事务时,通常使用一个时间戳或者事务ID来确定访问哪个状态的数据库及哪些版本的数据。读事务跟写事务彼此是隔离开来的,彼此之间不会影响。假设同一份数据,既有读事务访问,又有写事务操作,实际上,写事务会新建一个新的数据版本,而读事务访问的是旧的数据版本,直到写事务提交,读事务才会访问到这个新的数据版本

一个事务,不管其执行多长时间,其内部看到的数据是一致的。也就是事务在执行的过程中不会相互影响。下面我们简述一下MVCC在InnoDB中的实现。

InnoDB的MVCC,通过在每行记录后面保存两个隐藏的列来实现:一个保存了行的创建时间,一个保存行的过期时间(删除时间),当然,这里的时间并不是时间戳,而是系统版本号,每开始一个新的事务,系统版本号就会递增。在RR隔离级别下,MVCC的操作如下:

select操作:
InnoDB只查找版本早于(包含等于)当前事务版本的数据行。可以确保事务读取的行,要么是事务开始前就已存在,或者事务自身插入或修改的记录。
行的删除版本要么未定义,要么大于当前事务版本号。可以确保事务读取的行,在事务开始之前未删除。

insert操作:
将新插入的行保存当前版本号为行版本号。

delete操作:
将删除的行保存当前版本号为删除标识。

update操作:
变为insert和delete操作的组合,insert的行保存当前版本号为行版本号,delete则保存当前版本号到原来的行作为删除标识。

由于旧数据并不真正的删除,所以必须对这些数据进行清理,innodb会开启一个后台线程执行清理工作,具体的规则是将删除版本号小于当前系统版本的行删除,这个过程叫做purge

https://www.cnblogs.com/myseries/p/10930910.html


7.redis常用数据结构?各自底层实现?有没有用过?

在这里插入图片描述

Redis的五种数据结构


8.zookeeper做服务发现的优势是什么?

待查~


9.HTTP常用方法?

在这里插入图片描述


10.HTTP哪些头部字段是跟HTTP缓存有关的?哪些头部字段是跟cookie有关的?

请求头:浏览器向服务器发送请求的数据,资源。
响应头:服务器向浏览器响应数据,告诉浏览器具体操作。

常见的请求头
Accept: text/html,image/* 浏览器可以接收的类型
Accept-Charset: ISO-8859-1 浏览器可以接收的编码类型
Accept-Encoding: gzip,compress 浏览器可以接收压缩编码类型
Accept-Language: en-us,zh-cn 浏览器可以接收的语言和国家类型
Host: www.lks.cn:80 浏览器请求的主机和端口
If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT 某个页面缓存时间
Referer: http://www.lks.cn/index.html 请求来自于哪个页面
User-Agent: Mozilla/4.0 compatible; MSIE 5.5; Windows NT 5.0 浏览器相关信息
Cookie: 浏览器暂存服务器发送的信息
Connection: close1.0/Keep-Alive1.1 HTTP请求的版本的特点
Date: Tue, 11 Jul 2000 18:23:51GMT 请求网站的时间
Allow:GET 请求的方法 GET 常见的还有POST
Keep-Alive:5 连接的时间;5
Connection:keep-alive 是否是长连接
Cache-Control:max-age=300 缓存的最长时间 300s

常见的响应头
Location: http://www.lks.cn/index.html 控制浏览器显示哪个页面
Server:apache nginx 服务器的类型
Content-Encoding: gzip 服务器发送的压缩编码方式
Content-Length: 80 服务器发送显示的字节码长度
Content-Language: zh-cn 服务器发送内容的语言和国家名
Content-Type: image/jpeg; charset=UTF-8 服务器发送内容的类型和编码类型
Last-Modified: Tue, 11 Jul 2000 18:23:51GMT 服务器最后一次修改的时间
Refresh: 1;url=http://www.lks.cn 控制浏览器1秒钟后转发URL所指向的页面
Content-Disposition: attachment; filename=lks.jpg 服务器控制浏览器发下载方式打开文件
Transfer-Encoding: chunked 服务器分块传递数据到客户端
Set-Cookie:SS=Q0=5Lb_nQ; path=/search 服务器发送Cookie相关的信息
Expires: -1 资源的过期时间,提供给浏览器缓存数据,-1永远过期
Cache-Control: no-cache 告诉浏览器,一定要回服务器校验,不管有没有缓存数据。
Pragma: no-cache 服务器控制浏览器不要缓存网页
Connection: close/Keep-AliveHTTP 请求的版本的特点
Date: Tue, 11 Jul 2000 18:23:51 GMT 响应网站的时间
ETag:“ihfdgkdgnp98hdfg” 资源实体的标识(唯一标识,类似md5值,文件有修改md5就不一样)

关于缓存相关头的解释
Expires
一个GMT时间,试图告知浏览器,在此日期内,可以信任并使用对应缓存中的副本,缺点是,一但客户端日期不准确.则可能导致失效.
Pragma : no-cache
这个是http1.0中的常规头,作用同http1.1的 Cache-Control : no-cache
Last-Modified
一个GMT时间,告知被请求实体的最后修改时间.用于浏览器校验其缓存副本是否仍然可以信任.与其相关的两个条件请求标头:
1) If-Modified-Since
仅在get方法中意义,这个也是比较常见的。 如果实体在指定时间后,没有修改则返回一个304,否则返回一个常规的Get请求的响应(比如200),静态文件没有修改返回304是好的,因为它只是回服务器校验一下是否有修改,而并没有像200那样重新请求数据。
2) If-Unmodified-Since:
如果实体没有任何修改,那么就可以直接执行该请求, 而如果有修改,则返回一个412 Precondition Failed状态码,并且抛弃该方法对应的行为操作(GET方法除外).

Cache-Control (http1.1的常见头)
1) public
仅体现在响应头,通知浏览器可以无条件的缓存该响应。
2) private
仅体现在响应头,通知浏览器只针对单个用户缓存响应. 且可以具体指定某个字段.如private –“username”
3) no-cache
a) 请求头中:告诉浏览器回去服务器取数据,并验证你的缓存(如果有的话)。
b) 响应头中:告诉浏览器,一定要回服务器校验,不管有没有缓存数据。 如果确定没有被改,可以使用缓存中 的数据
4) no-store
告诉浏览器任何情况下都不要被缓存。
5) max-age
a) 请求头中:强制响应浏览器,根据该值,校验缓存.即与自身的Age值,与请求时间做比较.如果超出max- age值,则强制去服务器端验证.以确保返回一个新鲜的响应.其功能本质上与传统的Expires类似,但区别在于Ex pires是根据某个特定日期值做比较.一但缓存者自身的时间不准确.则结果可能就是错误的.而max-age,显然无 此问题. Max-age的优先级也是高于Expires的.
b) 响应头中:同上

https://blog.csdn.net/longholidays/article/details/62063295


11.HTTP1.1与HTTP2.0的区别?

HTTP2.0和HTTP1.X相比的新特性
1、新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。

2、多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。

3、header压缩,如上文中所言,对前面提到过HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。

4、服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。


12.select、poll、epoll的区别?

(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

https://www.cnblogs.com/Anker/p/3265058.html


13.nginx是单线程还是多线程?

Nginx会按需同时运行多个进程:一个主进程(master)和几个工作进支进程(worker),配置了缓存时还会有缓存加载器进程(cache loader)和缓存管理器进程(cache manager)等。Nginx主要通过“共享内存”的机制实现进程间通信。主进程以root用户身份运行,而worker、cache loader和cache manager均应以非特权用户身份运行。

在工作方式上,Nginx分为单工作进程和多工作进程两种模式。在单工作进程模式下,除主进程外,还有一个工作进程,工作进程是单线程的;在多工作进程模式下,每个工作进程包含多个线程。Nginx默认为单工作进程模式。


14.进程间的通信方式有哪些?最快的是哪种?

进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取

https://www.cnblogs.com/zgq0/p/8780893.html


推荐书籍:《HTTP权威指南》

猜你喜欢

转载自blog.csdn.net/weixin_43202635/article/details/112252956