c++面试 掌握的东西总结

长文警告⚠ 至少要读1小时!!!!建议先收藏

各位取其精华,有不对的,望指出,也有写不够详细,只是小总结。 

自己亲手制作,伴随我整个找工作过程,不喜勿喷,喜欢就收藏和点赞,我都是各种手机总结,错误之处,还请指出!!!(手动狗头)

C++ 知识面试总结

编程的oj输入和输出

1、你为什么要从事这份工作

2、你真的适合这份工作吗?

3、你是否愿意投入足够的时间和精力?

4、你未来是否长期投入这个工作方向?

5、你希望通过自己的努力实现什么目标?

职业规划参考模板

其他类

分布式和集群的区别?

Sizeof() 和strlen的区别

malloc calloc realloc

C++中sizeof的(大小)

Cpp  和Python的区别:

Cpp  编译过程

X C++左值和右值

指针和引用的定义和性质区别:

Static的作用

为什么传引用比传指针安全

头文件重复的包含和重复的引用

#define和inline的区别:

const和#define区别?

Const的用法

时间复杂度为o(1)、o(logn) 、o(n^2) 、o(nlogn)的含义是什么

判断机器大小端?

X  static_cast和dynamic_cast的区别

c++与类型转换相关的关键字static_cast

share-ptr

STL容器类

vector push_back 迭代器是否失效

map,unordered_map的区别,(unordered_map)内存利用率高

Vector和List(双向链表)的区别

堆与栈的比较

STL中的容器

hash_map和map的区别在哪里?

什么时候用hash_map、map?

内存分配

野指针?怎么避免

c、c++内存分配情况

strcpy、sprintf与memcpy的区别

Strcpy和mencpy的区别

内存耗尽怎么办?

有malloc/free为什么还要new/delete?

malloc/free的使用要点

new/delete的使用要点

malloc和 new有什么区别

内存泄漏的定义  (典型的例子)

内存泄漏的发生方式

检测内存泄漏

三种内存对象的比较

C++构造、多态、重载类

什么是多态 继承 封装

OOP的五个原则:

C++虚表

深拷贝、浅拷贝

成员变量的初始化顺序

为什么不能在类的内部定义以及初始化static成员变量,而必须要放到类的外部定义

重载和重写的区别

虚函数和纯虚函数的区别

虚函数表(vfptr)

右值引用

进程和线程类

Cpp  多线程怎么保证线程安全

进程通信 方式,哪个效率高

IPC方式(inter progress communication)

五种通讯方式总结:

线程的6种状态解释

进程和线程有什么区别 线程共享哪些东西?

生产者和消费者

死锁出现的场景

死锁产生的原因

产生死锁的四个必要条件:

避免死锁的方法:

死锁避免和死锁预防的区别:

利用银行家算法避免死锁:

死锁的解除

设计模式

单例模式:

计算机网络类

Get和post的区别

GET 方法参数写法是固定的吗?

POST比GET安全?

GET的长度限制是怎么回事?

POST会产生两个TCP数据包?

SMTP是基于TCP还是UDP?

Select、epoll的比较

TCP/IP层数和功能

包、帧、数据包

TCP三次握手和四挥手

为什么三次握手和四次挥手?

为什么客户端最后还要等待2MSL?

https的请求方法

Time_wait为啥是2ms而不是3或者1?

列举http的状态码

Linux类:

Makefile的书写

Make的实现原理

Make的工作流程

Makefile连接库:

Makefile的参数表示:

编译内核相关:

设置环境变量

编译步骤前提 图片:

编译步骤文字:



 

Boss :对于这个职位,如果有机会上岗,那么一天的工作情况?

1、贵公司从一个好想法的产生到最终产品实现的过程是怎样的?

2、为了更好地胜任这个岗位,我还需要补充哪些技能?

3、入职后是否有产品培训和技能培训?

4、这个职位在公司的发展前景是怎样的?

5、有什么晋升机制?在什么条件下,可以获得晋升机会?

6、赢得这个岗位需要几轮面试?接下来的流程是什么?

7、团队成员有多少人?大家怎么分工?目前团队的核心工作是哪些?

8、如果我来到公司之后,每天的日常工作是什么?

9、公司对我这个职位的期望是什么?

10、如何评估员工在试用期间的表现?考核标准是什么?

 

编程的oj输入和输出

牛客OJ C++输入输出看这一篇。

https://blog.csdn.net/xiao_jj_jj/article/details/107945024

1、你为什么要从事这份工作

所以,我们应该稍微思考得更加深度一点:

我看好一份工作,因为自己看好这个行业,另外加上工作带给自己真正的获得感、成就感,包括但不限于现金收入、成就感、自我实现等等。

所以,表达自己对这个行业、公司的认同,其次再表达出对这份工作的认同,才是正确的解答方式。

2、你真的适合这份工作吗?

这个问题的答案,不仅仅关乎到公司对你的评价,也关系到你是否真的适合从事这份工作。

比如,你今天要面试的是产品经理岗。

这个岗位的要求是,要有足够的用户洞察力、逻辑分析能力和团队协作能力,如果你的回答Get不到这个点,或者说你本身并不擅长这些事情,那真的不要勉强了。

关于如何判断自己适合什么工作,在 :如何给自己做一份职业规划? 中有详细的解释。

3、你是否愿意投入足够的时间和精力?

这个问题,其实是在考察里未来对这份工作的投入程度,如果你对未来没有野心和规划,只是想走一步看一步,那遇到困难的时候,也很难真的全身心想办法去解决。

所以,不管是自己设想,还是真的到了面试现场,都得问问自己,你是否愿意投入足够的时间和精力?

4、你未来是否长期投入这个工作方向?

你是否愿意长期从事这个方向,意在考察你的稳定性,是希望你不要入职后短时间就离职,毕竟公司的招聘、培训、管理成本都很高。

5、你希望通过自己的努力实现什么目标?

回答这一点的目的很简单,其实就是想了解你未来的规划和目标是否合理,考察你对这个岗位的认知、理解,也是变相考察你的用心程度。

职业规划参考模板

 

其他类

分布式和集群的区别?

简单来说 就是:分布式分开处理(一个人只能玩一个地方);集群集体一起搞(群p),手动狗头,方便理解了吧!!!!

Sizeof() 和strlen的区别

1、Sizeof() 是操作符,strlen是函数

2、数组做sizeof的参数不会退化,会计算出数组的大小(类型*数组元素),数组传递给strlen会退化为指针,计算的就不是数组的大小了,而是该类型的大小

3、大部分编译程序在编译的时候就就将sizeof的大小计算出来了,而strlen是在运行的时候才能计算出来

4、strlen是遇到‘\0’结束,大小不计算’\0’,

5、在malloc分配大小的时候,最好memset(A, 0, sizeof(A))

malloc calloc realloc

void * malloc (size_t size);

从堆中分配内存空间,内存分配大小为 size。如果内存分配成功,则返回首地址;如果内存分配失败,则返回 NULL。

void * calloc (size_t num, size_t size );

用于从堆中分配 num 个相邻的内存单元,每个内存单元的大小为 size。如果内存分配成功则返回第一个内存单元的首地址;否则内存分配失败,则返回 NULL。

calloc 函数与语句“malloc(num*size)的不同:使用 calloc 函数分配内存时,会将内存内容初始化为 0。

void * realloc (void * ptr, size_t size );

