面试题总结

目录

1.C和C++的区别:

(1)C面向对象C++面向过程(解释面向过程和面向对象):

(2)C++具有封装、继承、多态的性质    

(3)C++具有STL

2.#define和const的区别:

3.程序编译的过程(4个):

4.内存的布局:

5.进程间的通信方式有哪些?用过哪些?

6.malloc、calloc、realloc、new、free、delete的区别与联系:

(1)malloc函数

(2)calloc函数

(3)realloc函数

(4)new和delete

(5)相同之处:

(6)不同之点:

1.malloc/free和new/delete的区别  

7.堆和栈的区别:

(1)管理方式不同。

(2)空间大小不同。

(3)生长方向不同。

(4)分配方式不同。

(5)分配效率不同。

(6)存放内容不同。

8.vector和list区别:

1.区别:

2.适用场景: 

9.map和unordered_map区别:

区别:

优缺点以及适用处

总结:

set和unordered_set区别:

10.哈希桶

11.vector扩容

12.break和continue区别

一、语句作用

二、注意事项    

三、区别    

13.线程和进程的区别:

14.strcpy、strncpy和memcpy的用法与区别详解

一、strcpy

二、strncpy

三、memcpy

四、不同之处:

15.多进程、多线程的优缺点

16.什么时候用进程,什么时候用线程

17.多进程、多线程同步(通讯)的方法

18.互斥锁与信号量的区别?

19.进程线程的状态转换图,什么时候阻塞,什么时候就绪?

20.父进程、子进程的关系以及区别

21.并发,同步,异步,互斥,阻塞,非阻塞的概念

22.什么是线程同步和互斥

23.线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?

24.new和malloc的区别

25.指针与引用的相同和区别;如何相互转换?

26. C语言检索内存情况 内存分配的方式

27.new int()和new int[]的区别


1.C和C++的区别:

(1)C面向对象C++面向过程(解释面向过程和面向对象):

A. 面向过程
面向过程是一种以事件为中心的编程思想,编程时把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。

B.面向对象
在日常生活或编程中,简单的问题可以用面向过程的思路来解决,直接有效,但当问题的规模变得更大时,用面向过程的思想是远远不够的。所以慢慢就出现了面向对象的编程思想。世界上有很多人和事物,每个都可以看做一个对象,而每个对象都有自己的属性和行为,对象与对象之间通过方法来交互。面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。

C.优缺点比较
面向过程:
优点:a.流程化使得编程任务明确,在开发之前基本考虑了实现方式和最终结果,
具体步骤清楚,便于节点分析。
b.效率高,面向过程强调代码的短小精悍,善于结合数据结构来开发高效率的程序。
缺点:需要深入的思考,耗费精力,代码重用性低,扩展能力差,后期维护难度比较大。
面向对象:
优点:
a.结构清晰,程序是模块化和结构化,更加符合人类的思维方式;
b.易扩展,代码重用率高,可继承,可覆盖,可以设计出低耦合的系统;
c.易维护,系统低耦合的特点有利于减少程序的后期维护工作量。
缺点:
a.开销大,当要修改对象内部时,对象的属性不允许外部直接存取,所以要增加许多没有其他意义、只负责读或写的行为。这会为编程工作增加负担,增加运行开销,并且使程序显得臃肿。
b.性能低,由于面向更高的逻辑抽象层,使得面向对象在实现时,不得不做出性能上面的牺牲,计算时间和空间存储大小都开销很大。

(2)C++具有封装、继承、多态的性质    

(3)C++具有STL

2.#define和const的区别:

(1)一个发生在预处理阶段,一个发生在编译阶段

(2)#define只是简单的字符串替换没有进行类型的安全检查,const进行类型的安全检查
(3)#define的字符串会替换多份,更占用内存,const常量只保存一份(保存在常量区)

3.程序编译的过程(4个):

预处理:头文件展开,宏替换,条件编译,去注释

