Skip to content

Latest commit

 

History

History
630 lines (437 loc) · 19.5 KB

File metadata and controls

630 lines (437 loc) · 19.5 KB

STL

1. capacity,size,resize,reverse

capacity 只代表容器容量,不可以用下表访问,因为里面没有任何对象
size 指的是此时容器中实际的元素个数
resize 都改变,默认初始化
reverse 只改变capacity,不会自动创建对象,需要自己push_back进去

2. 容器类型

关联容器:set,map,multiset,multimap 红黑树,查找复杂度logn
				unordered_map,unordered_set 哈希表
序列容器:vector,list,deque
容器适配器:queue,stack,priority_queue

list:双向链表
vector插入元素:如果满了,会动态分配一块更大的内存,然后拷贝原来的过来,释放原来的空间,再加入进去。
set可以认为是没有value,value就是key,也不允许重复

3. erase的失效

序列容器 自动指向下一个
关联容器 不影响

------------------------erase不释放内存

4. STL 容器动态链接可能产生的问题

出现内存堆栈破坏问题。
因为动态加载的话,原理就是调用动态库里面的函数,如果那个函数参数是容器,然后容量初始大小不够大,传入的参数太大,就导致内存堆栈破坏。

5. push_back 和 emplace_back 的区别

push_back先创建临时对象,再调用拷贝构造函数插入末尾
emplace_back直接在末尾调用构造函数创建对象

操作系统

1.网络字节序

网络字节序是大端字节序,小端一般为主机字节序
相同字节序的平台在进行网络通信时可以不进行字节序转换,但是跨平台进行网络数据通信时必须进行字节序转换。

2.内存管理

寄存器,高速缓存,主寸,磁盘。
进程分配内存有两种方式,分别由两个系统调用完成:----------brk和mmap----------

3.手撕LRU

class LRUCache {
public:

    struct Node {
        int key,value;
        Node *left;
        Node *right;
        Node(int _key,int _value):key(_key),value(_value),left(nullptr),right(nullptr){};
    };
    unordered_map<int,Node*>hash;
    Node *L;
    Node* R;
    int n;

    void remove(Node* p)//删除p节点
    {
        p->right->left = p->left;
        p->left->right = p->right;
    }
    void insert(Node *p) //插到头节点的位置
    {
        p->right = L->right;
        p->left = L;
        L->right->left = p;
        L->right = p;
    }


    LRUCache(int capacity) {
        n = capacity;
        L = new Node(-1,-1);
        R = new Node(-1,-1);
        L->right = R;
        R->left = L;
    }
    
    int get(int key) {
        //如果key存在,那么把他放到链表头部
        //如果不存在,分为两种情况,第一容量够,插到链表头;第二容量不够,移除链表尾部,插入链表头部
        if(hash.count(key) == 0) return -1; //不存在关键字 key 
        auto p = hash[key];
        remove(p);
        insert(p);//将当前节点放在双链表的第一位
        return p->value;
    }
    
    void put(int key, int value) {
        //key存在 移到链表头
        //分为两种情况,第一容量够,插到链表头;第二容量不够,移除链表尾部,插入链表头部
        if(hash.find(key) != hash.end()) {
            auto p = hash[key];
            p->value = value;
            remove(p);
            insert(p);
        }
        else {
            if(hash.size() == n) //如果缓存已满,则删除双链表最右侧的节点
            {
                auto  p = R->left;
                remove(p);
                hash.erase(p->key); //更新哈希表
                delete p; //释放内存
            }
            //否则,插入(key, value)
            auto p = new Node(key,value);
            hash[key] = p;
            insert(p);
        }
    }
};

4.缺页中断

页表在内存中,存放虚拟地址到物理地址的映射关系
进行运行的时候不一定所有需要的页都在内存中,因此如果遇到了不在内存中的页,就会产生缺页中断,根据页面调度算法将其掉入内存。

5.虚拟内存的优缺点和必要性

必要性:早期的地址空间不隔离,会导致数据被随意修改
				内存使用效率低。
				程序运行的地址不确定。操作系统随机为进程分配内存空间,所以程序运行的地址是不确定的。
缺点:虚拟内存映射到物理内存花时间
			所建立的数据结构花空间
			页面置换算法耗时间
优点:扩大了地址空间
			内存保护 防止不同进程对物理内存的争夺和践踏
			可以实现内存共享,方便进程通信

