CS-Notes/notes/面经/C++后台开发面试常见问题汇总_讨论区_牛客网 -.txt

1259 lines
71 KiB
Plaintext
Raw Normal View History

2018-02-22 14:47:22 +08:00
下载APP 注册或登录 讨论区
C++后台开发面试常见问题汇总
oscarwin
2017-10-27 09:42:47
13 29
从牛客获得太多的资源,来一发面经,纯技术干货。面试的技巧,心态,简历也都很重要,不过牛客有很多大佬总结的太好了,就不重复了。
主要是C++后台开发,基本总结了我自己面试常问到的,看别人的面经问到过的,分享出来交流一下。 C++后台开发面试常见问题汇总
原文来自我的博客,欢迎点击: http://blog.csdn.net/shanghairuoxiao/article/details/72876248 C和C++语言基础
extern关键字作用
extern声明变量在在外部定义
extern修饰函数
extern C的作用用法
static关键字作用
static修饰局部变量
static全局变量(限定变量在一个编译单元内一个编译单元就是指一个cpp和它包含的头文件这个回答可以结合编译需要经历的几个过程来答)
static修饰普通函数
static修饰成员变量?
static修饰成员函数
volatile是干啥的
访问寄存器要比访问内存要块因此CPU会优先访问该数据在寄存器中的存储结果但是内存中的数据可能已经发生了改变而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile告诉CPU每次都从内存去读取数据。
一个参数可以即是const又是volatile的吗可以一个例子是只读状态寄存器是volatile是因为它可能被意想不到的被改变是const告诉程序不应该试图去修改他。
说说const的作用越多越好
const修饰全局变量
const修饰局部变量
const修饰指针const int *
const修饰指针指向的对象, int * const
const修饰引用做形参
const修饰成员变量必须在构造函数列表中初始化
const修饰成员函数说明该函数不应该修改非静态成员但是这并不是十分可靠的指针所指的非成员对象值可能会被改变
new与malloc区别
new分配内存按照数据类型进行分配malloc分配内存按照大小分配
new不仅分配一段内存而且会调用构造函数但是malloc则不会。new的实现原理但是还需要注意的是之前看到过一个题说int p = new int与int p = new int()的区别因为int属于C++内置对象不会默认初始化必须显示调用默认构造函数但是对于自定义对象都会默认调用构造函数初始化。翻阅资料后在C++11中两者没有区别了自己测试的结构也都是为0
new返回的是指定对象的指针而malloc返回的是void*因此malloc的返回值一般都需要进行类型转化
new是一个操作符可以重载malloc是一个库函数
new分配的内存要用delete销毁malloc要用free来销毁delete销毁的时候会调用对象的析构函数而free则不会
malloc分配的内存不够的时候可以用realloc扩容。扩容的原理new没用这样操作
new如果分配失败了会抛出bad_malloc的异常而malloc失败了会返回NULL。因此对于new正确的姿势是采用try...catch语法而malloc则应该判断指针的返回值。为了兼容很多c程序员的习惯C++也可以采用new nothrow的方法禁止抛出异常而返回NULL
new和new[]的区别new[]一次分配所有内存多次调用构造函数分别搭配使用delete和delete[]同理delete[]多次调用析构函数销毁数组中的每个对象。而malloc则只能sizeof(int) * n
如果不够可以继续谈new和malloc的实现空闲链表分配方法(首次适配原则,最佳适配原则,最差适配原则,快速适配原则)。delete和free的实现原理free为什么直到销毁多大的空间
C++多态性与虚函数表
C++多态的实现?
多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。
动态多态实现有几个条件:
(1) 虚函数;
(2) 一个基类的指针或引用指向派生类的对象;
基类指针在调用成员函数(虚函数)时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。
每个对象中保存的只是一个虚函数表的指针C++内部为每一个类维持一个虚函数表,该类的对象的都指向这同一个虚函数表。
虚函数表中为什么就能准确查找相应的函数指针呢?因为在类设计的时候,虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。
虚函数的作用?
虚函数用于实现多态,这点大家都能答上来
但是虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。
动态绑定是如何实现的?
第一个问题中基本回答了,主要都是结合虚函数表来答就行。
静态多态和动态多态 。静态多态是指通过模板技术或者函数重载技术实现的多态,其在编译器确定行为。动态多态是指通过虚函数技术实现在运行期动态绑定的技术。
虚函数表
虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的?
编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。
纯虚函数如何定义,为什么对于存在虚函数的类中析构函数要定义成虚函数
为了实现多态进行动态绑定,将派生类对象指针绑定到基类指针上,对象销毁时,如果析构函数没有定义为析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。
//纯虚函数定义
virtual ~myClass() = 0;
析构函数能抛出异常吗
答案肯定是不能。
C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。
(1) 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
(2) 通常异常发生时c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
构造函数和析构函数中调用虚函数吗?
指针和引用的区别
指针保存的是所指对象的地址,引用是所指对象的别名,指针需要通过解引用间接访问,而引用是直接访问;
指针可以改变地址,从而改变所指的对象,而引用必须从一而终;
引用在定义的时候必须初始化,而指针则不需要;
指针有指向常量的指针和指针常量,而引用没有常量引用;
指针更灵活,用的好威力无比,用的不好处处是坑,而引用用起来则安全多了,但是比较死板。
指针与数组千丝万缕的联系
一个一维int数组的数组名实际上是一个int* const 类型;
一个二维int数组的数组名实际上是一个int (*const p)[n];
数组名做参数会退化为指针除了sizeof
智能指针是怎么实现的?什么时候改变引用计数?
构造函数中计数初始化为1
拷贝构造函数中计数值加1
赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一;
析构函数中引用计数减一;
在赋值运算符和析构函数中如果减一后为0则调用delete释放对象。
share_prt与weak_ptr的区别
//share_ptr可能出现循环引用从而导致内存泄露
class A
{
public:
share_ptr p;
};
class B
{
public:
share_ptr p;
}
int main()
{
while(true)
{
share_prt pa(new A()); //pa的引用计数初始化为1
share_prt pb(new B()); //pb的引用计数初始化为1
pa->p = pb; //pb的引用计数变为2
pb->p = pa; //pa的引用计数变为2
}
//假设pa先离开引用计数减一变为1不为0因此不会调用class A的析构函数因此其成员p也不会被析构pb的引用计数仍然为2
//同理pb离开的时候引用计数也不能减到0
return 0;
}
/*
** weak_ptr是一种弱引用指针其存在不会影响引用计数从而解决循环引用的问题
*/
C++四种类型转换 static_cast, dynamic_cast, const_cast, reinterpret_cast
const_cast用于将const变量转为非const
static_cast用的最多对于各种隐式转换非const转constvoid*转指针等, static_cast能用于多态想上转化如果向下转能成功但是不安全结果未知
dynamic_cast用于动态类型转换。只能用于含有虚函数的类用于类层次间的向上和向下转化。只能转指针或引用。向下转化时如果是非法的对于指针返回NULL对于引用抛异常。要深入了解内部转换的原理。
reinterpret_cast几乎什么都可以转比如将int转指针可能会出问题尽量少用
为什么不使用C的强制转换C的强制转换表面上看起来功能强大什么都能转但是转化不够明确不能进行错误检查容易出错。
内存对齐的原则
从0位置开始存储
变量存储的起始位置是该变量大小的整数倍;
结构体总的大小是其最大元素的整数倍,不足的后面要补齐;
结构体中包含结构体,从结构体中最大元素的整数倍开始存;
如果加入pragma pack(n) 取n和变量自身大小较小的一个。
内联函数有什么优点?内联函数与宏定义的区别?
宏定义在预编译的时候就会进行宏替换;
内联函数在编译阶段,在调用内联函数的地方进行替换,减少了函数的调用过程,但是使得编译文件变大。因此,内联函数适合简单函数,对于复杂函数,即使定义了内联编译器可能也不会按照内联的方式进行编译。
内联函数相比宏定义更安全,内联函数可以检查参数,而宏定义只是简单的文本替换。因此推荐使用内联函数,而不是宏定义。
使用宏定义函数要特别注意给所有单元都加上括号,#define MUL(a, b) a b这很危险正确写法#define MUL(a, b) ((a) (b))
C++内存管理
C++内存分为那几块?(堆区,栈区,常量区,静态和全局区)
每块存储哪些变量?
学会迁移可以说到malloc从malloc说到操作系统的内存管理说道内核态和用户态然后就什么高端内存slab层伙伴算法VMA可以巴拉巴拉了接着可以迁移到fork()。
STL里的内存池实现
STL内存分配分为一级分配器和二级分配器一级分配器就是采用malloc分配内存二级分配器采用内存池。
二级分配器设计的非常巧妙分别给8k16k,..., 128k等比较小的内存片都维持一个空闲链表每个链表的头节点由一个数组来维护。需要分配内存时从合适大小的链表中取一块下来。假设需要分配一块10K的内存那么就找到最小的大于等于10k的块也就是16K从16K的空闲链表里取出一个用于分配。释放该块内存时将内存节点归还给链表。
如果要分配的内存大于128K则直接调用一级分配器。
为了节省维持链表的开销采用了一个union结构体分配器使用union里的next指针来指向下一个节点而用户则使用union的空指针来表示该节点的地址。
STL里set和map是基于什么实现的。红黑树的特点
set和map都是基于红黑树实现的。
红黑树是一种平衡二叉查找树与AVL树的区别是什么AVL树是完全平衡的红黑树基本上是平衡的。
为什么选用红黑数呢因为红黑数是平衡二叉树其插入和删除的效率都是N(logN)与AVL相比红黑数插入和删除最多只需要3次旋转而AVL树为了维持其完全平衡性在坏的情况下要旋转的次数太多。
红黑树的定义:
(1) 节点是红色或者黑色;
(2) 父节点是红色的话,子节点就不能为红色;
(3) 从根节点到每个页子节点路径上黑色节点的数量相同;
(4) 根是黑色的NULL节点被认为是黑色的。
STL里的其他数据结构和算法实现也要清楚
这个问题把STL源码剖析好好看看不仅面试不慌自己对STL的使用也会上升一个层次。
必须在构造函数初始化式里进行初始化的数据成员有哪些
(1) 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
(2) 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
(3) 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化
模板特化
(1) 模板特化分为全特化和偏特化模板特化的目的就是对于某一种变量类型具有不同的实现因此需要特化版本。例如在STL里迭代器为了适应原生指针就将原生指针进行特化。
定位内存泄露
(1)在windows平台下通过CRT中的库函数进行检测
(2)在可能泄漏的调用前后生成块的快照,比较前后的状态,定位泄漏的位置
(3)Linux下通过工具valgrind检测
手写strcpy
```
char strcpy(char dst, const char src)
{
assert(dst);
assert(src);
char ret = dst;
while(( dst++ = src++) != '\0');
return ret;
}
//该函数是没有考虑重叠的
char strcpy(char dst, const char src)
{
assert((dst != NULL) && (src != NULL));
char ret = dst;
int size = strlen(src) + 1;
if(dst > src || dst < src + len)
{
dst = dst + size - 1;
src = src + size - 1;
while(size--)
{
dst-- = src--;
}
}
else
{
while(size--)
{
dst++ = src++;
}
}
return ret;
}
- **手写memcpy函数**
void memcpy(void dst, const void src, size_t size)
{
if(dst == NULL || src == NULL)
{
return NULL;
}
void res = dst;
char pdst = (char )dst;
char psrc = (char )src;
if(pdst > psrc && pdst < psrc + size) //重叠
{
pdst = pdst + size - 1;
psrc = pdst + size - 1;
while(size--)
{
*pdst-- = *psrc--;
}
}
else //无重叠
{
while(size--)
{
*dst++ = *src++;
}
}
return ret;
}
- **手写strcat函数**
char strcat(char dst, const char src)
{
char ret = dst;
while(*dst != '\0')
++dst;
while((*dst++ = *src) != '\0');
return ret;
}
- **手写strcmp函数**
int strcmp(const char str1, const char str2)
{
while(*str1 == *str2 && *str1 != '\0')
{
++str1;
++str2;
}
return *str1 - *str2;
} 数据结构与算法
这一块考察范围太广主要靠多刷题吧牛客网剑指OFFERLeetCode等。 Hash表
Hash表实现拉链和分散地址
Hash策略常见的有哪些
STL中hash_map扩容发生什么
(1) 创建一个新桶,该桶是原来桶两倍大最接近的质数(判断n是不是质数的方法用n除2到$sqrt(n)$范围内的数)
(2) 将原来桶里的数通过指针的转换,插入到新桶中(注意STL这里做的很精细没有直接将数据从旧桶遍历拷贝数据插入到新桶而是通过指针转换)
(3) 通过swap函数将新桶和旧桶交换销毁新桶。 树
二叉树结构,二叉查找树实现;
二叉树的六种遍历;
二叉树的按层遍历;
递归是解决二叉树相关问题的神级方法;
树的各种常见算法题( http://blog.csdn.net/xiajun07061225/article/details/12760493)
什么是红黑树?
节点为红色或者黑色;
根节点为黑色;
从根节点到每个叶子节点经过的黑色节点个数的和相同;
如果父节点为红色,那么其子节点就不能为红色。
红黑树与AVL树的区别
红黑树与AVL树都是平衡树但是AVL是完全平衡的(平衡就是值树中任意节点的左子树和右子树高度差不超过1)
红黑树效率更高因为AVL为了保证其完全平衡插入和删除的时候在最坏的情况下要旋转logN次而红黑树插入和删除的旋转次数要比AVL少。
Trie树(字典树)
每个节点保存一个字符
根节点不保存字符
每个节点最多有n个子节点(n是所有可能出现字符的个数)
查询的复杂父为O(k)k为查询字符串长度 链表
链表和插入和删除,单向和双向链表都要会
链表的问题考虑多个指针和递归
(1) 反向打印链表(递归)
(2) 打印倒数第K个节点(前后指针)
(3) 链表是否有环(快慢指针)等等。b ggg 栈和队列
队列和栈的区别 (从实现,应用,自身特点多个方面来阐述,不要只说一个先入先出,先入后出,这个你会别人也会,要展现出你比别人掌握的更深)
典型的应用场景 海量数据问题
十亿整数随机生成可重复中前K最大的数
类似问题的解决方法思路首先哈希将数据分成N个文件然后对每个文件建立K个元素最小/大堆根据要求来选择。最后将文件中剩余的数插入堆中并维持K个元素的堆。最后将N个堆中的元素合起来分析。可以采用归并的方式来合并。在归并的时候为了提高效率还需要建一个N个元素构成的最大堆先用N个堆中的最大值填充这个堆然后就是弹出最大值指针后移的操作了。当然这种问题在现在的互联网技术中一般就用map-reduce框架来做了。
大数据排序相同的思路:先哈希(哈希是好处是分布均匀,相同的数在同一个文件中),然后小文件装入内存快排,排序结果输出到文件。最后建堆归并。
十亿整数(随机生成,可重复)中出现频率最高的一千个 排序算法
排序算法当然是基础内容了,必须至少能快速写出,快排,建堆,和归并
每种算法的时间空间复杂度,最好最差平均情况 位运算 布隆过滤器 几十亿个数经常要查找某一个数在不在里面,使用布隆过滤器,布隆过滤器的原理。布隆过滤器可能出现误判,怎么保证无误差? 网络与TCP/IP
TCP与UDP之间的区别
(1) IP首部TCP首部UDP首部
(2) TCP和UDP区别
(3) TCP和UDP应用场景
(4) 如何实现可靠的UDP
TCP三次握手与四次挥手
详细说明TCP状态迁移过程
(1) 三次握手和四次挥手状态变化;
(2) 2MSL是什么状态作用是什么
TCP相关技术
1. TCP重发机制Nagle算法
2. TCP的拥塞控制使用的算法和具体过程
3. TCP的窗口滑动
TCP客户与服务器模型用到哪些函数
UDP客户与服务器模型用到哪些函数
域名解析过程ARP的机制RARP的实现
1. RARP用于无盘服务器开机后通过发送RARP包给RARP服务器通过mac地址得到IP地址
Ping和TraceRoute实现原理
(1) Ping是通过发送ICMP报文回显请求实现。
(2) TraceRoute通过发送UDP报文设置目的端口为一个不可能的值将IP首部中的TTL分别设置从1到N每次逐个增加如果收到端口不可达说明到达目的主机如果是因为TTL跳数超过路由器会发送主机不可达的ICMP报文。
HTTP http/https 1.0、1.1、2.0
1. http的主要特点:
简单快速: 当客户端向服务器端发送请求时,只是简单的填写请求路径和请求方法即可,然后就可以通过浏览器或其他方式将该请求发送就行了
灵活: HTTP 协议允许客户端和服务器端传输任意类型任意格式的数据对象
无连接: 无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。(当今多数服务器支持Keep-Alive功能使用服务器支持长连接解决无连接的问题)
无状态: 无状态是指协议对于事务处理没有记忆能力服务器不知道客户端是什么状态。即客户端发送HTTP请求后服务器根据请求会给我们发送数据发送完后不会记录信息。(使用 cookie 机制可以保持 session解决无状态的问题)
2. http1.1的特点
a、默认持久连接节省通信量只要客户端服务端任意一端没有明确提出断开TCP连接就一直保持连接可以发送多次HTTP请求
b、管线化客户端可以同时发出多个HTTP请求而不用一个个等待响应
c、断点续传ftghh
3. http2.0的特点
a、HTTP/2采用二进制格式而非文本格式
b、HTTP/2是完全多路复用的而非有序并阻塞的——只需一个HTTP连接就可以实现多个请求响应
c、使用报头压缩HTTP/2降低了开销
d、HTTP/2让服务器可以将响应主动“推送”到客户端缓存中 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安全性较高
返回状态码
200请求被正常处理
204请求被受理但没有资源可以返回
206客户端只是请求资源的一部分服务器只对请求的部分资源执行GET方法相应报文中通过Content-Range指定范围的资源。
301永久性重定向
302临时重定向
303与302状态码有相似功能只是它希望客户端在请求一个URI的时候能通过GET方法重定向到另一个URI上
304发送附带条件的请求时条件不满足时返回与重定向无关
307临时重定向与302类似只是强制要求使用POST方法
400请求报文语法有误服务器无法识别
401请求需要认证
403请求的对应资源禁止被访问
404服务器无法找到对应资源
500服务器内部错误
503服务器正忙
http 协议头相关
http数据由请求行首部字段空行报文主体四个部分组成
首部字段分为:通用首部字段,请求首部字段,响应首部字段,实体首部字段 https与http的区别如何实现加密传输
https就是在http与传输层之间加上了一个SSL
对称加密与非对称加密 浏览器中输入一个URL发生什么用到哪些协议 浏览器中输入URL首先浏览器要将URL解析为IP地址解析域名就要用到DNS协议首先主机会查询DNS的缓存如果没有就给本地DNS发送查询请求。DNS查询分为两种方式一种是递归查询一种是迭代查询。如果是迭代查询本地的DNS服务器向根域名服务器发送查询请求根域名服务器告知该域名的一级域名服务器然后本地服务器给该一级域名服务器发送查询请求然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的因此会用到UDP协议。
得到IP地址后浏览器就要与服务器建立一个http连接。因此要用到http协议http协议报文格式上面已经提到。http生成一个get请求报文将该报文传给TCP层处理。如果采用https还会先对http数据进行加密。TCP层如果有需要先将HTTP数据包分片分片依据路径MTU和MSS。TCP的数据包然后会发送给IP层用到IP协议。IP层通过路由选路一跳一跳发送到目的地址。当然在一个网段内的寻址是通过以太网协议实现(也可以是其他物理层协议比如PPPSLIP)以太网协议需要直到目的IP地址的物理地址有需要ARP协议。 安全相关
SQL注入
XSS
RCFS
APR欺骗 数据库
SQL语言(内外连接,子查询,分组,聚集,嵌套,逻辑)
MySQL索引方法索引的优化
InnoDB与MyISAM区别
事务的ACID
事务的四个隔离级别
查询优化(从索引上优化从SQL语言上优化)
B-与B+树区别?
MySQL的联合索引(又称多列索引)是什么?生效的条件?
分库分表 Linux 进程与线程 (1) 进程与线程区别?
(2) 线程比进程具有哪些优势?
(3) 什么时候用多进程?什么时候用多线程?
(4) LINUX中进程和线程使用的几个函数
(5) 线程同步?
在Windows下线程同步的方式有互斥量信号量事件关键代码段
在Linux下线程同步的方式有互斥锁自旋锁读写锁屏障(并发完成同一项任务时,屏障的作用特别好使)
知道这些锁之间的区别,使用场景? 进程间通讯方式
管道( pipe ) :管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
命名管道 (FIFO) 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
信号量 信号量用于实现进程间的互斥与同步而不是用于存储进程间通信数据有XSI信号量和POSIX信号量POSIX信号量更加完善。
消息队列( message queue ) 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。(原理一定要清楚,常考)
信号 ( sinal ) 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生,常见的信号。
套接字( socket ) 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
匿名管道与命名管道的区别 :匿名管道只能在具有公共祖先的两个进程间使用。
共享文件映射mmap
mmap建立进程空间到文件的映射在建立的时候并不直接将文件拷贝到物理内存同样采用缺页终端。mmap映射一个具体的文件可以实现任意进程间共享内存映射一个匿名文件可以实现父子进程间共享内存。
常见的信号有哪些? SIGINTSIGKILL(不能被捕获)SIGTERM(可以被捕获)SIGSEGVSIGCHLDSIGALRM 内存管理 1. 虚拟内存的作用?
2. 虚拟内存的实现?
3. 操作系统层面对内存的管理?
4. 内存池的作用STL里 内存池如何实现
5. 进程空间和内核空间对内存的管理不同?
6. Linux的slab层VAM
7. 伙伴算法
8. 高端内存 进程调度 1. Linux进程分为两种实时进程和非实时进程
2. 优先级分为静态优先级和动态优先级,优先级的范围;
3. 调度策略FIFOLRU时间片轮转
4. 交互进程通过平均睡眠时间而被奖励; 死锁 (1) 死锁产生的条件;
(2) 死锁的避免; 命令行
Linux命令 在一个文件中倒序打印第二行前100个大写字母
```
cat filename | head -n 2 | tail -n 1 | grep '[[:upper:]]' -o | tr -d '\n'| cut -c 1-100 | rev
与CPU内存磁盘相关的命令(topfree, df, fdisk)
网络相关的命令netstattcpdump等
sed, awk, grep三个超强大的命名分别用与格式化修改统计和正则查找
ipcs和ipcrm命令
查找当前目录以及字母下以.c结尾的文件且文件中包含"hello world"的文件的路径
创建定时任务 IO模型
五种IO模型 阻塞IO非阻塞IOIO复用信号驱动式IO异步IO
selectpollepoll的区别
select 是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理如果没有返回
存在的问题:
1. 内置数组的形式使得select的最大文件数受限与FD_SIZE
2. 每次调用select前都要重新初始化描述符集将fd从用户态拷贝到内核态每次调用select后都需要将fd从内核态拷贝到用户态
3. 轮寻排查当文件描述符个数很多时,效率很低; poll 通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体该结构体保存描述符的信息每增加一个文件描述符就向数组中加入一个结构体结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。轮寻排查的问题未解决。
epoll 轮寻排查所有文件描述符的效率不高使服务器并发能力受限。因此epoll采用只返回状态发生变化的文件描述符便解决了轮寻的瓶颈。
为什么使用IO多路复用最主要的原因是什么
epoll有两种触发模式这两种触发模式有什么区别编程的时候有什么区别
上一题中编程的时候有什么区别,是在边缘触发的时候要把套接字中的数据读干净,那么当有多个套接字时,在读的套接字一直不停的有数据到达,如何保证其他套接字不被饿死(面试网易游戏的时候问的一个问题,答不上来,印象贼深刻)。
1. select/poll/epoll区别
2. 几种网络服务器模型的介绍与比较
3. epoll为什么这么快 (搞懂这篇文章关于IO复用的问题就信手拈来了) 线程池 Linux的API
fork与vfork区别
fork和vfork都用于创建子进程。但是vfork创建子进程后父进程阻塞直到子进程调用exit()或者excle()。
对于内核中过程fork通过调用clone函数然后clone函数调用do_fork()。do_fork()中调用copy_process()函数先复制task_struct结构体然后复制其他关于内存文件寄存器等信息。fork采用写时拷贝技术因此子进程和父进程的页表指向相同的页框。但是vfork不需要拷贝页表因为父进程会一直阻塞直接使用父进程页表。
exit()与_exit()区别
exit()清理后进入内核_exit()直接陷入内核。
孤儿进程与僵死进程
1. 孤儿进程是怎么产生的?
2. 僵死进程是怎么产生的?
3. 僵死进程的危害?
4. 如何避免僵死进程的产生?
Linux是如何避免内存碎片的
1. 伙伴算法,用于管理物理内存,避免内存碎片;
2. 高速缓存Slab层用于管理内核分配内存避免碎片。
共享内存的实现原理?
共享内存实现分为两种方式一种是采用mmap另一种是采用XSI机制中的共享内存方法。mmap是内存文件映射将一个文件映射到进程的地址空间用户进程的地址空间的管理是通过vm_area_struct结构体进行管理的。mmap通过映射一个相同的文件到两个不同的进程就能实现这两个进程的通信采用该方法可以实现任意进程之间的通信。mmap也可以采用匿名映射不指定映射的文件但是只能在父子进程间通信。XSI的内存共享实际上也是通过映射文件实现只是其映射的是一种特殊文件系统下的文件该文件是不能通过read和write访问的。
二者区别:
1、 系统V共享内存中的数据从来不写入到实际磁盘文件中去而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注前面讲到系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的文件系统shm的安装点在交换分区上系统重新引导后所有的内容都丢失。
2、 系统V共享内存是随内核持续的即使所有访问共享内存的进程都已经正常终止共享内存区仍然存在除非显式删除共享内存在内核重新引导之前对该共享内存区域的任何改写操作都将一直保留。
3、 通过调用mmap()映射普通文件进行进程间通信时一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注这里没有给出shmctl的使用范例原理与消息队列大同小异。
系统调用与库函数(open, close, create, lseek, write, read)
同步方法有哪些?
1. 互斥锁,自旋锁,信号量,读写锁,屏障
2. 互斥锁与自旋锁的区别互斥锁得不到资源的时候阻塞不占用cpu资源。自旋锁得不到资源的时候不停的查询而然占用cpu资源。
3. 死锁 其他
++i是否是原子操作
明显不是,++i主要有三个步骤把数据从内存放在寄存器上在寄存器上进行自增把数据从寄存器拷贝会内存每个步骤都可能被中断。
判断大小端
```
union un
{
int i;
char ch;
};
void fun()
{
union un test;
test.i = 1;
if(ch == 1)
cout << "小端" << endl;
else
cout << "大端" << endl;
}
``` 设计模式
单例模式线程安全的写法
STL里的迭代器使用了迭代器模式
MVC的理解
分布式系统
map_reduce原理 (这篇文章讲的很通俗易懂)
负载均衡
CDN
29
精彩评论
13条回帖
康小城
2017-10-27 09:45:44
0 0
满满的干货,干货满满
77浩力百世不敌
2017-10-27 09:54:24
0 0
66666666666
苏Per
2017-10-27 09:55:13
0 0
干货!赞!
不知火
2017-10-27 10:59:40
0 0
此贴要顶
我家的狗不咬人
2017-10-27 11:17:04
0 0
干货,很棒!
JackLiao
2017-10-27 11:20:55
0 0
干货,多谢
寻ME
2017-10-27 12:37:47
0 0
太强辣
跋锋小寒
2017-10-27 13:02:45
0 0
总结的好
三和大神
2017-10-27 13:55:35
1 0
赞一个,有几个位置想提一下自己的意见
1new与malloc区别第二条对于POD对象并无默认构造函数。
第七条new有八个重载版本失败还可能返回空指针
2虚函数表这里每个类的虚函数表可以有多个的吧与继承的基类数目相同
3内联函数这里。内联函数并不一定只在编译期还可在编码期连接期如VC++7.0运行期如RVM取决于具体编译器的实现参考more exceptional c++ 条款8c++编程剖析第25条
4c++内存管理这里c++还有一个自由存储区new的内存不一定就是堆还可以是在栈中全局区中。参考exceptional c++条款35
5定位内存泄露这里无论是CRT还是Valgrind都只是在debug模式下使用在release模式下方式有1对象计数2,重载new,delete(记录分配点,定期打印堆栈)3hook 分配内存的API 4DiagLeak
6, tranceroute这里。tranceroute使用UDP的我印象中就是ICMP+TTL
7, get和post区别这里http设计中不是要考虑幂等性吗,怎么get就不安全了呢对于暴漏url不是有ssl/tls吗而且url长度限制貌似是取决于浏览器的吧。。
8DNS不是基于UDP,是基于UDP和TCP
9消息队列这里貌似Posix标准下消息队列本质是一个优先级队列在system v标准下才是通过消息的标识来提取感兴趣的消息
所以system v更灵活。参考linux从应用到内核 第十一章
10信号这里补充一下信号分为可靠信号和不可靠信号不可靠信号是用位图管理可能丢失可靠信号是由一个队列进行管理在一定限度内不丢失当队列塞不下的时候就丢失了
11进程调度这里非实时进程采用完全公平调度通过nice值计算虚拟运行时间内核对这些进程用红黑树进行管理。实现进程采用fifo和时间片转轮
12poll这里poll只拷贝一次到文件内核态吗是epoll吧原理是通过一个额外的文件描述符标识这个文件描述符集
那个问题边沿触发设置一个EPOLLONESHOT事件可以解决事件频繁触发的问题
御光飘扬
2017-10-27 15:55:25
0 0
很不错,大佬
TheBestIsYetToCome-
2017-10-27 16:16:57
0 0
牛批
红色小宝马
2017-10-28 19:38:23
0 0
好难
广州渣渣辉
2017-10-28 19:49:34
0 0
谢谢大佬
加载中...
查看更多回复
没有回复
添加回复
查看更多精彩内容 >
回复
0 /
确定
下载牛客APP把IT求职神器装进口袋
请登录后查看
知识点专项练习
笔试真题查看
公司真题套题练习
计算机期末备考专场
讨论区
我的练习
请登录后设置
从牛客获得太多的资源,来一发面经,纯技术干货。面试的技巧,心态,简历也都很重要,不过牛客有很多大佬总结的太好了,就不重复了。
主要是C++后台开发,基本总结了我自己面试常问到的,看别人的面经问到过的,分享出来交流一下。 C++后台开发面试常见问题汇总
原文来自我的博客,欢迎点击: http://blog.csdn.net/shanghairuoxiao/article/details/72876248 C和C++语言基础
extern关键字作用
extern声明变量在在外部定义
extern修饰函数
extern C的作用用法
static关键字作用
static修饰局部变量
static全局变量(限定变量在一个编译单元内一个编译单元就是指一个cpp和它包含的头文件这个回答可以结合编译需要经历的几个过程来答)
static修饰普通函数
static修饰成员变量?
static修饰成员函数
volatile是干啥的
访问寄存器要比访问内存要块因此CPU会优先访问该数据在寄存器中的存储结果但是内存中的数据可能已经发生了改变而寄存器中还保留着原来的结果。为了避免这种情况的发生将该变量声明为volatile告诉CPU每次都从内存去读取数据。
一个参数可以即是const又是volatile的吗可以一个例子是只读状态寄存器是volatile是因为它可能被意想不到的被改变是const告诉程序不应该试图去修改他。
说说const的作用越多越好
const修饰全局变量
const修饰局部变量
const修饰指针const int *
const修饰指针指向的对象, int * const
const修饰引用做形参
const修饰成员变量必须在构造函数列表中初始化
const修饰成员函数说明该函数不应该修改非静态成员但是这并不是十分可靠的指针所指的非成员对象值可能会被改变
new与malloc区别
new分配内存按照数据类型进行分配malloc分配内存按照大小分配
new不仅分配一段内存而且会调用构造函数但是malloc则不会。new的实现原理但是还需要注意的是之前看到过一个题说int p = new int与int p = new int()的区别因为int属于C++内置对象不会默认初始化必须显示调用默认构造函数但是对于自定义对象都会默认调用构造函数初始化。翻阅资料后在C++11中两者没有区别了自己测试的结构也都是为0
new返回的是指定对象的指针而malloc返回的是void*因此malloc的返回值一般都需要进行类型转化
new是一个操作符可以重载malloc是一个库函数
new分配的内存要用delete销毁malloc要用free来销毁delete销毁的时候会调用对象的析构函数而free则不会
malloc分配的内存不够的时候可以用realloc扩容。扩容的原理new没用这样操作
new如果分配失败了会抛出bad_malloc的异常而malloc失败了会返回NULL。因此对于new正确的姿势是采用try...catch语法而malloc则应该判断指针的返回值。为了兼容很多c程序员的习惯C++也可以采用new nothrow的方法禁止抛出异常而返回NULL
new和new[]的区别new[]一次分配所有内存多次调用构造函数分别搭配使用delete和delete[]同理delete[]多次调用析构函数销毁数组中的每个对象。而malloc则只能sizeof(int) * n
如果不够可以继续谈new和malloc的实现空闲链表分配方法(首次适配原则,最佳适配原则,最差适配原则,快速适配原则)。delete和free的实现原理free为什么直到销毁多大的空间
C++多态性与虚函数表
C++多态的实现?
多态分为静态多态和动态多态。静态多态是通过重载和模板技术实现,在编译的时候确定。动态多态通过虚函数和继承关系来实现,执行动态绑定,在运行的时候确定。
动态多态实现有几个条件:
(1) 虚函数;
(2) 一个基类的指针或引用指向派生类的对象;
基类指针在调用成员函数(虚函数)时,就会去查找该对象的虚函数表。虚函数表的地址在每个对象的首地址。查找该虚函数表中该函数的指针进行调用。
每个对象中保存的只是一个虚函数表的指针C++内部为每一个类维持一个虚函数表,该类的对象的都指向这同一个虚函数表。
虚函数表中为什么就能准确查找相应的函数指针呢?因为在类设计的时候,虚函数表直接从基类也继承过来,如果覆盖了其中的某个虚函数,那么虚函数表的指针就会被替换,因此可以根据指针准确找到该调用哪个函数。
虚函数的作用?
虚函数用于实现多态,这点大家都能答上来
但是虚函数在设计上还具有封装和抽象的作用。比如抽象工厂模式。
动态绑定是如何实现的?
第一个问题中基本回答了,主要都是结合虚函数表来答就行。
静态多态和动态多态 。静态多态是指通过模板技术或者函数重载技术实现的多态,其在编译器确定行为。动态多态是指通过虚函数技术实现在运行期动态绑定的技术。
虚函数表
虚函数表是针对类的还是针对对象的?同一个类的两个对象的虚函数表是怎么维护的?
编译器为每一个类维护一个虚函数表,每个对象的首地址保存着该虚函数表的指针,同一个类的不同对象实际上指向同一张虚函数表。
纯虚函数如何定义,为什么对于存在虚函数的类中析构函数要定义成虚函数
为了实现多态进行动态绑定,将派生类对象指针绑定到基类指针上,对象销毁时,如果析构函数没有定义为析构函数,则会调用基类的析构函数,显然只能销毁部分数据。如果要调用对象的析构函数,就需要将该对象的析构函数定义为虚函数,销毁时通过虚函数表找到对应的析构函数。
//纯虚函数定义
virtual ~myClass() = 0;
析构函数能抛出异常吗
答案肯定是不能。
C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分。
(1) 如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。
(2) 通常异常发生时c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。
构造函数和析构函数中调用虚函数吗?
指针和引用的区别
指针保存的是所指对象的地址,引用是所指对象的别名,指针需要通过解引用间接访问,而引用是直接访问;
指针可以改变地址,从而改变所指的对象,而引用必须从一而终;
引用在定义的时候必须初始化,而指针则不需要;
指针有指向常量的指针和指针常量,而引用没有常量引用;
指针更灵活,用的好威力无比,用的不好处处是坑,而引用用起来则安全多了,但是比较死板。
指针与数组千丝万缕的联系
一个一维int数组的数组名实际上是一个int* const 类型;
一个二维int数组的数组名实际上是一个int (*const p)[n];
数组名做参数会退化为指针除了sizeof
智能指针是怎么实现的?什么时候改变引用计数?
构造函数中计数初始化为1
拷贝构造函数中计数值加1
赋值运算符中,左边的对象引用计数减一,右边的对象引用计数加一;
析构函数中引用计数减一;
在赋值运算符和析构函数中如果减一后为0则调用delete释放对象。
share_prt与weak_ptr的区别
//share_ptr可能出现循环引用从而导致内存泄露
class A
{
public:
share_ptr p;
};
class B
{
public:
share_ptr p;
}
int main()
{
while(true)
{
share_prt pa(new A()); //pa的引用计数初始化为1
share_prt pb(new B()); //pb的引用计数初始化为1
pa->p = pb; //pb的引用计数变为2
pb->p = pa; //pa的引用计数变为2
}
//假设pa先离开引用计数减一变为1不为0因此不会调用class A的析构函数因此其成员p也不会被析构pb的引用计数仍然为2
//同理pb离开的时候引用计数也不能减到0
return 0;
}
/*
** weak_ptr是一种弱引用指针其存在不会影响引用计数从而解决循环引用的问题
*/
C++四种类型转换 static_cast, dynamic_cast, const_cast, reinterpret_cast
const_cast用于将const变量转为非const
static_cast用的最多对于各种隐式转换非const转constvoid*转指针等, static_cast能用于多态想上转化如果向下转能成功但是不安全结果未知
dynamic_cast用于动态类型转换。只能用于含有虚函数的类用于类层次间的向上和向下转化。只能转指针或引用。向下转化时如果是非法的对于指针返回NULL对于引用抛异常。要深入了解内部转换的原理。
reinterpret_cast几乎什么都可以转比如将int转指针可能会出问题尽量少用
为什么不使用C的强制转换C的强制转换表面上看起来功能强大什么都能转但是转化不够明确不能进行错误检查容易出错。
内存对齐的原则
从0位置开始存储
变量存储的起始位置是该变量大小的整数倍;
结构体总的大小是其最大元素的整数倍,不足的后面要补齐;
结构体中包含结构体,从结构体中最大元素的整数倍开始存;
如果加入pragma pack(n) 取n和变量自身大小较小的一个。
内联函数有什么优点?内联函数与宏定义的区别?
宏定义在预编译的时候就会进行宏替换;
内联函数在编译阶段,在调用内联函数的地方进行替换,减少了函数的调用过程,但是使得编译文件变大。因此,内联函数适合简单函数,对于复杂函数,即使定义了内联编译器可能也不会按照内联的方式进行编译。
内联函数相比宏定义更安全,内联函数可以检查参数,而宏定义只是简单的文本替换。因此推荐使用内联函数,而不是宏定义。
使用宏定义函数要特别注意给所有单元都加上括号,#define MUL(a, b) a b这很危险正确写法#define MUL(a, b) ((a) (b))
C++内存管理
C++内存分为那几块?(堆区,栈区,常量区,静态和全局区)
每块存储哪些变量?
学会迁移可以说到malloc从malloc说到操作系统的内存管理说道内核态和用户态然后就什么高端内存slab层伙伴算法VMA可以巴拉巴拉了接着可以迁移到fork()。
STL里的内存池实现
STL内存分配分为一级分配器和二级分配器一级分配器就是采用malloc分配内存二级分配器采用内存池。
二级分配器设计的非常巧妙分别给8k16k,..., 128k等比较小的内存片都维持一个空闲链表每个链表的头节点由一个数组来维护。需要分配内存时从合适大小的链表中取一块下来。假设需要分配一块10K的内存那么就找到最小的大于等于10k的块也就是16K从16K的空闲链表里取出一个用于分配。释放该块内存时将内存节点归还给链表。
如果要分配的内存大于128K则直接调用一级分配器。
为了节省维持链表的开销采用了一个union结构体分配器使用union里的next指针来指向下一个节点而用户则使用union的空指针来表示该节点的地址。
STL里set和map是基于什么实现的。红黑树的特点
set和map都是基于红黑树实现的。
红黑树是一种平衡二叉查找树与AVL树的区别是什么AVL树是完全平衡的红黑树基本上是平衡的。
为什么选用红黑数呢因为红黑数是平衡二叉树其插入和删除的效率都是N(logN)与AVL相比红黑数插入和删除最多只需要3次旋转而AVL树为了维持其完全平衡性在坏的情况下要旋转的次数太多。
红黑树的定义:
(1) 节点是红色或者黑色;
(2) 父节点是红色的话,子节点就不能为红色;
(3) 从根节点到每个页子节点路径上黑色节点的数量相同;
(4) 根是黑色的NULL节点被认为是黑色的。
STL里的其他数据结构和算法实现也要清楚
这个问题把STL源码剖析好好看看不仅面试不慌自己对STL的使用也会上升一个层次。
必须在构造函数初始化式里进行初始化的数据成员有哪些
(1) 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
(2) 引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
(3) 没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化
模板特化
(1) 模板特化分为全特化和偏特化模板特化的目的就是对于某一种变量类型具有不同的实现因此需要特化版本。例如在STL里迭代器为了适应原生指针就将原生指针进行特化。
定位内存泄露
(1)在windows平台下通过CRT中的库函数进行检测
(2)在可能泄漏的调用前后生成块的快照,比较前后的状态,定位泄漏的位置
(3)Linux下通过工具valgrind检测
手写strcpy
```
char strcpy(char dst, const char src)
{
assert(dst);
assert(src);
char ret = dst;
while(( dst++ = src++) != '\0');
return ret;
}
//该函数是没有考虑重叠的
char strcpy(char dst, const char src)
{
assert((dst != NULL) && (src != NULL));
char ret = dst;
int size = strlen(src) + 1;
if(dst > src || dst < src + len)
{
dst = dst + size - 1;
src = src + size - 1;
while(size--)
{
dst-- = src--;
}
}
else
{
while(size--)
{
dst++ = src++;
}
}
return ret;
}
- **手写memcpy函数**
void memcpy(void dst, const void src, size_t size)
{
if(dst == NULL || src == NULL)
{
return NULL;
}
void res = dst;
char pdst = (char )dst;
char psrc = (char )src;
if(pdst > psrc && pdst < psrc + size) //重叠
{
pdst = pdst + size - 1;
psrc = pdst + size - 1;
while(size--)
{
*pdst-- = *psrc--;
}
}
else //无重叠
{
while(size--)
{
*dst++ = *src++;
}
}
return ret;
}
- **手写strcat函数**
char strcat(char dst, const char src)
{
char ret = dst;
while(*dst != '\0')
++dst;
while((*dst++ = *src) != '\0');
return ret;
}
- **手写strcmp函数**
int strcmp(const char str1, const char str2)
{
while(*str1 == *str2 && *str1 != '\0')
{
++str1;
++str2;
}
return *str1 - *str2;
} 数据结构与算法
这一块考察范围太广主要靠多刷题吧牛客网剑指OFFERLeetCode等。 Hash表
Hash表实现拉链和分散地址
Hash策略常见的有哪些
STL中hash_map扩容发生什么
(1) 创建一个新桶,该桶是原来桶两倍大最接近的质数(判断n是不是质数的方法用n除2到$sqrt(n)$范围内的数)
(2) 将原来桶里的数通过指针的转换,插入到新桶中(注意STL这里做的很精细没有直接将数据从旧桶遍历拷贝数据插入到新桶而是通过指针转换)
(3) 通过swap函数将新桶和旧桶交换销毁新桶。 树
二叉树结构,二叉查找树实现;
二叉树的六种遍历;
二叉树的按层遍历;
递归是解决二叉树相关问题的神级方法;
树的各种常见算法题( http://blog.csdn.net/xiajun07061225/article/details/12760493)
什么是红黑树?
节点为红色或者黑色;
根节点为黑色;
从根节点到每个叶子节点经过的黑色节点个数的和相同;
如果父节点为红色,那么其子节点就不能为红色。
红黑树与AVL树的区别
红黑树与AVL树都是平衡树但是AVL是完全平衡的(平衡就是值树中任意节点的左子树和右子树高度差不超过1)
红黑树效率更高因为AVL为了保证其完全平衡插入和删除的时候在最坏的情况下要旋转logN次而红黑树插入和删除的旋转次数要比AVL少。
Trie树(字典树)
每个节点保存一个字符
根节点不保存字符
每个节点最多有n个子节点(n是所有可能出现字符的个数)
查询的复杂父为O(k)k为查询字符串长度 链表
链表和插入和删除,单向和双向链表都要会
链表的问题考虑多个指针和递归
(1) 反向打印链表(递归)
(2) 打印倒数第K个节点(前后指针)
(3) 链表是否有环(快慢指针)等等。b ggg 栈和队列
队列和栈的区别 (从实现,应用,自身特点多个方面来阐述,不要只说一个先入先出,先入后出,这个你会别人也会,要展现出你比别人掌握的更深)
典型的应用场景 海量数据问题
十亿整数随机生成可重复中前K最大的数
类似问题的解决方法思路首先哈希将数据分成N个文件然后对每个文件建立K个元素最小/大堆根据要求来选择。最后将文件中剩余的数插入堆中并维持K个元素的堆。最后将N个堆中的元素合起来分析。可以采用归并的方式来合并。在归并的时候为了提高效率还需要建一个N个元素构成的最大堆先用N个堆中的最大值填充这个堆然后就是弹出最大值指针后移的操作了。当然这种问题在现在的互联网技术中一般就用map-reduce框架来做了。
大数据排序相同的思路:先哈希(哈希是好处是分布均匀,相同的数在同一个文件中),然后小文件装入内存快排,排序结果输出到文件。最后建堆归并。
十亿整数(随机生成,可重复)中出现频率最高的一千个 排序算法
排序算法当然是基础内容了,必须至少能快速写出,快排,建堆,和归并
每种算法的时间空间复杂度,最好最差平均情况 位运算 布隆过滤器 几十亿个数经常要查找某一个数在不在里面,使用布隆过滤器,布隆过滤器的原理。布隆过滤器可能出现误判,怎么保证无误差? 网络与TCP/IP
TCP与UDP之间的区别
(1) IP首部TCP首部UDP首部
(2) TCP和UDP区别
(3) TCP和UDP应用场景
(4) 如何实现可靠的UDP
TCP三次握手与四次挥手
详细说明TCP状态迁移过程
(1) 三次握手和四次挥手状态变化;
(2) 2MSL是什么状态作用是什么
TCP相关技术
1. TCP重发机制Nagle算法
2. TCP的拥塞控制使用的算法和具体过程
3. TCP的窗口滑动
TCP客户与服务器模型用到哪些函数
UDP客户与服务器模型用到哪些函数
域名解析过程ARP的机制RARP的实现
1. RARP用于无盘服务器开机后通过发送RARP包给RARP服务器通过mac地址得到IP地址
Ping和TraceRoute实现原理
(1) Ping是通过发送ICMP报文回显请求实现。
(2) TraceRoute通过发送UDP报文设置目的端口为一个不可能的值将IP首部中的TTL分别设置从1到N每次逐个增加如果收到端口不可达说明到达目的主机如果是因为TTL跳数超过路由器会发送主机不可达的ICMP报文。
HTTP http/https 1.0、1.1、2.0
1. http的主要特点:
简单快速: 当客户端向服务器端发送请求时,只是简单的填写请求路径和请求方法即可,然后就可以通过浏览器或其他方式将该请求发送就行了
灵活: HTTP 协议允许客户端和服务器端传输任意类型任意格式的数据对象
无连接: 无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接,采用这种方式可以节省传输时间。(当今多数服务器支持Keep-Alive功能使用服务器支持长连接解决无连接的问题)
无状态: 无状态是指协议对于事务处理没有记忆能力服务器不知道客户端是什么状态。即客户端发送HTTP请求后服务器根据请求会给我们发送数据发送完后不会记录信息。(使用 cookie 机制可以保持 session解决无状态的问题)
2. http1.1的特点
a、默认持久连接节省通信量只要客户端服务端任意一端没有明确提出断开TCP连接就一直保持连接可以发送多次HTTP请求
b、管线化客户端可以同时发出多个HTTP请求而不用一个个等待响应
c、断点续传ftghh
3. http2.0的特点
a、HTTP/2采用二进制格式而非文本格式
b、HTTP/2是完全多路复用的而非有序并阻塞的——只需一个HTTP连接就可以实现多个请求响应
c、使用报头压缩HTTP/2降低了开销
d、HTTP/2让服务器可以将响应主动“推送”到客户端缓存中 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安全性较高
返回状态码
200请求被正常处理
204请求被受理但没有资源可以返回
206客户端只是请求资源的一部分服务器只对请求的部分资源执行GET方法相应报文中通过Content-Range指定范围的资源。
301永久性重定向
302临时重定向
303与302状态码有相似功能只是它希望客户端在请求一个URI的时候能通过GET方法重定向到另一个URI上
304发送附带条件的请求时条件不满足时返回与重定向无关
307临时重定向与302类似只是强制要求使用POST方法
400请求报文语法有误服务器无法识别
401请求需要认证
403请求的对应资源禁止被访问
404服务器无法找到对应资源
500服务器内部错误
503服务器正忙
http 协议头相关
http数据由请求行首部字段空行报文主体四个部分组成
首部字段分为:通用首部字段,请求首部字段,响应首部字段,实体首部字段 https与http的区别如何实现加密传输
https就是在http与传输层之间加上了一个SSL
对称加密与非对称加密 浏览器中输入一个URL发生什么用到哪些协议 浏览器中输入URL首先浏览器要将URL解析为IP地址解析域名就要用到DNS协议首先主机会查询DNS的缓存如果没有就给本地DNS发送查询请求。DNS查询分为两种方式一种是递归查询一种是迭代查询。如果是迭代查询本地的DNS服务器向根域名服务器发送查询请求根域名服务器告知该域名的一级域名服务器然后本地服务器给该一级域名服务器发送查询请求然后依次类推直到查询到该域名的IP地址。DNS服务器是基于UDP的因此会用到UDP协议。
得到IP地址后浏览器就要与服务器建立一个http连接。因此要用到http协议http协议报文格式上面已经提到。http生成一个get请求报文将该报文传给TCP层处理。如果采用https还会先对http数据进行加密。TCP层如果有需要先将HTTP数据包分片分片依据路径MTU和MSS。TCP的数据包然后会发送给IP层用到IP协议。IP层通过路由选路一跳一跳发送到目的地址。当然在一个网段内的寻址是通过以太网协议实现(也可以是其他物理层协议比如PPPSLIP)以太网协议需要直到目的IP地址的物理地址有需要ARP协议。 安全相关
SQL注入
XSS
RCFS
APR欺骗 数据库
SQL语言(内外连接,子查询,分组,聚集,嵌套,逻辑)
MySQL索引方法索引的优化
InnoDB与MyISAM区别
事务的ACID
事务的四个隔离级别
查询优化(从索引上优化从SQL语言上优化)
B-与B+树区别?
MySQL的联合索引(又称多列索引)是什么?生效的条件?
分库分表 Linux 进程与线程 (1) 进程与线程区别?
(2) 线程比进程具有哪些优势?
(3) 什么时候用多进程?什么时候用多线程?
(4) LINUX中进程和线程使用的几个函数
(5) 线程同步?
在Windows下线程同步的方式有互斥量信号量事件关键代码段
在Linux下线程同步的方式有互斥锁自旋锁读写锁屏障(并发完成同一项任务时,屏障的作用特别好使)
知道这些锁之间的区别,使用场景? 进程间通讯方式
管道( pipe ) :管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
命名管道 (FIFO) 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
信号量 信号量用于实现进程间的互斥与同步而不是用于存储进程间通信数据有XSI信号量和POSIX信号量POSIX信号量更加完善。
消息队列( message queue ) 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。(原理一定要清楚,常考)
信号 ( sinal ) 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生,常见的信号。
套接字( socket ) 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
匿名管道与命名管道的区别 :匿名管道只能在具有公共祖先的两个进程间使用。
共享文件映射mmap
mmap建立进程空间到文件的映射在建立的时候并不直接将文件拷贝到物理内存同样采用缺页终端。mmap映射一个具体的文件可以实现任意进程间共享内存映射一个匿名文件可以实现父子进程间共享内存。
常见的信号有哪些? SIGINTSIGKILL(不能被捕获)SIGTERM(可以被捕获)SIGSEGVSIGCHLDSIGALRM 内存管理 1. 虚拟内存的作用?
2. 虚拟内存的实现?
3. 操作系统层面对内存的管理?
4. 内存池的作用STL里 内存池如何实现
5. 进程空间和内核空间对内存的管理不同?
6. Linux的slab层VAM
7. 伙伴算法
8. 高端内存 进程调度 1. Linux进程分为两种实时进程和非实时进程
2. 优先级分为静态优先级和动态优先级,优先级的范围;
3. 调度策略FIFOLRU时间片轮转
4. 交互进程通过平均睡眠时间而被奖励; 死锁 (1) 死锁产生的条件;
(2) 死锁的避免; 命令行
Linux命令 在一个文件中倒序打印第二行前100个大写字母
```
cat filename | head -n 2 | tail -n 1 | grep '[[:upper:]]' -o | tr -d '\n'| cut -c 1-100 | rev
与CPU内存磁盘相关的命令(topfree, df, fdisk)
网络相关的命令netstattcpdump等
sed, awk, grep三个超强大的命名分别用与格式化修改统计和正则查找
ipcs和ipcrm命令
查找当前目录以及字母下以.c结尾的文件且文件中包含"hello world"的文件的路径
创建定时任务 IO模型
五种IO模型 阻塞IO非阻塞IOIO复用信号驱动式IO异步IO
selectpollepoll的区别
select 是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理如果没有返回
存在的问题:
1. 内置数组的形式使得select的最大文件数受限与FD_SIZE
2. 每次调用select前都要重新初始化描述符集将fd从用户态拷贝到内核态每次调用select后都需要将fd从内核态拷贝到用户态
3. 轮寻排查当文件描述符个数很多时,效率很低; poll 通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体该结构体保存描述符的信息每增加一个文件描述符就向数组中加入一个结构体结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。轮寻排查的问题未解决。
epoll 轮寻排查所有文件描述符的效率不高使服务器并发能力受限。因此epoll采用只返回状态发生变化的文件描述符便解决了轮寻的瓶颈。
为什么使用IO多路复用最主要的原因是什么
epoll有两种触发模式这两种触发模式有什么区别编程的时候有什么区别
上一题中编程的时候有什么区别,是在边缘触发的时候要把套接字中的数据读干净,那么当有多个套接字时,在读的套接字一直不停的有数据到达,如何保证其他套接字不被饿死(面试网易游戏的时候问的一个问题,答不上来,印象贼深刻)。
1. select/poll/epoll区别
2. 几种网络服务器模型的介绍与比较
3. epoll为什么这么快 (搞懂这篇文章关于IO复用的问题就信手拈来了) 线程池 Linux的API
fork与vfork区别
fork和vfork都用于创建子进程。但是vfork创建子进程后父进程阻塞直到子进程调用exit()或者excle()。
对于内核中过程fork通过调用clone函数然后clone函数调用do_fork()。do_fork()中调用copy_process()函数先复制task_struct结构体然后复制其他关于内存文件寄存器等信息。fork采用写时拷贝技术因此子进程和父进程的页表指向相同的页框。但是vfork不需要拷贝页表因为父进程会一直阻塞直接使用父进程页表。
exit()与_exit()区别
exit()清理后进入内核_exit()直接陷入内核。
孤儿进程与僵死进程
1. 孤儿进程是怎么产生的?
2. 僵死进程是怎么产生的?
3. 僵死进程的危害?
4. 如何避免僵死进程的产生?
Linux是如何避免内存碎片的
1. 伙伴算法,用于管理物理内存,避免内存碎片;
2. 高速缓存Slab层用于管理内核分配内存避免碎片。
共享内存的实现原理?
共享内存实现分为两种方式一种是采用mmap另一种是采用XSI机制中的共享内存方法。mmap是内存文件映射将一个文件映射到进程的地址空间用户进程的地址空间的管理是通过vm_area_struct结构体进行管理的。mmap通过映射一个相同的文件到两个不同的进程就能实现这两个进程的通信采用该方法可以实现任意进程之间的通信。mmap也可以采用匿名映射不指定映射的文件但是只能在父子进程间通信。XSI的内存共享实际上也是通过映射文件实现只是其映射的是一种特殊文件系统下的文件该文件是不能通过read和write访问的。
二者区别:
1、 系统V共享内存中的数据从来不写入到实际磁盘文件中去而通过mmap()映射普通文件实现的共享内存通信可以指定何时将数据写入磁盘文件中。注前面讲到系统V共享内存机制实际是通过映射特殊文件系统shm中的文件实现的文件系统shm的安装点在交换分区上系统重新引导后所有的内容都丢失。
2、 系统V共享内存是随内核持续的即使所有访问共享内存的进程都已经正常终止共享内存区仍然存在除非显式删除共享内存在内核重新引导之前对该共享内存区域的任何改写操作都将一直保留。
3、 通过调用mmap()映射普通文件进行进程间通信时一定要注意考虑进程何时终止对通信的影响。而通过系统V共享内存实现通信的进程则不然。注这里没有给出shmctl的使用范例原理与消息队列大同小异。
系统调用与库函数(open, close, create, lseek, write, read)
同步方法有哪些?
1. 互斥锁,自旋锁,信号量,读写锁,屏障
2. 互斥锁与自旋锁的区别互斥锁得不到资源的时候阻塞不占用cpu资源。自旋锁得不到资源的时候不停的查询而然占用cpu资源。
3. 死锁 其他
++i是否是原子操作
明显不是,++i主要有三个步骤把数据从内存放在寄存器上在寄存器上进行自增把数据从寄存器拷贝会内存每个步骤都可能被中断。
判断大小端
```
union un
{
int i;
char ch;
};
void fun()
{
union un test;
test.i = 1;
if(ch == 1)
cout << "小端" << endl;
else
cout << "大端" << endl;
}
``` 设计模式
单例模式线程安全的写法
STL里的迭代器使用了迭代器模式
MVC的理解
分布式系统
map_reduce原理 (这篇文章讲的很通俗易懂)
负载均衡
CDN
取消 确定
确定