《STL源码解析》
2.2.2 new Foo与new Foo()的区别?
区别在于初始化成员与否。使用 new Foo()会将成员初始化为对应的初始值。
2.2.4 两级空间配置器
new->底层malloc();delete->底层free()。
3.1 迭代器设计模式。
迭代器是一种智能指针,基础的迭代器版本至少重载了解引用运算符(*,->)。判断运算符(==,!=)。此外,为了使迭代器不暴露容器的内部信息,容器的设计者就将迭代器重载为容器的成员,并重载(++,--)等一系列运算符,这就是迭代器模式。
3.2 迭代器的型别参数?有什么作用?
为了使迭代器能够与任意一种容器相容,迭代器的设计者需要至少为迭代器设计5种型别参数(value _type,difference_type,pointer,reference,iterator_category)。iterator_category规定了5种迭代器类型。
STL为用户设计了一个基础的迭代器类,类中仅定义了5个类型变量。用户自定义的迭代器都可以继承自该基础类。基础类中不含其他元素,因此不带来额外的开销。
3.3 迭代器的参数推导机制,如何理解?实现?
首先要说明为什么有这个需求。在写泛型算法时,要给算法传一个迭代器(以Inputiterator为例),算法中可能需要用到迭代器所指对象的类型(T),函数可能为如下形式:
template <class Iter>
void func(Iter it){
//需要定义一个 (*it)类型的变量
}
无法在泛型函数中获取(*it)的具体类型,只能使用间接办法,将函数修改为:
template <class Iter, class T>
void func(Iter it, T ){
T tmp;
}//实际调用: func(it,*it)。通过实参*it推断T的实际类型。
为了使接口统一,STL算法的设计者通常会将func()再封装一层。
template <class Iter>
void f(Iter iter){
func(iter, *iter);
}
以上这个过程就是参数推导的实现。
3.4 如要使用迭代器所指对象的类型作为泛型算法的返回值类型,要如何实现?
首先迭代器有5种类型参数,其中value_type专门用来指代对象的相应类型。如果泛型算法要以value_type作为返回值类型,则要用到typename关键字以及内嵌类型声明。
template <class T>
struct myIter{
typedef T value_type; //声明内嵌类型,当定义一个myIter类型的对象时,自动推导出value_type。再有其他函数以myIter对象作为实参便可以知晓返回值类型。
T* ptr;
myIter(T* p):ptr(p){}
}
template <typename Iter>
typename Iter::value_type func( Iter iter){
Iter::value_type tmp;
return tmp;
}
myIter<int> ite(new int(8));
func(ite)
通过上述的办法可以使编译器在编译时获得迭代器所指对象的类型。除了一点:指针类型。由于指针类型没有内嵌value_type的东西,所以要针对指针类型单独定义一个特化版本。
template <class T> //特化版本
T* func(T* tmp){
return tmp;
}
3.5 解释迭代器萃取机制。
综合迭代器的5种型别参数,参数推导+内嵌类型技术,特化法。STL的设计者定义了:
template <class I>
struct iterator_traits{
typedef typename I::iterator_category iterator_category;
typedef typename I::value_type value_type;
typedef typename I::difference_type difference_type;
typedef typename I::pointer pointer;
typedef typename I::reference reference;
}
//特化版本,通常只有这3种类型
template <class T>
struct iterator_traits<T*>{
typedef typename I::value_type value_type;
typedef typename T* pointer;
typedef typename T& reference;
}
//
template <class I>
class myIter : iterator_traits{
template <class Iter>
typename iterator_traits<I>::difference_type count(Iter &ite){ //... return }
}
5种迭代器都继承自iterator_traits类。同时,为原生指针类定义了特化版本,traits类就可以适用任何类型的迭代器。
为这5类迭代器还定义了标识符,
struct input_iterator_tag { }
struct output_iterator_tag { } ...
容器概述
vector与array都是线性连续空间,其实vector底层也是通过array来管理数据。
4.1注意:vector与array的区别,vector与array是如何扩容的?
array是静态空间,定义时就指定了大小,不能够任意改变。vector通过动态管理容量(capacity),如果容量达到上限,则会自动申请新内存,将空间扩充一倍(例如)。这样做的目的是通过增加空间余量,避免经常扩充(申请新地址,拷贝所有元素,释放旧地址,这个操作时间成本很高)。
4.2 vector如何管理内存?
vector设置了3个普通指针,start, finish, end.管理内存
4.3 list的指针与vector指针有何不同?
c++普通指针支持operator++等操作。++操作的前提是指向对象的空间连续。而list的空间不连续,因此list需要重载指针的operator系列运算符。
4.4.1 deque如何管理内存?
deque存储数据也是一块一块的连续内存。注意,这里的连续内存并不是一整块的内存,而是可能分布在系统各处的小块,这一点与vector不同。因此对于这些散碎的内存块的管理,deque还维护了一个指针成员map(中控器),map中记录了这些内存块的头结点的地址。
中控器,迭代器与缓冲区
// T** map
4.4.2 deque在插入元素(push_front,push_back)时是如何使用内存的?
显然deque需要一个中控器map和两个迭代器start,end。start和end迭代器维护了4个指针:cur, first, last, node。deque的中控器map设计为vector。初始状态下有一块缓冲区buffer,start,end迭代器都指向这块buffer。且buffer的头指针存储在map的中间。
如图所示。
start,end迭代器的使用情况
1.使用push_back(),如果当前end指向的buffer中存在容量(cur<=last-1),则可以直接写入当前的buffer,否则需要重新申请新buffer。
push_back()增加元素
2.使用push_front(),则需要申请新buffer,将头地址保存到start.node之前。
push_front()增加元素
4.7 heap结构?上浮,下沉操作原理?
heap又称完全二叉树,所谓完全的意思,是指其只在树结构的最右下角处存在空缺。因此,heap使用了一个vector存储数据。父结点与子结点的关系又可以映射到vector的下标。父结点(i)的子结点(2i+1,2i+2)。
heap是一种树结构,没有迭代器。维护heap的操作分别为上浮和下沉(注: heap的所有维护工作都是由这两个操作实现的)。
上浮:向一个heap中增加元素,首先将元素连接到heap的末尾,然后将该元素上浮到合适的位置。得到的仍是heap。
下沉: 要删除heap的顶元素,则顶部位置空缺。再将末尾元素放到顶位置,然后下沉到合适的位置。得到的仍是heap。
heap自顶向下的一条路径是有序的,但不同路径上的元素没有绝对的大小关系。heap可以以O(1)的时间复杂度获取最大元素(Max-heap)。
heap可形成priority_queue。
5.1 树结构之一: 二叉搜索树。
二叉搜索树如何插入和删除?
二叉搜索树的插入操作比较简单,给定一个待插入结点node,只需要自根结点向下比较node值与树中各结点值的大小,找到合适的位置。
二叉树的删除操作:当删除一个结点时,若该结点只有一个子结点,则将子结点连接到被删除结点位置。若有两个结点,则需要将右子树上的最小结点(该值>被删除结点值>左子树,但在右子树上最小)填充到该位置。
5.2 AVL树。
(先验: 树平衡的概念。结点高度求解算法。)
AVL树(首先是一颗二叉搜索树),对平衡性的要求:| height(left)-height(right) | <=1;
首先说明:如何对一颗给定的二叉树,求结点高度?(以根结点root为例)。
使用递归法求解。结点p的高度depth[p]=1+max(depth[p.left], depth[p.right])。
int depth(TreeNode * root){
if(root==NULL){return 0;}
if(root.left==NULL && root.right==NULL) {return 1;}
return 1+max(depth(root.left),depth(root.right));
}
5.3 单旋与双旋
5.4 (新)RB-树
先说明,RB-树的概念,红黑树是一颗二叉搜索树,同时要满足4个条件。
RB树是set,map,hashtable的底层容器结构。set的key与value值相同
7.1 解释仿函数配接过程
首先所谓仿函数,就是指一个类class,或者struct,定义或者重载函数调用符(),因此用户可以像使用函数一样调用这个类。显然,定义调用符()需要明确其参数和返回值类型。
STL的机制是,使用萃取机制。定义unary_function,binary_function这两个类,这两个类中分别定义了参数类型和返回值类型。仿函数都继承这两个类,从而获取到对应的参数类型。
//初始模板unary_function, binary_function,分别是一元、二元仿函数模板参数萃取机
template <class Arg, class Result>
struct unary_function{
typedef Arg argument_type;
typedef Result result_type;
}
template <class Arg1, class Arg2, class Result>
struct unary_function{
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
}
//仿函数都应当继承这个模板
//以plus(T t1, T t2)为例
template <class T>
struct plus:public binary_function<T,T,T>{
T operator()(const T& x, const T& y)const{ return x+y;}
}