稀里糊涂进了面试。感觉爬过了笔试,什么都是赚的。但该准备还是准备,生活就是由一个个偶然走向必然。(考研后遗症,马基知识点)
- 这次知识点主要是围绕着牛客网的C++面试小册子进行复习。
- 相信自己平时自己的积累。
- 可我还是很慌。。。。。。
基础知识
- static关键字:
- 分为全局静态变量 、局部静态变量、静态函数、类的静态成员、类的静态函数
- C++的四种转换。
static_cast,
//强制类型转换
dynamic_cast,
//用于动态类型转换。只能用于含有虚函数的类,用于类层次间的向上和向下转化。
//只能转指针或引用。向下转化时,如果是非法的对于指针返回NULL,对于引用抛异常。
//要深入了解内部转换的原理。
//向上转换:指的是子类向基类的转换
//向下转换:指的是基类向子类的转换
//const_cast,
int& cast_value=const_cast<int&>(const_value);
int* pCast_value=const_cast<int*>(&const_value);
reinterpret_cast
//几乎什么都可以转,比如将int转指针,可能会出问题,尽量少用;
- 指针和引用的区别
- 指针有自己的空间,而引用知识一个别名
- 使用sizeof看指针的大小是4,而引用是被引用对象的大小 。
- 指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;
- 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;
- 可以有const指针,但是没有const引用
- 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
- 指针可以有多级指针(**p),而引用至多一级;
- 指针和引用使用++运算符的意义不一样;
- 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。(WHY)
- smart point 智能指针(前一个已经被C++11弃用,后三个是c++11支持)
- auto_ptr
- shared_ptr,
- weak_ptr,
- unique_ptr
- 数组与指针的区别:
指针 | 数组 |
---|---|
保存数据的地址 | 保存数据 |
间接访问数据,首先获得指针的内容,然后将其作为地址,从改地址中提取数据 | 直接访问数据 |
通常用于动态的数据结构 | 通常用于固定数目且数据类型相同的元素 |
通过Malloc分配内存,free释放内存 | 隐式的分配和删除 |
通常指向匿名数据,操作匿名函数 | 自身即为数据名 |
-
野指针:指向一个已删除的对象或者未申请访问受限内存区域的指针
-
虚函数、析构函数
- 虚函数:参考链接:https://www.jianshu.com/p/d07e0ac0ba3c?from=singlemessage
- 析构函数为什么一定是虚函数。将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
- 虚函数需要成本:额外的虚函数表和虚表指针。这是针对需要继承的类的要求,不是针对于非继承类。
- 虚函数原理
-
函数指针
- 定义:指向函数变量的指针。
- 用途:调用函数和做函数的参数,比如回调函数。
-
fork函数
- 创建一个和当前进程映像一样的进程可以通过fork( )系统调用:
- 参考链接:https://blog.csdn.net/qq_17268389/article/details/84650956
-
main()函数前后运行的函数
void before() {
printf("this is function %s\n",__func__);
return;
}
void after(){
printf("this is function %s\n",__func__);
return;
}
- C++函数栈空间的最大值:默认是1M
- extern “C”:C++调用C函数需要extern C,因为C语言没有函数重载。
- RTTI
算法题
- 给定三角形ABC和一点P(x,y,z),判断点P是否在ABC内,给出思路并手写代码。
//面积法:如果P在三角形ABC内,那么三角形ABP的面积+三角形BCP的面积+三角形ACP的面积应该等于三角形ABC的面积。
#include<iostream>
#include<math.h>
using namespace std;
#define ABS_FLOAT_0 0.0001
struct point_float{
float x;
float y;
};
float GetTriangleSquar(const point_float pt0,const point_float pt1,const point_float pt2)
{
//最简单方法的就是向量积:面积=(1/2)|a×b|=(1/2)*|A|*|B|*sin<A,B>;
point_float AB,BC;
AB.x=pt1.x-pt0.x;
AB.y=pt1.y-pt1.y;
BC.x=pt2.x-pt1.x;
BC.y=pt2.y-pt1.y;
return fabs((AB.x*BC.y-AB.y*BC.x))/2.0f;
}
bool IsInTriangle(const point_float A,const point_float B,
const point_float C,const point_float D)
{
float SABC,SADB,SBDC,SADC;
SABC= GetTriangleSquar(A,B,C);
SADB= GetTriangleSquar(A,D,B);
SBDC= GetTriangleSquar(B,D,C);
SADC= GetTriangleSquar(A,D,C);
float SumSquar=SADB+SBDC+SADC;
if((-ABS_FLOAT_0<(SABC-SumSquar))&&(SABC-SumSquar)<ABS_FLOAT_0)
return true;
else
return false;
}
- Top(K)问题的思路
- 直接全部排序(只适合内存够用的情况下)
- 快速排序的变形,快速排序是每一次分成两个区域,一个区域比另一个区域大,分完后,再判断在哪个区域继续寻找。(类似二分法,但非完全二等分)
- 最小堆法 :这是一种局部淘汰法。先读取前K个数,建立一个最小堆。然后将剩余的所有数字依次与最小堆的堆顶进行比较,如果小于或等于堆顶数据,则继续比较下一个;否则,删除堆顶元素,并将新数据插入堆中,重新调整最小堆。当遍历完全部数据后,最小堆中的数据即为最大的K个数。
- 分治法:将全部数据分成N份,前提是每份的数据都可以读到内存中进行处理,找到每份数据中最大的K个数。此时剩下NK个数据,如果内存不能容纳NK个数据,则再继续分治处理,分成M份,找出每份数据中最大的K个数,如果M*K个数仍然不能读到内存中,则继续分治处理。直到剩余的数可以读入内存中,那么可以对这些数使用快速排序的变形或者归并排序进行处理。
- 哈希法:如果这些数据中有很多重复的数据,可以先通过hash法,把重复的数去掉。这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间。处理后的数据如果能够读入内存,则可以直接排序;否则可以使用分治法或者最小堆法来处理数据。
- 两个栈实现一个堆
//两个栈,一个栈做入队操作,一个栈做出队操作
class Queue
{
public:
void push(int node){
stack1.push(node);
}
int pop(){
if(stack2.size()!=0){
int temp=stack2.top();
stack2.pop();
return temp;
}
else{
while(stack1.size()!=0){
int temp=stack1.top();
stack1.pop();
stack2.push(temp);
}
return pop();
}
}
private:
stack<int> stack1;
stack<int> stack2;
}
- 一个长度为N的整形数组,数组中每个元素的取值范围是[0,n-1],判断该数组否有重复的数,请说一下你的思路并手写代码
//把每个数放到自己对应序号的位置上,
//如果其他位置上有和自己对应序号相同的数,那么即为有重复的数值。
//时间复杂度为O(N),同时为了节省空间复杂度,可以在原数组上进行操作,空间复杂度为O(1)
bool IsDuplicateNumber(int *array, int n)
{
if(array==NULL) return false;
int i,temp;
for(i=0;i<n;i++)
{
while(array[i]!=i)
{
if(array[array[i]]==array[i])
return true;
temp=array[array[i]];
array[array[i]]=array[i];
array[i]=temp;
}
}
return false;
}
- 给定一个数字数组,返回哈夫曼树的头指针【未理解】
struct BTreeNode* CreateHuffman(ElemType a[], int n)
{
int i, j;
struct BTreeNode **b, *q;
b = malloc(n*sizeof(struct BTreeNode));
for (i = 0; i < n; i++)
{
b[i] = malloc(sizeof(struct BTreeNode));
b[i]->data = a[i];
b[i]->left = b[i]->right = NULL;
}
for (i = 1; i < n; i++)
{
int k1 = -1, k2;
for (j = 0; j < n; j++)
{
if (b[j] != NULL && k1 == -1)
{
k1 = j;
continue;
}
if (b[j] != NULL)
{
k2 = j;
break;
}
}
for (j = k2; j < n; j++)
{
if (b[j] != NULL)
{
if (b[j]->data < b[k1]->data)
{
k2 = k1;
k1 = j;
}
else if (b[j]->data < b[k2]->data)
k2 = j;
}
}
q = malloc(sizeof(struct BTreeNode));
q->data = b[k1]->data + b[k2]->data;
q->left = b[k1];
q->right = b[k2];
b[k1] = q;
b[k2] = NULL;
}
free(b);
return q;
}
数据结构与算法
树
-
二叉平衡树:平衡二叉树又称为AVL树,是一种特殊的二叉排序树。其左右子树都是平衡二叉树,且左右子树高度之差的绝对值不超过1。
-
红黑树
- 红黑树五个性质的总结:
- 每个结点要么红,要么黑
- 根结点是黑的。
- 每个叶结点都是黑的。
- 如果一个结点是红的,那么他的两个儿子都是黑的
- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。
- 插入和删除操作破坏了红黑树的性质,为了继续保持红黑树的性质可以通过对结点进行旋转和重新着色等操作来满足性质。
- 红黑树五个性质的总结:
map 和 unordered_map的辨析:
- map的底层实现为红黑树
- unordered_map的底层实现为哈希表
- 哈夫曼编码:实现为贪心算法,常用于文件压缩。
- B树与B+树
参考博文:B树与B+树
B+树:B+是一种多路搜索树,主要为磁盘或其他直接存取辅助设备而设计的一种平衡查找树,在B+树中,每个节点的可以有多个孩子,并且按照关键字大小有序排列。(结合操作系统)
- 二叉树序列化与反序列化【IMPORTANT*】
- 序列化:记录中序遍历和其他任意一种遍历序列
- 反序列化:根据两种遍历方式还原为二叉树
堆栈【更多是关于代码编译部分的堆栈】
- 栈溢出:程序向栈中的某一个变量中写入的字节数超过了这个变量所申请的字节数。因而导致栈中与其相邻的变量的值被改变。
- 栈与堆的区别,为什么栈要快
- 堆是由低地址向高地址扩展;栈是由高地址向低地址扩展。
- 堆中的内存需要手动申请和手动释放;栈中内存是由OS自动申请和自动释放,存放着参数、局部变量等内存。
- 堆中频繁调用malloc和free,会产生内存碎片,降低程序效率;而栈由于其先进后出的特性,不会产生内存碎片
- 栈的效率高的原因:栈是操作系统提供的数据结构,计算机底层对栈提供了一系列支持:分配专门的寄存器存储栈的地址,压栈和入栈有专门的指令执行;而堆是由C/C++函数库提供的,机制复杂,需要一些列分配内存、合并内存和释放内存的算法,因此效率较低。
- 堆与栈的区别
- 申请方式 :栈由系统自动分配和管理,堆由程序员手动分配和管理。
- 效率:栈由系统分配,速度快,不会有内存碎片。堆由程序员分配,速度较慢,可能由于操作不当产生内存碎片。
- 扩展方向:栈从高地址向低地址进行扩展,堆由低地址向高地址进行扩展。
- 程序局部变量是使用的栈空间,new/malloc动态申请的内存是堆空间,函数调用时会进行形参和返回值的压栈出栈,也是用的栈空间。
数组
- 数组与链表的比较:略
排序(用自己的话来描述其实现方式)
- 直接插入排序:序列将分为两部分:有序区间和无序区间。有序区间不断从左往右拓展。无序区间不断减小。插入的方法是:首先从右往左遍历插入的位置,然后再不断向后移动空出位置,最后赋值。
- 希尔排序(shell sort)::直接插入法的改进版。先将整个待排序记录分割为若干个子序列,然后进行直接插入排序,分割的方法为不断缩小的增量。最简单的增量方法为delta/=2
- 归并排序:采用分治法。主要有三个函数:一个是将两个有序序列合并成一个有序序列;第二是:一趟递归。将一整个需要排序的数组分割成若干个序列对,对每个数列对进行函数1的操作。第三个方法是不断的将序列对的每个序列长度迭代为m(整个序列的大小)
- 冒泡排序,第一和第二个比较,第三和第四个比较……若为逆序,则交换两个值的位置,一趟完成后,最大值一定会再序列末端。因此称作“冒泡排序”。有一个减小复杂度的判断是:当一趟完成后发现没有交换操作,便可证明序列中无逆序,便可直接获得排序后的结果。
- 快速排序:将序列分成独立的两部分,前一部分比后一部分的数都要小,再进行递归(重复对分成的两个序列再次进行相同操作)
- 选择排序:最容易理解的算法。每次选择最小的元素放在数组最前端
- 堆排序:利用构建大根堆/小根堆的方式来进行排序
哈希
构造哈希与处理哈希冲突两大问题。
- 构造哈希:直接地址法、平方取中法、除留余数法(MOD)等
- rehash概念:当负载因子为1时(即关于哈希表的存储地址已经存满),则需要重新构造哈希(放在扩容后的地址中)
- 解决哈希冲突:开放定址法、再哈希法、链地址法、建立公共溢出区
- 开放定址法:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。
- 线性探查:+1
- 二次探查:冲突发生时,在表的左右进行跳跃式探测,比较灵活
- 伪随机探测:
- 再哈希法:当发生哈希冲突时使用另一个哈希函数计算地址值,直到冲突不再发生。这种方法不易产生聚集,但是增加计算时间,同时需要准备许多哈希函数。
- 链地址法:将所有哈希值相同的Key通过链表存储。key按顺序插入到链表中。
- 建立公共溢出区:采用一个溢出表存储产生冲突的关键字。如果公共溢出区还产生冲突,再采用处理冲突方法处理。
- 开放定址法:当发生地址冲突时,按照某种方法继续探测哈希表中的其他存储单元,直到找到空位置为止。
动态规划
- 最长公共连续子序列
- 求一个字符串最长回文子串
- 查找最长回文子串
链表
- 合并两个有序链表
其他
- C组合与继承地优缺点
- 组合是黑盒复用、静态复用;继承是白盒复用。
- 优缺点如下表
组合关系 | 继承关系 |
---|---|
优点:不破坏封装,整体类和局部类之间松耦合,彼此相互独立 | 缺点:破坏封装,子类与父类之间紧密耦合,子类依赖于父类的实现,子类缺乏独立性 |
优点:具有比较好的拓展性 | 缺点:支持拓展,但往往以增加系统结构的复杂度为代价 |
优点:支持动态足额。再运行时,整体对象可以选择不同类型的局部对象 | 缺点:不支持动态继承,再运行时,子类无法选择不同的父类 |
优点:整体类可以对局部类进行包装,封装局部类的结构,提供新的接口 | 缺点:子类不能改变父类的接口,但子类可以覆盖父类的接口 |
缺点:整体类不能自动获得和局部类同样的接口 | 优点:子类能自动继承父类的接口 |
缺点:创建整体对象时,需要创建所有的局部类的对象 | 优点:创建子类的对象时,无需创建父类的对象 |
- stl中map和hashmap区别。hashmap是否总是优于map
- 底层不同:map使用的是红黑树作为存储结构;而hashmap使用的是哈希表的存储结构。
- hashmap不会总是优于map,具体情况具体分析。
- Android APP消息推送实现原理:相关博文
- 无重复集合子集编程:相关博文
- .28法则的了解
- 链表反转