C++笔记
1.求数组中每个数字右边第一个比它大的数
使用单调栈,每当栈顶元素小于处理元素时,记录该值,循环到栈空;
否则继续入栈,直到数组末尾。
2.5种IO模型
Unix下的5种,前4种属于同步IO,第5种属于异步IO:
- 1.阻塞式IO
应用程序阻塞,直到数据拷贝完成
- 2.非阻塞式IO
在等待数据时,应用程序轮询,CPU开销大
- 3.IO复用(select、poll)
select阻塞应用程序,数据准备好后结束阻塞,然后才调用IO操作函数,select可管理多个套接字
- 4.信号驱动式IO
在等待数据前建立SIGIO信号处理程序,不阻塞应用程序,数据准备好后收到信号,然后调用IO操作函数
- 5.异步IO
整个过程都不阻塞,将任务交给内核处理,当数据拷贝完成后才通知应用程序处理数据
IO请求步骤:
存储介质→内核缓冲区(数据准备好)→用户缓冲区(数据拷贝完成)→应用程序处理数据
阻塞与非阻塞:进程线程是否需要进入阻塞状态(等待)
同步与异步:用户进程是否主动读写数据,前4种都需要将数据从内核缓冲区拷贝到用户缓冲区
select和poll、epoll的区别
- 1.性能,select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,epoll只需要调用一次
- 2.最大文件描述符个数限制
- 3.跨平台相关
3.操作系统为什么要分内核态和用户态
- 1.保证内核数据安全,高特权栈内存不被低特权任意修改(避免操作系统崩溃)
- 2.会带来一定的额外开销,切换任务时需要保存用户态的寄存器信息,系统调用需要保存用户态的寄存器信息
4.STL里resize和reserve的区别
- resize操作size,reserve操作capacity
- resize后[]一定合法,reserve只是分配预留空间不小于指定值,下标访问不一定合法
- resize = erase + insert,可大可小
5.lower_bound与upper_bound
#include <iostream>
using namespace::std;
int main()
{
int n, a, tmp;
vector<int> vec;
while (cin >> n >> a)
{
while (cin >> tmp)
vec.push_back(tmp);
int left = 0, right = n - 1;
while (left < right)
{
int mid = (left + right) >> 1;
if (vec[mid] < a)
{
left = mid + 1;
}
else
{
right = mid;
}
}
}
return 0;
}
6.C++11新特性
- 基于范围的for循环
- auto
- Lambda函数
- override和final
- nullptr
- 模板别名,using TypedefName = SomeType<OtherType, SecondType>;
- sizof可直接用于成员变量
- std::thread,暂不支持线程池
- 元组、正则表达式
7.LRU缓存
hash+双向链表
8.二叉树的持久化与层序遍历
用栈或者队列辅助
9.TCP与UDP区别
- 连接
- 系统资源(包头大小,维护连接状态)
- 程序结构简单
- 拆包合并(流模式与数据报模式)
- 拥塞控制
- TCP保证数据正确性、数据顺序
TCP四次握手
10.HTTP协议
https://www.jianshu.com/p/80e25cb1d81a
GET和POST区别:
- 1.GET请求数据附带在URL之后用&连接,POST在包内容中
- 2.浏览器操作系统可能对URL长度有限制,POST也可能被限制
- 3.POST安全性更高,页面可能被缓存
- 4.GET使用Request.QueryString,POST使用Request.Form
11.hash_map
hash函数与桶
解决冲突:开放地址法(堆积)、拉链法(额外空间)、再散列法(计算时间)、公共溢出区
12.二叉树相距最远的两个节点的距离
情况A: 路径经过左子树的最深节点,通过根节点,再到右子树的最深节点。
情况B: 路径不穿过根节点,而是左子树或右子树的最大距离路径,取其大者
13.合并K个有序链表
最小堆(O(Nlog(K))
14.根据字典顺序返回1~N间的所有数
15.进程和线程的区别
进程是系统资源分配的最小单位,线程是CPU调度的最小单位
通信机制不一样:
- 线程:共享变量(全局变量、Windows消息队列)
- 进程:管道、信号、消息队列、共享内存、信号量、SOCKET
得到的资源: - 线程:但共享进程的地址空间,各自独立的程序计数器、一组寄存器和栈(所有不独享会导致线程运行错误的资源),
- 进程:系统资源,地址空间、全局变量、文件、子进程
多线程内存共享方便、上下文切换开销小
多进程更强的容错性、多核可扩展性
16.操作系统堆栈空间的增长方向
通常来说,栈的地址比堆高,并且栈是向下增长的,堆是向上增长的,但是具体还要看操作系统怎么实现,堆其实是随机分配的内存管理机制
内存分区:BSS(未初始化的全局变量)、数据段(赋了初值的全局变量、常量、静态变量)、代码段、栈区、堆区
17.两个有序数组第K大的数
每次都删除一定在第 k 大元素之前的所有元素
18.洗牌算法
1.Fisher-Yates Shuffle算法 O(n*n) O(n)
从未处理的k个数的数组中,产生随机数p<k;
取出未处理数组中的第p个数;
2.Knuth-Durstenfeld Shuffle O(n) O(1)
从未处理的k个数的数组中,产生随机数p<k;
互换第k个数和整个数组倒数第k个数的位置;
缺点:必须知道数组长度,无法处理动态数组
3.Inside-Out Algorithm O(n) O(n)
反向Knuth-Durstenfeld,可处理动态数组
19.数组去重
标正负号,注意数组长度和数字范围
20.数组一个区间的和乘以这个区间最小值为S,求S最大值
使用向上递减单调栈,如果栈为空或入栈元素≥栈顶元素,则入栈;否则出栈
- 入栈设置栈右R=入栈元素
- 出栈确定元素左=出栈元素,出栈元素右=R
可以算出保证数组中每个元素最小的最长区间。
另外开一个O(n)的空间用数组保存从第0项加到第i项的值,即可计算S。
21.矩阵最长递增路径
- 方法1.记忆化搜索(dfs+动态规划)
- 方法2.拓扑排序?
22.最大连续子序列和
有O(n)解法,判断当前值+前面总和是否为负数
扩展:最大连续子序列乘积,考虑到负数,需要保存最小dp值和最大dp值
23.寻找无序数组第K大的数
- 方法1.partition算法 O(n)
- 方法2.二分查找
24.new和malloc的区别
https://www.cnblogs.com/QG-whz/p/5140930.html
- 1.申请内存的位置
- 2.返回类型安全性
- 3.内存分配失败的返回
- 4.是否需要指定内存大小
- 5.是否调用构造函数
- 6.对数组的处理
- 7.相互调用
- 8.能否重载
- 9.能否直观地重新分配内存
- 10.客户处理内存分配不足
25.线程池设计
- 线程池管理器
- 工作线程
- 任务接口
- 任务队列
26.单例模式
1.饿汉式单例
方法调用前,实例就已经创建好了(多线程安全,但是无论是否使用都会创建)
class SingletonHungry
{
private:
SingletonHungry(){}
static SingletonHungry* singleton;
public:
static SingletonHungry* getInstance()
{
return singleton;
}
}
SingletonHungry* SingletonHungry::singleton = new SingletonHungry;
2.懒汉式单例
方法调用获取实例时才创建实例,不使用就不创建(多线程不安全,需要上锁+双重判断)
懒汉式单例如果要析构,需要定义嵌套类的析构函数释放单例,绝对不要直接在析构函数析构!!
class Singleton
{
private:
static Singleton* p;
Singleton(){pthread_mutex_init(&mutex);}
public:
static pthread_mutex_t mutex;
static Singleton* instance();
}
Singleton* Singleton::instance()
{
if (p == NULL)
{
pthread_mutex_lock(&mutex);
if (p == NULL)
{
p = new Singleton();
pthread_mutex_unlock(&mutex);
}
}
return p;
}
27.网络相关
- tcp握手挥手,报文格式
- url步骤
- arp协议
28.多态与虚函数
- 1.运行时多态,通过重写(覆盖)父类的方法实现,父类指针指向子类对象并调用相应的虚函数
- 2.重载,允许同名函数的形参不同,在编译期确定函数的调用地址,称为早绑定;不能根据返回类型区分
- 3.隐藏,子类函数名与父类相同,但是参数不同;子类函数名形参都与父类相同,但是父类没有virtual
虚函数,纯虚函数与抽象类,虚函数表
构造函数调用顺序:父类→子类
对象的维护虚表指针__vptr指向自己所属类的虚表,虚表的指针会指向继承的最近的一个类的虚函数,
所以当父类指针指向子类对象时,父类指针能访问到子类对象的虚表指针并访问虚函数,从而实现多态
为什么不在析构函数中调用虚函数
这样会导致在调用父类的析构函数有两种选择: - 1.调用虚函数的父类版本,失去了运行时调用正确版本的意义(目前编译器使用的方法),如果是纯虚函数会发生严重错误
- 2.调用虚函数的子类版本,此时子类已经被析构,调用会导致未知行为