c++面试题

目录
一、谈谈共享内存相关的api 1
二、介绍一下智能指针… 2
三、请你谈谈Redis和memcached的区别… 2
四、谈谈stl中的容器… 3
五、请你讲讲reactor模型组成… 4
六、请你谈谈深拷贝和浅拷贝… 5
七、请你说明智能指针是否存在内存泄露的情况… 5
八、c++的三大特性… 5
九、请你谈谈C++中类成员的访问权限… 6
十、请你说明一下static关键字的作用… 6
十一、请你简要说明c++和c的区别… 6

一、谈谈共享内存相关的api

Linux允许不同进程访问同一个逻辑内存,提供了一组API,头文件在sys/shm.h中。

新建共享内存

shmget int shmget(key_t key,size_t size,int
shmflg);

key:共享内存键值,可以理解为共享内存的唯一性标记。

size:共享内存大小 shmflag:创建进程和其他进程的读写权限标识。
返回值:相应的共享内存标识符,失败返回-1

连接共享内存到当前进程的地址空间

shmat void *shmat(int shm_id,const void
*shm_addr,int shmflg);

shm_id:共享内存标识符

shm_addr:指定共享内存连接到当前进程的地址,通常为0,表示由系统来选择。 shmflg:标志位 返回值:指向共享内存第一个字节的指针,失败返回-1

当前进程分离共享内存

shmdt int shmdt(const void *shmaddr);

控制共享内存shmctl 和信号量的semctl函数类似,控制共享内存

int shmctl(int shm_id,int command,struct
shmid_ds *buf);

shm_id:共享内存标识符

command:有三个值
IPC_STAT:获取共享内存的状态,把共享内存的shmid_ds结构复制到buf中。

IPC_SET:设置共享内存的状态,把buf复制到共享内存的shmid_ds结构。 IPC_RMID:删除共享内存 buf:共享内存管理结构体。

常见的IPC

   pipe:只能在有血缘关系的的进行进程间通信


   fifio:在无血缘关系的进程间通信

内部数据只能读一次

   mmap:在无血缘关系的进程间通信

内部数据支持反复读取

   信号:开销小。携带数据简单

本地套接字:稳定性好 实现复杂

shm: 。共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程

二、介绍一下智能指针

智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。

std::unique_ptr c++11

std::unique_ptr是一个独享所有权的智能指针,它提供了一种严格语义上的 所有权,包括:

拥有它所指向的对象

无法进行复制、赋值操作

保存指向某个对象的指针,当它本身被删除释放的时候,会使用给定的删除器释放它指向的对象

具有移动(std::move)语义,可做为容器元素

std::shared_ptr c++11

std::shared_ptr是一个引用计数智能指针,用于共享对象的所有权

1.引进了一个计数器shared_count,用来表示当前有多少个智能指针对象共享指针指向的内存块

2.析构函数中不是直接释放指针对应的内存块,如果shared_count大于0则不释放内存只是将引用计数减1,只有计数等于0时释放内存
3.复制构造与赋值操作符只是提供一般意义上的复制功能,并且将引用计数加1.

问题:循环引用
std::weak_ptr c++11

std::shared_ptr是强引用智能指针

std::weak_ptr是弱引用智能指针

强引用,只要有一个引用存在,对象就不能被释放

弱引用,并不增加对象的引用计数,但它知道对象是否存在。

如果存在,提升为shared_ptr成功;否则,提升失败

通过weak_ptr访问对象的成员的时候,要提升为shared_ptr

三、请你谈谈Redis和memcached的区别

1)数据类型 :redis数据类型丰富,支持set liset等类型;

memcache支持简单数据类型,需要客户端自己处理复杂对象

2)持久性:redis支持数据落地持久化存储;memcache不支持数据持久存储。)

3)分布式存储:redis支持master-slave复制模式;memcache可以使用一致性hash做分布式。

4)value大小不同:memcache是一个内存缓存,key的长度小于250字符,单个item存储要小于1M,不适合虚拟机使用

5)数据一致性不同:redis使用的是单线程模型,保证了数据按顺序提交;memcache需要使用cas保证数据一致性。CAS(Check and Set)是一个确保并发一致性的机制,属于“乐观锁”范畴;原理很简单:拿版本号,操作,对比版本号,如果一致就操作,不一致就放弃任何操作

6)cpu利用:redis单线程模型只能使用一个cpu,可以开启多个redis进程

四、谈谈stl中的容器

(1)vector