编译:语义分析,语法分析,目标代码生成,优化,生成汇编代码

汇编:生成二进制文件

链接:将目标文件进行链接形成可执行程序(解决符号表的问题)

4.内存的布局:

从高地址到低地址(自顶向下),栈区,共享区,堆区,代码段,数据段

5.进程间的通信方式有哪些?用过哪些?

管道,消息队列,共享内存,信号量,Socket网络通信,项目中使用了Socket网络通信

6.malloc、calloc、realloc、new、free、delete的区别与联系:

(1)malloc函数

void* malloc(int n);n表示要申请空间的字节数。如果函数执行成功则返回申请空间的首地址,并且malloc函数的返回值是void*,因此使用时必须根据数据类型进行强制转换,函数执行失败则返回NULL。malloc函数申请的空间是没有初始化的,需要使用memset函数进行初始化

(2)calloc函数

void* calloc(int n, int size)n表示需要申请元素的个数,size表示一个元素的大小,申请空间的总大小则是n与size的乘积。如果函数执行成功则返回申请空间的首地址,且realloc函数的返回值void*,因此使用时必须根据数据类型进行强制转换,函数执行失败则返回NULL。calloc函数申请的空间是经过初始化的,全部初始化为0。

(3)realloc函数

void* realloc(void* p, int n);p表示已经在堆上存在的一块空间的地址,n表示调整后空间的字节数。realloc函数的作用是将p指向的空间调整为n个字节大小。常与以上两个函数搭配使用。在增容时,如果此时该空间之后的空闲空间足够,则直接扩容,如果其之后的空闲空间不够,那么会去重新找一块足够大的空间,将旧空间的内容全部复制到新空间上,然后返回新空间的地址,释放原来的旧空间。realloc函数申请的空间也是没有初始化的。

(4)new和delete

int* p1 = new int; //申请一个 int 型的空间
int* p2 = new int(1); // 申请一个 int 型的空间并初始化为 1
int* p3 = new int[10]; // 申请10个类型为 int 的空间
delete p1;
delete p2;
delete[] p3;
申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]

(5)相同之处:

malloc、calloc、realloc和new的共同点是:都是从堆上申请空间,并且需要用户手动释放。

(6)不同之点:

a.malloc、calloc、realloc和free是函数,new和delete是操作符
b.malloc、realloc申请的空间不会初始化,new可以初始化
c.malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
d.malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
e.malloc申请空间失败时,返回NULL,因此使用时必须判空,new不需要,但new需要捕获异常
f.申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理
g.new/delete比malloc和free的效率稍微低点,因为new/delete的底层封装了malloc/free

1.malloc/free和new/delete的区别  

malloc/free和new/delete的共同点是:

都是从堆上申请空间,并且需要用户手动释放。

不同的地方是:
  1.malloc和free是函数,new和delete是操作符。
  2.malloc申请的空间不会初始化,new可以初始化。
  3.malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可。
  4.malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型。
  5.malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
  6.申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。

7.堆和栈的区别:

(1)管理方式不同。

栈由操作系统自动分配释放,无需手动控制;堆的申请和释放工作由程序员控制,会产生内存泄漏; 

(2)空间大小不同。

每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小64bits的Windows默认1M,64bits的Linux默认10M; 

(3)生长方向不同。

堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。 

(4)分配方式不同。

堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需手工实现。 

(5)分配效率不同。

栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。 

(6)存放内容不同。

栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内存,然后是当前栈帧的底部地址,即扩展基址指针寄存器内容,再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是调用函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的。

栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作,这一端被称为栈顶,相对地,把另一端称为栈底。把新元素放到栈顶元素的上面,使之成为新的栈顶元素称作进栈、入栈或压栈;把栈顶元素删除,使其相邻的元素成为新的栈顶元素称作出栈或退栈。这种受限的运算使栈拥有“先进后出”的特性。