6.malloc底层实现-------------------

当开辟的空间小于 128K 时,调用 brk函数;当开辟的空间大于 128K 时,调用mmap。malloc采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块,每一个空闲块记录了一个未分配的、连续的内存地址。

7.堆溢出和栈溢出

不断的new ------OutOfMemory Error
栈中将被依次压入:参数,返回地址等 -------StackOverflow Error

8.协程和守护进程

1.没有锁机制,多个协程从属于一个线程,不存在同时写变量冲突,效率比线程高
2.直接操作栈,没有内核切换的开销

守护进程:----------------
		独立于终端,在后台的一种生存期长的进程,处理一些系统级别任务。

9.进程通信,同步----------------

通信:管道,系统IPC(消息队列,共享内存,信号量,信号),套接字
同步:信号量,管道,消息队列(信号量本质上是计数器)

线程通信:互斥锁,条件变量,信号量,读写锁
线程同步:临界区,互斥量,信号量,事件

10.常见信号

SIGCHLD  子进程结束时, 父进程会收到这个信号。
SIGKILL  用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。般用于强制中止进程
SIGTERM 正常结束进程的信号,kill 命令的默认信号。如果进程已经发生了问题,那么这 个信号是无法正常中止进程的,这时我们才会尝试 SIGKILL 信号,也就是信号 9

11有了进程 为什么要线程

进程信息难以共享
创建,切换时间太多
进程频繁切换将引起额外开销,从而严重影响系统的性能。为了减少进程切换的开销,人们把两个任务放到一个进程中,每个任务用一个更小粒度的执行单元来实现并发执行,这就是线程。

12进程上下文切换

保护现场
保护PCB信息,如进程状态等
将被中断的 控制快加入相关队列
选择下一个占用处理器的进程/线程

进程:切换内核栈,上下文(包括分配的内存,数据段,堆栈段等),切换页目录
线程:切换内核栈和硬件上下文(切换堆栈,以及各寄存器)

恢复现场

13线程池

实现线程池有以下几个步骤:
(1)设置一个生产者消费者队列,作为临界资源。
(2)初始化n个线程,并让其运行起来,加锁去队列里取任务运行
(3)当任务队列为空时,所有线程阻塞。
(4)当生产者队列来了一个任务后,先对队列加锁,把任务挂到队列上,然后使用条件变量去通知阻塞中的一个线程来处理。

14.select,poll,epoll

每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
select支持的文件描述符数量太小了,默认是1024

poll采用了链表,虽然解决了1024 大小的问题,但是也需要fd集合从用户态拷贝到内核态

同时这两种方法都会遍历所有的fd不管有没有事件发生

epoll只要一次拷贝。epoll返回的时候只返回有事件的,epoll只要判断一下就绪链表是否为空就行了。但是fd一直在内核里,所以每次处理fd都需要通过系统调用。
epoll还有水平触发和边缘触发模式

15 Reactor、Proactor模式

Reactor模式用于同步I/O,而Proactor运用于异步I/O操作
Reactor模式下,应用程序读就绪之后让线程去处理,所以线程是需要处理包括读数据的
Proactor模式下,应用程序读取完成之后,将读取的内容放入用户传递过来的缓存区中,再给线程处理,所以线程是直接从缓冲区里读数据的,也就是应用程序已经处理好了数据。

16.五种IO模型

阻塞IO
非阻塞IO
IO多路复用
信号驱动IO
异步IO

计算机网络

1拥塞控制

慢启动和拥塞避免:发送方维护一个拥塞窗口,每次收到TCP接收窗口的确认就会加倍,直到到达预先设置的阈值,
每次增加一,然后当发生丢失事件,窗口阈值减半,拥塞窗口置1。

2.TCP保证有序

(1)为了保证数据包的可靠传递,发送方必须把已发送的数据包保留在缓冲区;

(2)并为每个已发送的数据包启动一个超时定时器;

(3)如在定时器超时之前收到了对方发来的应答信息(可能是对本包的应答,也可以是对本包后续包的应答),则释放该数据包占用的缓冲区;

(4)否则,重传该数据包,直到收到应答或重传次数超过规定的最大次数为止。

(5)接收方收到数据包后,先进行CRC校验,如果正确则把数据交给上层协议,然后给发送方发送一个累计应答包,表明该数据已收到,如果接收方正好也有数据要发给发送方,应答包也可方在数据包中捎带过去。

