C++-----STL与泛型编程(三)

此系列博客,图片文字观点均是来自侯捷老师讲义课程,仅作为学习用途。

1、深度探索list

        GNU C2.9版本实现如下:

       

        list的定义如下:可以看到其中的数据只有一个指针,所以sizeof(list)等于4。这是在GNU C2.9版本下的结果。

        

        双向链表的节点定义:可以看到其中有一个数据,有两个指针,一个指针指向前一个,一个指向后一个。

        

        链表中的iterator的定义:在前面list的定义中有一个typedef *** iterator,所有的容器都要有这一句,而中间的***其实是一个class,这个class用来实现这个容器的iterator的相关操作。下图并不完整。这个class里必须包含一部分typedef和很多的操作符重载。

        

        list的迭代器的前++操作如下:其实就是将链表节点的next赋值给迭代器指针。

        

        后置++的操作如下:首先第一句,这个=号其实是拷贝构造函数,这样后面的*this并不是使用重载的*。第二句的++使用的是上面的重载的++。两个++的返回类型是不一样的,前++返回的是带有&的,说明返回的是引用,可以连续两次++。后++返回的是不带有&,不能做两次后++。这个过程是模拟整数的。

        

        list的取值:两种不同的取值方式。

        

        在前面看到的GNU C2.9版本的源代码中,lsit设计中有两个很奇怪的地方,一个是节点设计中使用的是void*类型。另一个是迭代器模板参数有三个。但是这两点在4.9版本里改了过来,具体对比如下:

        

        虽然在4.9版本里做了这样的改进,但是在list的具体实现上,4.9版本变得更为复杂,里面多了一些继承。如下图所示:

        

        很明显这个和一开始2.9版本比较更为复杂。list的实现中,会在尾端加上一个空白节点,使得list是一个前闭后开区间。根据上面的图可以看出,在4.9版本下sizeof(list<int>)的大小是8。其实就是包含了两个指针。

2、迭代器的设计原则和Iterator Traits的作用与设计

        我们知道的是迭代器是容器和算法之间的桥梁,所以迭代器要针对算法的设计提供算法需要的东西。比如下面这个算法:

        

        迭代器需要针对算法的提问给出回答,也就是迭代器的设计必须提供的一些类型。而迭代器必须提供以下的五种types:

        

        指针其实也是一种Iterator,是一种退化的iterator,迭代器是一种泛化的指针。那么指针并不能做上面的工作,这时候就需要Traits来萃取。如下图:

        

也就是这个萃取机可以判断传入的是迭代器class还是一般指针,如果是迭代器那么可以直接使用::来获得它自己的types。如果是指针的话,就用另外一种方式得到前面需要的types。

        iterator_traits的具体实现如下:

        

上图的执行过程,首先最下面算法需要知道一个value_type,那么便需要迭代器或者指针来回答这个问题,在最上面第一步,我们定义了一个iterator_traits,在算法里面就通过这个来间接的提问,而上图的2和3也都是iterator_traits,但是其传入参数是指针,也就是说,在算法这个函数里,通过iterator_traits获得算法需要的几个types,然后iterator_traits会根据算法里面传入的I来判断进入上面1、2、3哪个函数。

        完整的iterator_traits实现如下:

        

        还会有各种各样的traits:

        

3、vector深度探索

        vector是动态数组,当其容量被用完时,会自动扩充。这个扩充不会是在内存中的原地扩充,而是会找到另一个地方,建立一个比以前大两倍的数组,再把之前的元素复制过去。在VC、GNU C都是以两倍的情况增长的。

        

        上图为vector的结构,vector的具体实现如下:可以对照上图,可以看到,vector里面有三个指针,所以vector的sizeof大小为12。并且vector里面的成员函数的实现都是通过这三个指针实现的。所有的连续的空间的容器都必须重载[]以用于像数组那样来访问元素。

        

        vector扩容的具体操作:以push_back()来为例:

        

        如上图,当我们想要push_back()一个元素时,首先检查vector是否还有备用空间,备用空间就是上图的fish到end_of_storage之间,我们现在假设已经没有了,那么会转到下面这个函数,insert_aux(),在这个函数一开始,还进行了一次检查是否有备用空间,这是因为这个函数不仅仅被push_back()调用,也会被其他函数调用。我们还是假设并没有备用空间,这样就转到else之后,else后面的代码如下:

        

首先记录原来的大小,如果原来的大小为0,则分配1,如果不为0将原来的大小放大两倍。重新申请足够的空间。在try-catch语句里,首先将原来的内容拷贝到新的vector里面,再将新的元素放入其后的位置。之后将安插点之后的元素拷贝过来,这是因为,这个辅助函数也有可能被insert()函数调用,所以拷贝元素会分为这两部分来进行。

        所以,可以看出,如果要进行扩容,需要进行大量的拷贝动作,拷贝动作会使用拷贝构造函数,拷贝完成之后还要将原来的元素删除掉,所以这个过程的成本是很大的,这是在使用vector时需要注意的内容。

        vector的迭代器实现如下:可以看到vector的迭代器其实是一个指针,

        

当需要使用到迭代器的几种型别时,如下图:

        

也就是将ite这个迭代器放入iterator_traits萃取机,就可以得到迭代器的型别。萃取机的源代码如上图所示,当这个迭代器是一个类类型时,萃取机进入泛化的版本,当是一个指针类型时,会进入下面两个特化的版本。

        在4.9版本下vector的结构如下:和前面的list一样,也是从单一的class变成了几个class的继承实现。

        

        GNU C4.9的vector的iterator的实现如下:可以看到,在4.9版本里,整个迭代器的实现也变得十分复杂,最终还是指针。

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值