栈分顺序栈和链式栈两种。栈是一种线性结构,所以可以使用数组或链表作为底层数据结构。使用数组实现的栈叫做顺序栈,使用链表实现的栈叫做链式栈,二者的区别是顺序栈中的元素地址连续,链式栈中的元素地址不连续。

堆是一种常用的树形结构,是一种特殊的完全二叉树,当且仅当满足所有节点的值总是不大于或不小于其父节点的值的完全二叉树被称之为堆。堆的这一特性称之为堆序性。因此,在一个堆中,根节点是最大(或最小)节点。如果根节点最小,称之为小根堆,如果根节点最大,称之为大根堆。
堆的左右孩子没有大小的顺序。

8.vector和list区别:

1.区别:

1.vector底层实现是数组,list 是双向链表
2.vector支持随机访问,list 不支持
3.vector是顺序内存,list 不是
4.vector在中间节点进行插入删除会导致内存拷贝,list 不会
5.vector一次性分配好内存,不够时才进行扩容,list每次插入新节点都会进行内存申请
6.vector随机访问性能好,插入删除性能差,list 随机访问性能差,插入删除性能好

2.适用场景: 

1. vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。
2. list 拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。

9.map和unordered_map区别:

map、unordered_map 是 C++ STL 中的两个容器

区别:

需要引入的头文件不同

map: #include < map >
unordered_map: #include < unordered_map >

内部实现机理不同
map:

map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。

unordered_map:

unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。
 

优缺点以及适用处

map:

优点:

有序性,这是map结构最大的优点,其元素的有序性在很多应用中都会简化很多的操作
红黑树,内部实现一个红黑书使得map的很多操作在lgn的时间复杂度下就可以实现,因此效率非常的高

缺点:

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

适用处:

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

unordered_map:

优点: 因为内部实现了哈希表,因此其查找速度非常的快
缺点: 哈希表的建立比较耗费时间
适用处:对于查找问题,unordered_map会更加高效一些,因此遇到查找问题,常会考虑一下用unordered_map

总结:

内存占有率的问题就转化成红黑树 VS hash表 , 还是unorder_map占用的内存要高。
但是unordered_map执行效率要比map高很多
对于unordered_map或unordered_set容器,其遍历顺序与创建该容器时输入的顺序不一定相同,因为遍历是按照哈希表从前往后依次遍历的

set和unordered_set区别:

1、set基于红黑树实现,红黑树具有自动排序的功能,因此map内部所有的数据,在任何时候,都是有序的。

2、unordered_set基于哈希表,数据插入和查找的时间复杂度很低,几乎是常数时间,而代价是消耗比较多的内存,无自动排序功能。底层实现上,使用一个下标范围比较大的数组来存储元素,形成很多的桶,利用hash函数对key进行映射到不同区域进行保存。

set与unordered相比:

  1、set比unordered_set使用更少的内存来存储相同数量的元素。

  2、对于少量的元素,在set中查找可能比在unordered_set中查找更快。

  3、尽管许多操作在unordered_set的平均情况下更快,但通常需要保证set在最坏情况下有更好的复杂度(例如insert)。

  4、如果您想按顺序访问元素,那么set对元素进行排序的功能是很有用的。

  5、您可以用<、<=、>和>=从字典顺序上比较不同的set集。unordered_set集则不支持这些操作。

一般来说,在如下情况,适合使用set:

  1、我们需要有序的数据(不同元素)。

  2、我们必须打印/访问数据(按排序顺序)。

  3、我们需要知道元素的前任/继承者。

一般来说,在如下情况,适合使用unordered_set:

  1、我们需要保留一组元素,不需要排序。

  2、我们需要单元素访问,即不需要遍历。

  3、仅仅只是插入、删除、查找的话。

10.哈希桶