更改已经配置的内存空间,它同样是从堆中分配内存的。当程序需要扩大一块内存空间时,realloc 函数试图直接从堆上当前内存段后面的字节中获得更多的内存空间,即它将首先判断当前的指针是否有足够的连续存储空间,如果有,则扩大 ptr 指向的地址,并且将 ptr 返回(返回原指针);如果当前内存段后面的空闲字节不够,那么将先按照 size 指定的大小分配空间(使用堆上第一个能够满足这一要求的内存块),并将原有数据从头到尾拷贝到新分配的内存区域,然后释放原来 ptr 所指内存区域,同时返回新分配的内存区域的首地址,即重新分配存储器块的地址。

C++中sizeof的(大小)

x86

sizeof(char)  1

sizeof(char*)  4

sizeof(int)  4

sizeof(int*)  4

sizeof(double)  8

sizeof(double*)  4

sizeof(float)  4

sizeof(float*)  4

sizeof(string)  28

sizeof(string*)  4

 

x64

sizeof(char)  1

sizeof(char*)  8

sizeof(int)  4

sizeof(int*)  8

sizeof(double)  8

sizeof(double*)  8

sizeof(float)  4

sizeof(float*)  8

sizeof(string)  40

sizeof(string*)  8

指针只与系统有关,与类型无关

 

Cpp  和Python的区别:

性质不同:

Python 是脚本语言 边解释边运行  可以不用编译就直接运行  这样效率慢

C++ 先编译在运行。运行速度快 对于大型程序,效果显著,但是程序更新的时候 需要重新编译,

语法不同:

Python语法简单。语句简单。上手容易

C++语法复杂。格式要求高。上手较难,比如使用之前都需声明

应用场景不同:

Python在人工智能领域有很大的优势,具有很多的库以及很多api可以直接调用

C++ 在游戏开发 界面开发以及自动驾驶等方面应用广泛

 

 

Cpp  编译过程

预处理-> 编译-> 链接成应用程序

预处理先解决头文件的包含以及宏定义关键字等的处理

编译过程将源码转换为高级语言转换为机器语言

链接过程就是将目标文件和整个程序所用到的库文件链接形成最终的应用程序

 

X C++左值和右值

在 c 中,左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),右值指的则是只能出现在等号右边的变量(或表达式)

当一个右值被 const 引用指向时,它的生命周期就被延长了,其中暗藏的逻辑其实就是:右值不能当成左值使用(但左值可以当成右值使用)

右值的两个特性:

1) 允许调用成员函数。

2) 只能被 const reference 指向

 

一个左值没有数组类型,没有不完全类型,没有const修饰的类型,并且如果它是结构体或联合体,则没有任何const修饰的成员(包含,递归包含,任何成员元素的集合)

int a = 1;     //a是一个左值

int b = 2;     //b是一个左值

int c = a + b; //+需要右值,所以a和b都转换成右值,并且返回一个右值‘&’ 符号在C++中扮演了另一个重要角色-它允许定义应用类型。这被称为“左值引用”。非const左值引用不能被赋右值,因为这将要求一个无效的右值到左值的转换

 

指针和引用的定义和性质区别:

(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:

(2)引用不可以为空,当被创建的时候,必须初始化,而指针可以是空值,可以在任何时候被初始化。

(3)可以有const指针,但是没有const引用;

(4)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)

(5)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;

(6)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。

(7)”sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;

(8)指针和引用的自增(++)运算意义不一样;

(9)如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄漏;

C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。

 

Static的作用

这个里面有详细介绍

https://www.cnblogs.com/XuChengNotes/p/10403523.html

 

1)先来介绍它的第一条也是最重要的一条:隐藏

 当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。同理,对于一个函数而言,在本模块内加static后,只能在本模块中可见,其余模块不可见

 

 

如果加了static,就会对其它源文件隐藏,此时,main函数中就获取不到a的值了

2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。

3)在类中的static修饰的成员变量属于类可以共享,相当于全局变量,不属于具体的某个实例或者对象,也就是说当某个类的实例修改了该静态成员变量,其修改值为该类的其它所有实例所见,还有必须在类外初始化,static成员变量访问形式:1°可以被对象访问 2° 可以被类访问 3°可以被对象指针访问

4)普通函数有this指针,指针可访问所有成员,但在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,所以只能访问类的static成员变量。static只能在声明函数时加,不能再定义时候加(类中声明,类外初始化)

char a = 'A'; // global variable

void msg()

{
    printf("Hello\n");

}               //存在a.C文件中

int main(void)

{   

    extern char a;    // extern variable must be declared before use

    printf("%c ", a);

    (void)msg();

    return 0;

}               //存在main.c文件中

程序的运行结果是:

A Hello

 

为什么传引用比传指针安全

由于不存在空引用,并且引用一旦被初始化为指向一个对象,就不能改变为另一个对象的应用,因此引用很安全

对于指针来说  他可以随时指向别的对象 并且可以不被初始化或者NULL 所以不安全,const指针仍然存在空指针,并且有可能产生野指针

 

头文件重复的包含和重复的引用

1、头文件重复包含带来的最大坏处是会使程序在编译链接的时候崩溃

2、多次包含相同的头文件,会导致编译器在后面的编译步骤多次编译该头文件,工程代码量小还好,工程量一大会使整个项目编译速度变的缓慢,后期的维护修改变得困难

解决办法:

#ifndef _XXX

#define _XXX

…

#endif

 

 

#define和inline的区别:

Define只是原样替换,inline是内嵌,会做安全检查,而define不会做安全检查,两者效率都很高,inline的更高,因为安全检查

Define属于关键字,inline属于类中的函数,是可以使用类中的权限的

const和#define区别?

(1)const和#define都可以定义常量,但是const用途更广。

(2)const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

(3) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试

Const的用法

Const int* a;        /常量指针 指针指向可以改变 值不可以改变

Int *const a          //指针常量 指向的地址是常量不可改变,地址里面的值是可以改变的

Const int const int  //指针和数据都是不能更该的,

这一篇专门整理了:

https://blog.csdn.net/xiao_jj_jj/article/details/107683212

时间复杂度为o(1)、o(logn) 、o(n^2) 、o(nlogn)的含义是什么

O(1)—常数阶:最低的时空复杂度,也就是耗时与输入数据大小无关,无论输入数据增大多少倍,耗时/耗空间都不变。 哈希算法就是典型的O(1)时间复杂度,无论数据规模多大,都可以在一次计算后找到目标。

时间复杂度为O(n)—线性阶,就代表数据量增大几倍,耗时也增大几倍。比如常见的遍历算法

时间复杂度O(n^2)—平方阶, 就代表数据量增大n倍时,耗时增大n的平方倍,这是比线性更高的时间复杂度。比如冒泡排序,就是典型的O(n x n)的算法,对n个数排序,需要扫描n x n次

时间复杂度O(logn)—对数阶,当数据增大n倍时,耗时增大logn倍(这里的log是以2为底的,比如,当数据增大256倍时,耗时只增大8倍,是比线性还要低的时间复杂度)。二分查找就是O(logn)的算法,每找一次排除一半的可能,256个数据中查找只要找8次就可以找到目标

时间复杂度O(nlogn)—线性对数阶,就是n乘以logn,当数据增大256倍时,耗时增大256*8=2048倍。这个复杂度高于线性低于平方。归并排序就是O(nlogn)的时间复杂度。

判断机器大小端?

大端存储:高字节存储在低地址中,即高位先存

小端存储:低字节存储在高地址中,即低位先存;、

那这个大小端到底有什么区别,对我们编写程序有什么影响呢?这在我们正常编程下都是没有问题的也没有什么影响,但是当我们在跨平台进行操作的时候,假如我们在程序中进行位运算很有可能我们在当前小端平台下测试没有问题,但是在大端平台下就会使程序出现bug。我们目前常用的linux和windows都是小端系统

实现

