STL容器与EffectiveC++

文章深入讨论了STL中的迭代器设计模式,包括其类型参数、参数推导机制以及如何作为返回值类型。同时介绍了迭代器与容器的关系,如vector和array的内存管理,以及list和deque的不同指针操作。此外,还提到了heap和二叉搜索树的数据结构。
摘要由CSDN通过智能技术生成

《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种迭代器类型。

0625a57951b04b4f93c85b403c411d98.jpg

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中记录了这些内存块的头结点的地址。

63da70d0456c4004a0897859fc607240.jpg

中控器,迭代器与缓冲区

// 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的中间。

如图所示。

59b4bbc89c9b4817950c157eaa3ec474.jpg

 start,end迭代器的使用情况

1.使用push_back(),如果当前end指向的buffer中存在容量(cur<=last-1),则可以直接写入当前的buffer,否则需要重新申请新buffer。

dff36c9aa23240cc9b6954bcfd996f09.jpg

 push_back()增加元素

2.使用push_front(),则需要申请新buffer,将头地址保存到start.node之前。

7e0caf4f8cb34f4abce568f07a750415.jpg

 push_front()增加元素

4.7 heap结构?上浮,下沉操作原理?

heap又称完全二叉树,所谓完全的意思,是指其只在树结构的最右下角处存在空缺。因此,heap使用了一个vector存储数据。父结点与子结点的关系又可以映射到vector的下标。父结点(i)的子结点(2i+1,2i+2)。

8cf6dfee54c84ca9aaa0556ec85fe4f3.jpg

 heap是一种树结构,没有迭代器。维护heap的操作分别为上浮和下沉(注: heap的所有维护工作都是由这两个操作实现的)。

上浮:向一个heap中增加元素,首先将元素连接到heap的末尾,然后将该元素上浮到合适的位置。得到的仍是heap。

下沉: 要删除heap的顶元素,则顶部位置空缺。再将末尾元素放到顶位置,然后下沉到合适的位置。得到的仍是heap。

heap自顶向下的一条路径是有序的,但不同路径上的元素没有绝对的大小关系。heap可以以O(1)的时间复杂度获取最大元素(Max-heap)。

heap可形成priority_queue。

5.1 树结构之一: 二叉搜索树。

二叉搜索树如何插入和删除?

二叉搜索树的插入操作比较简单,给定一个待插入结点node,只需要自根结点向下比较node值与树中各结点值的大小,找到合适的位置。

二叉树的删除操作:当删除一个结点时,若该结点只有一个子结点,则将子结点连接到被删除结点位置。若有两个结点,则需要将右子树上的最小结点(该值>被删除结点值>左子树,但在右子树上最小)填充到该位置。

494207b96b8a403f80ce6d601f2ee41c.jpg

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 单旋与双旋

7f3ddb1f07a84b30b270a1f95e22e1c5.jpg

 

0161d06956ad4d53ae1315a138b5aae8.jpg

 23963f4bcff34d839b03b4743e8ffc37.jpg

5.4 (新)RB-树

先说明,RB-树的概念,红黑树是一颗二叉搜索树,同时要满足4个条件。

742fd6fdd31a474d8343308dc84a4dfd.jpg

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;}
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Content Containers...................................................................................................................1 Item 1. Choose your containers with care...........................................................1 Item 2. Beware the illusion of container-independent code................................4 Item 3. Make copying cheap and correct for objects in containers.....................9 Item 4. Call empty instead of checking size() against zero..............................11 Item 5. Prefer range member functions to their single-element counterparts...12 Item 6. Be alert for C++'s most vexing parse...................................................20 Item 7. When using containers of newed pointers, remember to delete the pointers before the container is destroyed............................................................22 Item 8. Never create containers of auto_ptrs....................................................27 Item 9. Choose carefully among erasing options..............................................29 Item 10. Be aware of allocator conventions and restrictions..........................34 Item 11. Understand the legitimate uses of custom allocators........................40 Item 12. Have realistic expectations about the thread safety of STL containers. 43 容器 条款1: 仔细选择你要的容器 条款2: 小心对“容器无关代码”的幻想 条款3: 使容器里对象的拷贝操作轻量而正确 条款4: 用empty来代替检查size是否为0 条款5: 尽量使用范围成员函数代替他们的单元素兄弟 条款6: 警惕C++的及其令人恼怒的分析 条款7: 当使用new得指针的容器时,切记在容器销毁前delete那些指针 条款8: 千万不要把auto_ptr放入容器中 条款9: 小心选择删除选项 条款10: 当心allocator的协定和约束 条款11: 了解自定义allocator的正统使用法 条款12: 对STL容器的线程安全性的期待现实一些
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值