unordered_map容器和map容器一样,以键值对(pair类型)的形式存储数据,存储的各个键值对的键互不相同且不允许被修改。但由于 unordered_map 容器底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序。底层采用哈希表实现无序容器时,会将所有数据存储到一整块连续的内存空间中,并且当数据存储位置发生冲突时,解决方法选用的是“链地址法”(又称“开链法”)。
可以看到,当使用无序容器存储键值对时,会先申请一整块连续的存储空间,但此空间并不用来直接存储键值对,而是存储各个链表的头指针,各键值对真正的存储位置是各个链表的节点。不仅如此,在C++STL标准库中,将图中的各个链表称为桶,每个桶都有自己的编号(从 0 开始)。当有新键值对存储到无序容器中时,
整个存储过程分为如下几步:
1. 将该键值对中键的值带入设计好的哈希函数,会得到一个哈希值(一个整数,用 H 表示);
2. 将 H 和无序容器拥有桶的数量 n 做整除运算(即 H % n),该结果即表示应将此键值对存储到的桶的编号;
3. 建立一个新节点存储此键值对,同时将该节点链接到相应编号的桶上。
另外,哈希表存储结构还有一个重要的属性,称为负载因子。该属性同样适用于无序容器,用于衡量容器存储键值对的空/满程序,即负载因子越大,意味着容器越满,即各链表中挂载着越多的键值对,这无疑会降低容器查找目标键值对的效率;反之,负载因子越小,容器肯定越空,但并不一定各个链表中挂载的键值对就越少。如果设计的哈希函数不合理,使得各个键值对的键带入该函数得到的哈希值始终相同(所有键值对始终存储在同一链表上)。这种情况下,即便增加桶数是的负载因子减小,该容器的查找效率依旧很差。无序容器中,负载因子的计算方法为:负载因子 = 容器存储的总键值对 / 桶数.默认情况下,无序容器的最大负载因子为 1.0。如果操作无序容器过程中,使得最大复杂因子超过了默认值,则容器会自动增加桶数,并重新进行哈希,以此来减小负载因子的值。需要注意的是,此过程会导致容器迭代器失效,但指向单个键值对的引用或者指针仍然有效。这也就解释了,为什么我们在操作无序容器过程中,键值对的存储顺序有时会“莫名”的发生变动。

11.vector扩容

当vector的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么vector就需要扩容。vector容器扩容的过程需要经历以下3步:
1. 完全弃用现有的内存空间,重新申请更大的内存空间;
2. 将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
3. 最后将旧的内存空间释放。因为vector扩容需要申请新的空间,所以扩容以后它的内存地址会发生改变。vector扩容是非常耗时的,为了降低再次分配内存空间时的成本,每次扩容时vector都会申请比用户需求量更多的内存空间(这也就是vector容量的由来,即capacity>=size),以便后期使用。

12.break和continue区别

区别
    在循环语句中,可以用break语句跳出循环,用continue语句结束本次循环。

一、语句作用

1.break语句的作用

(1)在分支结构程序设计中用break语句可以使流程跳出switch结构,继续执行switch语句下面的一个语句;
(2)break语句可以用来从循环体内中途跳出循环体,即提前结束循环操作,接着执行循环下面的语句。

2.continue语句的作用

continue语句是跳过循环体中剩余的语句而强制执行下一次循环操作。其作用为结束本次循环,即跳过循环体中下面尚未执行的语句,接着进行下一次是否执行循环的判定。

二、注意事项    

(1)在循环语句中,break语句一般都是与if语句一起使用;
(2)break语句不能用于循环语句和switch语句之外的任何其它语句中;
(3)continue语句只能用在循环语句中。一般都是与if语句一起使用。

三、区别    

(1)continue语句只结束本次循环,而不是终止整个循环的执行;   

(2)break语句则是结束整个循环过程,不再判断执行循环的条件是否成立。

13.线程和进程的区别:

线程具有许多传统进程所具有的特征,故又称为轻型进程或进程元;而把传统的进程称为重型进程,它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行
 