int main()

 {
     int a = 0x12345678;   //12为高位  78为低位   

     char i = a;        
     printf("%x", i);    //得到12 (高位存在低位 大端)    //若得到78 (低位存在低位  小端)

     return 0;

 }

定义一个十六进制的数据,数据类型为int型,定义一个char类型的数据,int数据类型的大小为四个字节,而char类型的数据为一个字节,所以将int类型的数据赋值给char时会丢失三个字节的数据,char类型中存储的是int类型中低地址的数据,这时候char类型获取的数据输出之后,如果输出的是12那就说明你低地址位置的数据是12,那就说明你的数据是大端存储,如果输出的结果是78当前条件下就是小端存储。

 

X  static_cast和dynamic_cast的区别

前者属于隐士转换,后者属于动态转换,多用于含有继承关系的基类和父类进行转换,并且子转父属于安全的,父转子是不安全的

c++与类型转换相关的关键字static_cast

static_cast:

运算符完成相关类型之间的转换 float到int   

reinterpret_cast:

处理互不相关类型之间的转换,从整数到指针,一种类型的指针到另一种类型的指针

dynamic_cast:

处理基类型到派生类型的转换,基类必须有虚函数,即为多态的时候,可以转换

const_cast:

用来移除变量的const或volatile限定符。强制去除const或者volatile(是防止编译器对代码进行优化)

share-ptr

这个是智能指针,可以在任何地方都不使用时自动删除相关指针,从而帮助彻底消除内存泄漏和悬空指针的问题。它遵循共享所有权的概念,即不同的 shared_ptr 对象可以与相同的指针相关联,并在内部使用引用计数机制来实现这一点。

每个 shared_ptr 对象在内部指向两个内存位置:

1、指向对象的指针。

2、用于控制引用计数数据的指针。

共享所有权如何在参考计数的帮助下工作:

1、当新的 shared_ptr 对象与指针关联时,则在其构造函数中,将与此指针关联的引用计数增加1。

2、当任何 shared_ptr 对象超出作用域时,则在其析构函数中,它将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,它使用delete函数删除该内存

STL容器类

vector push_back 迭代器是否失效

对于序列式容器,比如vector,删除当前的iterator会使后面所有元素的iterator都失效。这是因为顺序容器内存是连续分配(分配一个数组作为内存),删除一个元素导致后面所有的元素会向前移动一个位置。(删除了一个元素,该元素后面的所有元素都要挪位置,所以,iter++,已经指向的是未知内存)。

map,unordered_map的区别,(unordered_map)内存利用率高

map map内部实现了一个红黑树,该结构具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素,因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行这样的操作,故红黑树的效率决定了map的效率。

unordered_map: unordered_map内部实现了一个哈希表,因此其元素的排列顺序是杂乱的,无序的

map优点:

有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作

红黑树,内部实现一个红黑书使得map的很多操作在lgnlgn的时间复杂度下就可以实现,因此效率非常的高

缺点

空间占用率高,因为map内部实现了红黑树,虽然提高了运行效率,但是因为每一个节点都需要额外保存父节点,孩子节点以及红/黑性质,使得每一个节点都占用大量的空间

适用处,对于那些有顺序要求的问题,用map会更高效一些

unordered_map优点:

因为内部实现了哈希表,因此其查找速度非常的快

缺点:

哈希表的建立比较耗费时间

适用处,对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map

 

Vector和List(双向链表)的区别

底层结构

  vector的底层结构是动态顺序表,在内存中是一段连续的空间

  list的底层结构是带头节点的双向循环链表,在内存中不是一段连续的空间

随机访问

  vector支持随机访问,可以利用下标精准定位到一个元素上,访问某个元素的时间复杂度是O(1)。

  list不支持随机访问,要想访问list中的某个元素只能是从前向后或从后向前依次遍历,时间复杂度是O(N)。

插入和删除

  vector任意位置插入和删除的效率低,因为它每插入一个元素(尾插除外),都需要搬移数据,时间复杂度是O(N),而且插入还有可能要增容,这样一来还要开辟新空间,拷贝元素,是旧空间,效率会更低。

  list任意位置插入和删除的效率高,他不需要搬移元素,只需要改变插入或删除位置的前后两个节点的指向即可,时间复杂度为O(1)。

空间利用率

  vector由于底层是动态顺序表,在内存中是一段连续的空间,所以不容易造成内存碎片,空间利用率高、缓存利用率高

  list的底层节点动态开辟空间,容易造成内存碎片,空间利用率低,缓存利用率低。

迭代器

  vector的迭代器是原生态指针。

  list对原生态指针(节点的指针)进行了封装。

迭代器失效

  vector在插入元素时的时候,要重新给所有的迭代器赋值,因为插入元素有可能导致扩容,只是原来的迭代器失效,删除元素时当前迭代器同样需要重新赋值,否则会失效。

  list在插入元素的时候不会导致迭代器实现,删除元素的时候指挥导致当前迭代器失效,其他的迭代器不会受到影响。

使用场景

  vector适合需要高效率存储,需要随机访问,并且不管行插入和删除效率的场景。

  list适合有大量的插入和删除操作,并且不关心随机访问的场景

 

堆与栈的比较

  (1)申请方式

     stack(栈):由系统分配,例如声明在函数中一个局部变量 int b;

     heap (堆):需要程序员自己申请并指明大小,如 malloc和new

  (2)申请后系统的响应

    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则 将报异常提示栈溢出。

    堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所 申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

  对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。

  由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

 (3)申请大小的限制

    栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的, 在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

    堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

 (4)申请效率的比较

    栈由系统自动分配,速度较快。但程序员是无法控制的。

    堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便,另外,在 WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是栈,而是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

 (5)堆和栈中的存储内容

    栈:在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的 下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

STL中的容器

一、顺序容器:

vector:可变大小数组;

deque:双端队列;

list:双向链表;

forward_list:单向链表;

array:固定大小数组;

string:与vector相似的容器,但专门用于保存字符。

二、关联容器:

按关键字有序保存元素:(底层实现为红黑树)

map:关联数组;保存关键字-值对;

set:关键字即值,即只保存关键字的容器;

multimap:关键字可重复的map;

multiset:关键字可重复的set;

无序集合:

unordered_map:用哈希函数组织的map;

unordered_set:用哈希函数组织的set;

unordered_multimap:哈希组织的map;关键字可以重复出现;

unordered_multiset:哈希组织的set;关键字可以重复出现。

hash_map和map的区别在哪里?

构造函数。hash_map需要hash函数,等于函数;map只需要比较函数(小于函数).

存储结构。hash_map采用hash表存储,map一般采用红黑树(RB Tree)实现。因此其memory数据结构是不一样的。

什么时候用hash_map、map?

总体来说,hash_map 查找速度会比map快,而且查找速度基本和数据数据量大小,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n)小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且hash_map的构造速度较慢。

 

内存分配

野指针?怎么避免

野指针不是 NULL指针,它是随即指向一块内存的指针。野指针是很危险的,会导致内存泄漏,if语句对它不起作用。

    导致野指针的原因有两种:

   (1)野指针指向了一块没有访问权限的内存。(即指针没有初始化)

   (2)野指针指向了一个已经释放的内存。

    因为野指针是因为我们的不良编程习惯造成的,所以我们养成良好的编程习惯才能尽可能避免野指针的出现。下面是一些好的编程习惯:

    首先,尽量将指针初始化为NULL,用完后也可以将其赋值为NULL。因为NULL代表0地址,0地址不能进行操作。这样做在代码出现段错误时,有利于找到错误并修改。

    其次,使用malloc分配内存。(在堆空间内分配)分配后要进行检查是否分配成功,最后要进行释放内存

c、c++内存分配情况

C、C++中内存分配方式可以分为三种:

