C++面试小题(一)

总结面试中,可能出现的问题,记录其中,并且规范化学习,检查自己的知识点是否有遗漏

今日面试题:什么是线程,什么是进程,线程之间怎么通信,进程之间如何通信。

  • 进程是对运行时程序的封装,是系统进行资源调度和分配的基本单位,实现了操作系统的并发。
  • 线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发。线程是操作系统可识别的最小执行和调度单位。每个线程都独自用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等),打开的文件队列和其他内核资源。

    区别

  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有个线程。线程依赖于进程而存在。
  • 进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
  • 进程是资源分配的最小单位,线程是CPU调度的最小单位。
  • 系统开销:进程切换的开销远大于线程切换的开销
  • 通信:由于同一个进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。
  • 进程编程调试简单可靠性强,但创建销毁开销大;线程恰恰相反
  • 进程之间不会相互影响;线程一个线程挂掉将导致整个进程挂掉
  • 进程适应于多核、多机分布;线程适合于多核

    进程间通信的方式

    主要包括管道、系统IPC,以及套接字socket

1、管道

  • 管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信
  • 普通管道PIPE:是半双工,具有固定的读端和写端;只能用于具有亲缘关系的进程之间的通信,它是一种文件,只存于内存中
  • 命名管道FIFO:在无关进程中之间交换数据,有路径名与之相关联,以一种特殊设备文件形式存在文件系统中

2、系统IPC

  • 消息队列,是消息的链接表,存在内核中。一个消息队列由一个标识符来标记。
  • 消息队列,是面向记录的,消息具有特定的格式以及特定的优先级。
  • 消息队列,是独立于发送与接收进程。
  • 消息队列,是可以实现消息的随机查询。
  • 信号量使用于进程间同步
  • 信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作
  • 信号量支持信号量组
  • 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生
  • 共享内存是最快的一种IPC,进程是直接对内存进行存取
  • 因为多个进程可以同时操作,所以需要进行同步
  • 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
  • 套接字SOCKET,是一种进程间通信机制,用于不同主机之间的进程通信

    线程间通信方式

  • 临界区:通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问
  • 互斥量Synchronized/Lock:采用互斥对象机制,只有用用互斥对象的线程才有访问公共资源的权限
  • 信号量Semphare:为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目
  • 事件(信号)Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作

引用和指针有什么区别,能用引用的地方都能用指针代替,那为什么还要用引用呢?h

引用和指针

  • 引用在创建时必须初始化,指针在定义时不必初始化,可以再定义后的任何地方重新赋值,指向内存一个存储单位
  • 引用是对象的别名,一旦初始话指向一个对象,就不能更改。
  • 引用的创建和销毁不会调用类的拷贝构造函数和析构函数

区别

  • 指针可以用NULL,指针不能用NULL
  • 指针可以重新赋值,引用不可以重新赋值
  • 指针作为函数参数时候,需要在函数内对传入指针是否NULL进行检验,而相同途径的引用不需要检验
  • 指针可以有多级,但是引用只能是一级
  • 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能会引起内存泄漏。
  • 可以有const 指针,不能有const 引用, const int& a可以 int& const a 不需要
  • sizeof 引用为所指变量的大小,sizeof 指针是指针的大小

    引用功能指针都能实现,而且引用还这么多限制,为什么花妖有引用呢?

  • 引用不需要额外空间存放变量地址,因为他只是变量的别名,能省下存储空间,也能提高程序的效率。
  • 还有区别的第三点