14.strcpy、strncpy和memcpy的用法与区别详解

一、strcpy

函数原型:char* strcpy(char* dest,const char* src)

功能:把src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间,返回指向dest的指针。需要注意的是,src和dest所指内存区域不可以重叠且dest必须需有足够的空间来容纳src的字符串,strcpy它只用于字符串复制。

安全性:strcpy是不安全的,strcpy在遇到结束符时才会正常的结束运行,会因为src长于dest而造成dest栈空间溢出以致于崩溃异常,它的结果未定,可能会改变程序中其他部分的内存的数据,导致程序数据错误,不建议使用。(如果不考虑src串的完整性,可以把dest数组最后一元素置为NULL,从dest串长度处插入NULL截取字串)

char* strcpy(char* dest,const char* src)//src到dest的复制
{
    if(dest == nullptr || src == nullptr)
        return nullptr;
    char* strdest = dest;
    while((*strdest++ = *src++) != '\0')
    {
        return strdest;
    }
}

二、strncpy

函数原型:char* strncpy(char* dest,const char* src,size_t n);

功能:将字符串src中最多n个字符复制到字符数组dest中(它并不像strcpy一样只有遇到NULL才停止复制,而是多了一个条件停止,就是说如果复制到第n个字符还未遇到NULL,也一样停止),返回指向dest的指针。只适用于字符串拷贝。如果srct指向的数组是一个比n短的字符串,则在dest定义的数组后面补空字符,直到写入了n个字符。

注意:如果n>dest串长度,dest栈空间溢出产生崩溃异常。一般情况下,使用strncpy时,建议将n置为dest串长度(除非你将多个src串都复制到dest数组,并且从dest尾部反向操作),复制完毕后,为保险起见,将dest串最后一字符置NULL。

安全性:比较安全,当dest的长度小于n时,会抛出异常。

char *strncpy(char *dest, const char *src, int len)
{     
    assert(dest!=NULL && src!=NULL);      
    char *temp;     
    temp = dest;     
    for(int i =0;*src!='\0' && i<len; i++,temp++,src++)          
        *temp = *src;    
    *temp = '\0';     
    return dest; 
}

三、memcpy

函数原型:void* memcpy(void* dest,const void* src,size_t n);

功能:提供了一般内存的复制,即memcpy对于需要复制的内容没有任何限制,可以复制任意内容,因此,用途广泛。

注意:memcpy没有考虑内存重叠的情况,所以如果两者内存重叠,会出现错误。

void *memcpy(void *memTo, const void *memFrom, size_t size) 
{     
    if((memTo == NULL) || (memFrom == NULL)) //memTo和memFrom必须有效          
        return NULL;     
    char *tempFrom = (char *)memFrom;             //保存memFrom首地址     
    char *tempTo = (char *)memTo;                  //保存memTo首地址          
    while(size -- > 0)                //循环size次,复制memFrom的值到memTo中            
        *tempTo++ = *tempFrom++ ;      
    return memTo; 
}

四、不同之处:

主要说一下strcpy和memcpy的区别,主要有以下3方面的区别。
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。

3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

使用情况:
1、dest指向的空间要足够拷贝;使用strcpy时,dest指向的空间要大于等于src指向的空间;使用strncpy或memcpy时,dest指向的空间要大于或等于n。
2、使用strncpy或memcpy时,n应该大于strlen(src),或者说最好n >= strlen(s1)+1;这个1 就是最后的“\0”。
3、使用strncpy时,确保dest的最后一个字符是“\0”。
 

15.多进程、多线程的优缺点

1)多进程更健壮,一个进程死了不影响其他进程,子进程死了也不会影响到主进程,毕竟系统会给每个进程分配独立的系统资源。多线程比较脆弱,一个线程崩溃很可能影响到整个程序,因为多个线程是在一个进程里一起合作干活的。