(1)从静态存储区域分配

   内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快、不容易出错,因为有系统会善后。例如全局变量,static变量等。

(2)在栈上分配

  在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(3)从堆上分配

  即动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活。如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块。

  一个C、C++程序编译时内存分为5大存储区:堆区、栈区、全局区、文字常量区、程序代码区。

strcpy、sprintf与memcpy的区别

(1)操作对象不同,strcpy的两个操作对象均为字符串,sprintf的操作源对象可以是多种数据类型,目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。

(2)执行效率不同,memcpy最高,strcpy次之,sprintf的效率最低。

(3)实现功能不同,strcpy主要实现字符串变量间的拷贝,sprintf主要实现其他数据类型格式到字符串的转化,memcpy主要是内存块间的拷贝。

  说明:strcpy、sprintf与memcpy都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来选择合适的函数实现拷贝功能。

Strcpy和mencpy的区别

char *strcpy(char* dest, const char *src);

void *memcpy(void *destin, void *source, unsigned n)

复制的内容不同。Strcpy只能复制字符串,mencpy可以复制任何内容 比如字符数组、整形、结构体、类等

复制的方法不同,strcpy不需要指定长度,他遇到的字符串\0就结束,mencoy取决于第三个参数的大小,也就是说 第三个参数决定这个mencpy的复制长度

用途不同:通常在复制字符串的时候用strcpy 而若需要复制其他数据类型的时候用mencpy

 

内存耗尽怎么办?

  1. 判断指针是否为NULL,如果是则马上用return语句终止本函数
  2. 判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行
  3. 为new和malloc设置异常处理函数。例如Visual C++可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,也可以让malloc享用与new相同的异常处理函数
  4. 对于32位以上的应用程序,“内存耗尽”错误处理程序毫无用处。这下可把Unix和Windows程序员们乐坏了:反正错误处理程序不起作用,我就不写了,省了很多麻烦。我不想误导读者,必须强调:不加错误处理

 

有malloc/free为什么还要new/delete?

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

对于非内部数据类型的对象而言,光用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。

 所以我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存

malloc/free的使用要点

void * malloc(size_t size);

int *p = (int *) malloc(sizeof(int) * length);

我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。

* malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。

* malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住int, float等数据类型的变量的确切字节数。在malloc的“()”中使用sizeof运算符是良好的风格, 例如int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节

void free( void * memblock );

为什么free函数不象malloc函数那样复杂呢?这是因为指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p是NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。

new/delete的使用要点

int *p2 = new int[length];

因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式

在用delete释放对象数组时,留意不要丢了符号‘[]

delete []objects; // 正确的用法

delete objects; // 错误的用法

malloc和 new有什么区别

(1)new、delete 是操作符,可以重载,只能在C++中使用。

(2)malloc、free是函数,可以覆盖,C、C++中都可以使用。

(3)new 可以调用对象的构造函数,对应的delete调用相应的析构函数。

(4)malloc仅仅分配内存,free仅仅回收内存,并不执行构造和析构函数。

(5)new、delete返回的是某种数据类型指针,malloc、free返回的是void指针。

 注意:malloc申请的内存空间要用free释放,而new申请的内存空间要用delete释放,不要混用。因为两者实现的机理不同。

内存泄漏的定义  (典型的例子)

一般我们常说的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。应用程序一般使用malloc,realloc,new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。以下这段小程序演示了堆内存发生泄漏的情形:

void MyFunction(int nSize)

{

 char* p= new char[nSize];

 if( !GetStringFrom( p, nSize ) ){

  MessageBox(“Error”);

  return;

 }

 …//using the string pointed by p;

 delete p;

}

函数GetStringFrom()返回零的时候,指针p指向的内存就不会被释放。这是一种常见的发生内存泄漏的情形。程序在入口处分配内存,在出口处释放内存,但是c函数可以在任何地方退出,所以一旦有某个出口处没有释放应该释放的内存,就会发生内存泄漏。

广义的说,内存泄漏不仅仅包含堆内存的泄漏,还包含系统资源的泄漏(resource leak),比如核心态HANDLE,GDI Object,SOCKET, Interface等,从根本上说这些由操作系统分配的对象也消耗内存,如果这些对象发生泄漏最终也会导致内存的泄漏。而且,某些对象消耗的是核心态内存,这些对象严重泄漏时会导致整个操作系统不稳定。所以相比之下,系统资源的泄漏比堆内存的泄漏更为严重。

void CMyView::OnPaint( CDC* pDC )

{

 CBitmap bmp;

 CBitmap* pOldBmp;

 bmp.LoadBitmap(IDB_MYBMP);

 pOldBmp = pDC->SelectObject( &bmp );

 …

 if( Something() ){

  return;

 }

 pDC->SelectObject( pOldBmp );

 return;

}

当函数Something()返回非零的时候,程序在退出前没有把pOldBmp选回pDC中,这会导致pOldBmp指向的HBITMAP对象发生泄漏。这个程序如果长时间的运行,可能会导致整个系统花屏。这种问题在Win9x下比较容易暴露出来,因为Win9x的GDI堆比Win2k或NT的要小很多。

 

 

内存泄漏的发生方式

以发生的方式来分类,内存泄漏可以分为4类:

1. 常发性内存泄漏。发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。比如例二,如果Something()函数一直返回True,那么pOldBmp指向的HBITMAP对象总是发生泄漏。

2. 偶发性内存泄漏。发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生。比如例二,如果Something()函数只有在特定环境下才返回True,那么pOldBmp指向的HBITMAP对象并不总是发生泄漏。常发性和偶发性是相对的。对于特定的环境,偶发性的也许就变成了常发性的。所以测试环境和测试方法对检测内存泄漏至关重要。

3. 一次性内存泄漏。发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。比如,在类的构造函数中分配内存,在析构函数中却没有释放该内存,但是因为这个类是一个Singleton,所以内存泄漏只会发生一次。另一个例子:

如果程序在结束的时候没有释放g_lpszFileName指向的字符串,那么,即使多次调用SetFileName(),总会有一块内存,而且仅有一块内存发生泄漏。

char* g_lpszFileName = NULL;

void SetFileName( const char* lpcszFileName )

{

 if( g_lpszFileName ){

  free( g_lpszFileName );

 }

 g_lpszFileName = strdup( lpcszFileName );

}

 

4. 隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏。举一个例子:

lass Connection

{

 public:

  Connection( SOCKET s);

  ~Connection();

  …

 private:

  SOCKET _socket;

  …



};

class ConnectionManager

{

 public:

  ConnectionManager(){}

  ~ConnectionManager(){

   list::iterator it;

   for( it = _connlist.begin(); it != _connlist.end(); ++it ){

    delete (*it);

   }

   _connlist.clear();

  }

  void OnClientConnected( SOCKET s ){

   Connection* p = new Connection(s);

   _connlist.push_back(p);

  }

  void OnClientDisconnected( Connection* pconn ){

   _connlist.remove( pconn );

   delete pconn;

  }

 private:

  list _connlist;

};

 假设在Client从Server端断开后,Server并没有呼叫OnClientDisconnected()函数,那么代表那次连接的Connection对象就不会被及时的删除(在Server程序退出的时候,所有Connection对象会在ConnectionManager的析构函数里被删除)。当不断的有连接建立、断开时隐式内存泄漏就发生了。

从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它不会堆积,而隐式内存泄漏危害性则非常大,因为较之于常发性和偶发性内存泄漏它更难被检测到。

检测内存泄漏

 检测内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住这两个函数,我们就能跟踪每一块内存的生命周期,比如,每当成功的分配一块内存后,就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这样,当程序结束的时候,list中剩余的指针就是指向那些没有被释放的内存。这里只是简单的描述了检测内存泄漏的基本原理,详细的算法可以参见Steve Maguire的<<Writing Solid Code>>。

