@[T
机器学习
计算语义相似度:DSSM(Deep Structured Semantic Models,有监督),可解决query-doc结果对的召回问题(“快递软件”-“菜鸟裹裹”,“机票”-“携程”),已经语义顺序或细微不同的词引起的语义变化(小宝宝生病怎么办-狗宝宝生病怎么办,深度学习-学习深度)
把搜索引擎里的Query和Title转为低维向量。
输入层-----表示层(词袋模型,转为向量)----匹配层(计算距离)
梯度消失的解决办法:LSTM-门机制,ResNet残差网络(有多条梯度更新的路径),ReLu,highway(空洞卷积)。
梯度爆炸:权重变为NaN,或者每个节点和层的误差梯度持续超过1.0。
解决办法:梯度截断(即设置阈值限制梯度大小),权重正则化。
Relu的好处:导数是常数,避免梯度消失;导数是常数,易于求导,加速计算;稀疏激活:负半轴的激活值为0。
降低过拟合风险(正则化):
① 数据增强:图平移,机器翻译重新生成,GAN生成新数据
② 降低模型复杂度:NN:减少层数和神经元个数,决策树:剪枝,降低树的深度
③ 权值约束:L1L2正则化
④ 集成学习:dropout、随机森林、GBDT
⑤ 提前终止
RNN会有梯度消失问题,LSTM引入输入门、遗忘们和输出门来解决这个问题,长期记忆:输入门为0,遗忘门为1(忽略当前信息,记忆之前的信息。)。短期记忆:输入门为1,遗忘门为0(忽略之前信息,记忆当前信息)。GRU则是更新门,重置门。(LSTM只能考虑当前时刻之前的词)
CNN:提取局部特征,只能考虑窗口附近的词,所以需要滑动窗口。相对于RNN不会梯度消失。
Attention机制,保存全局信息,可以把两个任意模块关联起来,比如把第100个词和第1个词放在一起考虑,而且attention矩阵可视化,也可解决encoder decoder框架中中间变量长度固定的问题。
改进SGD:1.动量 2.自适应学习率RMSprop 、Adam(带有动量项的RMSprop)
Seq2Seq:Encode、Decode
交叉熵:https://blog.csdn.net/red_stone1/article/details/80735068
给定输入x,预测标签y=1的概率为t, 即P(y=1|x)=t
,那么预测标签y=0的概率为1-t, 即P(y=0|x)=1-t
那么P(y|x)=t(y)×(1-t)(1-y)
我们希望P(y|x)越大越好。则先对其引入log,因为log不会改变函数本身的单调性。
则-logP(y|x)=-ylogt-(1-y)log(1-t)
这就是交叉熵损失函数。
在神经网络中的应用:神经网络的原始输出不是一个概率值,实质上只是输入的数值做了复杂的加权和与非线性处理之后的一个值而已,那么如何将这个输出变为概率分布?Softmax。
交叉熵刻画的是实际输出(概率)与期望输出(概率)的距离,也就是交叉熵的值越小,两个概率分布就越接近。假设概率分布p为期望输出,概率分布q为实际输出,H(p,q)为交叉熵,则
GBDT和XGBoost区别
① 传统的GBDT以CART树作为基学习器,XGBoost还支持线性分类器,这个时候XGBoost相当于L1和L2正则化的逻辑斯蒂回归(分类)或者线性回归(回归);
② 传统的GBDT在优化的时候只用到一阶导数信息,XGBoost则对代价函数进行了二阶泰勒展开,得到一阶和二阶导数;
③ XGBoost在代价函数中加入了正则项,用于控制模型的复杂度。从权衡方差偏差来看,它降低了模型的方差,使学习出来的模型更加简单,防止过拟合,这也是XGBoost优于传统GBDT的一个特性;
词嵌入:相当于把单词嵌入到n维的坐标系中(n是特征向量的长度)。是一种分散式表示。包含了外部的信息。
Word2vec窗口随机1-5,增加随机性。
Trick:hierarchical softmax(降低复杂度, 本质是把 N 分类问题变成 log(N)次二分类)negative sampling(降低复杂度,本质是预测总体类别的一个子集)
15 样本不均衡的解决办法:
① 采样:采样分为上采样(Oversampling)和下采样(Undersampling),上采样是把小众类复制多份,下采样是从大众类中剔除一些样本,或者说只从大众类中选取部分样本。
② 合成新数据
③ 加权,对不同类别分错的代价不同
kmeas的计算方法:随机选取k个中心点,然后遍历所有数据点,把它们都分配给最近的中心点,再重新计算每个聚类的平均值作为新的中心点。
基尼指数类似熵,是一种不确定性的度量,越大表示包含的类别越杂乱,所以在CART中选择那些使基尼指数更小的特征。
特征生成 ① 组合特征:几个已有特性,相加相乘。
② 分离特征:比如数字中的小数部分。
① SVM优点:基于结构风险最小化原则,有更好的泛化性能。是凸二次规划函数,可以求得全局最优解。
SVM缺点:对于每个高维空间在此空间的映射F,如何确定F也就是核函数,现在还没有合适的方法,所以对于一般的问题,SVM只是把高维空间的复杂性的困难转为了求核函数的困难.而且即使确定核函数以后,在求解问题分类时,要求解函数的二次规划,这就需要大量的存储空间.这也是SVM的一个问题。
② 函数间隔:y(wx+b),如果成比例改变w,x,虽然超平面不会变,但是函数间隔会变大。所以要最优化几何间隔,也就是函数间隔/||w||。
③ 支持向量:距离超平面最近的且满足一定条件的几个训练样本点被称为支持向量。
④ 硬间隔最大化: 最小化1/2 ||w||^2
软间隔最大化:
C是惩罚因子,越大表示对目标函数的损失越大,当C无限大,退化为硬间隔最大化,因为一个离群点都不能容忍。
22朴素贝叶斯的缺点:
① 理论上,NBC模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为NBC模型假设属性之间相互独立,这个假设在实际应用中往往是不成立的(可以考虑用聚类算法先将相关性较大的属性聚类),这给NBC模型的正确分类带来了一定影响。在属性个数比较多或者属性之间相关性较大时,NBC模型的分类效率比不上决策树模型。而在属性相关性较小时,NBC模型的性能最为良好。
② 需要知道先验概率。
23.逻辑回归(判别模型)虽然sigmoid函数是非线性的,但决策边界也就是wx+b是线性的。但逻辑回归也能解决非线性分类,需要引入核函数。 应用场景:特征很多的时候。
25: 1.为什么多次小卷积核要好于一次大卷积核? -增强非线性映射。
26. 神经网络存在退化现象(degradation),即会随着深度增加达到饱和后,再持续增加深度准确率下降(测试集和训练集准确率均下降,故不是过拟合).
为了解决这个问题,引入了残差单元,一个残差单元学习的目标为输入和输出的差别H(x)-x,而不是完整的输入H(x)。
推荐算法:①基于内容的推荐,喜欢物品a的用户,可能也会喜欢和物品a相似的东西。 优点:没有冷启动问题。 缺点:可能推荐重复。
②协同过滤:协同过滤推荐算法的前提假设是:如果用户a与用户b均对一系列相同的物品表示喜欢,那么a极有可能也喜欢b用户喜欢的其他物品。先找和用户a品位相似的top n用户,并把top n用户喜欢的东西推荐给a。 优点:个性化程度高、容易让用户发现新的兴趣点。 缺点:基于目标用户的历史行为,存在冷启动问题。
③ 基于规则的推荐:比如基于最多用户点击,最多用户浏览。缺点:个性化程度很低,规则难以定义。
hinge loss(折页损失函数)
https://blog.csdn.net/fendegao/article/details/79968994
偏差与方差分别是用于衡量一个模型泛化误差的两个方面;
模型的偏差,指的是模型预测的期望值与真实值之间的差,用于描述模型的拟合能力。引起偏差的可能原因是做了错误的假设,或者模型不够复杂。(欠拟合)
模型的方差,指的是模型预测的期望值与预测值之间的差平方和,用于描述模型的稳定性。
引起方差过大的可能原因是模型的复杂度相对于训练集过高,导致过拟合。
Attention和transformer:
https://baijiahao.baidu.com/s?id=1622064575970777188&wfr=spider&for=pc
RNN网络的当前输出和前面的输出是相关的,也就是说网络会对前面的信息进行记忆并在当前输出的计算中利用前面的信息,其网络的隐藏层之间节点相互连接,隐藏层的输入不仅包括输入层输出而且包括前面隐藏层的输出。
神经网络局限性:不论如何划分训练集和测试集,他们的数据分布都是一致的。所以我们不知道训练的是信号还是噪声。所以过拟合难以避免。
VAE结合了深度学习和统计学习。
(面试必考) 线性回归中,是要预测y=kx+b中,k和b的值就是我们得到的输出。
softmax函数加exp是为了让神经元的输出非负。
反向传播的学习率一般在1e-3到1e-4之间,切记宁可小,不要大。常用的动态学习方法是adam。
权重矩阵w一般是随机初始化的。
sigmoid的值域是0到1,tanh的值域是-1到1。 一般来说,tanh更经常被使用为激活函数。因为:
sigmoid的导数的峰值是0.25,所以更新效率比较低,而且,当x的值远离0时,会出现梯度消失的问题。梯度消失,意味着这个神经元的更新信息无法回传了。
全连接会提取全局的信息,包括相似或重复的信息。
sgd会陷入鞍点。
文本输入方式有两种,一种是one-hot,假设词之间没有联系,也就是没有任何额外的背景信息,特征完全由神经网络训练提供。AE(自编码器)的输入一般就是one-hot。
第二种是词嵌入,能体现出词之间的关联,而且跟one-hot比起来没那么稀疏。
稀疏自编码是有dropout的,防止过拟合。
CNN主要特征是局部连接(也可以叫稀疏连接)和参数(权值)共享,可以大大减少参数规模,加快训练速度。参数共享指的是,使用同一个卷积核的神经元,往下一层传的时候权值是一样的。卷积后一般会作最大池化,而不是平均值池化。
CTM(correlational topic model)跟LDA类模型不同,主题模型之间是有相关性的。
45.从深层网络角度来讲,不同的层学习的速度差异很大,表现为网络中靠近输出的层学习的情况很好,靠近输入的层学习的很慢,有时甚至训练了很久,前几层的权值和刚开始随机初始化的值差不多。因此,梯度消失、爆炸,其根本原因在于反向传播训练法则,属于先天不足。
为什么要用padding? 为了让卷积核多次处理边界上的信息。 也可以用于调整输出的规模。
batch-epoch更新机制:假如有1w张图片,一次更新耗时太大。于是可以选择20张图片为一个batch,更新500个batch,(batch数也可以理解为参数更新的次数)步长设置得小一些。
NER(Name Entity Recognition)命名实体识别,可以用RNN做(多输入多输出)。
一般是用双向LSTM+CRF(条件随机场),后者有标签转移概率。
数据结构
顺序表和链表各自的优缺点?
① 基于空间的比较:顺序表的内存是静态分配的,链表是动态分配的。顺序表的储存密度=1,链表的储存密度<1(密度=数据容量/结构容量,链表结构包括数据
② 基于时间的比较:顺序表支持随机存取,而链表只支持顺序存取。顺序表的插入和删除平均需要移动近一半的元素,而链表插入删除不需要移动元素,只需要修改指针。顺序表的查找要比链表快。
在堆中的变量:动态分配内存的变量,new,malloc的变量。
在栈中的变量:局部变量(比如函数中声明的非静态变量,函数形参等)。
程序为堆变量分配动态内存,在程序结束时为栈变量清除内存,但是堆变量不会被清除,需要自己手动delete,否则会造成内存泄露。
18 哈希:哈希表中的元素是由哈希函数确定的,映射关系。常见哈希函数:
直接地址法: H(key)=key 或 H(key)=a·key+b
除留余数法: H(key)=key MOD p,p<=m (m为哈希表的长度)
解决冲突:开放地址法,再哈希法,链接地址法
STL底层实现: vector-数组
list-双向链表
deque-一个中央控制器和多个缓冲区(合并了vector和list),支持首尾快速增删,但中间不行,可以随机访问
Stack和queue: list或deque(封闭头部)
priority_queue的底层数据结构一般为vector为底层容器,堆heap为处理规则来管理底层容器实现
map和set的底层实现都是红黑树,map的查找效率是O(logn)。
拓展:红黑树是一种平衡二叉树,根节点和叶子节点都是黑色的,每个红色节点的两个子节点也是黑色的,从任意节点到每个叶子的所有路径都包含相同的黑色节点。
不用中间变量交换两个数:
加减法: a=a+b; b=a-b; a=a-b;
异或法: a=a^b; b=a^b; a=a^b;
21.八大排序:
冒泡排序可以优化成O(n),在序列本身有序时。优化方法:通过设置标志位来确认后面的序列是否已经有序,来决定是否跳出外层循环,
堆排序中,建堆的复杂度是O(n),排序重建堆的复杂度是O(nlgn)。
选择排序的比较次数是固定的,和序列的初始状态无关。
快速排序的优化:取基准值时,用三数取中法,即取首元素,尾元素,中间元素的中间值。 当子序列长度小于一定值时,改用插入排序法。
22 顺序结构即可以储存线性结构(数组,链表等),可以储存非线性结构(树,图-比如邻接矩阵)。
归并排序:将两个各有N个元素的有序表归并成一个有序表,其最少的比较次数是N,假设第一个表的元素均小于第二个表的第一个元素,那么只要经过比较N个第一个表中的元素,然后直接取出第二个表中的所有元素。最多的比较次数是2N-1.
最小生成树(包括所有节点,但不需要包括所有的边)的树形可能不唯一,因为可能存在权重相等的边。
Prim算法:从一个点开始连通地找最小权重的边。
Kruskal算法:找遍所有权重最小的边。
AVL树调整: http://blog.csdn.net/zhanghaotian2011/article/details/8459281
25.拓扑排序:
①定义:对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。
拓扑排序对应施工的流程图具有特别重要的作用,它可以决定哪些子工程必须要先执行,哪些子工程要在某些工程执行后才可以执行。为了形象地反映出整个工程中各个子工程(活动)之间的先后关系,可用一个有向图来表示,图中的顶点代表活动(子工程),图中的有向边代表活动的先后关系,即有向边的起点的活动是终点活动的前序活动,只有当起点活动完成之后,其终点活动才能进行。通常,我们把这种顶点表示活动、边表示活动间先后关系的有向图称做顶点活动网(Activity On Vertex network),简称AOV网。
一个AOV网应该是一个有向无环图,即不应该带有回路,因为若带有回路,则回路上的所有活动都无法进行(对于数据流来说就是死循环)。在AOV网中,若不存在回路,则所有活动可排列成一个线性序列,使得每个活动的所有前驱活动都排在该活动的前面,我们把此序列叫做拓扑序列(Topological order),由AOV网构造拓扑序列的过程叫做拓扑排序(Topological sort)。AOV网的拓扑序列不是唯一的,满足上述定义的任一线性序列都称作它的拓扑序列。
②实现步骤:1.在有向图中选择一个没有入边的顶点输出。
2.从有向图中删除该顶点以及所有和它有关的边。
3. 重复上述两步,直至所有顶点输出,或者当前图中不存在无前驱的顶点为止,后者代表我们的有向图是有环的,因此,也可以通过拓扑排序来判断一个图是否有环。
AOE-网还有一个特点就是:只有一个起点(入度为0的顶点)和一个终点(出度为0的顶点),并且AOE-网有两个待研究的问题:完成整个工程需要的时间
哪些活动是影响工程进度的关键。
关键路径:从起点到终点长度最长的路径,不一定只有一条。
关键活动:关键路径上的边
两条原则:
Ø 只有某顶点所代表的事件发生后,从该顶点出发的各活动才能开始
Ø 只有进入某顶点的各活动都结束,该顶点所代表的事件才能发生
则我们称从v0到vi的最长路径的长度为vi的最早发生时间,同时,vi的最早发生时间也是所有以vi为尾的弧所表示的活动的最早开始时间。
一个事件的最迟开始时间为以该事件为尾的弧的活动最迟开始时间与该活动的持续时间的和。
如果一个活动的最早开始时间=最晚开始时间,就称其为关键活动。
27.哈夫曼树:
定义节点的带权路径长度=从根节点算起的路径长度*节点权重。
树的带权路径长度=树中所有叶子节点的带权路径长度之和。
带权路径最短的树即为哈夫曼树(最优二叉树)。
哈夫曼树的构造:①1.根据给定的n个权值{w1,w2,…,wn}构成二叉树集合F={T1,T2,…,Tn},其中每棵二叉树Ti中只有一个带权为wi的根结点,其左右子树为空.
②在F中选取两棵根结点权值最小的树作为左右子树构造一棵新的二叉树,且置新的二叉树的根结点的权值为左右子树根结点的权值之和.
④ 在F中删除这两棵树,同时将新的二叉树加入F中.
⑤ 重复2、3,直到F只含有一棵树为止.(得到哈夫曼树),例:
哈夫曼编码属于哈夫曼树在通信中的应用之一,用于数据文件压缩。在通信业务中一般用二进制编码来表示字符序列,但面临两个问题:编码的唯一性问题和编码总长度最短问题。
对于唯一性问题,我们可以用二叉树设计二进制前缀编码,以电文中的字符作为叶子结点构造二叉树。然后将二叉树中结点引向其左孩子的分支标 ‘0’,引向其右孩子的分支标 ‘1’; 每个字符的编码即为从根到每个叶子的路径上得到的 0, 1 序列。如:
A:0 C:10 B:110 D:111
这样任何字符的编码都不会是其他字符的前缀。
对于总长度最短问题:把每个字符的出现频率作为权重,构造哈夫曼树,即可得到每个字符的哈夫曼编码。
关于二叉树:设度为1的结点数为n1,二叉树中总结点数为N,因为二叉树中所有结点均小于或等于2,所以有:N=n0+n1+n2 (1)
再看二叉树中的分支数,除根结点外,其余结点都有一个进入分支,设B为二叉树中的分支总数,则有:N=B+1。(可以理解为边的个数)
由于这些分支都是由度为1和2的结点射出的,所以有:B=n1+2n2;
N=B+1=n1+2n2+1(2)
由式(1)和(2)得到:n0+n1+n2=n1+2*n2+1; n0=n2+1
2-3查找树:为了保证查找树的平衡性,我们需要一些灵活性,因此在这里我们允许树中的一个结点保存多个键。
2-结点:含有一个键(及值)和两条链接,左链接指向的2-3树中的键都小于该结点,右链接指向的2-3树中的键都大于该结点。
3-结点:含有两个键(及值)和三条链接,左链接指向的2-3树中的键都小于该结点,中链接指向的2-3树中的键都位于该结点的两个键之间,右链接指向的2-3树中的键都大于该结点。
(2-3指的是2叉-3叉的意思)如图:
一颗完美平衡的2-3查找树中的所有空链接到根结点的距离都是相同的。
红黑树就是用红色表示3-结点的2-3树。
C++\C python Java
②纯虚函数没有函数体,只在函数后面加个=0,例如:
virtual void out()=0;
包含纯虚函数的类叫作抽象类,抽象类是不允许实例化对象的。而对于抽象类的子类来说,只有把抽象类中的纯虚函数全部实现之后,那么这个子类才可以实例化对象。为什么要定义虚函数? 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
③ 虚析构,是为了防止内存泄漏的。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。析构顺序是先析构子类,再析构父类。析构函数不建议抛出异常,因为抛出异常后可能不会释放内存。
④ 虚表:只有每个建立了虚函数的类才有虚表,只要父类中声明为了virtual的函数,在父类和子类的虚表中,都有这个函数(如果子类中有重写)。虚表记录的是函数的地址。虚表在声明了类之后就已经创建了。
⑤ 虚指针是类示例对象指向虚表的指针,是每个对象的隐藏成员,在对象头部,大小为4个字节。对象通过虚指针来调用虚函数。
构造函数不能为虚函数的原因?
虚函数表在对象实例化之后才能被调用,但在构造对象之前是无法调用的。
2. Python垃圾回收:
引用计数法:每个对象维护一个ob_ref字段,用来记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数ob_ref加1,每当该对象的引用失效时计数ob_ref减1,一旦对象的引用计数为0,该对象立即被回收,对象占用的内存空间将被释放。它的缺点是需要额外的空间维护引用计数,这个问题是其次的,不过最主要的问题是它不能解决对象的“循环引用”,因此,也有很多语言比如Java并没有采用该算法做来垃圾的收集机制。
什么是循环引用?A和B相互引用而再没有外部引用A与B中的任何一个,它们的引用计数虽然都为1,但显然应该被回收。为了解决对象的循环引用问题,Python引入了标记-清除(Mark-Sweep)和分代回收两种GC机制。
标记清除:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
在上图中,我们把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存。
分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。
3. 一门编程语言的效率包括编码效率和运行效率。Python和C++比起来运行效率低,是因为:
第一:python是动态语言
一个变量所指向对象的类型在运行时才确定,编译器做不了任何预测,也就无从优化。举一个简单的例子: r = a + b。 a和b相加,但a和b的类型在运行时才知道,对于加法操作,不同的类型有不同的处理,所以每次运行的时候都会去判断a和b的类型,然后执行对应的操作。而在静态语言如C++中,编译的时候就确定了运行时的代码。
另外一个例子是属性查找,关于具体的查找顺序在《python属性查找》中有详细介绍。简而言之,访问对象的某个属性是一个非常复杂的过程,而且通过同一个变量访问到的python对象还都可能不一样(参见Lazy property的例子)。而在C语言中,访问属性用对象的地址加上属性的偏移就可以了。
第二:python是解释执行,而不是编译执行。
第三:python中一切都是对象,每个对象都需要维护引用计数,增加了额外的工作。
第四:垃圾回收。
单例模式:确保一个类只有一个实例被建立,且提供了一个对对象的全局访问指针。
懒汉式:
饿汉式,定义的时候就创建了实例,本来就是线程安全的:
线程安全懒汉,定义互斥量,需要包含头文件
…
标识符[=整型常数],
} 枚举变量;
如果枚举没有初始化, 即省掉"=整型常数"时, 则从第一个标识符开始,
依次赋给标识符0, 1, 2, …。但当枚举中的某个成员赋值后, 其后的成员按依次
加1的规则确定其值。 注意标识符是字符串时不需要双引号。
6 public:public表明该数据成员、成员函数是对所有用户开放的,所有用户都可以直接进行调用。
private:private表示私有,私有的意思就是除了class自己的成员函数之外,任何人都不可以直接使用,包括本类的对象也不能直接使用。
protected:protected对于子女、朋友的成员函数来说,可以自由使用,没有任何限制,而对于其他的外部class,protected就变成private。
7. 1>公有继承中:
(1)基类的公有成员就相当于是派生类的公有成员,也就是说派生类可以像访问自身公有成员一样访问从基类继承的公有成员。
(2)基类的保护成员就相当于是派生类的保护成员,即,派生类可以像访问自身的保护成员一样,访问基类的保护成员。
(3)对于基类的私有成员,派生类内部成员是无法直接访问的,派生类使用者也无法通过派生类对象直接访问。
2>受保护继承中:
(1)基类的公有成员和保护成员都相当于派生类的保护成员,派生类可以通过自身的成员函数或其子类的成员函数访问它们。
(2)对于基类的私有成员,无论派生类内部成员或派生类的对象都无法直接访问。
3>私有继承中:
(1)基类公有成员和受保护成员都相当于派生类的私有成员,派生类只能通过自身的成员函数访问他们,派生类对象不能直接访问。
(2)对于基类的私有成员,无论派生类内部成员或派生类的对象都无法直接访问。
静态成员在创建对象之前就已经加载进内存了。
静态方法只能访问静态成员(包括成员变量和成员方法),非静态方法都可以访问。
静态成员在需要在类体外进行初始化。
静态成员函数不能是虚函数,也不能声明为const或是volatile。
为什么不能是虚函数呢?静态与非静态成员函数之间有一个主要的区别。那就是静态成员函数没有this指针,而且静态成员函数是属于整个类的,为所有的类对象实例所共有,它只对类内静态成员变量操作。而This指针是相当于一个类对象实例的指针。
虚函数依靠vptr和vtable来处理。vptr是一个指针,在类的构造函数中创建生成,并且只能用this指针来访问它,因为它是类的一个成员,并且vptr指向保存虚函数地址的vtable.对于静态成员函数,它没有this指针,所以无法访问vptr。所以静态成员函数不能是虚函数。
静态成员函数不能是const的原因是,被const修饰的成员函数必须含有this指针。
构造函数不能是虚函数。
析构函数可以是虚函数,而且通常声明为虚函数。但C++默认的析构函数不是虚函数,因为虚函数需要额外的虚表和虚指针,占用额外的内存。
重载:在同一个作用域内(比如说同一个类内)函数同名,但参数个数、类型或顺序不同。注意重载和多态是无关的!(这些函数的地址在编译时就已经确定了)
隐藏:在派生类中,定义与基类的同名函数(参数可以相同也可以不同),默认就会调用派生类的函数。
重写:在派生类中,定义与基类同名的函数,而且参数、返回值完全相同,只有函数体不同。基类中被重写的函数必须是虚函数,有virtual修饰。
全局对象是在main函数调用之前创建的,析构在main函数之后,程序结束之前。
① 指针常量,值为常量的指针,始终指向同一个地址,在定义时必须初始化。
例如:int a=10,b=20;
int* const p=&a;
*p=30; //正确
p=&b; //报错
p指向的地址是一定的,但指向的内容可以修改。
② 指向常量的指针(也叫常量指针),指针指向的内容不可改变,但可以指向其他地址。
int a=10, b=20;
const int *p=&a;
p=&b; //正确
p=b; //报错
p可以指向其他地址,但是指向的内容不能改变。
区别:const修饰p,表示指针本身不可修改(也就是地址不可修改)。Const修饰p,表示指针指向常量。
(JAVA)抽象类和abstract:
① 用abstract修饰的类,即抽象类;用abstract修饰的方法,即抽象方法。
② 抽象方法不能有主体,abstract void func();
③ 抽象类不能被实例化
④ 抽象类不一定要包含abstract方法,但含有abstract方法的类必须声明为abstract。
结构体和class的区别:
① 默认继承权限:结构体是public,class是private。
② struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
二维数组行号可以不指定,列号一定要指定,如a[][5]是正确的。
二进制逻辑加法: 逻辑加法,或运算,符号:+,/
逻辑乘法,与运算,符号:x,/\
逻辑否定,非运算,符号:┐。
局部变量可以和全局变量重名,在函数内局部变量会覆盖全局变量。
正数原码、反码、补码形式一致。 负数反码,为其原码的符号位不变,其他位取反; 负数补码,是其反码加1。
短路原则 if(a||b) 如果a为真,b是不执行的。
Int * p[10]表示一个10个元素的数组,数组里存放的是指针。
Int (*p)[10]表示一个指向一个数组的指针,是一个指针变量,可以再次赋值。
Int p[10],p表示一个指向数组第一个元素的指针常量,不可以再赋值。
关于析构:C++告诉我们在回收用 new 分配的单个对象的内存空间的时候用 delete,回收用 new[] 分配的一组对象的内存空间的时候用 delete[]。
关于 new[] 和 delete[],其中又分为两种情况:(1) 为基本数据类型分配和回收空间;(2) 为自定义类型分配和回收空间。
基本类型的对象没有析构函数,所以回收基本类型组成的数组空间用 delete 和 delete[] 都是应该可以的;但是对于自定义类对象数组,只能用 delete[]。
所以一个简单的使用原则就是:new 和 delete、new[] 和 delete[] 对应使用。
Char a[]=”123”;是合法的, char a[3]={‘1’,’2’,’3’}; 也是合法的。
但char a[3]=”123”;无法编译成功,因为末尾还需要一个”\0”。
构造函数没有被显式定义时,会默认提供一个无参的构造器。
构造函数是可以重载的。
C语言源程序的最小单位是字符,最小执行单元是函数。
定义符号常量不一定需要指定类型,比如用#define
数组初始化int a[10]={}; int a[10]={10}; int a[10]={10,1,1.3}
均合法,第一个全初始化为0,第二个a[0]=10,其他全为0。
第三个double类型会自动转为int。
字节对齐:struct 中字节对齐需要满足的条件:
1、某个变量存放的起始位置相对于结构的起始位置的偏移量是该变量字节数的整数倍;2、结构所占用的总字节数是结构种字节数最长的变量的字节数的整数倍。
C++特性:封装:面向对象的思想中,将数据和对数据的操作封装在一起,即类。
继承,多态。
C和C++的区别:
① C89标准的C语言不支持函数形参默认值,C++支持函数形参默认值,且需要遵循从右向左赋初始值。
② C是面向过程的结构化语言,比较注重算法和数据结构,C++是面向对象的。
③ C++相比C多了一些检查类型安全的功能。
④ C++支持范式编程,比如模板类,函数模版等等。
① C是C++的子集。
② C语言中只有结构体,C++中还有class。
③ C语言中只有宏定义,C++中还有内联函数。
④ C语言中不能重载,因为产生函数符号的规则是根据名称的,而C++中可以重载。
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo(int x, int y);
该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。
⑤ C语言中的const修饰的变量叫作只读变量或者常变量,可以不初始化但也没有机会再次初始化,不能当做数组的下标,可以被指针修改,初始化后不能作为左值。
C++中用const修饰的是常量,必须初始化,可以用作数组下标,可以通过指针修改。编译规则类似宏定义中的替换。但也可能退化为C语言中的常量,例如:
Int b=20; const int a=b; 此时这个a退化为了C语言中的常变量,因为引入了一个编译阶段不能确定的值。
如何通过指针修改const值? 前提是这个变量是局部的,储存在堆栈中,才能修改。
如果是在全局储存空间就不能修改,虽然能通过编译,但运行时错误。
Const int a=10;
int p=(int)&a; 或 int *p=const_cast(&a);
p=20;
这种情况下输出a仍是10,但实际上a的地址中保存的值是20。这是编译器在遇到a的时候已经把它替换为了10(变为立即数),编译器对const变量只读取一次。
⑥ Malloc、free && new delete
前两者是函数,后两者是运算符(operator)。
Malloc分配内存需要指定大小和类型转换,如果分配成功,则返回所分配内存空间的首地址,否则返回NULL,申请的内存不会进行初始化,所以这片空间中的值都是之前的使用者遗留的数据。
而new不需要指定大小和类型转换,还可能可以顺便初始化。
例: int p1=(int)malloc(sizeof(int)); //malloc返回的是void,在C语言中表示不确定类型的指针。
int *p2=new int(10); //注意new返回的也是指针
cout<<*p2<
New是先调用构造函数再申请空间。
⑦ C语言作用域:函数原型作用域,局部作用域。而C++有:函数原型作用域,局部作用域,类作用域和名字空间作用域。
⑧ C语言中没有引用这个概念。
内联函数:程序在调用一般函数时,需要跳转到该函数的地址,并在函数结束时返回。这样来回跳跃并记录跳跃位置需要一定的开销。
内联函数提供了另一种选择。编译器将使用相应的函数代码替换函数调用。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。用法就是在函数前加上inline关键字:
inline double square(int x){return xx;}
内联函数只是对编译器的建议,最终是否被使用是不一定的。
内联函数与带参数宏定义的区别:1.对参数的处理,宏定义是直接替换参数,而内联函数先会对参数进行运算。例:#define square(x) xx, 注意宏定义如果换行需要加
如果调用square(1+2) 计算的不是33,而是1+21+2
所以在写带参宏定义时,参数应该加括号!如:#define square(x) (x)*(x)
2.内联函数的参数和返回值都有指定类型,而宏定义中没有类型指定,在宏展开之后才由编译器检查语法,这就存在很多安全隐患,所以内联函数可以认为是安全的宏定义。
使用时机:如果执行函数代码比处理函数调用机制(函数的堆栈开销)所需的时间短,就可以使用,所以内联函数的函数体往往是非常小的。
30.指针是一个储存值的地址的变量。
对于一个变量a来说,&a就是它的地址。
运算符表示解除引用,对指针使用,就能得到指针所指地址存放的值。
Int 是一种复合类型,表示指向int类型变量的指针。
30. 函数传值无法改变外部的实际值,而传指针(也就是地址)和传引用(别名)是可以改变外部的实际值的。例如:
void plus(int *a) {*a=*a+1;}
void plus(int &a) {a=a+1;}
值传递:形参是实参的拷贝。
指针传递:形参是指向实参地址的指针,对形参的指向操作时,相当于对实参进行的操作。本质上也是一种值传递,传的是实参的地址,这个地址不会被改变。
引用传递:形参相当于是实参的别名,而不是实参的副本。对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形参虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。
引用传递和指针传递是不同的,虽然它们都是在被调函数栈空间上的一个局部变量,但是任何对于引用参数的处理都会通过一个间接寻址的方式操作到主调函数中的相关变量。而对于指针传递的参数,如果改变被调函数中的指针地址,它将影响不到主调函数的相关变量。
31. 引用只能在定义时被初始化一次且必须初始化,之后不可变,引用不能指向空值。(这三点也是和指针的区别,指针可以不初始化,可以改变所指对象,可以指向空值)
注意:一个引用在声明为一个变量的别名后,不能再作为其他变量的别名。数组不能建立引用。
注意:指针虽然可以不初始化,但并不建议这样做,如果不初始化,它就是野指针,把它指向一个值,程序会崩溃,例如:
Int *p;
p=1; //崩溃
Int x=0; p=&x; //不崩溃,而且接下来可以执行p=1了
也就是如果不初始化,下一步只能对指针本身赋予地址,而不能让它指向任何值。
引用的作用:①传递可变参数。②传递大型参数,例如类对象(避免调用拷贝构造函数等大消耗)等。③引用返回值,就可以对函数的返回值赋值,这个函数如果是类成员函数,就可以通过把它作为左值来改变类成员。例如:
double &M(double a,double b){return a>b?a:b;}
M(5,6)+=1;
这样做的好处是,在内存中不会产生函数返回值的副本。
注意事项:①不要返回局部变量的引用(或指针),因为在函数结束后,局部变量会被销毁,这个引用(或指针)就变成“无所指”。(编译器只会警告)
②不能返回函数内部new分配内存的引用。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
③ 可以返回类成员的引用,但最好是const。
④ 另外,重载流操作符和等号也需要返回引用。
32. Const的作用:
① 修饰函数形参,代替值传递,使用引用传递。既避免了值传递需要生成副本的损耗,又避免了对实参的更改。
② 定义常量。
③ 修饰不会修改类成员的的类成员函数,如 int get_Value() const; 如果修改了类成员,或者调用了其他非const成员函数,编译会出错。
④ 修饰函数的返回值。使函数调用不能作为左值,例如前面说到的get_value的例子:const int get_Value() const;
33. 防止内存泄露的手段:智能指针,它不需要手动释放内存空间,而可以自己管理堆内存的释放。智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类对象都是栈上的对象,所以当函数(或程序)结束时会自动被释放,C++提供了三种智能指针:shared_ptr ; unique_ptr ; weak_ptr (其实还有一个auto_ptr,但C++11已经弃用),windows下使用时要#include
① unique_ptr:同一时刻只能有一个unique_ptr指向给定对象。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。用法:
编译出错。因为这样会留下悬挂的p3。
② Share_ptr: shared_ptr多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。拷贝使得对象的引用计数增加1,当计数为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。调用release()时,引用计数减1。
③ Weak_ptr:weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。weak_ptr可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。使用weak_ptr的成员函数use_count()可以观测资源的引用计数。它是一种不控制对象生命周期的智能指针,它是用来解决shared_ptr之间循环引用时的死锁问题。
另外,weak_ptr不能直接访问对象的方法,而share_ptr可以,可以通过.lock()将weak_ptr转化为share_ptr。
具体用法例程序:https://www.nowcoder.com/ta/review-c/review?page=27
34. C++内存区域的分配:
栈区:有时候也称堆栈。存放局部变量和函数的形参,由系统分配内存,无法手动释放内存空间。效率比较高,但是分配的容量有限。
堆区: 存放由new和malloc关键字申请的对象,需要delete才能被释放。(但程序退出后,系统会自动回收资源)。
全局区(静态区):存放全局变量和静态变量,编译的时候就已经为它们分配内存,在程序执行结束时内存被释放,所以它们的生命周期就是整个程序执行过程。初始化的全局变量和静态变量在一块区域(.data),未初始化的全局变量和未初始化的静态变量在相邻的另一块区域(.bss)对于静态局部变量, 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束(但其本身还留存在全局数据区,只是函数结束,暂时没有能操作它的对象了,利用这一点,我们说给局部变量加上static就可以控制它的可见范围了,也叫作隐藏)。
常量区:存放字符串和常量,在这里存放的数据一经初始化就无法修改,程序结束后由系统释放。
以上四个区合称数据区。
代码区:存放编译后的二进制代码。
注意:一个类实例化前不会占用堆或栈的内存。
35. Static的作用有前面说到的隐藏,还有让变量具备持久性和默认值0。
① 非静态全局变量和静态全局变量都在全局区,但区别在于,非静态全局变量的作用域是整个源程序。当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。(这就是隐藏,实际上,这是static通过修改全局变量的链接属性,默认的external变为internal,但作用域和储存类型不变,由使这些变量只能在声明它们的源文件中被访问)
② 在函数内部的static局部变量只会被初始化一次,如果该函数被多次调用,那么下一次根据上一次的结果来取值。(static修改了局部变量的储存类型,但不改变作用域和链接属性。)
35函数调用过程:
执行某个非内联函数时,如果有参数,则在栈上为形参分配空间(如果是引用类型的参数则除外),继续进入到函数体内部,如果遇到变量,则按情况为变量在不同的存储区域分配空间(如果是static类型的变量,则是在进行编译的过程中已经就分配了空间),函数内的语句执行完后,如果函数没有返回值,则直接返回调用该函数的地方(即执行原点),如果存在返回值,则先将返回值进行拷贝传回,再返回执行原点,函数全部执行完毕后,进行退栈操作,将刚才函数内部在栈上申请的内存空间释放掉。
36. 结构体和联合(union,节省空间):
① 在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
② 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
一个union变量的长度等于各成员中最长的长度。
Union中的数据成员共享内存,所以不能存放静态变量,因为静态变量和其他变量不可能共享内存。
37. Char p=”123”; 注意这里相当于 const char p=”123”,所以不能修改p[0]。这里的“123”属于文字常量,地址是在常量区,p是指向这个常量首元素的指针。
如果再声明char t=”123”; 这个t和p所表示的地址是一样的,也就是t=p。
对p使用sizeof§返回的是这个指针本身的字节数, 无论是intp 还是 charp,得到的结果都一样。根据32位系统和64位系统,分别是4和8。
如果在函数中把数组当作形参传递,那么这个数组自动退化为指向同类型的指针,例如:
Void F(char a[]) {cout<
输出8(在64位系统中),而不是6。
38. 数组名和指针的区别?
① 用sizeof关键字对数组名进行运算得到的是整个数组大小,而对指针则不是(参考上一条)。
② 对数组名取地址是得到一个代表整个数组的地址(其实这个地址也就是首元素的地址),对指针取地址则不是。
Int a[3]={1,2,3}
Int p=a;
那么这里的p指向就是数组的首元素,p本身等于&a[0]。
对p取地址,就是地址的地址。
而&a等于&a[0]。
39.①数组指针:int(p)[4],表示一个指向四个元素的数组的指针,()和[]是同一优先级,所以p先和结合成为指针,再和[]结合,成为数组指针。
②指针数组:int p[10]; char
一个数组指针的例子:
P是一个指向四个元素的数组的指针,所以p+1的话就会移动4个int元素的位置。所以这段程序输出-4和-4的补码(%p表示以地址格式输出)。
③ 函数指针:这里定义void (p)()。一眼可知p先和结合,说明p是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
调用示例:
④ 函数指针数组:int (*p[4])(int x, int y) 存放函数名,例:
如何限制类对象只能在栈中建立呢:
把new运算符设为私有。如:
类型安全:类型安全是指同一段内存在不同的地方,会被强制要求使用相同的办法来解释(内存中的数据是用类型来解释的)。
Java语言是类型安全的,除非强制类型转换。
C语言不是类型安全的,因为同一段内存可以用不同的数据类型来解释,比如1用int来解释就是1,用boolean来解释就是true。
C语言中类型不安全的例子:printf(“%f\n”,10); //不报错但是输出0.00000
Int p=(int)malloc(sizeof(char*)); //也不报错
C++也不是类型安全的。但比起C,还是增加了一些保障机制。比如new(改进malloc),内联函数(改进#define),dynamic_cast(改进static_cast)。
C++中的强制类型转换:
①static_cast:没有运行时类型检查来保证转换的安全性。用法一般有:基本类型转换,以及类中父类和子类指针或引用的互相转换等等。
上行转换:派生类->父类,安全。
下行转换:父类->派生类,由于没有动态类型检查,所以不安全。
注意static_cast不能把const转为非const的类型,不过反过来是可以的。例:
Int e=1; const int b=static_cast(e); //编译成功
Const int e=1; int g=static_cast
②dynamic_cast:dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast),以及多继承。什么是交叉转换(只能用dynamic_cast)?基类A有两个直接派生类B、C。
那么,将B* pB转换为C* pC,这种由派生类B转换到派生类C的转换称之为交叉转换。
什么是多继承中的上行转换(也是只能用dynamic_cast)? 设有两个基类A1、A2,派生类B从A1、A2中派生。那么,将pB转换为pA1或是pA2即为多继承中的转换。
Dynamic_cast中被转换的必须是类的指针,类的引用或者是void *。
在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
注意,dynamic_cast使用条件是基类存在虚函数,而static_cast无此限制。其原因是:基类中存在虚函数,就说明它有想要让基类指针或引用指向派生类对象的情况,此时转换才有意义。下行转换例子,如果将一个指向基类对象的指针转为指向派生类对象的指针,指针会被置为NULL:
如果用static_cast,pb2就不会指向NULL。
③const_cast: const_cast用来将类型的const、volatile属性移除。常量指针被转换成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然引用原来的对象。例:
Const int e=1;
Int p=const_cast
⑤ Reinterpret_cast: 允许将任何指针类型转换为其它的指针类型;听起来很强大,但是也很不靠谱。它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针,在实际开发中,先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原来的指针值;特别是开辟了系统全局的内存空间,需要在多个应用程序之间使用时,需要彼此共享,传递这个内存空间的指针时,就可以将指针转换成整数值,得到以后,再将整数值转换成指针,进行对应的操作。
47.volatile关键字:volatile关键字是一种限定符用来声明一个对象在程序中可以被语句外的东西修改,比如操作系统、硬件或并发执行线程。
遇到该关键字,编译器不再对该变量的代码进行优化,不再从寄存器中读取变量的值,而是直接从它所在的内存中读取值,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。(此操作可以用于看指针修改const的效果,例)
例:
Typedef int NUM; NUM a; //定义一个整数
Typedef int NUM[100]; NUM n; //定义一个含有100个元素的int数组
Typedef struct time //声明结构体,可以用time或T定义变量
{
Int a; int b;
}T;
Typedef struct //声明结构体,只能用 T定义变量
{
Int a; int b;
}T;
typedef int* p; //定义了一个名为p的指针类型, 它指向int
typedef int f(); // 定义一个名为f, 参数为空, 返回值为int的函数类型
typedef int g(int); // 定义一个名为g, 含一个int参数, 返回值为int行的函数类型
它的作用主要有两点,一是简化复杂的声明,二是便于修改类型(有利于程序的可移植性)。例如:你可以有很多对象是int型的,那么你在第1次写程序的时候频繁地写int A; int B; int C;这样,但是后期你发现要修改程序从int变到char。难道你每个变量申明的int关键字都修改一次?这样显然很麻烦,你可以typedef int MyType,你要转成char 只要把typedef int MyType换成typedef char MyType就可以。
静态链接和动态链接两者最大的区别就在于链接的时机不一样,静态链接是在形成可执行程序(exe)前,而动态链接的进行则是在程序执行时(装载或运行时)。
https://blog.csdn.net/kang___xi/article/details/80210717
在我们的实际开发中,不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个*.c文件会形成一个*.o文件,为了满足前面说的依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接的过程就是静态链接。
静态链接的缺点很明显,一是浪费空间,因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,如多个程序中都调用了printf()函数,则这多个程序中都含有printf.o,所以同一个目标文件都在内存存在多个副本;另一方面就是更新比较困难,因为每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。下面简单介绍动态链接的过程:
假设现在有两个程序program1.o和program2.o,这两者共用同一个库lib.o,假设首先运行程序program1,系统首先加载program1.o,当系统发现program1.o中用到了lib.o,即program1.o依赖于lib.o,那么系统接着加载lib.o,如果program1.o和lib.o还依赖于其他目标文件,则依次全部加载到内存中。当program2运行时,同样的加载program2.o,然后发现program2.o依赖于lib.o,但是此时lib.o已经存在于内存中,这个时候就不再进行重新加载,而是将内存中已经存在的lib.o映射到program2的虚拟地址空间中,从而进行链接(这个链接过程和静态链接类似)形成可执行程序。
动态链接的优点显而易见,就是即使需要每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多份副本,而是这多个程序在执行时共享同一份副本;另一个优点是,更新也比较方便,更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就完成了升级的目标。但是动态链接也是有缺点的,因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。
51.在函数中声明的类对象,按声明顺序构造,然后入栈,函数结束时按相反顺序出栈,然后析构。
52.结构化程序设计强调程序的易读性。
53. ①calloc 函数: void *calloc(unsigned int num, unsigned int size)
按照所给的数据个数和数据类型所占字节数,分配一个 num * size 连续的空间。calloc申请内存空间后,会自动初始化内存空间为0。
②realloc 函数: void *realloc(void *ptr, unsigned int size)
动态分配一个长度为size的内存空间,并把内存空间的首地址赋值给ptr,把ptr内存空间调整为size。申请的内存空间不会进行初始化(malloc也是)。
③new是动态分配内存的运算符,自动计算需要分配的空间,在分配自定义类型的内存空间时,同时调用类的构造函数,对内存空间进行初始化,即完成类的初始化工作。
而对于内置类型:
int *p1 = new int[10];
int *p2 = new int10;
p1仅仅是用new分配内存,并不会初始化。
p2不仅分配内存,还会初始化为0。
54.组合类:指在一个类的数据成员中含有其他类的对象,拥有这样结构的类就叫组合类。这种以数据成员身份出现的类对象就叫子对象。
当一个类既是组合类又是派生类,它在创建对象时,系统对构造函数的调用顺序有相应的规定:
最先调用基类的构造函数,初始化基类的数据成员;
然后调用子对象所在类的构造函数,初始化子对象的数据成员;
最后调用本类的构造函数,初始化新增数据成员。
当对象消亡时,系统对析构函数的调用顺序为:
最先调用本类的析构函数;
然后调用子对象所在类的析构函数;
最后调用基类的析构函数。
程序示例:
56 浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针。
例:
输出:
执行结果:调用一次构造函数,调用两次析构函数,两个对象的指针成员所指内存相同,这会导致什么问题呢?name指针被分配一次内存,但是程序结束时该内存却被释放了两次,会导致崩溃!这是由于编译系统在我们没有自己定义拷贝构造函数时,会在拷贝对象时调用默认拷贝构造函数,进行的是浅拷贝!即对指针name拷贝后会出现两个指针指向同一个内存空间。(浅拷贝带来问题的本质在于析构函数释放多次堆内存,使用std::shared_ptr,可以完美解决这个问题。)
所以,在对含有指针成员的对象进行拷贝时,必须要自己定义拷贝构造函数,在自己定义的拷贝构造函数里使用new,为拷贝后的对象指针成员分配自己的内存空间,即进行深拷贝。
57. windows下创建线程:在创建新的线程时一定要使用_beginthreadex函数而不要使用CreateThread函数,因为CreateThread函数对系统中的全局变量没有保护,所以多个线程环境下容易出现系统的全局变量的值被覆盖的情况.
还有AfxBeginThread(用于MFC应用程序)。
58. 有名字的变量或对象都是左值,匿名变量都是右值。
右值引用例子: ①int &&x=0; 这里绑定一个右值0。
②A getA() {return A();}
Main(){A a=getA();}
不考虑编译器优化的情况下,这里会构造出三个类对象,一个是函数内的临时变量,返回之后被销毁。一个是main函数里的右值,也是临时变量,a调用拷贝构造函数后,这个临时变量也被销毁。第三个就是对象a。
如果把代码改成:
Main() {A &&a=getA();}
还是不考虑编译器优化的情况下,现在只会构造出两个类对象了,第一个跟前面说的一样,函数返回时的临时变量。第二个也是main函数里的右值,但这个临时变量的生命周期被右值引用延长了,就不需要构造新的对象a了。
右值引用的意义:
直观意义:为临时变量续命,也就是为右值续命,否则右值在表达式结束后就消亡了。
右值引用是用来支持转移语义(性能优化)的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 (创建和销毁) 对性能有严重影响。
转移语义是和拷贝语义相对的,可以类比文件的剪切与拷贝,当我们将文件从一个目录拷贝到另一个目录时,速度比剪切慢很多。
通过转移语义,临时对象中的资源能够转移其它的对象里。
在现有的 C++ 机制中,我们可以定义拷贝构造函数和赋值函数。要实现转移语义,需要定义转移构造函数,还可以定义转移赋值操作符。对于右值的拷贝和赋值会调用转移构造函数和转移赋值操作符。如果转移构造函数和转移拷贝操作符没有定义,那么就遵循现有的机制,拷贝构造函数和赋值操作符会被调用。 如果传递的参数是左值,就调用拷贝构造函数,反之,就调用转移构造函数.
普通的函数和操作符也可以利用右值引用操作符实现转移语义。
右值引用是左值还是右值都有可能,这取决于它的初始化,例:
template
void f(T&& t){}
f(10); //t是右值
int x = 10;
f(x); //t是左值
右值引用作为形参,可以构成转移构造函数,例:
对于一个数据成员包括指针的类,我们知道要定义拷贝构造函数来深拷贝,防止指针悬挂的问题。但当对象很大的时候,深拷贝对性能影响较大。于是我们就定义了转移构造函数,它以右值引用作为参数。
我们提供转移构造函数的同时也会提供一个拷贝构造函数,以防止移动不成功的时候还能拷贝构造,使我们的代码更安全。
std::move方法来将左值强制转换为右值,从而方便应用移动语义。move是将对象资源的所有权从一个对象转移到另一个对象,只是转移,没有内存的拷贝。例:
{
list< string> tokens;
//省略初始化…
list t = tokens; //这里存在拷贝
}
list tokens;
list t = move(tokens); //这里没有拷贝
如果不用std::move,拷贝的代价很大,性能较低。使用move几乎没有任何代价,只是转换了资源的所有权。他实际上将左值变成右值引用,然后应用移动语义,调用移动构造函数,就避免了拷贝,提高了程序性能。如果是一些基本类型比如int和char[10]定长数组等类型,使用move的话仍然会发生拷贝(因为没有对应的移动构造函数)。所以,move对于自定义类型的大对象来说更有意义。
auto(它也是C++11的新特性)可以在声明变量的时候根据变量初始值的类型自动为此变量选择匹配的类型。auto的自动类型推断发生在编译期,所以使用auto并不会造成程序运行时效率的降低。
它一般有两个作用:①代替冗长复杂的声明,例如把
Vector::iterator换成auto
② 在定义模版函数时,修饰依赖模版参数的变量或者返回值。例:
template
void Multiply(_Tx x, _Ty y)
{
auto v = xy;
std::cout << v;
}
因为不清楚xy会返回什么类型。
或者:
template
auto multiply(_Tx x, _Ty y)->decltype(xy) //拖尾返回类型
{
return xy;
}
当模板函数的返回值依赖于模板的参数时,我们依旧无法在编译代码前确定模板参数的类型,故也无从知道返回值的类型,这时我们可以使用auto。decltype操作符用于查询表达式的数据类型,也是C++11标准引入的新的运算符,其目的也是解决泛型编程中有些类型由模板参数决定,而难以表示它的问题。auto在这里的作用也称为返回值占位,它只是为函数返回值占了一个位置,真正的返回值是后面的decltype(x*y)。
使用auto的注意事项:① 必须在定义时初始化,const也是。
②定义在一个auto序列的变量必须始终推导成同一类型:
auto b4 = 10, b5 = 20.0, b6 = ‘a’;//错误,没有推导为同一类型
③auto被赋值为引用/const时,会去除引用/const语义,例:
int a=10; int &b=a; auto c=b; //c是int而不是int&
auto &d=b; //d才是int&,也就是a的引用。
④ 如果auto后面带上&,被赋值为const,则不会去除const语义。例:
Const int a=1; auto &b=a; //b是const int&
⑤ 被赋值为数组时,auto推导类型为指针,例:
Int a[3]={1,2,3}; auto b=a; //b是int*
但如果带上&
Int a[3]={1,2,3}; auto &b=a; //b是int[3]
⑥ 函数形参不能声明为auto类型
⑦ auto仅仅是一个占位符,它并不是一个真正的类型,不能使用一些以类型为操作数的操作符,如sizeof或者typeid。
61.memcpy: void *memcpy(void *str1, const void *str2, size_t n) 从存储区 str2 复制 n 个长度数据到存储区 str1。可以拷贝任何数据类型的对象并指定拷贝数据的长度:char a[100],b[50]; memcpy(b, a, sizeof(b))。 那么在自己实现Memcpy的时候,就需要从后往前复制。 62.在main中,int a[10]或是int *b=new int[10],里面的值并不是全都是0。 63.完整版自己实现strcpy,要点有三,一是const防止原指针被修改,二是断言防止空指针,三是返回目标字符串首地址,而不是void: 指针free之后,free函数只是把指针指向的内存空间释放了,即内存中存储的值,但是并没有将指针的值赋为NULL,指针仍然指向这块内存。野指针”与空指针不同,“野指针”有地址,或者说指向指定的内存,对野指针进行操作很容易造成内存错误,破坏程序数据等危险。“野指针”无法简单地通过if语句判断其是否为 NULL来避免,而只能通过养成良好的编程习惯来尽力减少,free函数执行后,一定要接着设置指针为NULL。 指针定义后不初始化,就是野指针,野指针不能指向值,否则程序崩溃,只能重新赋地址。 66.判断两个float变量是否相等,不能直接用==,而应该用一个范围,例如: 67.预处理指令:#undef取消已定义的宏 ① 宏可以用于新旧代码的调试修改,保护旧代码。 69…①重载=号,+号之类的运算符为什么要返回引用? 72.Java六大储存区域: 74.函数的定义和声明在默认情况下都是extern的,但如果加了static修饰,就只能在此文件中可见。 不能为虚函数的原因:编译器在编译一个类的时候,需要确定这个类的虚函数表的大小,表中是此类所有虚函数的地址。但成员模版函数只有在实例化的时候才会被使用,如果将其声明为虚函数,那么此类虚函数表的大小就无法确定了。 作用3:实参推断:在调用上面的函数模版时,不需要在参数列表指定参数的类型,直接调用即可,编译器会进行实参推断。 这时,就必须指定返回值的类型了,而且必须按照从左到右的指定顺序。 作用4:类模版: 和函数模版不同的是,类模版不能实参推断,必须给出模版参数类型: 作用5: 类模版中的成员函数模版: 调用成员模版函数时,不需要指定第二个参数的类型,编译器可以推断出是double。 Tips: 类模版中的static成员,在类外定义的时候,要加上模版参数那一行,而且不同的模版实例有不同的static成员。 类模版实例化后,并不会实例化所有成员函数,而只实例化所有被调用的成员函数: 76.explict关键字:https://www.cnblogs.com/rednodel/p/9299251.html 项目: 海量数据处理: 负载均衡算法(调度策略),也就是选择哪一个应用服务器:随机,轮询,最小连接(选择处理连接数最少的服务器),最快响应时间,哈希,IP地址散列,URL散列。 Zookeeper是一个分布式管理机制,基于选举制度。 Kafka是一种消息队列。 Impala能查看Hbase中的数据。 Hadoop是一个由Apache基金会所开发的分布式系统基础架构。 HDFS是分布式文件系统HDFS简化了文件的一致性模型,通过流式数据访问,提供高吞吐量应用程序数据访问功能,适合带有大型数据集的应用程序。它提供了一次写入多次读取的机制,数据以块的形式,同时分布在集群不同物理机器上。 MapReduce是一种分布式计算模型,用以进行大数据量的计算。它屏蔽了分布式计算框架细节,将计算抽象成map和reduce两部分, HBase是一个建立在HDFS之上,面向列的针对结构化数据的可伸缩、高可靠、高性能、分布式和面向列的动态模式数据库。 HBase采用了BigTable的数据模型:增强的稀疏排序映射表(Key/Value),其中,键由行关键字、列关键字和时间戳构成。HBase提供了对大规模数据的随机、实时读写访问,同时,HBase中保存的数据可以使用MapReduce来处理,它将数据存储和并行计算完美地结合在一起。Hbase是列式储存,不支持sql语句。 Hive定义了一种类似SQL的查询语言(HQL),将SQL转化为MapReduce任务在Hadoop上执行。通常用于离线分析。 ACID 原子性:事务的操作要么全部成功要么全部失败 索引用B+数的原因:B/B+树是为了磁盘或其它存储设备而设计的一种平衡多路查找树(相对于二叉,B树每个内节点有多个分支),与红黑树相比,在相同的的节点的情况下,一颗B/B+树的高度远远小于红黑树的高度。 数据库保护又叫做数据库控制,是通过四方面实现的,即安全性控制,完整性控制,并发性控制和数据恢复。 三级模式指的是 外模式 、 模式 和 内模式: 一、模式(Schema) 关系的基本运算有两类:一类是传统的集合运算(并、差、交,笛卡尔积等),另一类是专门的关系运算(选择、投影、连接(内外)、除法等)。 数据库范式: 标准的 SQL 的解析顺序为: ①关系的实体完整性: 每一个表中的主键字段不能为空或者重复的值。实体完整性指表中行的完整性。要求表中的所有行都有唯一的标识符,称为主关键字; 数据库主要解决的是数据的共享问题。 事务之间的对数据的并发操作会带来三种问题:丢失修改,脏读,不可重复读。 DBS(数据库系统)=DBMS(数据库管理系统)+DB(数据库)+应用系统+DBA(数据库管理员) 闭包就是由一个属性直接或间接推导出的所有属性的集合。 ER图:实体:矩形, 属性:椭圆, 关系:菱形 弱实体:一个实体必须依赖于另一个实体存在,那么前者是弱实体,后者是强实体,弱实体必须依赖强实体存在,例如上图的学生实体和成绩单实体,成绩单依赖于学生实体而存在,因此学生是强实体,而成绩单是弱实体。弱实体与联系之间的联系也是用的双线菱形。上面实例根据弱实体的情况更改如下图: 结果为: 列式存储(Column-based)是相对于行式存储来说的,新兴的 Hbase、HP Vertica、EMC Greenplum 等分布式数据库均采用列式存储。在基于列式存储的数据库中, 数据是按照列为基础逻辑存储单元进行存储的。同一列数据一定是存储到同一个块中,换句话说就是不同的列可以放到不同块中,在进行select查询的时候可以单独查询某一列。同一行数据存到同一个块中,当查询某个或某几个字段式,只需要查找储存这几个字段的这几个块,大大减少了数据的查询范围,提高了查询效率。 hive默认的文件格式是textfile 如果发送两次就可以建立连接话,那么只要客户端发送一个连接请求,服务端接收到并发送了确认,就会建立一个连接。 如果加了第三次客户端确认,服务端在接受到一个客户端的ACK后,后面再接收到的客户端连接请求就可以抛弃不管了。 ③ 有人会困惑为什么要进行三次握手呢(两次确认)?这主要是为了防止已失效的请求连接报文忽然又传送到了,从而产生错误。 IP地址划分: big endian 是指内存的低地址存放最高有效字节( MSB ),比较符合我们的阅读习惯。而 little endian 则是内存的低地址存放最低有效字节( LSB )。 所有网络协议也都是采用 big endian 的方式来传输数据的。所以有时我们也会把 big endian 方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。 C=Blog2(1+S/N) 香农公式,C表示最大传输速率,B表示信道带宽,S/N表示信噪比,但这里的信噪比是没有单位的。SNR(以分贝为单位的信噪比)=10log10(S/N)。 集线器、中继器工作在物理层。 域名解析过程:1)客户机向本地域名服务器发出DNS请求报文; 以太网的物理拓扑是星型拓扑,逻辑拓扑是总线拓扑。 套接字由IP地址和端口号构成。 信道复用技术: PPP(点对点)协议是数据链路层的协议,是面向字符的。 TCP拥塞控制由4个核心算法组成:慢启动、拥塞避免、快速重传、快速恢复。 FTP是应用层协议,是基于TCP的,因为需要保证文件的完整性。 流量控制:通过滑动窗口实现,窗口的大小单位是字节。根据接收端的接收能力来决定发送端的发送速度。 接收端窗口rwnd,拥塞窗口cwnd,发送窗口swnd。 操作系统 进程同步的方式:信号量,管道,消息队列,共享内存,套接字。 线程同步的方式:信号量,临界区,事件,互斥量。 进程五状态模型: 进程与线程差别: CPU常用调度算法: 根据新的更高优先级进程能否抢占正在执行的进程,可将该调度算法分为: 非剥夺式优先级调度算法。当某一个进程正在处理机上运行时,即使有某个更为重要或紧迫的进程进入就绪队列,仍然让正在运行的进程继续运行,直到由于其自身的原因而主动让出处理机时(任务完成或等待事件),才把处理机分配给更为重要或紧迫的进程。 剥夺式优先级调度算法。当一个进程正在处理机上运行时,若有某个更为重要或紧迫的进程进入就绪队列,则立即暂停正在运行的进程,将处理机分配给更重要或紧迫的进程。 应设置多个就绪队列,并为各个队列赋予不同的优先级,第1级队列的优先级最高,第2级队列次之,其余队列的优先级逐次降低。 赋予各个队列中进程执行时间片的大小也各不相同,在优先级越高的队列中,每个进程的运行时间片就越小。例如,第2级队列的时间片要比第1级队列的时间片长一倍, ……第i+1级队列的时间片要比第i级队列的时间片长一倍。 当一个新进程进入内存后,首先将它放入第1级队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第2级队列的末尾,再同样地按FCFS 原则等待调度执行;如果它在第2级队列中运行一个时间片后仍未完成,再以同样的方法放入第3级队列……如此下去,当一个长进程从第1级队列依次降到第 n 级队列后,在第 n 级队列中便釆用时间片轮转的方式运行。 ②银行家算法:通过对进程需求、占有和系统拥有资源的实时统计,在分配资源时判断是否会出现死锁,只在不会出现死锁的情况下才分配资源。 信号量和互斥锁的区别:尽管两个概念有点类似,但是他们的侧重点不一样,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进行操作。不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。 URL 的管理和调度 数据解析。 限制反爬虫策略,不要太暴力访问服务器资源。 需要登录时有三种方法,cookie,session,和selenium模拟登录(需要装对应浏览器的驱动 )。 如何求一个数的二进制表示有多少个1? 判断一个long型的数是否是 2^n(n从0-31)? 怎么找出链表的倒数第k个节点? 合并有序链表: Linux C表示CPU使用的资源百分比,SZ表示占用的内存大小,TTY表示登入者的终端机位置,TIME表示使用掉的CPU时间,CMD表示所下达的指令。S表示进程的状态。 Mounted on 是挂载点。 查看ip地址和接口: ifconfig Let 可以进行整数型的数学运算 查看一个命令的概要和用法:whatis+命令名 输出命令 更多用法可参照:https://blog.csdn.net/jin970505/article/details/79056457
注意memcpy可能遇到内存重叠问题,如图中src
或者使用Memmove,原理其实也是从后往前复制。
Strcpy:char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest,注意它只能用于字符串,而memcpy能用于任意数据。而且strcpy不需要指定长度,它会在遇到src表示的字符串中的’\0’后停止。
Strncpy就是可以指定长度n的strcpy。
Memset: 用来对一段内存空间全部设置为某个数据,例如用在对定义的字符串进行初始化为‘ ’或‘\0’
char a[100];memset(a, ‘\0’, sizeof(a));
int a[100]; memset(a,0,sizeof(a));
char *my_strcpy(char dest, const charsrc)
{
assert((dest!=NULL)&&(src!=NULL));
char *res=dest;
while(*src!=’\0’) *dest++=*src++;
return res;
}
Float a=10.5f; float b=10.5f;
If(abs(a-b)<1e-6)…
#if如果给定条件为真,则编译(而不是执行)下面代码。
#ifdef如果宏已经定义(如果是带参宏定义,只判断名字也为真),则编译下面代码
#ifndef如果宏没有定义,则编译下面代码
#elif如果前面的#if给定条件不为真,当前条件为真,则编译下面代码
#endif结束一个#if……#else条件编译块
#error停止编译并显示错误信息
② 宏可以根据不同的平台来包含不同的头文件:很多时候, 我们的代码是需要跨系统平台编译和运行的. 比如: 一个小功能代码, 需要既可以在Win下面运行, 还要可以在Max, linux上面运行. 可是, 因为系统的不一样, 有些时候, 头文件的包含的名字是不一样的. 所以,这时候, 就是用到了宏. 因为我们使用编程工具分不同的系统平台, 编程工具自身的环境就会包含不同平台的系统宏, 假设OS_Win, OS_Mac, OS_Linux 分别代码三种系统不同的宏. 而且,Win版本的编程工具中已经定义了OS_Win, 类似的Mac下, 编程工具定义的是OS_Mac, Linux.例如:
#ifdef OS_Win
#include
#endif
#ifdef OS_Mac
#include
#endif
68. extern:①extern是计算机语言中的一个关键字,可置于变量或者函数前,以表示变量或者函数的定义在别的文件中。提示编译器遇到此变量或函数时,在其它模块中寻找其定义。例:
extern int i; //声明,不是定义
int i; //声明,也是定义
注意:声明可以多次,定义只能一次!
②为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo。如果把extern "C"加在头文件之前,相当于这个头文件中的声明都加了extern “C”.
在连用运算符的时候可以减少临时对象的构造和析构,提高代码执行效率。
②为什么重载cin cout只能用友元函数?
实际上流操作符左侧必须为cin或cout,即istream或ostream类,不是我们所能修改的类;或者说因为流操作符具有方向性。
这导致我们不能使用成员函数重载,只能使用类外的普通函数重载。
由于我们将类内部的私有成员进行输入和输出,所以重载函数必须有对内部成员访问的权限。
这导致我们不能使用普通的函数重载,只能使用友元函数重载。
当然,如果我们不需要输出私有成员,或者能用get访问私有成员,用普通函数重载即可。
70.友元不是成员函数,不属于任何类,但能够访问类中的私有成员。友元函数一般在类体内声明(放在公有或是私有部分都一样),但在类体外实现。
一般情况下:
将双目运算符重载为友元函数,这样就可以使用交换律,比较方便
单目运算符一般重载为成员函数,因为直接对类对象本身进行操作
71.(Java)对象就是指类的示例,而对象引用是对象的别名。例如:
Person p=new Person(“张三”); 这里的p不是对象,而是对象引用。
因为我们可以:
Person p;
p=new Person(“张三”);
p=new Person(“李四”);
真正创建对象的语句是右边的new Person。p可以指向任何Person类的对象。
一个对象也可以被多个引用所指,例如:
Person p1=new Person(“张三”);
Person p2=p1;
①寄存器:这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器的数量极其有限,所以寄存器由编译器根据需求进行分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。
② 栈:一般储存Java对象引用。堆栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序的时候,JAVA编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它必须生成相应的代码,以便上下移动堆栈指针。
③ 堆:一般储存Java对象。堆不同于堆栈的好处是:编译器不需要知道要从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多长时间。因此,在堆里分配存储有很大的灵活性。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。用堆进行存储分配比用堆栈进行存储存储需要更多的时间。
④ 静态区:静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里。
⑤ 常量区。
⑥ 非RAM储存区:若数据完全独立于一个程序之外,则程序不运行时仍可存在,并在程序的控制范围之外。
调用一次,返回两个值,子进程返回0,父进程返回子进程id。
Linux下才能使用!例程序:https://blog.csdn.net/yidu_fanchen/article/details/80494041
作用1:普通函数模版,使不同参数可以用同一个函数名:
这样,调用show(“123”)或是show(1)都可以,就不需要定义两个show函数了。
作用2:成员函数模版:
如:print(“1”); print(1);
但是,如果返回值也是模版参数的话,就无法通过实参推断获取所有模版参数的具体类型:
在类外定义时,要有两层模版参数:
可以避免类构造函数中不合时宜的类型转换。
https://blog.csdn.net/v_july_v/article/details/7382693
① 至于multiset/multimap,他们的特性及用法和set/map完全相同,唯一的差别就在于它们允许键值重复。
② 一般来说,STL容器分两种,
1.序列式容器(vector/list/deque/stack/queue/heap),
2.关联式容器。关联式容器又分为set(集合)和map(映射表)两大类,以及这两大类的衍生体multiset(多键集合)和multimap(多键映射表),这些容器均以RB-tree完成。此外,还有第3类关联式容器,如hashtable(散列表),以及以hashtable为底层机制完成的hash_set(散列集合)/hash_map(散列映射表)/hash_multiset(散列多键集合)/hash_multimap(散列多键映射表)。也就是说,set/map/multiset/multimap都内含一个RB-tree,而hash_set/hash_map/hash_multiset/hash_multimap都内含一个hashtable。
解决方法一:大文件数据通过hash映射取模n到n个小文件->在小文件中用hashmap统计频率,在小文件内排序->小文件之间归并排序
解决方法二:多层划分,用于n个数(很大)中找中位数。如果内存足够,用快排搜索即可。如果不够,就可以按二进制从高到低划分多个桶,直到内存能装下一个桶为止。统计一个桶中数的数量,以此推断中位数会在哪个桶中。
解决方法三:BloomFilter:将元素通过k个独立的哈希函数映射,映射到的地方设为1。便于查找,统计。Bitmap: 用于排序,用二进制串来表示数。
解决方法四:对字符串的查找,字典树Trie。倒排索引。
https://blog.csdn.net/u013521220/article/details/78794980
我们已经了解了MapReduce的大概流程:
1)maptask从目标文件中读取数据
2)mapper的map方法处理每一条数据,输出到文件中
3)reducer读取map的结果文件,按key进行分组,把每一组交给reduce方法进行处理,最后输出到指定路径。
另外还要保证③会话保持,如何保证一个用户的两次http请求转发到同一个服务器,这就要求负载均衡设备配置会话保持。绝大多数的负载均衡产品都支持两类基本的会话保持方式:源/目的地址会话保持和cookie会话保持,另外像hash,URL Persist等也是比较常用的方式,但不是所有设备都支持。基于不同的应用要配置不同的会话保持,否则会引起负载的不均衡甚至访问异常。
负载均衡方式:
① 反向代理负载均衡:反向代理服务器是一个位于实际服务器之前的服务器,所有向我们网站发来的请求都首先要经过反向代理服务器,服务器根据用户的请求要么直接将结果返回给用户,要么将请求交给后端服务器处理,再返回给用户。
② 四层负载均衡
四层负载均衡工作在OSI模型的传输层,由于在传输层,只有TCP/UDP协议,这两种协议中除了包含源IP、目标IP以外,还包含源端口号及目的端口号。四层负载均衡服务器在接受到客户端请求后,以后通过修改数据包的地址信息(IP+端口号)将流量转发到应用服务器。
③七层负载均衡工作在OSI模型的应用层,应用层协议较多,常用http、radius、DNS等。七层负载就可以基于这些协议来负载。这些应用层协议中会包含很多有意义的内容。比如同一个Web服务器的负载均衡,除了根据IP加端口进行负载外,还可根据七层的URL、浏览器类别、语言来决定是否要进行负载均衡。(1)HTTP重定向实现负载均衡:当用户向服务器发起请求时,请求首先被集群调度者截获;调度者根据某种分配策略,选择一台服务器,并将选中的服务器的IP地址封装在HTTP响应消息头部的Location字段中,并将响应消息的状态码设为302,最后将这个响应消息返回给浏览器。
(2)DNS负载均衡: 数据包采用IP地址在网络中传播,而为了方便用户记忆,我们使用域名来访问网站。那么,我们通过域名访问网站之前,首先需要将域名解析成IP地址,这个工作是由DNS完成的。也就是域名服务器。我们提交的请求不会直接发送给想要访问的网站,而是首先发给域名服务器,它会帮我们把域名解析成IP地址并返回给我们。我们收到IP之后才会向该IP发起请求。
那么,DNS服务器有一个天然的优势,如果一个域名指向了多个IP地址,那么每次进行域名解析时,DNS只要选一个IP返回给用户,就能够实现服务器集群的负载均衡。
首先需要将我们的域名指向多个后端服务器(将一个域名解析到多个IP上),再设置一下调度策略,那么我们的准备工作就完成了,接下来的负载均衡就完全由DNS服务器来实现。当用户向我们的域名发起请求时,DNS服务器会自动地根据我们事先设定好的调度策略选一个合适的IP返回给用户,用户再向该IP发起请求。
Hadoop生态圈:
https://www.cnblogs.com/hanzhi/articles/8969109.html
用户可以在不了解分布式底层细节的情况下,开发分布式程序。充分利用集群的威力进行高速运算和存储。
具有可靠、高效、可伸缩的特点。
Hadoop的核心是YARN,HDFS和Mapreduce。
其中Map对数据集上的独立元素进行指定的操作,生成键-值对形式中间结果。Reduce则对中间结果中相同“键”的所有“值”进行规约,以得到最终结果。
MapReduce非常适合在大量计算机组成的分布式并行环境里进行数据处理。
HQL用于运行存储在Hadoop上的查询语句,Hive让不熟悉MapReduce开发人员也能编写数据查询语句,然后这些语句被翻译为Hadoop上面的MapReduce任务。
数据库:
一致性:所有事务在对同一数据的读取都是一致的。
隔离性:一个事务在完成之前对其他事务是不可见的。
持久性:事务对数据的修改是持续有效的。
https://blog.csdn.net/jacke121/article/details/78268602。
B+树相对于B树的区别:
B+树每个节点存储的关键字数更多,树的层级更少所以查询数据更快。B+的非叶子节点只进行数据索引,不会存实际的关键字记录的指针,所有地址关键字指针都存放在叶子节点,所以每次查找的次数都相同,查询速度更稳定;
定义:也称逻辑模式,是数据库中全体数据的逻辑结构和特征的描述,是所有用户的公共数据视图。
二、外模式(External Schema)
定义:也称子模式(Subschema)或用户模式,是数据库用户(包括应用程序员和最终用户)能够看见和使用的局部数据的逻辑结构和特征的描述,是数据库用户的数据视图,是与某一应用有关的数据的逻辑表示。
三、内模式(Internal Schema)
定义:也称存储模式(Storage Schema),它是数据物理结构和存储方式的描述,是数据在数据库内部的表示方式(例如,记录的存储方式是顺序存储、按照B树结构存储还是按hash方法存储;索引按照什么方式组织;数据是否压缩存储,是否加密;数据的存储记录结构有何规定)
从关系模式中挑选若干属性组成新的关系称为投影,这是从列的角度进行的运算。
在关系代数中,除法运算可理解为笛卡尔积的逆运算。
第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能同时有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。
第二范式(2NF)满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式要求表中每个属性都完全依赖于主键,即主键能够唯一区分这个属性。如果主键中只有一个属性,那么这个表自然满足第二范式。如果主键中有多个属性,而且有主键之外的属性只依赖于主键中的部分属性,那么这个表就不符合第二范式。
第三范式(3NF)满足第三范式(3NF)必须先满足第二范式(2NF)。任何非关键字段不能传递依赖任一侯选关键字。一个表中的如果存在一个非主键,它除了依赖于主键,同时也能决定其他属性。这样就是一种传递依赖。一般数据库满足到3NF就足够了。
BC范式:任何字段都不能传递依赖任一侯选关键字。
与第三范式相比,一个是“任何非关键字段不能”,一个是“任何字段不能”,显然更严格了。
(1).FROM 子句, 组装来自不同数据源的数据 (2).WHERE 子句, 基于指定的条件对记录进行筛选 (3).GROUP BY 子句, 将数据划分为多个分组 (4).使用聚合函数(如sum, average等)进行计算 (5).使用 HAVING 子句筛选分组 (6).计算Select所有的表达式 (7).使用 ORDER BY 对结果集进行排序。
②参照完整性:参照的完整性要求关系中不允许引用不存在的实体。例:如学生实体和专业实体可以用下列关系模式来表示,其中学号是学生的主键,专业号是专业的主键:
学生(学号,姓名,性别,专业号,年龄)
专业(专业号,专业名)
这两个关系之间存在着属性的引用(含有相同的属性“专业号”),学生关系引用了专业关系的主键“专业号”,专业号则是学生关系的外键。而且按照参照完整性规则,学生关系(并非专业关系)中的每个元组的“专业号”属性只能取两种值:
(1)空值,表示尚未给学生分配专业。
(2)非空值,这时该值必须是专业关系中某个元组的“专业号”值,表示该学生不可能分配到一个不存在的专业中去。就是说学生关系中的某个属性的取值需要参照专业关系的属性取值。
③域完整性:针对某一具体关系数据库的约束条件,它保证表中某些列不能输入无效的值;比如性别只能取男或女。
丢失修改:一个事务读取一个数据时,另外一个事务也访问该同一数据。那么,在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
脏读:当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,根据脏数据的操作可能是不正确的。概括:读到了修改之前的数据。
不可重复读:一个事务内多次读同一数据。在这个事务还没有结束时,另外一个事务也修改同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改,这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为是不可重复读。
视图(view)是在基本表之上建立的表,它的结构(即所定义的列)和内容(即所有数据行)都来自基本表,它依据基本表存在而存在。一个视图可以对应一个基本表,也可以对应多个基本表,所以视图的数据定义功能优于表。
视图的数据控制功能跟表相当,都有grand revoke.
传统的关系型数据库,如 Oracle、DB2、MySQL、SQL SERVER 等采用行式存储法(Row-based),在基于行式存储的数据库中, 数据是按照行数据为基础逻辑存储单元进行存储的。行式存储一定会把同一行数据存到同一个块中,在select查询的时候,是对所有字段的查询,不可以单独查询某一列。优点是全字段查询比较快,当查询一张表里的几个字段的时候,底层依旧是读取所有的字段,这样查询效率降低,并且会造成不必要的资源浪费,而且,生产中很少会出现需要全字段查询的场景。
SEQUENCEFILE:生产中绝对不会用,k-v格式,比源文本格式占用磁盘更多
TEXTFILE:生产中用的多,行式存储
RCFILE:生产中用的少,行列混合存储,OCR是他得升级版
ORC:生产中最常用,列式存储
PARQUET:生产中最常用,列式存储
计网:
SYN表示请求建立连接,所以在三次握手中前两次要SYN=1,表示这两次用于建立连接。
ACK=1表示确认接受,可以理解为收到。
FIN:表示请求关闭连接,在四次分手时,我们发现FIN发了两遍。这是因为TCP的连接是双向的,所以一次FIN只能关闭一个方向。客户端发送FIN是表示,客户端已经没有数据需要发送给服务端了。
seq 表示数据包的序列号。
ack 表示下一个希望收到的数据包的序列号。注意和ACK区分。
每个数据包都会有seq和ack。
为什么挥手需要四次,而不是像建立连接一样只需要三次呢?
在服务端发送ACK同意关闭客户端到服务端的连接后,服务端到客户端的连接还没有关闭,要等服务端把要发的数据向客户端发完,再发送第二个FIN。
两次握手可能出现的问题:① 如果服务器发送到客户端的连接请求在网络中跑的慢,超时了,这时客户端会重发请求,但是这个跑的慢的请求最后还是跑到了服务端,然后服务端就接收了两个连接请求,然后全部回应就会创建两个连接,浪费资源!
② 无法确认服务端的序列号,TCP需要根据序列号来保证传输的可靠性(比如保证顺序那些)。
假定A向B发送一个连接请求,由于一些原因,导致A发出的连接请求在一个网络节点逗留了比较多的时间。此时A会将此连接请求作为无效处理 又重新向B发起了一次新的连接请求,B正常收到此连接请求后建立了连接,数据传输完成后释放了连接。如果此时A发出的第一次请求又到达了B,B会以为A又发起了一次连接请求,如果是两次握手:此时连接就建立了,B会一直等待A发送数据,从而白白浪费B的资源。 如果是三次握手:由于A没有发起连接请求,也就不会理会B的连接响应,B没有收到A的确认连接,就会关闭掉本次连接。
3. TCP和UDP的区别:
① 基于连接与无连接;
② 对系统资源的要求(TCP较多,UDP少);
③ UDP程序结构较简单,用于传输大量数据。速度快。
④ 流模式与数据报模式 ;
⑤ TCP保证数据正确性,UDP可能丢包,TCP保证数据顺序,UDP不保证。(UDP没有流量控制和拥塞控制)
⑥ TCP是全双工的,UDP是半双工的。
、 、
但一般的操作系统是小端的。
如何判断大小端?
例:
int i=1;
char b=(char)&i;
if(b==1) //小端 else//大端
i的十六进制表示是00 00 00 01。那么b只有一个字节,只会取到i的低地址字节。如果低地址存放i的低位,也就是01,那么就是小端。如果地址存放i的高位,也就是00,那么就是大端。
交换机、网桥工作在数据链路层。
路由器工作在网络层。
网关工作在网络层以上。
2)本地域名服务器收到请求,查询本地缓存;若没有该记录,以DNS客户的身份向根域名服务器发出解析请求,最终返回ip地址。
时分多路复用TDM
频分多路复用FDM
波分多路复用WDM
码分多路复用CDMA与正交频分复用OFDM
慢启动:当主机开始发送数据时,如果立即将大量数据字节注入到网络,那么就有可能引起网络拥塞,因为现在并不清楚网络的负荷情况。因此,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。通常在刚刚开始发送报文段时,先把拥塞窗口cwnd 设置为一个最大报文段MSS的数值。而在每收到一个对新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。
拥塞避免:让拥塞窗口cwnd缓慢地增大,即每经过一个往返时间RTT就把发送方的拥塞窗口加1,而不是加倍。这样拥塞窗口按线性规律缓慢增长,比慢开始算法的拥塞窗口增长速率缓慢得多。在执行慢开始算法时,拥塞窗口 cwnd 的初始值为1。以后发送方每收到一个对新报文段的确认ACK,就把拥塞窗口值加1,然后开始下一轮的传输。因此拥塞窗口cwnd随着传输轮次按指数规律增长。当拥塞窗口cwnd增长到慢开始门限值ssthresh时(即当cwnd=16时),就改为执行拥塞控制算法,拥塞窗口按线性规律增长。
快速重传:要求接收方在收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)。快重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段。
快速恢复:第一次丢包后,把拥塞窗口减半,恢复线性增加。
超时反应:把窗口降为1MSS,进入慢启动,当CongWin指数增长到超时事件前窗口值的一半时,恢复线性。
T(trival)FTP是基于UDP的。
为什么要设置窗口?
我们可以把窗口理解为缓冲区。如果没有这些“窗口”,那么TCP每发送一段数据后都必须等到接收端确认后才能发送下一段数据,这样做的话TCP传输的效率实在是太低了。
解决的办法就是在发送端等待确认的时候继续发送数据,假设发送到第X个数据段是收到接收端的确认信息,如果X在可接受的范围内那么这样做也是可接受的。这就是窗口(缓冲区)引入的缘由。
接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;窗口大小字段越大, 说明网络的吞吐量越高;
接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后, 就会减慢自己的发送速度;
如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端。
发送窗口的上限值 = Min [rwnd, cwnd]
发送窗口的大小实际上就是发送方收到ACK前能发送的最大数据量。
当 rwnd < cwnd 时,是接收端的接收能力限制发送窗口的最大值。
当 cwnd < rwnd 时,则是网络的拥塞限制发送窗口的最大值。
滑动窗口包括四个范围,定义base为最早未被确认的packet序号,next为下一个要发送的packet序号。
[0,base-1]:已经发送并确认的packet。
[base,next-1]:已经发送但未确认的packet。 窗口长度N
[next,base+N-1]:序号可用的将要发送的分组
[base+N,…]:无法使用的序号
窗口已满的状态就是有N个已经发送但是还未确认的分组。
滑动窗口协议是累计确认的,意思是确认序号为N的分组带便确认N之前的所有分组。
网关实质上是一个网络通向其他网络的IP地址。比如有网络A和网络B,网络A的IP地址范围为“192.168.1.1~192. 168.1.254”,子网掩码为255.255.255.0;网络B的IP地址范围为“192.168.2.1~192.168.2.254”,子网掩码为255.255.255.0。在没有路由器的情况下,两个网络之间是不能进行TCP/IP通信的,即使是两个网络连接在同一台交换机(或集线器)上,TCP/IP协议也会根据子网掩码(255.255.255.0)判定两个网络中的主机处在不同的网络里。而要实现这两个网络之间的通信,则必须通过网关。如果网络A中的主机发现数据包的目的主机不在本地网络中,就把数据包转发给它自己的网关,再由网关转发给网络B的网关,网络B的网关再转发给网络B的某个主机。
所以说,只有设置好网关的IP地址,TCP/IP协议才能实现不同网络之间的相互通信。
OSPF(open shortest path first,开放最短路径优先)也是一种内部网关协议,用于在同一个自治域中的路由器之间作路由选择。
BGP(Border Gateway protocol)是自治域之间的路由选择协议。
ICMP(Internet Control Message Protocol)控制报文协议,不属于路由选择协议。
(1)节点处理时延:用于检查包的首部,决定往何处转发,接着导向队列。
(2)排队时延,路由器的输出口有缓存队列。
(3)传输时延(发送时延),将整个包导向链路所需要的时间。=数据帧长度/发送速率
(4)传播时延,:电磁波在信道中传播消耗的时间,=路由器之间的距离/传播速率。(电磁波在同一介质中传播速率一般恒定。)
一种是根据网络所使用的传输技术:计算机网络可以分为广播式网络和点对点式网络
另一种是根据覆盖范围与规模:计算机网络可以分为局域网、城域网和广域网。
(1)从发送端解决:发送时使用usleep(1)延迟1微妙发送,即控制发送频率不要过快。
此方法适用于可控的发送端,以及可以接受微妙级的延迟。
(2)从接收端解决:使用setsockopt修改接收端的缓冲区大小。
B/S结构是随着互联网的发展,web出现后兴起的一种网络结构模式。这种模式统一了客户端,让核心的业务处理在服务端完成。你只需要在自己电脑或手机上安装一个浏览器,就可以通过web Server与数据库进行数据交互。在手机或电脑上用浏览器上百度搜索、看新闻等就是在使用“B/S”结构进行数据交互。
优点:① 只需要安装浏览器,面向范围广。 ② 维护简单,更新页面即可实现面向所有用户的更新。 ③ 共享性强,可再利用。
缺点:① 因为面向范围广,面向不可知的用户群体,安全性较低。 ② 较难满足个性化需求。③服务器压力比较大。
C/S结构:客户机、服务器模式:服务器通常采用高性能的PC、工作站或小型机,并采用大型数据库系统,如Oracle、Sql Server等。客户端需要安装专用的客户端软件。举个栗子:以电脑上的qq为例,qq APP就是Client客户端,存放所有用户信息的地方就是qq的Server服务器。
优点:① 需要特定的客户端,所以信息安全属于一个可控的范围。② 客户端的服务器直接向量,数据传输比较快。③ 更能满足个性化需求。④ 结构比较稳定,有较强的事务处理能力,可以实现较复杂的业务逻辑。
缺点:① 需要特定的客户端,对pc有一定的要求,面向群体固定。② 维护成本高。
临界区:在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。
事件:事件可以处于激发状态(signaled or true)或未激发状态(unsignal or false)。
互斥量(用于多线程互斥):采用互斥对象机制。 只有拥有互斥(mutex)对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。
信号量(用于多线程同步):它的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。注意,信号量的值仅能由PV操作来改变。信号量的当前值如果是正值N,该值表示有N个可用资源。
如果为0,则表示所有资源全部被分配,同时没有进程处于等待状态
如果为负数N,则表示全部资源分配完毕,且还有N个进程处于等待该资源的状态。
执行一次P操作表示一个单位的资源被请求分配出去(N–)。如果此时没有可用的资源,请求者必须等待直至有资源被释放。
执行一次V操作表示一个单位的资源被释放(N++),如果有进程正在等待该资源,就要唤醒正在等待状态的资源并把资源分配给它运行。
① 从调度角度:进程是资源分配的基本单位,线程是系统(cpu)调度的基本单位;
③ 从资源拥有角度看:进程拥有系统资源,而线程没有,但它能够共享所属进程的资源;
④ 从系统开销上看:线程不独立拥有系统资源,所以线程切换的系统开销远小于进程,除非线程切换会引起进程切换。
⑤ 从并发角度看:线程和进程一样,都能够并发运行,不仅同一个进程的线程能够并发运行,不同进程的线程也相同能够并发运行。
① FCFS,先到先服务。缺点:但若一个长作业先到达系统,就会使后面许多短作业等待很长时间。
② SJS,短作业优先。缺点:对长作业不利,长作业会饥饿。
③ 优先级调度算法。在作业调度中,优先级调度算法每次从后备作业队列中选择优先级最髙的一个或几个作业,将它们调入内存,分配必要的资源,创建进程并放入就绪队列。在进程调度中,优先级调度算法每次从就绪队列中选择优先级最高的进程,将处理机分配给它,使之投入运行。
④ 时间片轮转调度算法:主要适用于分时系统。在这种算法中,系统将所有就绪进程按到达时间的先后次序排成一个队列,进程调度程序总是选择就绪队列中第一个进程执行,即先来先服务的原则,但仅能运行一个时间片,如100ms。在使用完一个时间片后,即使进程并未完成其运行,它也必须释放出(被剥夺)处理机给下一个就绪的进程,而被剥夺的进程返回到就绪队列的末尾重新排队,等候再次运行。
⑤ 多级反馈队列调度算法:多级反馈队列调度算法的实现思想如下:
6. 死锁:多个进程执行过程中因争夺资源而形成的僵局;
产生死锁必要条件:
1)互斥条件:一个资源每次只能被一个进程使用。
2)请求与保持:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3)不剥夺条件:一个进程已经获得的资源,在未使用完之前,不能被强行剥夺。
4)循环等待条件:若干进程形成一种头尾相接的循环等待资源关系。
破坏后三个条件中的任一个即可解开死锁。
(1) 破坏“请求保持”:所有进程在开始运行前,必须一次性申请在运行过程中所需的所有资源。但这样会造成资源浪费和饥饿现象。所以一种改进的方法是,在运行前允许进程获取初期需要的资源,在运行中先释放已经使用完毕的资源,再去请求新的资源。(这种方法也可以叫作撤销进程)
(2) 破坏“不可抢占”:当一个已经持有了一些资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请。这就意味着进程已占有的资源会被短暂地释放或者说是被抢占了。
该种方法实现起来比较复杂,且代价也比较大。释放已经保持的资源很有可能会导致进程之前的工作实效等,反复的申请和释放资源会导致进程的执行被无限的推迟,这不仅会延长进程的周转周期,还会影响系统的吞吐量。
(3) 破坏“循环等待”:
①通过定义资源类型的线性顺序来预防,可将每个资源编号,当一个进程占有编号为i的资源时,那么它下一次申请资源只能申请编号大于i的资源。如图:
7. 指令寄存器是存放指令的,而不是存放指令地址的。
8. 计算机内的传输是并行传输,而通信线路上的传输是串行传输。
9. 组的作用域:
通用 :来自全林用于全林 ;全局 :来自本域用于全林 ; 本地 :来自全林用于本域
10. 两个进程各自拥有自己的程序段和数据段,即有各自的全局变量,所以不可能通过全局变量交换数据。
11. 访管指令会产生中断事件,将CPU从用户态转换到核心态,但访管指令本身是在用户态下执行的。特权指令是在核心态下执行的指令。所以访管指令不属于特权指令。
12. 设系统中有 m 个同类资源数, n 为系统中的并发进程数,当 n 个进程共享 m 个互斥资源时,每个进程的最大需求数是 w。当m>n*(w-1)时不会发生死锁。
13. 操作系统中有一种特殊系统调用,它们不能被系统中断,在操作系统中称其为原语。
14. 进行分页式存储管理,需要把相对地址变为(页号,页内位移),用页号去查页表,得到绝对地址后才能真正的访问该地址。所以访存2次
① 查页表,得到绝对地址
② 访问绝对地址,然后读数据。
15. 在分页系统中页面大小由硬件决定。页表的作用是实现从页号到物理块号的地址映射。
16. 重定位或地址映射就是把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程。它是实现多道程序在内存中同时运行的基础。重定位有两种,分别是动态重定位与静态重定位。
1、静态重定位:即在程序装入内存的过程中完成,是指在程序开始运行前,程序中的各个地址有关的项均已完成重定位,地址变换通常是在装入时一次完成的,以后不再改变,故成为静态重定位。
2、动态重定位:它不是在程序装入内存时完成的,而是CPU每次访问内存时 由动态地址变换机构(硬件)自动进行把相对地址转换为绝对地址。动态重定位需要软件和硬件相互配合完成。
重定位时机:1、程序编译链接时。2、程序装入内存时。3、程序执行时。
17. 内存分配管理:分为连续内存分配和非连续内存分配。
连续内存分配:①固定分区分配。将内存空间划分为若干个固定大小的区域,每个分区只能装入一道作业。无外部碎片但是有内部碎片(分区内部有空间的浪费)
② 动态分区分配,可能存在外部碎片,无内部碎片。外部碎片指的是还没有被分配出去(不属于任何进程),但由于太小了无法分配给申请内存空间的新进程的内存空闲区域。
非连续内存分配管理允许一个程序分散的装入到不相邻的内存分区中,这需要额外的空间去存储它们的存储区索引,使得非连续分配方式的存储密度低于连续存储方式(因为需要储存数据本身和索引),但也有可能产生外部碎片。
分页:把主存空间划分为大小相等且固定的块,块相对较小(比分区小得多),作为主存的基本单位,每个进程也以块为基本单位划分,进程在执行时,以块为单位逐个申请主存中的块空间。
(逻辑地址)地址结构:包含两部分,第一部分为页号P,后一部分为页内偏移量W,地址长度为32位(表示2^32个字节,因为以字节为编址单位),其中0~11位为页内地址,即每页大小为4 KB,12~31位为页号,地址空间最多允许有2^20页。
计算:逻辑地址=页号页大小(一般是4KB)+页内偏移量.
页表:为了便于在内存中找到进程的每个页面对应的物理块,系统为每个进程建立了一张页表,记录页面在内存中对应的物理块号,页表一般存在内存中。页表项的第一部分存的是页号,第二部分存的是物理内存中的块号,页表项的第二部分与地址的第二部分共同组成物理地址。页表项长度表示一个页面的地址占多大的储存空间。
物理地址E=物理块号b页大小(也就是块大小)+页内偏移量
计算:页大小页数=内存大小
页表项长度页数=页表长度
18. 页面置换算法:
① 先到先出(FIFO)。实现原理:淘汰最先进入内存的页面,即选择在页面待的时间最长的页面淘汰。
② 最近最久未使用(LRU)实现原理:选择最近且最久未被使用的页面进行淘汰(向前看)。
③ 最佳置换算法(OPT)实现原理:每次选择未来长时间不被访问的或者以后永不使用的页面进行淘汰(向后看)。
19. I++并非原子操作,它分为三步,把值从内存传到寄存器,在寄存器中自增,再返回内存。
20. 进程执行的相对速度不由进程自身决定,而与调度策略有关。
21. 互斥:互斥是进程(线程)之间的间接制约关系。当一个进程(线程)进入临界区使用临界资源时,另一个进程(线程)必须等待。只有当使用临界资源的进程退出临界区后,这个进程才会解除阻塞状态。简单的来说,就是某一资源同一时间只能由一个进程(线程)访问。
类似的场景:
比如进程B需要访问打印机,但此时进程A占有了打印机,进程B会被阻塞,直到进程A释放了打印机资源,进程B才可以继续执行
比如某一个共享代码片段,同一个时间,只能由一个线程执行,当有一个线程执行时,其它线程将会等待。
22. 同步是进程(线程)之间直接的制约关系,是为完成某种任务而建立的两个或多个线程,这个线程需要在某些位置上协调他们的工作次序而等待、传递信息所产生的制约关系,进程(线程)间的直接制约关系来源于他们之间的合作关系(依赖关系)。所以同步是一种更为复杂的互斥。PV操作可以实现线程(进程)同步和互斥的操作。
23. 在单处理机系统中,同一时刻只能有一个进程占用处理机,因此进程之间不能并行执行。
24. 并发和并行:
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 (不一定是同时的)
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力。
25. 上下文切换,有时也称做进程切换或任务切换,是指CPU 从一个进程或线程切换到另一个进程或线程。
26. 多道程序设计:在一台处理机上并发运行多个程序。
27. 分页虚拟储存管理能扩充主存容量。
28. 计算机中的虚拟内存是指用硬盘中的一部分储存空间当作内存。
29. 线程安全问题都是由全局变量及静态变量引起的。局部变量是线程安全的,因为每个线程执行时会把局部变量放在各自栈帧的工作内存中,这个工作内存线程之间是不共享的。
30. I/O通道设置的目的是使原来由CPU处理的I/O任务由通道来承担,从而把CPU从繁杂的I/O任务中解脱出来 。CPU只需要向通道发送一条I/O指令,便可以从内存中取出本次要执行的通道程序,然后执行。
31. 在微指令序列地址的形成中,若微指令的顺序控制字段直接给出了后续微指令的地址,这种方式就称为断定方式。其基本思想就是根据微指令顺序控制字段由设计者指定或者由设计者指定的判断字段控制产生后继微指令地址。
32. 同步和异步针对应用程序来,关注的是程序中间的协作关系;阻塞与非阻塞更关注的是请求者进程的执行状态。
同步:调用方发出一个请求后,等待结果才结束调用。
异步:调用方发出一个请求后,不等待被调用方返回结果,就结束调用。然后可以去发出其他请求,然后等待通知再回来接收结果。
阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。
非阻塞:进程给CPU传达任务后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。
只有同步才分阻塞非阻塞,异步就是异步,必定是非阻塞的。
多路IO复用都属于同步IO。
33. 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:
(1)同步阻塞IO(Blocking IO):即传统的IO模型。
(2)同步非阻塞IO(Non-blocking IO)。
(3)IO多路复用(IO Multiplexing) -EPOLL
(4)异步IO(Asynchronous IO)。
在这里,模拟一个最典型的使用semaphore的场景:a源自一个线程,b源自另一个线程,计算c = a + b也是一个线程。(即一共三个线程)显然,第三个线程必须等第一、二个线程执行完毕它才能执行。在这个时候,我们就需要调度线程了:让第一、二个线程执行完毕后,再执行第三个线程。此时,就需要用semaphore了。
简而言之,锁是服务于共享资源的;而semaphore是服务于多个线程间的执行的逻辑顺序的(就是用于调度线程的)。
爬虫注意事项
算法:
因为x&(x-1)的操作每次都会把二进制x中最右边的1变为0。
其实和第一条所用的方法一样,将x=x&(x-1),如果这个数是2^n,二进制表示只有一个1,相与之后就会变为0.
为了能够只遍历一次就能找到倒数第k个节点,可以定义两个指针:
(1)第一个指针从链表的头指针开始遍历向前走k-1,第二个指针保持不动
(2)从第k步开始,第二个指针也开始从链表的头指针开始遍历;
(3)由于两个指针的距离保持在k-1,当第一个(走在前面的)指针到达链表的尾结点时,第二个指针(走在后面的)指针正好是倒数第k个结点。
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if (pHead1 == NULL)
return pHead2;
if (pHead2 == NULL)
return pHead1;
if (pHead1 -> val <= pHead2 -> val) {
pHead1 -> next = Merge(pHead1 -> next, pHead2);
return pHead1;
} else {
pHead2 -> next = Merge(pHead2 -> next, pHead1);
return pHead2;
}
}
};
常用命令: pwd(print work directory) 打印目前所处的文件夹位置
ls(list)+相对路径/绝对路径 列出路径下的所有文件,例如:
ls /home/ryan
参数:
-a 全部的文件,连同隐藏文件( 开头为 . 的文件) 一起列出来
-d 仅列出目录本身,而不是列出目录内的文件数据
-l 长数据串行出,包含文件的属性与权限等等数据
cd(change directory)+相对路径/绝对路径 改变所处文件夹
一个巨方便的操作:使用tab键。当你在输入路径的第一个字母时,使用tab键会自动帮你补全路径信息,如果在那个字母下有多种选择,那么按两次tab键,终端会显示所有可能结果。
…(两点):这代表你所处的文件夹的上一级文件夹。你可以多次使用这个快捷表示,一直让你的位置往根文件夹走。
(潮水符号):这是主目录的快捷表示。如果你的主目录是/home/ryan,那么你就可以通过/home/ryan或来操作这个文件夹。
清屏: clear
退出当前命令: ctrl+c 彻底退出
建立新文件:touch,vi。后面加filename.
建立新文件夹:mkdir, 但在同一目录下,文件名不能和文件夹名重复。因为linux系统不是通过扩展名来识别文件类型的。
查看当前用户信息:id
建立链接: ln(link) [参数][源文件或目录][目标文件或目录] 它的功能是为某一个文件在另外一个位置建立一个不同的链接,这个命令最常用的参数是-s,具体用法是:ln -s 源文件 目标文件。当我们需要在不同的目录,用到相同的文件时,我们不需要在每一个需要的目录下都放一个必须相同的文件,我们只要在某个固定的目录,放上该文件,然后在其它的 目录下用ln命令链接(link)它就可以,不必重复的占用磁盘空间。
.软链接,以路径的形式存在。类似于Windows操作系统中的快捷方式。硬链接,以文件副本的形式存在。但不占用实际空间。
复制文件: cp 源文件 目标地点
-r参数表示复制文件夹
-i 若目标地点存在同名文件,询问是否覆盖
文件权限修改: chmod(change mode)
查看&编辑文件内容:
vi 文件名 #编辑方式查看,可修改
cat 文件名 #显示全部文件内容
more 文件名 #分页显示文件内容
less 文件名 #与 more 相似,更好的是可以往前翻页
tail 文件名 #仅查看尾部,还可以指定行
head 文件名 #仅查看头部,还可以指定行数
在命令行下输出内容:echo
移动文件:mv 源文件 目标地点
文件改名:也是mv 原文件名 目标文件名
删除文件:rm
-rf 删除当前命令下的所有文件,所删除的文件,一般无法恢复
-f(force) 忽略不存在的文件,不提示任何信息。
-i(interactive) 交互式删除
-r 递归删除 删除文件夹
-v 删除时显示详细信息
删除文件夹 rmdir
统计文件信息:wc(word count)+文件名,输出:行数 单词数 字节数
显示当前进程:ps(process status)
-l显示PID详细信息 -A显示所有进程 -f 显示更详细的信息
显示在后台运行的进程: jobs -l
把后台任务调到前台执行: fg(ForeGround)
把暂停的后台任务在后台执行起来 bg(BackGround)
暂停前台进程: ctrl+Z
终止前台进程:Ctrl+C
终止进程:kill -9彻底杀死进程
Kill -l 查看系统支持的所有信号
查找文件:find 如果使用该命令时,不设置任何参数,则find命令将在当前目录下查找子目录与文件。并且将查找到的子目录和文件全部进行显示。
一般格式是:find path -option
Who:显示关于当前在本地系统上的所有用户的信息。
Whoami: 显示自己的登录名。
History 查看命令输入历史
显示网络信息:netstat
显示文件系统的磁盘使用情况:df(disk free) -h(human readable)
显示目录或文件的大小 du(disk usage), df 命令获得真正的文件系统数据,而 du 命令只查看文件系统的部分情况。
查看环境变量:env+环境变量名,如果不加环境变量名,则默认查看所有。
查找指令所在的文件,这些文件只能是二进制文件,说明文件或是源代码文件。
Whereis [-参数] [指令名] -b 只查找二进制文件 -m只查找说明文件 -s只查找源代码文件
查找指令所在的文件,这些文件只能是可执行文件。
Which [-参数] [指令名]
compgen -c将列出您可以运行的所有命令。
compgen -a将列出您可以运行的所有别名。
compgen -b将列出您可以运行的所有内置函数。
compgen -k将列出您可以运行的所有关键字。
compgen -A函数将列出您可以运行的所有函数。
显示当前目录栈:dirs
移除正在运行的所有进程:disown -r
打印出使用过的命令以及使用次数:hash
Linux是向左的
在Linux中,绝对路径永远都是相对于根文件夹的。它们的标志就是第一个字符永远都是“/”。如 /home/ryan
相对路径永远都是相对于我们所处的文件夹位置。它们的第一个字符没有“/”。
在命令行可以一次执行多个命令,有以下几种:
①每个命令之间用;隔开
说明:各命令的执行给果,不会影响其它命令的执行。换句话说,各个命令都会执行, 但不保证每个命令都执行成功。
③ 每个命令之间用&&隔开()
说明:若前面的命令执行成功,才会去执行后面的命令。这样可以保证所有的命令执行完毕后,执行过程都是成功的。可以使这个命令在后台运行。
③每个命令之间用||隔开
说明:||是或的意思,只有前面的命令执行失败后才去执行下一条命令,直到执行成功 一条命令为止。
/dev/null文件可以被看作是一个“黑洞”文件。它等价于一个只写的的文件。所有写入它的内容都会永远丢失(因为不可读)。而尝试从它那里读取内容也什么都读不到。
当我们写一个脚本程序,负责启动一个服务,但是那个服务经常输出一些没用的,也不适合将显示的信息输出到一个文件中,显示的信息好似没有用途,于是可以借助Linux的黑洞之重定向!
符号 作用
? 匹配任何一个字符(不在括号内时)?代表任意1个字符
[abcd] 匹配abcd中任何一个字符
[a-z] 表示范围a到z,表示范围的意思 []匹配中括号中任意一个字符
{…} 表示生成序列. 以逗号分隔,且不能有空格
[!abcd] 或[^abcd]表示非,表示不匹配括号里面的任何一个字符
-i 为忽略大小写 -c输出文件中含有该字符串的行数
7.Linux进程状态:R(Running):正在运行或可被运行。
S(Sleep):正在睡眠,但可被某些信号唤醒。
T:停止或被追踪。
D: 不可中断。
Z:僵尸进程:该程序应该已经终止,但是其父程序却无法正常的终止他
X:已被杀死的进程。
\u 显示当前用户账号
\h 显示当前主机名
\W 只显示当前路径的最后一个目录
\w 显示当前绝对路径(当前用户目录会以~替代)
$PWD:显示当前全路径
\d: 表示日期
\t: 24小时格式HH:MM:SS
\T 12小时格式
\A 24小时格式 HH:MM
\v: BASH的版本信息
基本用法:
(1) 选择性格式化输出,假如一段文本:(2) 分割后输出:-F后面跟根据什么字符来分割
10. 把一个命令绑定给一个宏或者按键:bind
参数-l 可以列出所有功能
-v 可以列出目前的按键配置与其功能
-q [功能] 显示指定功能的按键组合
11. 如何分页查看大文件内容?
用管道连接命令:
Cat 文件名 | more
11. 数据字典是属于’SYS’用户的,用户‘SYS’ 和 ’SYSEM’是由系统默认自动创建的
OC](这里写自定义目录标题)