2) 进程性能大于线程,每个进程独立地址空间和资源,而多个线程是一起共享了同个进程里的空间和资源,结果就很明显了,线程的性能上限一定比不上进程。

3) 正因为进程性能大于线程。所以这也引发了另一重要知识点,创建多进程的系统花销远大于创建多线程。

4)多进程通讯因为需要跨越进程边界,不适合大量数据的传送,更适合小数据或者密集数据的传送。而多线程无需跨越进程边界,适合各线程间大量数据的传送,甚至还有很重要的一点,多线程可以共享同一进程里的共享内存和变量哦。

5) 多进程逻辑控制比多线程复杂,需要与主进程做好交互。根据上面几点,我们不难知道多进程是“要用来做大事”的,而多线程是“各自做件小事,合作完成大事”。所以要做大事自然就需要更复杂的逻辑控制,不像做小事那么目标明显。

6)虽然多线程逻辑控制比较简单,但是却需要复杂的线程同步和加锁控制等机制。

16.什么时候用进程,什么时候用线程

答:

1)创建和销毁较频繁使用线程,因为创建进程花销大嘛。

2)需要大量数据传送使用线程,因为多线程切换速度快,不需要跨越进程边界。

3)并行操作使用线程。线程是为了实现并行操作的一个手段,也就是刚才说的需要多个并行操作“合作完成大事”,当然是使用线程啦。

4)最后可以总结为:安全稳定选进程;快速频繁选线程;

17.多进程、多线程同步(通讯)的方法

进程间通讯:

(1)管道/无名管道(2)信号(3)共享内存(4)消息队列(5)信号量(6)socket

注意:临界区则是一种概念,指的是访问公共资源的程序片段,并不是一种通信方式。

线程通讯:

(1)信号量(2)读写锁(3)条件变量(4)互斥锁(5)自旋锁

18.互斥锁与信号量的区别?

:互斥锁用于线程的互斥,信号量用于线程的同步。这是互斥锁和信号量的根本区别,也就是互斥和同步之间的区别。同时互斥锁的作用域仅仅在于线程,信号量可以作用于线程和进程。  

19.进程线程的状态转换图,什么时候阻塞,什么时候就绪?

在此之前,我们先看看一个进程一生中,从蛋生到死亡都有可能出现什么状态。

创建态(New):一个进程正在被创建,还没到转到就绪状态之前的状态。

就绪态(Ready):一个进程获得了除CPU时间片之外的一切所需资源,一旦得到CPU时间片调度时即可运行。

运行/执行态(Running):当一个进程得到CPU调度正在处理机上运行时的状态。

睡眠/挂起态:由于某些资源暂时不可得到而进入“睡眠态”,将进程挂起,等待唤醒。

阻塞/暂停态(Blocked):一个进程正在等待某一事件而暂停运行时,如等待某资源成为可用,或等待文件读取完成等。

结束/僵尸态(Exit):一个进程正在从系统中消失时的状态,这是因为进程结束或其它因流产所导致。

死亡态:进程生命周期结束了,将所占用的资源还给系统。

20.父进程、子进程的关系以及区别

子进程继承父进程:

      ○用户号UIDs和用户组号GIDs

      ○环境Environment

      ○堆栈

      ○共享内存

      ○打开文件的描述符

      ○执行时关闭(Close-on-exec)标志

      ○信号(Signal)控制设定

      ○进程组号

      ○当前工作目录

      ○根目录

      ○文件方式创建屏蔽字

      ○资源限制

      ○控制终端

子进程独有的:

      ○进程号PID

      ○不同的父进程号

      ○自己的文件描述符和目录流的拷贝

      ○子进程不继承父进程的进程正文(text),数据和其他锁定内存(memory locks)

      ○不继承异步输入和输出    

