系列文章目录
第一篇:语言基础
第二篇:设计模式
第三篇:数据库
第四篇:计算机网络
第五篇:操作系统
第六篇:LInux
第七篇:数据结构
第八篇:智力题
[181]友元
友元提供了不同类的成员函数之间、类的成员函数和一般函数之间进行数据共享的机制。通过友元,一个不同函数或者另一个类中的成员函数可以访问类中的私有成员和保护成员。友元的正确使用能提高程序的运行效率,但同时也破坏了类的封装性和数据的隐藏性,导致程序可维护性变差。
一个函数可以是多个类的友元函数,但是每个类中都要声明这个函数
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。
但是另一个类里面也要相应的进行声明
添加链接描述
友元类中申明友元函数:告诉类内的东西,这个函数是friend,可以访问类内任何东西
[182]解释下 C++ 中类模板和模板类的区别
类模板是模板的定义,不是一个实实在在的类,定义中用到通用类型参数
模板类是实实在在的类定义,是类模板的实例化。类定义中参数被实际类型所代替。
[183]空类
空类的⼤⼩不是0,是1字节。为了确保两个不同对象的地址不同。,空类也会实例化,所以编译器会给空类隐含的添加⼀个字节
共享虚函数地址表:派⽣类继承的第⼀个是基类,且该基类定义了虚函数地址表,则派⽣类就共享该表⾸址占⽤的存储单元。
[184]STL优势
1.实现数据结构和算法的分离,使得STL非常通用。
2.STL具有高可重用性,高性能,高移植性,夸平台的优点。
高可重用性:STL中几乎所有的代码都采用了模板类和模板函数的方式实现,代码重用性高。
高性能:如 map 可以⾼效地从⼗万条记录⾥⾯查找出指定的记录,如map,采用红黑数的变体实现,效率高。
高移植性:STL模块很容易移植。
[185]请说说 STL 的基本组成部分
广义上讲,STL分为3类:Algorithm(算法)、Container(容器)和Iterator(迭代器),容器和算法通过迭代器可以进行无缝地连接。
详细的说,STL由6部分组成:容器(Container)、算法(Algorithm)、 迭代器(Iterator)、仿函数(Function object)、适配器(Adaptor)、空间配制器(Allocator)。
标准模板库STL主要由6大组成部分:
容器(Container)
是一种数据结构, 如list, vector, 和deques,以模板类的方法提供。为了访问容器中的数据,可以使用由容器类输出的迭代器。
算法(Algorithm)
是用来操作容器中的数据的模板函数。例如,STL用sort()来对一 个vector中的数据进行排序,用find()来搜索一个list中的对象, 函数本身与他们操作的数据的结构和类型无关,因此他们可以用于从简单数组到高度复杂容器的任何数据结构上。
迭代器(Iterator)
提供了访问容器中对象的方法。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。 迭代器就如同一个指针。事实上,C++ 的指针也是一种迭代器。 但是,迭代器也可以是那些定义了operator*()以及其他类似于指针的操作符方法的类对象;
仿函数(Function object)
仿函数又称之为函数对象, 其实就是重载了操作符的struct(class),没有什么特别的地方。
适配器(Adaptor):queue,stack(底层用queue实现),priority_queue(底层用vector实现)
简单的说就是一种接口类,专门用来修改现有类的接口,提供一中新的接口;或调用现有的函数来实现所需要的功能。主要包括3中适配器Container Adaptor、Iterator Adaptor、Function Adaptor。
空间配制器(Allocator)::alloctor
为STL提供空间配置的系统。其中主要工作包括两部分:
(1)对象的创建与销毁;
(2)内存的获取与释放。
[186]Stl部件关系
STL六⼤组件的交互关系:
- 容器通过空间配置器取得数据存储空间
- 算法通过迭代器存储容器中的内容
- 仿函数可以协助算法完成不同的策略的变化
- 适配器可以修饰仿函数。
[187]请说说 STL 中常见的容器,并介绍一下实现原理
容器可以用于存放各种类型的数据(基本类型的变量,对象等)的数据结构,都是模板类,分为顺序容器、关联式容器、容器适配器三种类型,三种类型容器特性分别如下:
顺序容器
容器并非排序的,元素的插入位置同元素的值无关。包含vector、deque、list,具体实现原理如下:
(1)vector 头文件
动态数组。元素在内存连续存放。随机存取任何元素都能在常数时间完成。在尾端增删元素具有较佳的性能。
(2)deque 头文件(类似于vector的性质)
双向队列。元素在内存连续存放。支持随机存取任何元素都能在常数时间完成(仅次于vector)。在两端增删元素具有较佳的性能(大部分情况下是常数时间)。
采用多个连续的存储块,在一个映射结构中保存对这些块及其顺序的跟踪。(不要说map,说成映射结构)
(3)list 头文件
双向循环链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。
关联式容器
元素是排序的;插入任何元素,都按相应的排序规则来确定其位置;在查找时具有非常好的性能;通常以平衡二叉树的方式实现。包含set、multiset、map、multimap,具体实现原理如下:
(1)set/multiset 头文件
set 即集合。set中不允许相同元素,multiset中允许存在相同元素。
(2)map/multimap 头文件
map与set的不同在于map中存放的元素有且仅有两个成员变,一个名为first,另一个名为second, map根据first值对元素从小到大排序,并可快速地根据first来检索元素。
Set、map的插入删除比vector,deque快的原因:
插入删除操作的效率比序列容器高,因为对于关联容器来说,不需要做内存的拷贝和内存的移动
注意:map同multimap的不同在于是否允许相同first值的元素。
容器适配器
封装了一些基本的容器,使之具备了新的函数功能,比如把deque封装一下变为一个具有stack功能的数据结构。这新得到的数据结构就叫适配器。包含stack,queue,priority_queue,具体实现原理如下:
(1)stack 头文件 栈既可以用数组来实现,也可以用链表来实现,也可以用deque实现
栈是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项(栈顶的项)。后进先出。
(2)queue 头文件
队列。插入只可以在尾部进行,删除、检索和修改只允许从头部进行。先进先出。
(3)priority_queue 头文件
优先级队列。内部维持某种有序,然后确保优先级最高的元素总是位于头部。最高优先级元素总是第一个出列。
哈希缺点:它是基于数组的,数组创建后难于扩展,某些哈希表被基本填满时,性能下降得非常严重
队列。栈的插入、删除时间复杂度为O(1)
[188]String at和【】
[189]说说 STL 中 map hashtable deque list 的实现原理
map、hashtable、deque、list实现机理分别为红黑树、函数映射、双向队列、双向链表,他们的特性分别如下:
map实现原理
map内部实现了一个红黑树(红黑树是非严格平衡的二叉搜索树,它的左右子树高差有可能大于 1,,而AVL是严格平衡二叉搜索树),红黑树有自动排序的功能,因此map内部所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找、删除、添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉树(又名二叉查找树、二叉排序树)存储的,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值。使用中序遍历可将键值按照从小到大遍历出来。
hashtable(也称散列表,直译作哈希表)实现原理
hashtable采用了函数映射的思想记录的存储位置与记录的关键字关联起来,从而能够很快速地进行查找。这决定了哈希表特殊的数据结构,它同数组、链表以及二叉排序树等相比较有很明显的区别,它能够快速定位到想要查找的记录,而不是与表中存在的记录的关键字进行比较来进行查找。
deque实现原理
deque内部实现的是一个双向队列。元素在内存连续存放。随机存取任何元素都在常数时间完成(仅次于vector)。所有适用于vector的操作都适用于deque。在两端增删元素具有较佳的性能(大部分情况下是常数时间)。
deque内部有一个指针指向map,map是一小块连续空间,其中的每个元素称为一个节点,node,每个node都是一个指针,指向另一段较大的连续空间,称为缓冲区,这里就是deque中实际存放数据的区域,默认大小512bytes。整体结构如上图所示
list实现原理
list内部实现的是一个双向链表。元素在内存不连续存放。在任何位置增删元素都能在常数时间完成。不支持随机存取。无成员函数,给定一个下标i,访问第i个元素的内容,只能从头部挨个遍历到第i个元素。
[190]set和map的区别,multimap和multiset的区别
set只提供一种数据类型的接口,但是会将这一个元素分配到key和value上,,set的key和value其实是一样的
map则提供两种数据类型的接口,分别放在key和value的位置上
他们两个的insert都是采用红黑树的insert_unique() 独一无二的插入
multimap和map的唯一区别就是:multimap调用的是红黑树的insert_equal(),可以重复插入而map调用的则是独一无二的插入insert_unique(),multiset和set也一样,底层实现都是一样的,只是在插入的时候调用的方法不一样。
[191]STL 的空间配置器(allocator)
(1)对象的创建与销毁;
(2)内存的获取与释放。
将对象的构造切分开来,分成空间配置和对象构造两部分。
内存配置操作: 通过alloc::allocate()实现
内存释放操作: 通过alloc::deallocate()实现
对象构造操作: 通过::construct()实现
对象释放操作: 通过::destroy()实现
关于内存空间的配置与释放,SGI STL采用了两级配置器:一级配置器主要是考虑大块内存空间,利用malloc和free实现;二级配置器主要是考虑小块内存空间而设计的(为了最大化解决内存碎片问题,进而提升效率),采用链表free_list来维护内存池(memory pool),free_list通过union结构实现,空闲的内存块互相挂接在一块,内存块一旦被使用,则被从链表中剔除,易于维护。
用在vector
[192]Vector和数组array扩容
Vector大概是1.5倍扩容
其实二者扩容相似,唯一区别是vector扩容更加灵活,不是由操作者扩,而是由空间配置器扩。
都是找一个更快内存,拷贝原值,删除原容器
区分sizeof(vector):占多少内存
以及vector.size():有几个元素
[193]Deque原理
[194]List原理
双向循环链表
[195]priority_queue的底层原理
priority_queue:优先队列,其底层是用堆来实现的。在优先队列中,队首元素一定是当前队列中优先级最 高的那一个。而堆的底层是用数组实现的
[196]为何map和set的插入删除效率比其他序列容器高
因为不需要内存拷贝和内存移动
为何map和set每次Insert之后,以前保存的iterator不会失效?
因为插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变
[197]红黑数
三种操作:左旋、右旋、变色
[198]set怎么保证插入不重复的性质
rb_tree 提供两种 insert 操作:insert_unique() insert_equal()。
set / multiset 是以 rb_tree 为底层结构,因此有元素自动排序特性排序的依据就是key,而 set / multiset 元素的key和value合一。
因此set insert 用的是 rb_tree 的 insert_unique() 函数。multiset 元素的 key 可以重复。因此 insert 用的是 rb_tree 的insert_equal() 函数。
[199]为何 map 和 set 的插入删除效率比其他序列容器高?
对于关联容器来说,插入和删除元素,不需要做内存拷贝和内存移动的动作,容器内所有元素都是以节点的方式来存储,其节点指向父节点和子节点,一切操作就是指针的操作