什么时候使用指针?什么时候使用引用

  • 接口参数多引用、数组,少传指针;

  • 不作为参数的情况下尽量使用指针,只有在以下两种情况下优先使用引用:1、确认指向不会变化,且不是空值的时候;2、使用指针无法完成预期的功能的时候

    c++是c的plus plus,c++相对于c,有什么特性吗,为什么推出c++。这个问题答要点即可,不用展开

  • C面向过程的、结构化语言,C++是面向对象的语言,C++具有继承、封装、多态。

    面向过程,面向对象是啥

  • 面向过程:

  1. 采用函数(或过程)来描述对数据的操作。
  2. 以功能为中心来设计功能模块,难于维护。
  3. 控制流程由程序中预定顺序来决定。
  • 面向对象:
  1. 是一种以对象为基础,以事件或消息来驱动对象执行处理的程序设计技术。
  2. 它具有抽象性、封装性、继承性及多态性。
  3. 将数据和对数据的操作封装在一起,作为一个整体来处理。
  4. 以数据为中心来描述系统,数据相对功能具有较强的稳定性,便于维护。
  5. 控制流程由运行时各种事件的实际发生来触发。

封装有什么好处

  • 区别:在于封装之后,面对对象提供了面向过程不具备的各种特性,最主要的就是继承和多态。
  • 封装: 隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合
  1. 提高数据的安全性
  2. 操作简单,调用者不需要进行判断隐藏了实现
  3. 隐藏了实现,调用者不知道具体实现过程

多态怎么实现

  • 多态:是指通过基类的指针或者引用,在运行时调用实际绑定对象函数的行为。允许子类类型的指针赋值给父类类型的指针
  1. 覆盖:是指子类重新定义父类的虚函数的做法
  2. 重载:是指允许存在多个同名函数,而这些函数的参数表不同
  • 接口的多种不同实现方式即为多态,有模板(编译期多态)和虚函数(运行期多态)这两种。
  • 模板 –编译耗时,代码膨胀,效率更高,泛型设计,
  • 虚函数— vtable vbpr 空间开销大,编译期不可优化,运行时开销。
    参考文献

继承的实现方式

  1. 实现继承是指使用基类的属性和方法而无需额外编码的能力
  2. 接口继承是指仅使用属性和方法的名称,但是子类必须提供实现的能力
  3. 可视继承是指子窗体(类)使用基窗体的外观和实现代码的能力

指针常量与常量指针

指针常量(* const):

  • 是指指针本身是常量,它指向的地址不可改变,但地址内的内容可以通过指针改变。
  • 指针常量必须赋初值,不能为NULL,也不能释放;
  • 注意:数组名相当于一个指针常量

    常量指针(const *):

  • 是指指向常量的指针,它不能指向变量,它指向的内容不可改变,不能通过修改指针它指向的内容
    参考文献

空指针和野指针

  • 空指针:没有存储任何内存地址的指针成为空指针
    空指针就是被赋值为0的指针,在没有被具体初始化之前,其值为0

  • 野指针:是指向“垃圾”内存(非法的或已销毁的内存)的指针。对系统造成不可预知的错误

函数指针有什么作用

  1. 将函数作为参数传递给函数
  2. 用函数指针变量调用函数

int *p()和int (*p)()区别

  • int *p()是返回指针的函数,一个函数的返回值肯定是指针类型
  • int (*p)()是指向函数的指针,专门存放函数的入口地址

死锁的定义,处理死锁的办法(从四个特性出发)

死锁的定义

  • 死锁:是指多个进程可以竞争有限数量的资源,两个或者两个以上的进程(线程)在运行过程中因争夺资源而造成的一种局面,若无外力,这些进程都将会一直等待。

产生死锁的四个必要原因

  • 互斥:只有一个资源必须处于非共享模式,即一次只有一个进程可以使用
  • 占有并等待:一个进程必须占有至少一个资源,并等待另外一个资源,而该资源为其他进程所占有
  • 非抢占:资源不能被强占,即资源只能在进程完成任务后自动释放
  • 循环等待:资源一直被占用,循环成为一个圈子。

处理死锁的办法

  • 预防死锁:通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或几个条件,来防止死锁的产生
  • 避免死锁:在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免死锁的发生
  • 检测死锁:允许系统在运行过程中发生死锁,但可设置检测机构及时检测死锁的发生,并采取适当措施加以清楚
  • 解除死锁:当检测出死锁后,便采取适当措施将进程从死锁状态中解脱出来。

参考文献