父进程调用fork()以后,克隆出一个子进程,子进程和父进程拥有相同内容的代码段、数据段和用户堆栈。但其实父进程只复制了自己的PCB块,而代码段,数据段和用户堆栈内存空间是与子进程共享的。只有当子进程在运行中出现写操作时,才会产生中断,并为子进程分配内存空间。

子进程从父进程继承的主要有:用户号和用户组号;堆栈;共享内存;目录(当前目录、根目录);打开文件的描述符;但父进程和子进程拥有独立的地址空间和PID参数、不同的父进程号、自己的文件描述符。

21.并发,同步,异步,互斥,阻塞,非阻塞的概念

并发:在操作系统中,同个处理机上有多个程序同时运行即并发。并发可分为同步和互斥。 

1)同步、互斥:

互斥:分布在不同进程之间的若干程序片断,规定当某个进程运行其中一个程序片段时,其它进程就不能运行它们之中的任一程序片段,只能等到该进程运行完这个程序片段后才可以运行。如有同一个资源同一时间只有一个访问者可以进行访问,其他访问者需要等前一个访问者访问结束才可以开始访问该资源,但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:分布在不同进程之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。所以同步就是在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。

总结:同步是一种更为复杂的互斥,而互斥是一种特殊的同步。

2)同步、异步:

同步:同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。

异步:异步和同步是相对的,异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。

注意:

1)线程是实现异步的一个方式。可以在主线程创建一个新线程来做某件事,此时主线程不需等待子线程做完而是可以做其他事情。

2)异步和多线程并不是一个同等关系。异步是最终目的,多线程只是我们实现异步的一种手段。

3)阻塞,非阻塞:

    阻塞和非阻塞是当进程在访问数据时,根据IO操作的就绪状态不同而采取的不同处理方式,比如主程序调用一个函数要读取一个文件的内容,阻塞方式下主程序会等到函数读取完再继续往下执行,非阻塞方式下,读取函数会立刻返回一个状态值给主程序,主程序不等待文件读取完就继续往下执行。一般来说可以分为:同步阻塞,同步非阻塞,异步阻塞,异步非阻塞。

4)同步阻塞,同步非阻塞,异步阻塞,异步非阻塞:

以发送方发出请求要接收方读取某文件内容为例。

同步阻塞:发送方发出请求后一直等待(同步),接收方开始读取文件,如果不能马上得到读取结果就一直等,直到获取读取结果再响应发送发,等待期间不可做其他操作(阻塞)。

同步非阻塞:发送方发出请求后一直等待(同步),接收方开始读取文件,如果不能马上的得到读取结果,就立即返回,接收方继续去做其他事情。此时并未响应发送发,发送方一直在等待。直到IO操作(这里是读取文件)完成后,接收方获得读取结果响应发送方,接收方才可以进入下一次请求过程。(实际不应用)

异步阻塞:发送方发出请求后,不等待响应,继续其他工作(异步),接收方读取文件如果不能马上得到结果,就一直等到返回结果后,才响应发送方,期间不能进行其他操作(阻塞)。(实际不应用)

异步非阻塞:发送方发出请求后,不等待响应,继续其他工作(异步),接收方读取文件如果不能马上得到结果,也不等待,而是马上返回取做其他事情。当IO操作(读取文件)完成以后,将完成状态和结果通知接收方,接收方在响应发送方。(效率最高)

总结:

1)同步与异步是对应的,它们是线程之间的关系,两个线程之间要么是同步的,要么是异步的。

2)阻塞与非阻塞是对同一个线程来说的,在某个时刻,线程要么处于阻塞,要么处于非阻塞。

3)阻塞是使用同步机制的结果,非阻塞则是使用异步机制的结果。

22.什么是线程同步和互斥

线程同步:每个线程之间按预定的先后次序进行运行,协同、协助、互相配合。可以理解成“你说完,我再做”。有了线程同步,每个线程才不是自己做自己的事情,而是协同完成某件大事。

