一、C++语法
1. C++内存分配
C/C++程序编译时内存分为5大存储区,分别为:
- 栈区(stack) --编译器自动分配释放,主要存放函数的参数值,局部变量值等;
- 堆区(heap) --由程序员分配释放;
- 全局区或静态区 --存放全局变量和静态变量;程序结束时由系统释放,分为全局初始化区和全局未初始化区;
- 字符常量区 --常量字符串放与此,程序结束时由系统释放;
- 程序代码区–存放函数体的二进制代码
2. static关键字
- 类的静态成员变量
静态成员变量是该类的所有对象所共有的,在全局数据区分配内存,在初始化时分配内存,可以通过类名直接访问,且sizeof 运算符不会计算 静态成员变量。 - 类的静态成员函数
出现在类体外的函数定义不能指定关键字static;
静态成员之间可以相互访问,即静态成员函数(仅)可以访问静态成员变量、静态成员函数;
静态成员函数不能访问非静态成员函数和非静态成员变量;
非静态成员函数可以任意地访问静态成员函数和静态数据成员;
由于没有this指针的额外开销,静态成员函数与类的全局函数相比速度上会稍快;
可以通过类名直接访问。 - 静态全局变量
在全局数据区分配内存;
未经初始化的静态全局变量会被程序自动初始化为0(自动变量的自动初始化值是随机的);
静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;
静态变量都在全局数据区分配内存。 - 静态局部变量
在全局数据区分配内存;
在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
静态局部变量始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束; - 静态函数
只能在声明它的文件当中可见,不能被其它文件使用
参考:https://www.zhihu.com/collection/645574188
3. const关键字
const修饰表示表示变量值不能改变,const对象必须初始化,使用值传递初始化时,被初始化的对象是否为const与初始化对象是否为const无关,使用引用初始化时,可以允许引用类型和对象类型不一致,也可以引用绑定至非左值上,因为const引用将会额外创建一个临时变量,并绑定上去。
-
顶层&底层const
修饰指针本身的const称为顶层const,修饰k所指向变量i的const成为底层const。(注意const修饰的是紧跟在其后的对象,所以顶层const之后会紧跟着指针名)
引用相当于内置顶层const,所以只可以被底层const修饰。
可以将底层const的指针(或引用)指向(或绑定)到非const对象,但不允许非底层const的指针(或引用)指向(或绑定)到const对象。 -
const与函数
在函数中const修饰的值传递形参可以传入非const实参,不能实现函数重载。
const修饰的指针/引用形参,顶层const与值传递相同,底层const允许重载。 -
const与类
对象的const修饰表示该对象的成员变量不允许被修改;
当对象被声明为const时,该对象不能调用非const函数;
声明const函数的方法为在其参数列表后添加const关键字;
并非所有成员函数都可以被声明为const函数,C++会在编译时检查被声明为const的函数是否修改了成员变量,若是,则报错,编译不通过;
const成员函数也可以实现重载;
参考:https://zhuanlan.zhihu.com/p/27919970
4. 类型转换
旧式风格:
type(expr); // 函数形式的强制类型转换
(type)expr; // C语言风格的强制类型转换
现代风格:
cast-name<type>(expression)
type是转换的目标类型。
expression是被转换的值。
cast-name有static_cast,dynamic_cast,const_cast和reinterpret_cast四种,表示转换的方式。
- static_cast
不提供运行时检查,用途如下:
把子类指针/引用转为父类(安全)
把父类指针/引用转为子类(不安全)
基本数据类型转换
void指针转换为其他类型指针(不安全) - dynamic_cast
提供运行时的检查,仅适用于指针或引用,指针转换失败会返回空指针,引用转换失败会抛出异常。 - const_cast
用于移除类型的const、volatile和__unaligned属性 - reinterpret_cast
在编译期完成,可以转换任何类型的指针
参考:https://zhuanlan.zhihu.com/p/27966225
5. C++程序的编译过程
- 预编译
.cpp文件被预编译,这个阶段会处理预处理命令,如:#include、#ifdef 、#else、#endif、#define、#pragma,把.h文件替换到.cpp文件中#include的位置上 - 编译
编译器编译每一个源文件为.o/.obj文件,将其翻译为汇编代码。 - 链接
将所有.o文件链接为可执行文件
6. 静态库与动态库
静态库的函数被打包进可执行文件
动态库的函数在程序运行时才真正链接进来
二者区别:
- 可执行文件大小:静态链接的可执行文件更大
- 静态库中某个函数改变后,所有用到的程序都需要重新编译
- 静态链接的程序运行时不需要依赖其他内容
- 静态链接加载速度更快
7. 智能指针——RAII
C++11引入了新的3种智能指针:
std::unique_ptr
std::shared_ptr
std::weak_ptr
- unique_ptr:独占资源所有权的指针
特点为:
自动管理内存,离开智能指针作用域时自动释放内存
只能被std::move()移交所有权,无法被赋值 - shared_ptr:共享资源所有权的指针
特点为:
对资源做引用计数——当引用计数为 0 的时候,自动释放资源
实现中需要维护一个引用计数 - weak_ptr:共享资源所有权的指针的观察者
不影响shared_ptr的引用计数,当观察的shared_ptr管理的资源被释放时,自动变为nullptr
8. 继承
C++中一个类可以继承自另一个类,也可以继承自多个类。
单继承:class<派生类名>:<继承方式><基类名>
多继承:class<派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
继承方式:
public :公有继承;
private :私有继承;(默认)
protected :保护继承;
在继承中,子类不会继承父类的构造方法和析构方法,不能直接访问父类的私有数据。
公有继承中,所继承的修饰符权限不修改。
私有继承中,所有继承下来的属性都变为私有。
9. 虚函数
被virtual修饰的类成员函数,分为虚函数和纯虚函数。
当使用指针调用函数时,普通函数版本由指针决定,虚函数版本由实例类型决定。普通函数、虚函数、虚函数表都是类的所有对象所共有,仅有虚函数表指针是每个对象私有。虚函数表指针占用4个字节。
类的布局中,成员函数存放在代码区,虚函数表指针vptr存放在全局区,指向虚函数表vtbl,表里存有很多函数指针,指向函数具体的存放位置。
构造函数不能为虚函数,析构函数可以为虚函数。因为构造函数在完成之前,vptr是没有值的。子类比父类可能多一些成员,需要调用自己的析构版本来把新成员析构掉。
纯虚函数在后面加上=0,拥有纯虚函数的类无法实例化,其子类必须提供父类纯虚函数的实现。
10. C++11的新特性
- auto关键字
- 基于范围的for循环
- lambda表达式
- 初始化参数列表
- nullptr,传统为NULL,一个宏
- 新的智能指针
- decltype,在编译期判断类型
11. 类的访问权限
12. 内存泄漏
内存泄漏主要是由于疏忽导致未能正确释放掉程序中已经不使用的内存,造成内存浪费,系统运行速度慢,崩溃等后果。主要来源有两块:一是没能够正确使用free/delete释放malloc/new出的内存,导致堆内存泄漏。第二是系统资源泄露。主要指程序使用系统分配的资源比如SOCKET等没有使用相应的API释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
13. malloc & new
int *a = new int;
int *p = (int*) malloc(sizeof(int));
- new和delete是C++关键字,需要编译器支持;malloc和free是库函数,需要头文件支持。
- 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
- new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
- 对于自定义类型,new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)
14. class & struct
C++中的 struct 和 class 基本是通用的,唯有几个细节不同:
- 使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
- class 继承默认是 private 继承,而 struct 继承默认是 public 继承。
- class 可以使用模板,而 struct 不能。
15. 内存对齐
c++内存对齐
位 :计算机内部数据储存的最小单位
字节:计算机中数据处理的基本单位, 1 个字节等于 8 位
在64位系统下:
- int 4字节
- short 2字节
- char 1字节
- float 4字节
- long 8字节
- long long 8字节
- double 8字节
- 汉字:GBK 2字节 UTF-8 3字节
CPU从内存中获取数据时起始地址必须是地址总线宽度的倍数,内存对齐的目的是让cpu能够更加高效便捷取得数据。
内存对齐三原则:
- 内置类型数据成员:结构(struct/class)的内置类型数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的起始位置要从自身大小的整数倍开始存储
- 结构体作为成员: 如果一个结构里有某些结构体成员,则结构体成员要从其内部“最宽基本类型成员”的整数倍地址开始存储(如struct a里存有struct b,b里有char, int, double等元素,那b应该从8的整数倍位置开始存储)。
- 收尾工作: 结构体的总大小,也就是sizeof的结果必须要对齐到内部"最宽基本类型成员"的整数倍,不足的要补齐。(基本类型不包括struct/class/union)。
需要内存对齐的原因:
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:经过内存对齐后,CPU的内存访问速度大大提升。 - 在程序员看来,内存是由一个个的字节组成。而CPU并不是这么看待的,CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory access granularity(粒度) 本人把它翻译为“内存读取粒度” 。
16. 迭代器失效
迭代器失效就是因为插入和删除,使得原本通过迭代器可以访问到容器内的元素,变得无法再访问。因为插入和删除可能更改了元素在内存中的位置,原来迭代器指向的位置不再存储原有的值。
-
数组型数据结构:该数据结构的元素是分配在连续的内存中,insert和erase操作,都会使得删除点和插入点之后的元素挪位置,所以,插入点和删除掉之后的迭代器全部失效,也就是说insert(iter)(或erase(iter)),然后在iter++,是没有意义的。解决方法:erase(iter)的返回值是下一个有效迭代器的值。 iter =cont.erase(iter);
-
链表型数据结构:对于list型的数据结构,使用了不连续分配的内存,删除运算使指向删除位置的迭代器失效,但是不会失效其他迭代器.解决办法两种,erase(iter)会返回下一个有效迭代器的值,或者erase(iter++).
-
树形数据结构: 使用红黑树来存储数据,插入不会使得任何迭代器失效;删除运算使指向删除位置的迭代器失效,但是不会失效其他迭代器.erase迭代器只是被删元素的迭代器失效,但是返回值为void,所以要采用erase(iter++)的方式删除迭代器。
二、操作系统
1. 线程与进程的区别
进程是资源分配的基本单位,是一个程序的一次运行。
线程是CPU运行的基本单位,一个进程的多个线程共有进程的所有资源,但是每个线程拥有自己的程序计数器、寄存器和栈。
线程间的通信更方便,共享全局变量、静态变量,调度和切换的代价、创建的开销均小于线程。
多进程程序的鲁棒性会高于多线程。
2. 进程之间的通信方式IPC
- 管道(无名管道)pipe
#include <unistd.h>
int pipe(int pipefd[2]);
在内核中开辟一个缓冲区用于通信,pipefd[0]为读端,pipefd[1]为写端。
一个管道仅能实现单向通信,想要双向通信需要再创建一个管道或使用sockpair,且管道只能用于有亲缘关系的进程间通信,如父子、兄弟进程。
-
有名管道 FIFO
FIFO是一个有地址路径的设备文件,不存在亲缘关系的进程也可以使用FIFO相互通信。 -
消息队列
-
信号量Semphore
信号量本质也是一种数据操作锁,用于管理临界资源,具有P操作和V操作 -
共享内存
3. 内存管理
物理地址:硬件支持的地址空间
逻辑地址:一个运行的程序所拥有的内存范围
三、数据库
1. 关系型数据库 MySQL
-
三大范式:
第一范式:每个列都不可以再拆分。
第二范式:在第一范式的基础上,非主键列完全依赖于主键,而不能是依赖于主键的一部分。
第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。 -
存储引擎:MyISAM和InnoDB
MyISAM:不支持事务、行锁、外键,索引与数据分开存放(非聚簇索引)。不支持哈希索引,支持全文索引
InnoDB:聚簇索引,支持哈希索引,不支持全文索引
MyISAM适合以读写插入为主的应用程序 -
聚簇索引和非聚簇索引:
聚簇的主键索引叶子节点存储的是行数据
非聚簇的主键索引叶子节点存储的是行数据的索引 -
索引
优点:可以大幅增加数据的检索速度
缺点:创建和维护索引需要时间,在对表中数据进行增删查改的时候也需要动态维护索引。索引需要占用物理空间。
索引类型:- 主键索引:不能重复,不允许NULL,一张表仅有一个
- 唯一索引:不能重复,允许NULL
- 普通索引:可以重复,可以为NULL
- 全文索引
左匹配原则:where子句中最频繁的一句应该在最左边,在匹配中,MySQL会一直向右匹配直至遇到范围查询(<、>、between、like),其后的子句无法使用索引。
而=和in可以任意顺序,优化器会优化成可以识别的顺序
hash索引和B+树索引的区别:
hash索引速度更快,但无法进行范围查询,不支持索引排序 -
事务
事务的ACID性质:- Atom:原子性:事务是原子操作,要么一次成功,要不所有语句均不执行
- Consistant:一致性
- Isolation:隔离性
- Duration: 持久性
四种问题:
- 脏读:某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的
- 不可重复读:在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据
- 幻读:在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的
四种隔离级别:
- RU(Read Uncommitted)读取未提交:脏读、不可重复读、幻读
- RC(Read Comiitted)读取已提交:不可重复读、幻读
- RR(Repeatable Read)可重复读:幻读(默认)
- Serialization 串行化:无问题
四种隔离级别的实现:
RU:不加锁
RC:加共享锁,语句执行后释放锁
RR:加共享锁,事务提交后释放锁
Serialization:锁定整个范围的键
-
锁
共享锁:读锁,可以同时加多个
排他锁:写锁,一时间只能加一个
参考:https://blog.csdn.net/ThinkWon/article/details/104778621?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161504935616780262522388%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=161504935616780262522388&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_v2~hot_rank-2-104778621.first_rank_v2_pc_rank_v29&utm_term=%E5%90%8E%E5%8F%B0%E5%BC%80%E5%8F%91%E7%9F%A5%E8%AF%86%E7%82%B9
2. SQL语法
- 基本语法
- SELECT FROM
- SELECT DISTINCT去除重复
- WHERE添加条件选择,其中可以使用between、=、!=、>、<、like、and、or
- 联表查询 join
语法:SELECT FROM 表1 JOIN 表2 on 表1.列=表2.列
left join:保留左表数据
right join:保留右表数据
inner join:取两表公共数据 - 排序
语法:ORDER BY 列 desc(降序)/asc(升序)
LIMIT 起始位置,截取长度:跳过若干数据,截取若干数据
OFFSET 起始位置:跳过若干数据 - 窗口函数
应用于排名问题
语法:窗口函数 OVER (ORDER BY 列)
rank():并列名次同序号,但占用后续者的序号(1,1,1,4)
dense_rank():并列名次同序号,不占用后续者的序号(1,1,1,2)
row_number():不考虑并列,同rank不同序号(1,2,3,4) - 分组GROUP BY与聚合函数
语法:GROUP BY 列 HAVING COUNT(列)
聚合计数函数COUNT()可以统计每个GROUP中的条目数
注意:GROUP BY无法使用WHERE,只能使用HAVING
3. 非关系型数据库NoSQL:Redis
- Redis(Remote Dictionary Server) 是一个使用 C 语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库。
- Redis的数据均存储在内存中,且为单线程,避免了上下文切换,使用多路IO复用,非阻塞IO,性能很高
- 5种数据类型
STRING:字符串
LIST:列表
SET:无序集合
HASH:散列表
ZSET:有序集合 - 持久化
持久化即把内存中的数据写入磁盘,避免因为宕机丢失数据。Redis提供两种持久化机制:RDB和AOF
RDB:RedisDataBase缩写快照(默认)
fork一个子进程来进行持久化,每隔一个时间段进行一次,如果持久化之间发生故障,只能恢复到上一次持久化
AOF:AppendOnlyFile:所有命令均存储在日志中,速度慢,但是可靠性高
四、计算机网络
1. 七层/五层协议模型
- OSI七层网络模型:
- 应用层
- 表示层
- 会话层
- 运输层
- 网络层
- 数据链路层
- 物理层
- 五层网络模型
- 应用层
- 运输层
- 网络层
- 数据链路层
- 物理层
- TCP/IP四层模型
- 应用层
- 运输层
- 网络层
- 网络接口层
2. 运输层协议:TCP/UDP
1. TCP
- TCP三次握手、四次挥手
三次握手建立连接,四次挥手断开连接
三次握手:
假设A为客户端,B为服务器端;
1.首先B处于LISIEN(监听)状态,等待客户 的连接请求;
2.A向B发送链接请求报文,SYN=1,ACK=0,选择一个初始的序号x;
3.B收到请求,如果同意建立连接,则向A发送连接确认报文,SYN=1,ACK=1,确认号为x(包含数据长度)+1,同时也选择一个初始的序号为y;
4.A收到B的连接确认报文后,还要向B发出确认,确认号为y+1,序号为x+1;
四次挥手:
1.A(客户机)发送连接释放报文,FIN=1;
2.B收到之后发出的确认,此时TCP属于半关闭状态,B可以向A发送数据但A不能向B发送数据;
3.当B不再需要连接时,发送链接释放报文,FIN=1;
4.A收到后发出确认,进入TIME-WAIT状态,等待2MSL(最大报文存活时间)后释放链接; 目的为了确保最后一个报文能够到达服务端。
B收到确认后释放连接;
三次挥手的原因:
四次挥手的原因:
等待2MSL的原因:
- TCP的流量控制:滑动窗口
- TCP的拥塞控制:状态机
慢启动、拥塞避免、快重传、快恢复
2. UDP
- 使用UDP的应用层协议、常见端口号
- 如何实现UDP的可靠传输,需要在哪一层做改变
3. 应用层协议:HTTP
- HTTP几个版本之间的区别
- HTTPs的加密过程
- 常见HTTP响应码的含义
- 从输入一个URL到看到页面发生了什么
- DNS寻址过程
- GET和POST的区别
4. 网络层协议:IP
- IPv4与IPv6
- IP地址分类、子网、子网掩码
五、数据结构
1. 哈希表
- 如何避免hash碰撞
2. 树
二叉搜索树、AVL树、红黑树、B树、B+树
六、Linux系统
1. 常用命令
2. GREP
3. AWK
4. 硬链接和软链接
七、网络编程
1. Socket与常用API
- Socket()
- bind()
- listen()
- recv()
- close()
2. I/O多路复用:Select与Epoll
1. Select
2. Epoll
- Epoll的三个API
- Epoll的信号
- ET边沿触发和LT水平触发
- Epoll底层实现、数据结构