如果要检测堆内存的泄漏,那么需要截获住malloc/realloc/free和new/delete就可以了(其实new/delete最终也是用malloc/free的,所以只要截获前面一组即可)。对于其他的泄漏,可以采用类似的方法,截获住相应的分配和释放函数。比如,要检测BSTR的泄漏,就需要截获SysAllocString/SysFreeString;要检测HMENU的泄漏,就需要截获CreateMenu/ DestroyMenu。(有的资源的分配函数有多个,释放函数只有一个,比如,SysAllocStringLen也可以用来分配BSTR,这时就需要截获多个分配函数)

在Windows平台下,检测内存泄漏的工具常用的一般有三种,MS C-Runtime Library内建的检测功能;外挂式的检测工具,诸如,Purify,BoundsChecker等;利用Windows NT自带的Performance Monitor。这三种工具各有优缺点,MS C-Runtime Library虽然功能上较之外挂式的工具要弱,但是它是免费的;Performance Monitor虽然无法标示出发生问题的代码,但是它能检测出隐式的内存泄漏的存在,这是其他两类工具无能为力的地方。

 

三种内存对象的比较

  栈对象的优势是在适当的时候自动生成,又在适当的时候自动销毁,不需要程序员操心;而且栈对象的创建速度一般较堆对象快,因为分配堆对象时,会调用operator new操作,operator new会采用某种内存空间搜索算法,而该搜索过程可能是很费时间的,产生栈对象则没有这么麻烦,它仅仅需要移动栈顶指针就可以了。但是要注意的是,通常栈空间容量比较小,一般是1MB~2MB,所以体积比较大的对象不适合在栈中分配。特别要注意递归函数中最好不要使用栈对象,因为随着递归调用深度的增加,所需的栈空间也会线性增加,当所需栈空间不够时,便会导致栈溢出,这样就会产生运行时错误。

  堆对象,其产生时刻和销毁时刻都要程序员精确定义,也就是说,程序员对堆对象的生命具有完全的控制权。我们常常需要这样的对象,比如,我们需要创建一个对象,能够被多个函数所访问,但是又不想使其成为全局的,那么这个时候创建一个堆对象无疑是良好的选择,然后在各个函数之间传递这个堆对象的指针,便可以实现对该对象的共享。另外,相比于栈空间,堆的容量要大得多。实际上,当物理内存不够时,如果这时还需要生成新的堆对象,通常不会产生运行时错误,而是系统会使用虚拟内存来扩展实际的物理内存

接下来看看static对象。

  首先是全局对象。全局对象为类间通信和函数间通信提供了一种最简单的方式,虽然这种方式并不优雅。一般而言,在完全的面向对象语言中,是不存在全局对象的,比如C#,因为全局对象意味着不安全和高耦合,在程序中过多地使用全局对象将大大降低程序的健壮性、稳定性、可维护性和可复用性。C++也完全可以剔除全局对象,但是最终没有,我想原因之一是为了兼容C。

  其次是类的静态成员,上面已经提到,基类及其派生类的所有对象都共享这个静态成员对象,所以当需要在这些class之间或这些class objects之间进行数据共享或通信时,这样的静态成员无疑是很好的选择。

  接着是静态局部对象,主要可用于保存该对象所在函数被屡次调用期间的中间状态,其中一个最显著的例子就是递归函数,我们都知道递归函数是自己调用自己的函数,如果在递归函数中定义一个nonstatic局部对象,那么当递归次数相当大时,所产生的开销也是巨大的。这是因为nonstatic局部对象是栈对象,每递归调用一次,就会产生一个这样的对象,每返回一次,就会释放这个对象,而且,这样的对象只局限于当前调用层,对于更深入的嵌套层和更浅露的外层,都是不可见的。每个层都有自己的局部对象和参数。在递归函数设计中,可以使用static对象替代nonstatic局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问

 

C++构造、多态、重载类

什么是多态 继承 封装

概念:

多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果

封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏

继承:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。其继承的过程,就是从一般到特殊的过程。

C++中,实现多态有以下方法:虚函数(动态链接),抽象类,覆盖,模板(重载和多态无关)

多态有什么好处?

1、应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承

2、派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用

纯虚函数:virtual void fun()=0;即抽象类!必须在子类实现这个函数,即先有名称,没有内容,在派生类实现内容

 

OOP的五个原则:

1、单一职责原则:有且只有一个原因引起类的变更,即一个类、一个方法只应该做一件事

2、开放闭合原则:对扩展开发,对修改关闭,将执行操作的对象,以及执行的操作,以及要操作的对象,分别接口化,可以适应需求的频繁变更

3、里氏替换原则:只要父类能出现的地方子类就可以出现,而且替换为其任意子类也不产生任何异常。子类必须完全实现父类的方法,子类可以有自己特有的属性和方法

4、依赖倒置原则:高层模块不要依赖底层模块,也就是依赖接口不要依赖实例

5、接口隔离原则:不要依赖用不到的接口

C++虚表

C++中虚函数是通过一张虚函数表(Virtual Table)来实现的,在这个表中,主要是一个类的虚函数表的地址表;这张表解决了继承、覆盖的问题。在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以当我们用父类的指针来操作一个子类的时候,这张虚函数表就像一张地图一样指明了实际所应该调用的函数。

深拷贝、浅拷贝

深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用

浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,

深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存

成员变量的初始化顺序

  • 成员变量在使用初始化列表初始化时,与构造函数中初始化成员列表的顺序无关,只与定义成员变量的顺序有关。因为成员变量的初始化次序是根据变量在内存中次序有关,而内存中的排列顺序早在编译期就根据变量的定义次序决定

②如果不使用初始化列表初始化,在构造函数内初始化时,此时与成员变量在构造函数中的位置有关。

③类中const成员常量必须在构造函数初始化列表中初始化。

④类中static成员变量,必须在类外初始化。

 

为什么不能在类的内部定义以及初始化static成员变量,而必须要放到类的外部定义

答:因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。

重载和重写的区别

重载overload:在同一个类中,函数名相同,参数列表不同,编译器会根据这些函数的不同参数列表,将同名的函数名称做修饰,从而生成一些不同名称的预处理函数,体现编译时多态性

重写override:也叫覆盖,子类重新定义父类中有相同名称相同参数的虚函数,主要是在继承关系中出现的,被重写的函数必须是virtual的,重写函数的访问修饰符可以不同,尽管virtual是private的,子类中重写函数改为public,protected也可以,体现了多态

编译时多态性:通过重载函数实现

运行时多态性:通过虚函数实现。

虚函数和纯虚函数的区别

如果类中声明了虚函数,这个虚函数是实现的,哪怕是空实现,他的作用就是为了能让这个函数在子类里面被覆盖,这样编译器就可以使用后期绑定来达到多态了,纯虚函数只是一个接口,是个函数的声明而已。

虚函数在子类中可以不重载,纯虚函数必须在子类中去实现。

虚函数的类继承接口的同时也继承了父类的实现,纯虚函数的类关注的是接口的统一性,实现右子类去完成

带纯虚函数的类叫虚基类,这种基类不能直接生成对象。而只能被继承,并重写其虚函数后,才能使用,这样的类也叫做抽象类

 

虚函数表(vfptr)

1、指向数组指针的指针,注意不是数组,里面存放着这个类中的虚函数的入口

2、虚函数指针__vfptr位于所有的成员变量之前定义.

 

右值引用