线程互斥:当有若干个线程访问同一块资源时,规定同一时间只有一个线程可以得到访问权,其它线程需要等占用资源者释放该资源才可以申请访问。线程互斥可以看成是一种特殊的线程同步。

23.线程同步与阻塞的关系?同步一定阻塞吗?阻塞一定同步吗?

    解析:同步是个过程,阻塞是线程的一种状态:当多个线程访问同一资源时,规定同一时间只有一个线程可以进行访问,所以后访问的线程将阻塞,等待前访问的线程访问完。

    注意:线程同步不一定发生阻塞!线程同步的时候,需要协调推进速度,只有当访问同一资源出现互相等待和互相唤醒会发生阻塞。而阻塞了一定是同步,后访问的等待获取资源,线程进入阻塞状态,借以实现多线程同步的过程。

24.new和malloc的区别

解析:这个题目是我多次面试时被问到的最高频问题之一,建议各位童鞋必须弄懂。

答:

1)new、delete是C++中独有的操作符,而malloc和free是C/C++中的标准库函数。

2)使用new创建对象在分配内存的时候会自动调用构造函数,同时也可以完成对对象的初始化,同理要记得delete也能自动调用析构函数。因为malloc和 free是库函数而不是运算符,不在编译器控制范围之内,所以不能够自动调用构造函数和析构函数。也就是mallloc只是单纯地为变量分配内存,free也只是释放变量的内存。

3)new返回的是指定类型的指针,并且可以自动计算所申请内存的大小。而malloc返回的是void*类型,我们需要强行将其转换为实际类型的指针,并且需要指定好要申请内存的大小,malloc不会自动计算的。

4)C++允许重载new/delete操作符,而malloc和free是一个函数,并不能重载。

5)new内存分配失败时,会抛出bad_alloc异常。malloc分配内存失败时返回NULL。

6)内存区域:先了解自由存储区和堆,两者不相等于的。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配。new操作符从自由存储区上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。

25.指针与引用的相同和区别;如何相互转换?

指针指向一块内存,它的内容是所指内存的地址;引用是某块内存的别名。

从内存分配上看:两者都是占内存的,程序为指针变量分配内存区域,在32位系统指针变量一般占用4字节内存,而

引用本质是指针常量,所指向的对象不能改变,但指向的对象的值可以改变,引用和指针一样是地址概念,所以本身都是会占用内存的(有的编译器优化后就不占用内存了)

1. 指针是一个实体,而引用仅是个别名

2. 指针和引用的自增(++)运算意义不一样,指针是对内存地址的自增,引用是对值的自增;

量或对象的地址)的大小;

3. 引用使用时无需解引用(*),指针需要解引用;

4. 引用只能在定义时被初始化一次,之后不可变;指针可变;

5. 引用不能为空,指针可以为空;

6.引用没有const,指针有const;(本人当初看到这句话表示疑问,这里解释一下:指针有“指针常量”即int * const a,但是引用没有int& const a,不过引用有“常引用”即const int &a = 1)

7. “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小,在32位系统指针变量一般占用4字节内存。

1)指针转引用:把指针用*就可以转换成对象,可以用在引用参数当中。

2)引用转指针:把引用类型的对象用&取地址就获得指针了。

int a = 0;

int *pA = &a;

void fun(int &va){}

此时调用: fun(*pA);

pA是指针,加个*号后可以转换成该指针指向的对象,此时fun的形参是一个引用值,pA指针指向的对象会转换成引用va。

26. C语言检索内存情况 内存分配的方式

检索内存:顾名思义,对某段内存进行遍历搜索。

内存分配:

1、从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

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

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

27.new int()和new int[]的区别

new int[] 是创建一个int型数组,数组大小是在[]中指定,例如:
int * p = new int[3]; //申请一个动态整型数组,数组的长度为[]中的值

new int()是创建一个int型数,并且用()括号中的数据进行初始化,例如:
int *p = new int(10); // p指向一个值为10的int数。
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨柟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值