3.TCP可靠性

检验和、序列号/确认应答、超时重传、最大消息长度、滑动窗口控制
TIME_WAIT 2MSL : 为了FIN + ACK 
msl是报文段最大生存时间,端口复用,一但客户端最后发送的ACK丢失的话,服务器就无法正常的进入关闭连接状态。

4.TCP的粘包和拆包

TCP是个“流”协议,所谓流,就是没有界限的一串数据。大家可以想想河里的流水,是连成一片的,其间并没有分界线。
所以一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

5.DoS

SYN Flood是当前最流行的DoS(拒绝服务攻击)与DDoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,使被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式.

6滑动窗口

主要是进行流量控制的方法,发送方维持,接收方高速发送方自己能接受的大小,控制发送当窗口的大小,防止发送过多数据导致被淹没

面试题

HTTP 和 TCP的区别

\1. https握手流程?http和https之间有什么区别?哪个性能更好?如何优化http使得和https的性能相似?从tcp级别考虑呢?

\2. DNS的原理是什么?了解DNS劫持吗?如何防劫持?

\3. HTTP Get和Post有什么区别?不只要懂原理,还要会实际运用。

\4. tcp的滑动窗口和拥塞控制,目前的算法是怎样的。

\5. https为什么安全?还能不能从某些方面变得更加安全?

\6. TCP和UDP的区别?是否可以使用UDP变得像TCP一样可靠?

7如果第三次握手失败

客户端在第二次握手的时候就已经建立了链接,如果第三次握手服务端没有收到,就会重新发送SYN+ACK包,以便Client重新发送ACK包。
client 一般是通过 connect() 函数来连接服务器的,而connect()是在 TCP的三次握手的第二次握手完成后就成功返回值。也就是说 client 在接收到 SYN+ACK包,它的TCP连接状态就为 established (已连接)。那么如果 第三次握手中的ACK包丢失的情况下,Client 向 server端发送数据,Server端将以 RST包响应,方能感知到Server的错误。

C++内存

1类多继承下初始化的顺序

父类构造函数
类成员构造函数
自身构造函数

2.虚继承,虚函数

虚基类指针 虚基类表
每个虚继承的子类都有一个虚基类指针(占用类的空间,4字节),指向虚基类表(不占用类对象空间),里面存放着该类到虚基类的偏移地址

虚函数指针 虚函数表(在对象的空间中)
如果子类重写了虚函数,虚函数表里面对应的虚函数入口地址就会发生改变

static不能是虚函数,因为没有this指针,虚函数是对于对象的

3.什么函数不能是虚函数

构造函数 : 虚函数指针在构造函数里面初始化
友元函数 : 友元关系都不能被继承
静态成员函数 : 没有this指针,属于类的
内联函数 : 编译时候展开,但是虚函数式动态的 是运行时的

---------虚函数表在构造函数之前写入----------
虚表(vftable)在编译阶段生成,对象内存空间开辟以后,写入对象中的 vfptr,然后调用构造函数
虚表的二次写入机制,如果重写了,需要重新写入虚表

4.程序启动的过程

1.操作系统首先创建进程,并且分配空间,把数据段和代码段映射到进程的虚拟内存空间中
2.加载器读入可执行程序的导入符号表,根据这些符号表可以查找动态库
3.加载器针对该程序的每一个动态链接库调用LoadLibrary
4.初始化应用程序的全局变量,对于全局对象自动调用构造函数。
5.进入应用程序入口点函数开始执行。

5.内存分配

text
data
bss 存放程序中未初始化的或者初始化为0的全局变量和静态变量的一块内存区域
栈
文件映射区
堆

6.内存对齐---------------------

struct/class/union

C++基础知识

面试题:手撕智能指针share_ptr

1.C++和C的区别

1.编程思想,面向对象,面向过程
2.类,封装继承多态
3.函数重载,虚函数,nullptr
4.模版,易于扩充
5.智能指针,四类转换----------------

2.struct和class

struct一般是用来封装数据结构的,class一般对象数据的封装
struct默认public,以及public继承

c和c++struct:
c里面声明的时候需要使用struct关键字,而且不能继承,不能设置private,没有静态成员以及成员函数

3""和<>查找路径

“”是自定义文件
<>是系统文件

