一、C++基础
1、指针和引用
1)指针和引用的区别
指针 | 引用 | |
---|---|---|
int *p=&a; | int &b=a; | |
本质 | 保存的是指向对象的地址 | 对象的别名 |
初始化 | 没有要求(野指针,不安全) | 必须初始化 |
指向 | 可以改变指向 | 不能改变指向 |
占用内存 | 4个字节 | 被引用对象的大小 |
级数 | 指针没有级数限制 | 引用只有一级 |
自++含义 | p++,改变指针指向 | b++,改变变量内容 |
int**表示二级指针 | int&&表示右值引用 | |
传参方式 | 地址 | 内容 |
2)函数指针,指针函数
指针函数 | 函数指针 | |
---|---|---|
定义 | 返回一个指针的函数,本质是一个函数。 | 是指向函数的指针变量。函数指针本身首先是一个指针变量,该指针指向具体的一个函数。 |
声明格式 | char* func(char *p) ; | char* (*pf)(char *p); |
实例:
pf=func;
函数指针pf指向函数func
pf(p);
通过函数指针pf调用函数func
C++编译的时候,每个函数都有一个入口地址,这个入口地址就是函数指针所指向的地址。可以用函数指针这个指针变量来调用函数,也是一个可调用对象。 引出虚函数和虚函数指针
3) 数组指针和指针数组(易混淆)
数组指针(行指针) | 指针数组 | |
---|---|---|
定义 | int (*p)[n]; | int *p[n]; |
说明 | ()优先级高,首先说明p是一个指针,指向一个整型的一维数组 | []优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素 |
4)常量指针(const*)和指针常量(*const)(易混淆)
常量指针 | 指针常量 | |
---|---|---|
定义 | int const *p;或者const int *p; | int* const p |
说明 | 指向常量的指针,不能通过指针来修改所指向对象的值。但指针本身可以指向其他对象 | 指针本身的值不能被修改(指向不能变),但可以通过指针修改其所指向变量的值 |
记法 | const修饰的是*p | const修饰的是p |
将*p整体看为内容,p看为地址,使用原则const修饰什么,什么就不能改变;则得出结论:常量指针(int const *p
)表示指针指向对象的内容不能通过该指针改变,但是可以改变地址;指针常量(int * const p
),表示指针的指向不能改变,但是指向对象的内容可以改变。
2、const、#define、static关键字
1)const与#define的区别
2)static关键字
3、new和 malloc
new | malloc | |
---|---|---|
内存分配 | 按照数据类型进行分配 | 按照指定的大小分配 |
返回指针 | 指定对象的指针 | void* |
是否调用构造函数 | 调用构造函数,销毁的时候调用对象的析构函数 | 否 |
是否可以重载 | 是操作符,可以重载 | 是库函数,不可以重载 |
4、内存对齐
我们现在的计算法大多都是64bit字长也就是8字节的CPU,对于这类CPU取8个字节的数要比取一个字节要高效,也更方便。所以结构体中每个成员的首地址都是4的整数倍的话,取数据元素就会相对高效,这就是内存对齐的由来。每个特定平台上的编译器都有自己默认的“对齐系数”,程序员可以通过预编译命令#pragma pack(n)
,n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
在没有#pragma pack宏定义的情况下
数据对齐:第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置必须是该成员大小的整数倍。
结构体对齐:
1.如果一个结构体里有某些结构体成员,则该结构体成员要从其内部最大元素大小的整数倍地址开始存储。
2.结构体大小也就是sizeof的结果,必须是其内部成员中最大的对齐参数的整数倍,不足的要补齐。
在存在#pragma pack宏定义的情况下
数据成员对齐规则:结构(struct)或联合(union)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照# pragma pack指定的数值(n)和这个数据成员自身长度中,比较小的那个进行。
结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack 指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
5、堆和栈的区别
堆 | 栈 | |
---|---|---|
管理方式 | 由程序员控制 | 编译器自动管理 |
系统响应 | 遍历记录空闲内存地址的链表,寻找第一个大于所申请空间的节点,进行内存分配 | 不超出栈的剩余空间,系统自动分配 |
空间大小 | 4G(具体看情况) | 2M,有的IDE可以设置 |
碎片问题 | 容易产生内存碎片 | 先进后出,不会产生碎片 |
生长方向 | 向高地址增长 | 向低地址增长 |
分配方式 | 动态分配 | 静态分配和动态分配(编译器自动释放) |
分配效率 | 效率低 | 效率高 |
6、类型转换
- const_cast
用于将const变量转为非const - static_cast
用于各种隐式转换,比如非const转const,void*转指针等,static_cast能用于多态向上转化,如果向下转能成功但是不安全,结果未知,即子类可以转父类,父类不能转子类。 - dynamic_cast
用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。只能转化指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。 - reinterpret_cast
几乎什么都能转,比如将int转指针,可能会出现问题,尽量少用。
7、编译连接过程
C语言编译过程分成四个步骤:
- 由.c文件到.i文件,这个过程叫预处理
- 由.i文件到.s文件,这个过程叫编译
- 由.s文件到.o文件,这个过程叫汇编
- 由.o文件到可执行文件,这个过程叫链接
8、C++11新特性
9、虚函数的实现
表现形式:在类中的成员函数前加上一个关键字virtual
本质:如果一个类中存在虚函数,那么这个类中的“第一个成员”就是一个虚表指针,虚表指针指向的是一个虚函数表,虚函数表里存放的是这个类中每个虚函数的入口地址。在构造函数中进行虚表的创建和虚表指针的初始化。
当基类和子类都包含虚函数的时候,子类会继承基类的虚函数表,如果子类没有重写父类的虚函数,那么用一个指针/引用调用某个方法是,要看这个指针/引用指向的是基类对象还是子类对象,指向基类对象的指针/引用就调用基类方法,指向子类,就在子类中去寻找相应的方法,找不到就去基类中去找,这个就是静态绑定,在编译阶段就确定了。如果子类重写了父类的虚函数,也就是涉及多态时候,那么方法的调用就不会再编译时候确定而是在运行的时候确定,也就是根据对象中虚表指针指向虚表中的虚函数地址来确定调用哪个函数。多态就是,一个接口有多种不同的实现方式。
1)为什么析构函数要定义成虚函数?
基类指针指向父类对象时,如果基类析构函数没有定义为虚函数,删除这个基类指针时,编译器执行静态绑定,只会调用基类的析构函数而不调用子类的析构函数,这样就会造成子类对象占用内存没有被释放,从而造成内存泄漏。相反,如果基类析构函数被定义为虚函数,那么删除基类指针时,首先会调用子类的析构函数析构子类中的内容,再调用基类析构函数析构基类对象。
2)不能定义为虚函数的函数
普通函数:普通函数只能重载不能重写。
静态成员函数:
(1)从技术层面来说,静态函数的调用不需要传递this指针,但是虚函数的调用需要this指针来找到虚函数表。
(2)
内联函数:内联函数在编译时被展开,虚函数在运行时在能动态绑定的函数
友元函数:不是类的成员函数,而且不可以被继承
构造函数:
从存储空间角度,虚函数对应一个指向虚函数表的虚表指针,可是这个指向虚函数表的指针其实是储存在对象的内存空间中的,即在构造函数中进行虚表的创建和虚表指针的初始化。如果构造函数是虚的,就需要通过虚函数表来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找到虚函数表呢。虚函数表是在构造函数被调用之后才建立的,那如果构造函数是虚的,得需要通过虚函数表来调用,不就有点像死锁了吗?
10、智能指针,三种指针解决的问题以及区别?
智能指针解决的问题是自动内存释放。
shared_ptr,采用一个引用计数机制,当我们拷贝一个share_ptr指向的对象时,引用计数会加1,给shared_ptr赋予一个新的值或者shared_ptr被销毁,计数器减1,当计数器变为0时,它就会自动释放自己所管理的对象。
unique_ptr独占所指向的对象,不支持普通的拷贝和赋值操作,只能移动,release,reset,move。
当share_ptr管理一个双向链表的结构时,会出现循环引用导致的内存泄漏。两个对象相互引用,通俗来讲就是要释放一个对象必须先释放另外一个对象,所以两个对象都没办法释放。
weak_ptr允许共享,但不拥有一个对象。
11、STL标准库
1)vector和list的区别
- 概念:
vector连续存储的容器,动态数组,在堆上分配空间,底层实现是数组,扩容时两倍容量增长。适用于随机访问,对尾结点插入和删除很快,中间插入删除需要内存拷贝,插入时都先要判断内存够不够,不够的话对之前数据进行拷贝,拷贝到一段连续且足够大的内存空间上,并对之前数据所在内存进行释放。
list是一个动态链表,在堆上分配空间,每插入一个元素都会分配空间,每删除一个元素都会释放空间,底层实现是双向链表,随机访问的性能很差,只能快速访问头尾节点。 - 区别:
- vector底层实现是数组;list是双向链表;
- vector支持随机访问,list不支持
- vector是顺序内存,list不是
- vector在中间节点进行插入删除会导致内存拷贝,list不会
- vector一次性分配好内存,不够时才进行2倍扩容,list每次插入新节点都会进行内存申请
- vector随机访问性能好,插入删除性能差,list随机访问性能差,插入删除性能好。
12、C和C++的区别
13、附加
1)产生临时变量的三种情况,
1值传递的时候
2参数为const类型
3类型转换的时候
2)必须要用列表初始化的几种情况
1类成员为const类型
2类成员为引用类型
3类成员是没有默认构造函数的类类型
4如果类存在继承关系,子类必须在其初始化列表中调用基类的构造函数
二、操作系统
1、进程、线程、协程是什么?区别是什么?
1.进程
进程是程序的执行过程,是一个动态的概念,是进行系统资源分配、管理和调度的基本单位,每个进程都有自己独立的内存空间,
2、线程
线程是CPU调度和分派的基本单位。线程是共享同一进程的大部分资源,但是拥有自己独立的程序计数器、寄存器,栈等等。
3、协程
进程与线程之间的关系
线程是依赖进程存在的,一个进程中有多个线程。一个进程崩溃后,其他进程不会产生影响;但同一进程中一个线程崩溃了,整个进程都会死掉。
进程与线程之间的区别
(1)根本区别:进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
(2)开销:进程的开销比线程大。
(3)内存分配:进程拥有自己独立的地址空间,线程没有独立的地址空间。
同一进程中线程的共享资源以及独占资源
进程间通信的方式有哪些?
1管道
2消息队列
3共享内存
4信号量
5套接字
6信号
线程间通信方式
1全局变量
2消息
3事件
线程间的同步方式
1临界区
2互斥量
3信号量
4事件
2、死锁的原理及避免死锁的方法
3、线程池
4、内存池
5、I/O多路复用
6、常用linux命令含义
- ls 显示文件或目录
-l 列出文件详细信息l(list)
-a 列出当前目录下所有文件及目录,包括隐藏的a(all) - mkdir 创建目录
-p 创建目录,若无父目录,则创建p(parent) - cd 切换目录
- touch 创建空文件
- echo 创建带有内容的文件。
- cat 查看文件内容
- cp 拷贝
- mv 移动或重命名
- rm 删除文件
-r 递归删除,可删除子目录及文件
-f 强制删除 - find 在文件系统中搜索某文件
- wc 统计文本中行数、字数、字符数
- grep 在文本文件中查找某个字符串
- rmdir 删除空目录
- tree 树形结构显示目录,需要安装tree包
- pwd 显示当前目录
- ln 创建链接文件
- more、less 分页显示文本文件内容
- head、tail 显示文件头、尾内容
7、虚拟地址到物理地址怎么映射的?
三、计算机网络
1、三次握手、四次挥手的过程
三次挥手
第一次握手:客户端向服务端发送第一个包,其中SYN同步标志位为1, 确认标记ACK=0,发送序列号sequence=X(随机int)。客户端进入SYN发送状态SYN_SENT,等待服务器确认。
第二次握手:服务器确认收到客户端发送的连接请求数据包,向客户端发送一个SYN=1,ACK=1,序列号,确认号的数据报,此时服务器进入确认SYN接收SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认标记ACK=1,确认号,序列号(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
四次挥手
第一次挥手:主动释放连接的客户端结束ESTABLISHED状态,向服务器端发送一个数据包,其中FIN标志位为1,发送顺序号seq为X,随后客户端进入FIN-WAIT-1阶段,并且停止往服务器发送数据。
第二次挥手:服务器端接收到从客户端发过来的数据包之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并向客户端发送一个带有标记位ACK,序列号,确认号的数据包。
双方都知道对方想要释放连接。客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段
第三次挥手:服务器再向客户端发送一个FIN=1,ACK=1,序列号,确认号的数据包,随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止给客户端发送数据。
第四次挥手:客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一个带有标记位ACK,确认号,序列号的报文。
最后,服务器收到客户端发送的报文后,进入CLOSED阶段,客户端等待完2MSL之后,进入CLOSED阶段。
为什么“握手”是三次
三次握手才能让双方都确定自己和对方发送接受都是正常的。假如握手两次,客户端给服务器发送一个连接请求没有收到应答,会再次向服务器发送一个连接请求,然后建立连接,开始传输数据,然后服务器收到了客户端第一次发送的连接请求,开始给客户端一个应答,但是由于是两次握手客户端不会响应这个应答,然后服务器就会等待客户端的响应造成资源浪费甚至死锁。
“挥手”却要四次?
释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。
为什么客户端在TIME-WAIT阶段要等2MSL?
MSL:最长报文寿命,报文在双方之间传输花费的时间。
需要等待客户端给服务器发送的第四次挥手的数据包发送成功,假如发送失败,服务器会再次发送第三次挥手的数据报,那么如果此时客户端已经关闭连接,就收不到这个数据报了。(无法保证最后发送的ACK报文会一定被对方收到,所以可能需要重发丢失的ACK报文。)
2、TCP和UDP的区别
UDP | TCP | |
---|---|---|
是否连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠 | 可靠传输,使用流量控制和拥塞控制 |
传输方式 | 面向报文 | 面向字节流 |
首部开销 | 首部开销小,仅8字节 | 首部最小20字节,最大60字节 |
通信方式 | 全双工 | |
支持单播、多播和广播 | 仅支持单播 |
3、TCP的流量控制和拥塞控制
流量控制
流量控制:防止发送方发的太快,耗尽接收方的资源,从而使接受方来不及处理。
利用滑动窗口实现流量控制:
- 接受端将自己可以接受的缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK来通知发送端
- 窗口大小字段越大说明网络吞吐量越高
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值
- 操作系统内核为了维护滑动窗口,需要开辟发送缓冲区,来记录当前还有那些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉。
- 接受端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端,发送端收到这个值后,就会减慢自己的发送速度。
- 如果接受端发现自己的缓冲区满了,就会将窗口的大小设置为0,此时发送端不再发送数据,但是需要定期发送一个窗口探测数据段,使接受端把窗口大小告诉发送端。
拥塞控制
工作过程:
(1)初始化:设置拥塞窗口cwnd为1。
注:cwnd=1,“1”代表一个报文段,长度是一个最大报文段长度MSS。
(2)慢开始阶段:拥塞窗口cwnd以指数增长(前提是收到确认发送应答),直到遇到慢开始门限(ssthresh)。
(3)拥塞避免阶段:拥塞窗口按线性规律增长,直到发生网络拥塞。
(4)拥塞调整阶段:发生网络拥塞后,将慢开始门限ssthresh设置为发生网络拥塞时拥塞窗口的一半,并将拥塞窗口重新初始为1,进入慢开始阶段…循环往复。
快重传
收到3个重复的确认应答,执行快重传算法
快恢复
网络中出现拥塞,即出现快重传的时候,执行快恢复算法。
快恢复算法:拥塞窗口cwnd初始为新慢开始门限,然后直接进入拥塞避免阶段。
4、http和https的区别
http和https的区别
HTTP必知必会
1、HTTP 是超文本传输协议,信息是明文传输,HTTPS 则是具有安全性的 SSL 加密传输协议。https比http更安全,同时不如http高效
2、HTTP 和 HTTPS 使用的是完全不同的连接方式,用的端口不一样,前者是80,后者是443。
3、HTTP 的连接很简单,是无状态的。HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 协议安全。(无状态的意思是其数据包的发送、传输和接收都是相互独立的。无连接的意思是指通信双方都不长久的维持对方的任何信息。)
GET方法与POST方法的区别
区别一:
get重点在从服务器上获取资源,post重点在向服务器发送数据;
区别二:
get传输数据是通过URL请求,以field(字段)= value的形式,置于URL后,并用"?“连接,多个请求数据间用”&"连接,http://127.0.0.1/Test/login.action?name=admin&password=admin,这个过程用户是可见的;
post传输数据通过Http的post机制,将字段与对应值封存在请求实体中发送给服务器,这个过程对用户是不可见的;
区别三:
Get传输的数据量小,因为受URL长度限制,但效率较高;
Post可以传输大量数据,所以上传文件时只能用Post方式;
区别四:
get是不安全的,因为URL是可见的,可能会泄露私密信息,如密码等;
post较get安全性较高;
区别五:
get方式只能支持ASCII字符,向服务器传的中文字符可能会乱码。
post支持标准字符集,可以正确传递中文字符。
常见的HTTP相应状态码
- 1xx:指示信息–表示请求已接收,继续处理
- 2xx:成功–表示请求已被成功接收、理解、接受
- 3xx:重定向–要完成请求必须进行更进一步的操作
- 4xx:客户端错误–请求有语法错误或请求无法实现
- 5xx:服务器端错误–服务器未能实现合法的请求
常见具体状态码:
200:请求被正常处理
204:请求被受理但没有资源可以返回
206:客户端只是请求资源的一部分,服务器只对请求的部分资源执行GET方法,相应报文中通过Content-Range指定范围的资源。
301:永久性重定向
302:临时重定向
303:与302状态码有相似功能,只是它希望客户端在请求一个URI的时候,能通过GET方法重定向到另一个URI上
304:发送附带条件的请求时,条件不满足时返回,与重定向无关
307:临时重定向,与302类似,只是强制要求使用POST方法
400:请求报文语法有误,服务器无法识别
401:请求需要认证
403:请求的对应资源禁止被访问
404:服务器无法找到对应资源
500:服务器内部错误
503:服务器正忙
5、访问一个网页的过程
- 域名解析成IP地址;
- 与目的主机进行TCP连接(三次握手);
- 发送与收取数据(浏览器与目的主机开始HTTP访问过程);
- 与目的主机断开TCP连接(四次挥手);
总结:首先通过域名找到IP,如果缓存里没有就要请求DNS服务器;得到IP后开始与目的主机进行三次握手来建立TCP连接;连接建立后进行HTTP访问,传输并获取网页内容;传输完后与目的主机四次挥手来断开TCP连接。
6.网络编程具体步骤
四、数据结构与算法
哈希表
红黑树
1.平衡二叉树
平衡二叉树是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。一句话表述为:以树中所有结点为根的树的左右子树高度之差的绝对值不超过1。将二叉树上结点的左子树深度减去右子树深度的值称为平衡因子BF,那么平衡二叉树上的所有结点的平衡因子只可能是-1、0和1。只要二叉树上有一个结点的平衡因子的绝对值大于1,则该二叉树就是不平衡的。
2.红黑树
红黑树是一种二叉查找树,但在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑)。通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍,因此,红黑树是一种弱平衡二叉树,相对于要求严格的AVL树来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,通常使用红黑树。
特点:
- 每个节点非红即黑
- 根节点是黑的;
- 每个叶节点都是黑的;
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 不能有连在一起的红色节点
- 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点;
旋转和颜色变换规则:所有插入的点默认是红色
- 变颜色的情况:当前节点的父节点是红色,且他的叔叔节点也是红色
(1) 把父节点和叔叔节点都设为黑色
(2) 把爷爷节点设为红色
(3) 把指针定义到爷爷节点 - 左旋:当前父节点是红色,叔叔节点是黑色的时候,且当前节点在右子树上。以父节点左旋
- 当前父节点是红色,叔叔节点是黑色的时候,且当前节点在左子树上,右旋
(1)把父节点变为黑色
(2)把爷爷节点变为红色
(3)以爷爷节点右旋
3.区别
AVL树是高度平衡的,频繁的插入和删除,会引起频繁的rebalance,导致效率下降;
红黑树不是高度平衡的,算是一种折中,插入最多两次旋转,删除最多三次旋转。