目的是提供一些涉及临时对象时可以选用特定的方法(主要是复制构造函数以及operator=,但并不限于此)。由于知道临时对象会被销毁,通过右值引用,某些涉及复制大量数据的操作可以通过简单地复制指向这些值的指针来实现,它的意义在于减少不必要的拷贝操作,提高程序性能

左值:用来存储数据的变量,有实际的内存地址,表达式结束后任然存在。

右值:匿名的临时变量,表达式结束时被销毁,不能存放数据,可以被修改或者不修改;字面常量也是右值。

进程和线程类

遇到进程和线程优先想流水线,工作台(全局的资源),工人(线程),多线程而不是多进程,就是因为多进程浪费资源

Cpp  多线程怎么保证线程安全

1、Volatile也不能保证全局整形变量是多线程的安全,volatile仅仅是告诫compiler不要对这个变量作优化,每次都要从memory取数值,而不是从register,函数静态变量、全局原生变量多线程读写是不安全的,

2、局部变量局部使用是安全的:因为每个thread 都有自己的运行堆栈,而局部变量是生存在堆栈中,大家不干扰。

确保线程安全的方法:竞争与原子操作(适合简单的场合,复杂的场合就不行啦)、同步与锁(二元信号或者多元信号)、可重入(函数可重入,表示函数没有执行完,又被重入之后不会产生任何不良后果,这种方式可以在多线程中放心使用)、过度优化

进程通信 方式,哪个效率高

管道、消息队列、信号量、共享内存

https://www.cnblogs.com/CheeseZH/p/5264465.html

IPC方式(inter progress communication)

IPC的方式通常有管道(包括无名管道和命名管道)、消息队列,信号量、共享存储、Socket、Streams等,其中Socket和Streams支持在不同主机上的两个进程IPC

  1. 管道:通常称为无名管道,它是半双工的(即数据只能在一个方向上流动),它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间),它可以看成是一种特殊的文件,但并不属于其他任何文件系统,并且只存在于内存中。是UNIX系统IPC最古老的形式
  2. FIFO:也称为命名管道,是一种文件类型,可以在无关的进程之间交换数据,FIFO有路径名与之相关联,以一种特殊设备文件形式存放于文件系统中
  3. 消息队列:是消息的链接表,存放于内核中。一个消息队列由一个标识符(即队列ID)来标识。消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级,独立于发送和接收进程,进程终止时,消息队列及其内容并不会被删除。消息不一定要以先进先出的次序读取,也可以按消息的类型读取
  4. 信号量:它是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于存储进程间的通信数据
  5. 共享内存:指两个或多个进程共享一个给定的存储区,是最快的一种IPC,因为进程是直接对内存进行存取,通常信号量+共享内存结合在一起使用,信号量用来同步对共享内存的访问

 

五种通讯方式总结:

  1. 管道:速度慢,容量有限,只有父子进程能通讯
  2. FIFO:任何进程间都能通讯,但是速度慢
  3. 消息队列:容量受到系统限制,且第一次读的时候,要考虑上一次没有读完数据的问题
  4. 信号量:不能传递复杂消息,只能用来同步
  5. 共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
  6. 还有一种就是我们常用的Socket

 

线程的6种状态解释

结合上面的那张图一起看:

NEW:线程被创建出来了,但是还没有start()。

RUNNABLE:可运行状态,这个状态比较特殊,我在图中把这个状态拆分成了两部分:一部分是READY,顾名思义是准备状态,另一部分是RUNNING,即运行状态。

准备状态:只能说明你有资格运行,但只要调度程序没有调度到你,你就永远是准备状态。

运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。 就是线程跑起来了

BLOCKED:线程因为某种原因放弃了cpu的使用权,直到线程再次进入就绪态

WAITING:线程不会被分配 CPU 执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

TIMED_WAITING:超时等待状态的线程不会被分配 CPU 执行时间,也无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。这是和上面的 WAITING 状态的区别。

TERMINATED:线程执行结束了

进程和线程有什么区别 线程共享哪些东西?

1、进程:进程是系统进行资源分配和调度的一个独立单位.

线程 CPU调度和分派的最小单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资

2、一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线

3、进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见

4、调度和切换:线程上下文切换比进程上下文切换要快得多。

生产者和消费者

最普通的形式:然后演变到Swait(),和Ssignal();  这个也是为了写错,二产生死锁情况

 

死锁出现的场景

(1)多个线程:彼此申请对方资源而导致的死锁。A申请B的资源时,因为资源被占用,A会被挂起等待B释放资源,同时B申请A的资源,因资源被占用B挂起等待A释放资源,而AB都处于挂起状态又无法释放资源,便形成了死锁。

(2)单个线程:A有自己的资源,但还要申请新的资源,而新的资源被占用,则A会挂起等待,同时会保护的资源而不释放,形成死锁。

死锁产生的原因

1. 系统资源的竞争:系统资源的竞争导致系统资源不足,以及资源分配不当,导致死锁。

2. 进程运行推进顺序不合适:进程在运行过程中,请求和释放资源的顺序不当,会导致死锁。

产生死锁的四个必要条件:

互斥条件:一个资源每次只能被一个进程使用,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

请求与保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求时,该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。

不可剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。

循环等待条件: 若干进程间形成首尾相接循环等待资源的关系。

这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

避免死锁的方法:

1)死锁避免的基本思想:

系统对进程发出每一个系统能够满足的资源申请进行动态检查,并根据检查结果决定是否分配资源,如果分配后系统可能发生死锁,则不予分配,否则予以分配,这是一种保证系统不进入死锁状态的动态策略。

2)如何避免死锁? 

在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。 一般来说互斥条件是无法破坏的,所以在预防死锁时主要从其他三个方面入手 :

(1)破坏请求和保持条件:在系统中不允许进程在已获得某种资源的情况下,申请其他资源,即要想出一个办法,阻止进程在持有资源的同时申请其它资源。

方法一:在所有进程开始运行之前,必须一次性的申请其在整个运行过程中所需的全部资源,这样,该进程在整个运行期间便不会再提出资源请求,从而破坏了“请求”条件。系统在分配资源时,只要有一种资源不能满足进程的需要,即使其它所需的各资源都空闲也不分配给该进程,而让该进程等待,由于该进程在等待期间未占用任何资源,于是破坏了“保持”条件。

方法二:要求每个进程提出新的资源申请前,释放它所占有的资源。这样,一个进程在需要资源S时,需要先把它先前占有的资源R释放掉,然后才能提出对S的申请,即使它很快又要用到资源R。

两种协议比较:第二种协议优于第一种协议,因为第一种协议会造成资源的严重浪费,使资源利用率大大的降低,也会由于占据大量资源导致其它进程的饥饿问题。

(2)破坏不可抢占条件:允许对资源实行抢夺。

方式一:如果占有某些资源的一个进程进行进一步资源请求被拒绝,则该进程必须释放它最初占有的资源,如果有必要,可再次请求这些资源和另外的资源。

方式二:如果一个进程请求当前被另一个进程占有的资源,则操作系统可以抢占另一个进程,要求它释放资源,只有在任意两个进程的优先级都不相同的条件下,该方法才能预防死锁。

(3)破坏循环等待条件

对系统所有资源进行线性排序并赋予不同的序号,这样我们便可以规定进程在申请资源时必须按照序号递增的顺序进行资源的申请,当以后要申请时需检查要申请的资源的编号大于当前编号时,才能进行申请。

死锁避免和死锁预防的区别:

死锁预防是设法至少破坏产生死锁的四个必要条件之一,严格的防止死锁的出现,而死锁避免则不那么严格的限制产生死锁的必要条件的存在,因为即使死锁的必要条件存在,也不一定发生死锁。死锁避免是在系统运行过程中注意避免死锁的最终发生。