vector是一种动态数组,在内存中具有连续的存储空间,支持快速随机访问。由于具有连续的存储空间,所以在插入和删除操作方面,效率比较慢。vector有多个构造函数,默认的构造函数是构造一个初始长度为0的内存空间,且分配的内存空间是以2的倍数动态增长的,即内存空间增长是按照20,21,22,23…增长的,在push_back的过程中,若发现分配的内存空间不足,则重新分配一段连续的内存空间,其大小是现在连续空间的2倍,再将原先空间中的元素复制到新的空间中,性能消耗比较大,尤其是当元素是非内部数据时(非内部数据往往构造及拷贝构造函数相当复杂)。vector的另一个常见的问题就是clear操作。clear函数只是把vector的size清为零,但vector中的元素在内存中并没有消除,所以在使用vector的过程中会发现内存消耗会越来越多,导致内存泄露,现在经常用的方法是swap函数来进行解决:

优点:支持随机访问,所以查询效率高。

缺点:当向其分非尾插入元素时,因内存单元需要移动数据元素,所以插入的效率比较低。

适用场景:适用于对象简单,变化较小,并且频繁随机访问的场景。

2.list容器

list容器在内存中的结构是类似双向链表结构,每个元素的内存单元结构是不连续的,彼此之间通存储相关的地址进行关联。由于在内存中的单元不是连续的,所以其比支持随机访问,不具备[]操作运算。每一个节点都有三个域,前驱节点指针域,数据域,后驱节点指针域。

优点:因为只类似链表结构在内存中,所以任意位置删除节点和插入节点都是高效的。

缺点:因为内存单元不连续,所以不支持随机访问操作。

适用场景:对象变化大,并且对象数量变化频繁,删除和插入操作的情况。

(2)deque

deque和vector类似,支持快速随机访问。二者最大的区别在于,vector只能在末端插入数据,而deque支持双端插入数据。deque的内存空间分布是小片的连续,小片间用链表相连,实际上内部有一个map的指针。deque空间的重新分配要比vector快,重新分配空间后,原有的元素是不

(3)set

set也是一种关联性容器,它同map一样,底层使用红黑树实现,插入删除操作时仅仅移动指针即可,不涉及内存的移动和拷贝,所以效率比较高。set中的元素都是唯一的,而且默认情况下会对元素进行升序排列。所以在set中,不能直接改变元素值,因为那样会打乱原本正确的顺序,要改变元素值必须先删除旧元素,再插入新元素。不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取。

3.map容器

map容器是一个关联式容器。在其内部元素的存储结构是通过key-value结构进行存储,并且其key是唯一存在的。支持那种一对一的数据处理过程。其底层是通过rbtree来实现的,利用红黑树的一种严格的平衡二叉树结构实现。在其实现过程,自动创建key-value的插入数据的过程。并且支持快速的查找,通过键值可进行快速的查找对应的数据元素。以及快速删除操作。

3、容器适配器

(1)queue

queue是一个队列,实现先进先出功能,queue不是标准的STL容器,却以标准的STL容器为基础。queue是在deque的基础上封装的。之所以选择deque而不选择vector是因为deque在删除元素的时候释放空间,同时在重新申请空间的时候无需拷贝所有元素。

其模板为:

template < TYPENAME
_Sequence=“deque<_TP” typeneam _Tp,> > class queue;

(2)stack

stack是实现先进后出的功能,和queue一样,也是内部封装了deque,这也是为啥称为容器适配器的原因吧(纯属猜测)。自己不直接维护被控序列的模板类,而是它存储的容器对象来为它实现所有的功能。stack的源代码原理和实现方式均跟queue相同。

五、请你讲讲reactor模型组成

reactor模型要求主线程只负责监听文件描述上是否有事件发生,有的话就立即将该事件通知工作线程,除此之外,主线程不做任何其他实质性的工作,读写数据、接受新的连接以及处理客户请求均在工作线程中完成。

1)Handle:即操作系统中的句柄,是对资源在操作系统层面上的一种抽象,它可以是打开的文件、一个连接(Socket)、Timer等。由于Reactor模式一般使用在网络编程中,因而这里一般指Socket Handle,即一个网络连接。

2)Synchronous Event
Demultiplexer(同步事件复用器):阻塞等待一系列的Handle中的事件到来,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的执行返回的事件类型。这个模块一般使用操作系统的select来实现。

3)Initiation Dispatcher:用于管理Event Handler,即EventHandler的容器,用以注册、移除EventHandler等;另外,它还作为Reactor模式的入口调用Synchronous Event
Demultiplexer的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handler处理,即回调EventHandler中的handle_event()方法。

4)Event Handler:定义事件处理方法:handle_event(),以供InitiationDispatcher回调使用。 5)Concrete Event Handler:事件EventHandler接口,实现特定事件处理逻辑。

六、请你谈谈深拷贝和浅拷贝

如果变量都是在栈区,浅复制就可以完成,如果在堆区则语言深复制