查找路径:
(1)使用尖括号<>的头文件的查找路径:编译器设置的头文件路径-->系统变量。
(2)使用双引号""的头文件的查找路径:当前头文件目录-->编译器设置的头文件路径-->系统变量。

4.野指针

指针指向的位置是不可知的
原因:没有初始化,释放内存之后指针没有置空

避免方法:使用智能指针;使用后及时置空;记得初始化为nullptr

5.内联函数和宏函数

宏函数没有类型检查,无论对错都直接替换。不算是函数,只是替换,预处理阶段 (注意括号)
而内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用的开销,提高效率(但是以空间换时间)

6.i++,++i

赋值顺序
i++速度比较慢

7.如何实现哈希表

使用数组和映射函数,可以用线性探测法解决冲突
或者用链表,如果链表太长,可以用红黑树代替链表

8.B树和B+树和红黑树

索引一般用哈希或者B+树,为什么不用二分查找树:因为为了降低磁盘IO次数

B+树的信息都在叶子结点上,查询只需要循环链表即可。
所有查询都要查找到叶子节点,查询性能稳定。
单一节点存储更多的元素,使得查询的IO次数更少。
所有叶子节点形成有序链表,便于范围查询。
B+树特点:
所有的中间节点元素都同时存在于子节点,在子节点元素中是最大(或最小)元素。
所有的叶子结点中包含了全部元素的信息


B树:一个m阶的B树具有如下几个特征
1.根结点至少有两个子女。
2.每个中间节点都包含k-1个元素和k个孩子,其中 m/2 <= k <= m
3.每一个叶子节点都包含k-1个元素,其中 m/2 <= k <= m
4.所有的叶子结点都位于同一层。

9.new 和malloc的区别

new是操作符,而malloc是函数。
new在调用的时候先分配内存,在调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数。
new可以被重载;malloc不行
new发生错误抛出异常,malloc返回null

c++新特性

nullptr

统一的初始化方法,初始化列表

初始化列表:在 C++11 中,可以直接在变量名后面跟上初始化列表,来进行对象的初始化。

基于范围的for循环 :

final,override

fina修饰的类不能被继承,override修饰的函数表示被重写了

智能指针

unique_ptr
share_ptr
weak_ptr : 解决了share_ptr相互引用计数不能为0的bug

模版

匿名函数

正则表达式

auto关键字

编译器自动判断类型
使用auto定义迭代器
当我们不知道变量是什么类型,或者不希望指明具体类型的时候,比如泛型编程中

decltype关键字

decltype根据左边的表达式推断类型,跟右边的变量没有关系

// decltype 用法举例
nt a = 0;
decltype(a) b = 1;  //b 被推导成了 int
decltype(10.8) x = 5.5;  //x 被推导成了 double
decltype(x + 100) y;  //y 被推导成了 double

右值引用和move语义 让程序员有意识减少进行深拷贝操作

四种类型转换

const_cast : 将const常量转换成非const常量
static_cast : 可以用于各种隐式转换,比如非const转const,可以用于类向上转换,但向下转换能成功但是不安全。
dynamic_cast : 只能用于含有虚函数的类转换,用于类向上和向下转换。通过判断变量运行时类型和要转换的类型是否相同来判断是否能够进行向下转换。
reinterpret_cast :  reinterpret_cast可以做任何类型的转换,不过不对转换结果保证,容易出问题。

面试题目

  1. HTTP和HTTPS**(追问HTTPS为什么是安全,只知道SSL层加解密)**

  2. TCP三次握手,为什么不能是两次?两次会导致什么问题?

  3. 排序算法

  4. 段页式存储

    三次访问内存
    根据段表 找到段号 以及页表基址和页表长度, 根据页表找到块号和页内偏移
    
    页号 块号 (将地址解析成页号和页内偏移,然后根据页号找到块号)
    段号,段长,基址(将地址解析成段号和段内偏移) 
    段号,页号,页内地址()
    
  5. LRU怎么设计

    哈希表 + 双链表
    
    两个函数,头节点插入,删除
    
    put
    如果链表节点不存在,判断容量满不满,满了 删除尾节点,更新哈希表,插入头,更新哈希表;没满插入头,更新哈希表
    如果链表节点存在,更新哈希表,删除节点,重新插入头
    
    get
    如果没找到 返回-1
    找到了,删除当前节点,重新插入头节点,返回value
    
  6. HTTPS如何保障安全性 、讲讲SSL层的建立连接过程