利用银行家算法避免死锁:

        所谓银行家算法,是指在分配资源之前先看清楚,资源分配后是否会导致系统死锁。如果会死锁,则不分配,否则就分配。

按照银行家算法的思想,当进程请求资源时,系统将按如下原则分配系统资源

(1) 当一个进程对资源的最大需求量不超过系统中的资源数时可以接纳该进程

(2) 进程可以分期请求资源,当请求的总数不能超过最大需求量。

(3) 当系统现有的资源不能满足进程尚需资源数时,对进程的请求可以推迟分配,但总能使进程在有限的时间里得到资源。

(4) 当系统现有的资源能满足进程尚需资源数时,必须测试系统现存的资源能否满足该进程尚需的最大资源数,若能满足则按当前的申请量分配资源,否则也要推迟分配。

死锁的解除

一旦检测出死锁,就应立即采取相应的措施,以解除死锁。死锁的解除主要有两种方法:

(1)抢占资源。从一个或多个进程中抢占足够数量的资源,分配给死锁进程,以解除死锁状态。

(2)终止或者撤销进程。终止或者撤销系统中的一个或多个死锁进程,直到打破循环环路,使系统从死锁状态解脱出来。

设计模式

菜鸟教程:设计模式

https://www.runoob.com/design-pattern/design-pattern-tutorial.html

单例模式:

该类有且仅有一个访问方法,其他地方要调用这个对象就只能使用类名进行调用,该对象用static在类中进行声明,(单例的构造函数都是定义在私有成员中)

计算机网络类

Get和post的区别

GET 和 POST 只是 HTTP 协议中两种请求方式,而 HTTP 协议是基于 TCP/IP 的应用层协议,无论 GET 还是 POST,用的都是同一个传输层协议,所以在传输上,没有区别。

不带参数时最大区别就是第一行方法名不同

POST 方法请求报文第一行是这样的 POST /uri HTTP/1.1 \r\n

GET 方法请求报文第一行是这样的 GET /uri HTTP/1.1 \r\n

带参数时:GET 方法的参数应该放在url 中,POST 方法参数应该放在 body

GET 方法参数写法是固定的吗?

在约定中,我们的参数是写在 ? 后面,用 & 分割。我们知道,解析报文的过程是通过获取 TCP 数据,用正则等工具从数据中获取 Header 和 Body,从而提取参数。也就是说,我们可以自己约定参数的写法,只要服务端能够解释出来就行

 

POST比GET安全?

POST 比 GET 安全,因为数据在地址栏上不可见。

传输的角度来说,他们都是不安全的,因为 HTTP 在网络上是明文传输的,只要在网络节点上捉包,就能完整地获取数据报文。

要想安全传输,就只有加密,也就是HTTPS

GET的长度限制是怎么回事?

HTTP 协议没有 Body 和 URL 的长度限制,对 URL 限制的大多是浏览器和服务器的原因

服务器是因为处理长 URL 要消耗比较多的资源,为了性能和安全(防止恶意构造长 URL 来攻击)考虑,会给 URL 长度加限制。

POST会产生两个TCP数据包?

有些文章中提到,post 会将 header 和 body 分开发送,先发送 header,服务端返回 100 状态码再发送 body。

HTTP 协议中没有明确说明 POST 会产生两个 TCP 数据包,而且实际测试(Chrome)发现,header 和 body 不会分开发送。所以,header 和 body 分开发送是部分浏览器或框架的请求方法,不属于 post 必然行为

SMTP是基于TCP还是UDP?

SMTP是一种邮件传输协议,在邮件中,每一个数据包都很重要。如果在邮件中间丢失了几个数据包,收件人甚至可能不会收到该邮件,如果他们这样做,他们可能会丢失关键信息。这使得TCP更合适,因为它确保了每个数据包的传送。

Select、epoll的比较

select的缺点:

支持的fd数量有限

单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差; #define __FD_SETSIZE 1024)

每次调用select, 都会把fd从用户态拷贝到内存态

内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;

使用轮询的方式,需要内核遍历传进来的所有fd;

select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;

select只提供了一个函数——select函数。而epoll提供了三个函数,epoll_create, epoll_ctl 和 epoll_wait;

 

epoll_create是创建一个epoll句柄;

epoll_ctl是注册要监听的事件类型;

epoll_wait则是等待事件的产生。

epoll把原来select调用分成了3个部分:

1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)

2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字

3)调用epoll_wait收集发生的事件的连接

 

对于第一个缺点,fd数目有限:

  epoll没有这个限制它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max查看,一般来说这个数目和系统内存关系很大。

对于第二个缺点,重复把fd从用户态拷贝到内存态:

epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。

对于第三个缺点,轮询方式遍历所有的fd:

epoll的解决方案不像select那样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。

TCP/IP层数和功能

包、帧、数据包

s’s’s

TCP三次握手和四挥手

序号:表示发送的数据字节流,确保TCP传输有序,对每个字节编号

确认序号:发送方期待接收的下一序列号,接收成功后的数据字节序列号加 1。只有ACK=1时才有效。

ACK确认序号的标志,ACK=1表示确认号有效,ACK=0表示报文不含确认序号信息

SYN连接请求序号标志,用于建立连接,SYN=1表示请求连接

FIN结束标志,用于释放连接,为1表示关闭本方数据流

 

第一次:客户端发送初始序号x和syn=1请求标志

第二次:服务器发送请求标志syn,发送确认标志ACK,发送自己的序号seq=y,发送客户端的确认序号ack=x+1

第三次:客户端发送ACK确认号,发送自己的序号seq=x+1,发送对方的确认号ack=y+1

 

三次握手解析

第一次:客户端发送请求到服务器,服务器知道客户端发送,自己接收正常。SYN=1,seq=x

第二次:服务器发给客户端,客户端知道自己发送、接收正常,服务器接收、发送正常。ACK=1,ack=x+1,SYN=1,seq=y

第三次:客户端发给服务器:服务器知道客户端发送,接收正常,自己接收,发送也正常.seq=x+1,ACK=1,ack=y+1

 

握手两次达不到让双方都得出自己、对方的接收、发送能力都正常的结论的

             

四次挥手过程

第一次挥手:客户端发出释放FIN=1,自己序列号seq=u,进入FIN-WAIT-1状态

第二次挥手:服务器收到客户端的后,发出ACK=1确认标志和客户端的确认号ack=u+1,自己的序列号seq=v,进入CLOSE-WAIT状态

第三次挥手:客户端收到服务器确认结果后,进入FIN-WAIT-2状态。此时服务器发送释放FIN=1信号,确认标志ACK=1,确认序号ack=u+1,自己序号seq=w,服务器进入LAST-ACK(最后确认态)

第四次挥手:客户端收到回复后,发送确认ACK=1,ack=w+1,自己的seq=u+1,客户端进入TIME-WAIT(时间等待)。客户端经过2个最长报文段寿命后,客户端CLOSE;服务器收到确认后,立刻进入CLOSE状态。

 

四次挥手过程分析

第一次:客户端请求断开FIN,seq=u

第二次:服务器确认客户端的断开请求ACK,ack=u+1,seq=v

第三次:服务器请求断开FIN,seq=w,ACK,ack=u+1

第四次:客户端确认服务器的断开ACK,ack=w+1,seq=u+1

 

为什么三次握手和四次挥手?

三次握手时,服务器同时把ACK和SYN放在一起发送到了客户端那里

四次挥手时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方 ACK 和 FIN 一般都会分开发送。

 

为什么客户端最后还要等待2MSL?

Maximum Segment Lifetime  报文最大生存时间

客户端需要保证最后一次发送的ACK报文到服务器,如果服务器未收到,可以请求客户端重发,这样客户端还有时间再发,重启2MSL计时