浅拷贝是增加了一个指针,指向原来已经存在的内存。而深拷贝是增加了一个指针,并新开辟了一块空间让指针指向这块新开辟的空间。浅拷贝在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误。

例子:浅拷贝,也就是一个string对象中的char* ptr变量指向某处内存,另外一个string对象的char* ptr也指向了同一块内存,两个指针存储同一个地址值,析构时候就会报错 深拷贝,就是在浅拷贝基础上,额外开辟空间,两个ptr指针指向不同的内存,这时候析构,就会正常通过。

七、请你说明智能指针是否存在内存泄露的情况

当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。parent有一个shared_ptr类型的成员指向孩子,而child也有一个shared_ptr类型的成员指向父亲。然后在创建孩子和父亲对象时也使用了智能指针c和p,随后将c和p分别又赋值给child的智能指针成员parent和parent的智能指针成员child。从而形成了一个循环引用:

不要把一个原生指针给多个shared_ptr或者unique_ptr管理,在使用原生指针对智能指针初始化的时候,智能指针对象都视原生指针为自己管理的资源。换句话意思就说:初始化多个智能指针之后,这些智能指针都担负起释放内存的作用。那么就会导致该原生指针会被释放多次

不要把this指针交给智能指针管理,如果直接通过原生指针来初始化,就会导致m_sp和p都根本不知道对方的存在,然而却两者都管理同一块地方。

八、c++的三大特性

1.封装

何为封装?

在面向对象的思想中,将数据和对数据的操作封装在一起——即类。

类只对外界开放接口(即有权访问的函数接口),而将接口的实现细节和该类的一些属性(变量)隐藏起来,达到数据的抽象性(使具有相同行为的不同数据可以抽象为同一个类)、隐藏性和封装性。

2、继承

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码,能够大大的提高开发的效率。

3、多态

在C++中,多态是通过父类的指针或引用,调用了一个在父类中是virtual类型的函数,实现动态绑定机制。我们知道,若想使用父类的指针/引用调用子类的函数,需要在父类中将其声明为虚函数(virtual),且必须与子类中的函数参数列表相同(包括参数个数、类型、顺序),返回值也相同(若不同为协变的情况,需要各自返回各自类的指针/引用)。

C++中有两种多态,称为动多态(运行期多态)和静多态(编译期多态),而静多态主要通过模板来实现,宏也是实现静多态的一种途径。动多态在C++中是通过虚函数实现的,即在基类中存在一些接口(一般为纯虚函数),子类必须重载这些接口。这样通过使用基类的指针或者引用指向子类的对象,就可以实现调用子类对应的函数的功能。动多态的函数调用机制是执行期才能进行确定,所以它是动态的。

九、请你谈谈C++中类成员的访问权限

C++提供了三种不同程度的访问权限来实现,主要是通过public、private、protected三个关键字实现的

public限定符

 被public限定符所修饰的成员变量和函数可以被类的函数、子类的函数、友元函数,也可以由类的对象来访问,即可以使用成员运算符来访问。这里的友元函数,可以是该类的友元函数,也可以是该类的友元类的成员函数。使用形式如下:

protected限定符

protected限定符修饰的成员变量和成员函数可以被该类的成员函数访问,但是不能被类对象所访问,即不能通过类对象的成员运算符来访问。另外,这些成员可以被子类的函数和友元函数访问,相比public成员 少了一个可以使用类对象直接访问的特性。具体使用与public类似,这里不再贴出代码。

private限定符

被private限定符修饰的成员变量只能被该类的方法和友元函数访问,子类函数无法访问,在这三个限定符中封装程度是最高的,一般来说,应该尽可能将类的成员变量声明为private而不是其他,减少成员变量的暴露,只提供getter和settter方法给外界访问,这样能提高类的安全性。具体使用与public类似。

十、请你说明一下static关键字的作用

静态全局变量

全局变量作用于整个程序

静态全局变量作用于当前源文件

静态局部变量

生命周期:贯穿整个程序执行(只会被初始化一次)static count=0;

作用域:和普通局部变量一样

静态数据成员

作用:多个类的对象会共享这个静态成员,实现信息共享

静态成员函数

作用:管理静态数据成员(静态成员函数只能访问静态数据成员,理由是,静态成员函数是属于大家的,不是属于某个类的,他没有this指针)。

十一、请你简要说明c++和c的区别

设计思想上: C++是面向对象的语言,而C是面向过程的结构化编程语言 语法上: C++具有重载、继承和多态三种特性 C++相比C,增加多许多类型安全的功能,比如强制类型转换、 C++支持范式编程,比如模板类、函数模板等

你可能感兴趣的:(c++基础知识,c++)