详细的

第一,为了保证A发送的最后一个ACK报文能够到达B。这个ACK报文段有可能丢失,因而使处在LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认。B会超时重传这个FIN+ACK报文段,而A就能在2MSL时间内收到这个重传的FIN+ACK报文段。如果A在TIME-WAIT状态不等待一段时间,而是在发送完ACK报文段后就立即释放连接,就无法收到B重传的FIN+ACK报文段,因而也不会再发送一次确认报文段。这样,B就无法按照正常的步骤进入CLOSED状态。

 

第二,A在发送完ACK报文段后,再经过2MSL时间,就可以使本连接持续的时间所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求的报文段

https的请求方法

1、OPTIONS

返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性

2、HEAD

向服务器索与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。

3、GET

向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在Web Application中,其中一个原因是GET可能会被网络蜘蛛等随意访问。Loadrunner中对应get请求函数:web_link和web_url

4、POST

向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 Loadrunner中对应POST请求函数:web_submit_data,web_submit_form

5、PUT

向指定资源位置上传其最新内容

6、DELETE

请求服务器删除Request-URL所标识的资源

7、TRACE

回显服务器收到的请求,主要用于测试或诊断

8、CONNECT

HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。

Time_wait为啥是2ms而不是3或者1?

要是为3MSL,那如果本连接持续的时间所产生的所有报文段还没有从网络中消失。那下一个新的连接中就可能会出现这种旧的连接请求的报文段

如果是1MSL的话,服务端如果没有收到客服端最后发来的确认,那么根据TCP的可靠机制,服务端会重发FIN和ACK报文段,而客服端等待1MSL后就立即释放掉了,这会导致服务端无法按照正常的步骤就进入CLOSE状态。

 

列举http的状态码

200 OK  当您的操作将在响应正文中返回数据时,出现此结果。

204 No Content 当您的操作成功,但不在响应正文中返回数据时,出现此结果。

304 Not Modified(重定向)  当测试实体自上次检索以来是否被修改时,出现此结果。

403 Forbidden   客户端错误

401 Unauthorized 客户端错误

413 Payload Too Large(客户端错误) 当请求长度过长时,出现此结果。

400 BadRequest(客户端错误) 当参数无效时,出现此结果。

404 Not Found(客户端错误) 当资源不存在时,出现此结果。

405 Method Not Allowed(客户端错误)由于方法和资源组合不正确而出现此错误。 例如,您不能对一个实体集合使用 DELETE 或 PATCH。

412 Precondition Failed  客户端错误

501 Not Implemented(服务器错误) 当未实施某个请求的操作时,出现此结果。

503 Service Unavailable(服务器错误) 当 Web API 服务不可用时,出现此结果。

Linux类:

Makefile的书写

Makefile定制规则:目标(target)、前提条件(prerequisites)和命令动作(command)

1)定义变量

首先定义SOURCE,OBJS和TARGET变量,用于指代我们项目中的源文件、目标文件和可执行文件。

2) 设置编译参数

CC:配置编译器为g++,

LIBS:需要调用的链接库(-l开头,去掉lib和.so。例:对 libopencv_core.so链接库的调用要写作:-lopencv_core),

LDFLAGS:链接库的路径(-L开头),

INCLUDE:头文件的路径。

3)链接生成

此步骤生成可执行文件(ELF),链接需要用到目标文件,由下一步产生

4)编译

此步骤生成目标文件(.o)

5)清理                                //clean 伪目标

此步骤清理可执行文件和所有的目标文件

 

比如:

# 定义变量

SOURCE := main.cpp func.cpp

OBJS   := main.o func.o

TARGET := main


# 设置编译参数

CC      := g++   //设置编译器

LIBS    :=        //链接库

LDFLAGS := -L.                         //链接库的路径

DEFINES :=

INCLUDE := -I.         //头文件的路径

CFLAGS  :=

CXXFLAGS:=


# 链接

#$(TARGET):$(OBJS)

    $(CC) -o $@ $^


# 编译

#$(OBJS):$(SOURCE)

    $(CC) -c main.cpp -o main.o

    $(CC) -c func.cpp -o func.o


# 清理    

clean:

    rm -fr *.o

rm -fr $(TARGET)

 

Make的实现原理

很详细的一篇博客:

https://www.ruanyifeng.com/blog/2015/02/make.html


1. make命令会在当前目录下找名字为Makefile或makefile的文件;
2. 在Makefile文件中找第一个target,并将其作为最终目标文件也就是示例的main,所有的后续都是为了生成这个目标文件而执行;
3. 如果main文件不存在,或者main依赖的文件修改时间更新,就会执行command来生成main;如果main依赖的文件如main.o也不存在,那么就会寻找main.o的依赖文件,根据command生成main.o文件;
4. 最终的依赖文件就是.c,.h文件,如果这些文件不存在,就会报错。
5. make一层一层根据定义的依赖关系,最终编译出第一个目标文件,结束。

 

Make的工作流程

1.载入所有的Makefile,包括被inclue的文件
2.初始化变量定义
3.推导隐晦规则,分析文件依赖关系
4.根据定义的依赖关系生成依赖关系链
5.根据依赖关系链,决定哪些目标需要重新生成
6.执行生成命令

 

Makefile连接库:

1、makefile中添加 库文件依赖, -L 后面跟库文件的路径,  -l(小写)后面跟库的名字

对于使用libtest.a 和 libtest.so
LIB += -L/lib
LIB += -ltest
target: $(OBJS) 
    $(CC) -o target $(CFLAGS) $(OBJS) $(LIBS)

 

Makefile的参数表示:

$@指代当前目标,就是Make命令当前构建的那个目标

$< 指代第一个前置条件。比如,规则为 t: p1 p2,那么$< 就指代p1

$? 指代比目标更新的所有前置条件之间以空格分隔。规则为 t: p1 p2, p2 的时间戳比 t 新,$?就指代p2。

$^ 指代所有前置条件,之间以空格分隔。比如,规则为 t: p1 p2,那么 $^ 就指代 p1 p2

$* 指代匹配符 % 匹配的部分, 比如% 匹配 f1.txt 中的f1 ,$* 就表示 f1

 

 

编译内核相关:

设置环境变量

root@linux:~/linux-3.14-fs4412# vim /etc/bash.bashrc 

export PATH=/home/linux/soft/gcc-4.6.4/bin:$PATH

 

解压  

tar -xvf  linux-3.14.tar.xz                                   tar -zcvf                 z:压缩  c:创建

编译步骤前提 图片:

编译步骤文字:

第一步:在终端下进入内核所在目录 "/arm2410";
第二步:键入make menuconfig,进入内核配置菜单,如下图:
注意:
1)带有"-->"表示该选项包含选项;
2)每个选项前面有[ ]或,中括号表示仅有2种选择(*或空),尖括号表示有3种选择(M,*或空),按空格键可显示这几个选择;
3)M表示以模块方式编译进内核,在内核启动后,需要手工执行insmod命令才能使用该项驱动;*表示直接编译进内核;空表示不编译进内核;
第三步:按着自己的需求,配置内核.一共有21项.
第四步:键入make clean命令,删除已生成的模块和目标文件.
第五步:键入make dep命令,编译变量依赖关系等;
第六步:键入make zlmage生成经压缩以后的内核映像文件zlmage;
第七步:键入make modules编译模块;
第八步:键入make modules_install安装编译完成的模块;
内核映像文件zlmage存放在 ./arch/arm/boot/目录下.

这个是别人的一篇c++总结,可以结合看,进行知识互补:https://www.cnblogs.com/CheeseZH/p/5176970.html

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值