美文网首页
[GeekBand][C++ STL与泛型编程]第七周笔记

[GeekBand][C++ STL与泛型编程]第七周笔记

作者: readME_boy | 来源:发表于2017-08-31 20:49 被阅读0次

    源代码分布

    标准库STL的文件位置,与所采用的编译器有关:

    1. visual c++源代码文件:..\include 下面有这个c++标准库的头文件(自己的环境vs 2017,C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.10.25017\include在这个目录下)
    2. gnc c++4.9.2 头文件的位置:..\4.9.2\include\c++
      stl开头的头文件:\include\c++\bits
      扩充的(分配器)头文件:\include\c++\ext

    oop(面向对象)与gp(泛型编程)

    oop企图数据和操作放在一起
    这里的list不能使用::sort()排序,这是因为::sort()算法设计中的Iterator必须是RandomAccessIterator,而list并不是一个连续空间,在内存中它是由指针一个一个串起来,不能使用指针加法减法。list不能调用::sort(),因为只有随机访问的迭代器才能调用::sort(),list不支持随机访问。所以list只能用自己的sort.

    oopgp.png

    gp是将数据和操作分开
    容器存放数据,算法实现各种操作,它们之间通过迭代器联系起来。gp的好处在于容器和算法相互独立,他们之间只要具有可以相互使用的接口即可。
    两者分开的优点:

    1. Containers和Algorithms团队可各自闭门造车,其间以Iterator沟通即可;
    2. Algorithms通过Iterators确定操作范围,并通过Iterators取用Container元素。
    gp.png

    eg. ::min() 对于两个类对象比较大小,调用这个算法,就要重载小于号或是自定义比较大小的函数。所有的algorithms,最终设计元素本身的操作,无非就是比大小。比如说重新定义max函数,根据字符长度来比大小,我们就必须重写一个比较函数。

    max.png

    技术基础--运算符重载、模板

    四个运算符不能重载: :: , . , .*, :?
    类模板、函数模板、成员模板
    类模板中又分为泛化、特化、偏特化(局部特化,个数和范围上)

    template <typename T> struct __type_traits {...}  // 泛化
    template<> struct __type_traits<int> {...}        // 特化
    
    template <class Iterator> struct iterator_traits {...}         // 泛化
    template <class T> struct struct iterator_traits<T *> {}       //偏特化,传入指针
    template <class T> struct struct iterator_traits<const T *> {} //偏特化,传入常量指针
    

    分配器allocators

    分配器(Allocator)是容器管理内存的工具,在容器申请内存空间上起作用。
    分配器在底层实现上通过operator new()和operator delete()来完成内存分配和释放,而operator new()和operator delete()实际上是通过调用malloc()和free()函数来实现操作。

    allocators.png

    operator new()和operator delete()的源代码如下:

    • malloc会在申请分配的内存大小的基础上附加额外的空间,如果申请分配的内存小,那么额外的空间所占的比例就大。
    • 分配器是用来分配内存的,对于VC下的allocator类中有两个重要的函数allocate和deallocate. allocate函数内部会调用::operator new,operator new内部会调用malloc. deallocate内部会调用::operator delete,都没有任何特殊设计。
    使用方法:
    eg. int *p = allocator<int>().allocate(512, (int *)0);  //首先是创建一个临时allocator对象,之后调用allocate函数分配512字节,且全部初始化为0。
    allocator<int>().deallocate(p, 512);  //释放空间,这里要求告知分配的空间大小。
    
    • 对于BC5下的allocator类,头文件<memory.stl>. 且也是利用::operator new和::operator delete实现allocate和deallocate的,都没有任何特殊设计。
    使用方法:
    eg. int *p = allocator<int>().allocate(512);  //allocate函数第二个参数用默认值是0.
    allocator<int>().deallocate(p, 512);
    
    • 对于GCC2.9下也是利用::operator new和::operator delete实现allocate和deallocate的,都没有任何特殊设计。GCC2.9在容器中采用的不是allocator,而是alloc, 头文件<stl_alloc.h>. 在扩展空间时,尽量减少malloc次数,做法是创建16条链表,每条链表存放的字节是以8的倍数增长,第0条链表存放8字节,第1条链表存放16字节...。容器会和分配器要内存,容器中元素的大小会被调整为8的倍数,分配器会找到相应的链表上,再调用malloc分配内存,一个链上的每个内存块不带有上下cookie(8字节),只在链的头尾带有cookie,从而大幅节省额外空间开销。
    • GCC4.9则没有在沿用GCC2.9这种好的方式,而是使用的是std::allocator, 继承于new_allocator, 且又是采用和vc和bc一样的分配和回收方法。GCC4.9中alloc还存在,只是名字改为了pool_alloc. eg.vector<string, gnu_cxx::__pool_alloc<string>>vec;

    容器之间实现关系

    对于GCC2.9,属于早期的标准库版本,容器之间是复合的关系,而不是继承的关系。heap中拥有vector,priority-queue中拥有heap, stack和queue都拥有deque. 关联容器set、map、multiset、multimap都拥有rb_tree(高度平衡的二叉树).

    深度探索list

    • 容器中要提供迭代器,和一系列操作符重载。迭代器内部要定义迭代器相关的5种typedef。
    • ++运算符的重载:前++没有参数,后++是有一个函数参数的,为了与前++进行区分。后++首先会用tmp记录原有的指针,再对原有的指针自加,最后返回tmp。
    self operator++(int)
    {
       self tmp = *this; //会调用iterator的拷贝构造函数是对list中的node进行拷贝,并不是调用operator*。
       ++*this;
       return tmp;  //这里返回的是对象。而前++返回的是reference。
    }
    

    对于整数变量,前++可以加两次,后++则不行。迭代器的自加也是按照这个原则设计的。

    • GCC4.9相对于GCC2.9在list容器上的改进:1. 其中的_list_iterator类模板的typename进行了精简,由3个typename改为1个typename. 2. _list_node在2.9中是由data和两个void_pointer类型指针组成,而在4.9中则是先构造_list_node_base这个结构存放两个指向自己这种类型的指针,之后_list_node继承于它,并存放data,这样指针指向的类型就确定了。3. 在32位cpu下,4.9中sizeof(list<int>) = 8,有两个指针,分别指向prev和next节点; 2.9中是4,只有一个指向node的指针。
    • list最后一个有数据的节点的下一个节点是空的,end()指向的就是这个节点。这样是为了前闭后开遍历设计。

    iterator需要遵循的原则

    *迭代器内部定义的5种迭代器相关的typedef是为了给算法调用的。
    eg. rotate()算法的内部调用iterator_traits<_Iter>::iterator_category (); 萃取出iterator_category(迭代器的分类,如前向迭代器,只能++,或是双向迭代器等),还萃取出difference_type(两个iterator之间的距离)和value_type(容器中元素类型)。和迭代器相关的还有两种typedef,分别是reference和pointer从没有在标准库中使用过。

    • iterator_traits(迭代器的萃取机)有一个泛化的类模板,如果iterator是class,那么会使用这个泛化的类模板,还有两个偏特化类模板,一个模板参数特化为T ,另一个特化为const T。所以无论iterator是类还是指针,都可以萃取其中的5种类型,具体使用eg. iterator_traits<T>::value_type。value_type主要是用于声明变量的,所以即使是const T*类型,value_type也是T。
    • 标准库中还有其他的萃取机。

    深入探索vector

    • 各个编译器下,容量都会2倍增长。vector中有三个iterator(start指向第一个元素的位置,finish指向当前存放的最后一个元素的下一个位置,end_of_storage指向可以容纳最后一个元素的位置),vector的函数begin(),end(),size(),capactity(),empty(),front(),back(),operator[]等都是利用这三个iterator计算的。
    • push_back()首先会利用finish和end_of_storage指针判断是否还有空闲位置,如果有,则插入元素,finish自加;如果没有,那么会调用辅助函数insert_aux(end(), x),这是一个函数模板,其内部会再次判断是否还有剩余空间,因为其他函数也可以调用这个函数,如果没有剩余空间,那么会分配原有空间大小的二倍,(如果原有空间是0,那么新的空间大小会是1),之后将原有的元素拷贝到新的空间,再将新的元素放进去,finish自加,之后会在新元素后面(安插点后)将剩余的原有元素拷贝过来(这里也是因为这个函数会被其他函数调用)。
    • 32位下sizeof(vector<T>) = 12(有3个指针)。

    array和forward_list 单向链表

    • TR1版(是1998-2011中间的过度版本,大概是2003左右)
      array内部是用数组实现的,大小不能扩充,所以定义时要声明大小,eg. array<int, 10> arr; 如果给定的array的大小是0,那么在array内部会变为1. 这里的iterator是指针。
    • G4.9 array内部声明数组变得更加复杂了。
      typedef int T[100]; T c; 这样写是对的。

    forward_list

    与list类似,只是是单向的。

    相关文章

      网友评论

          本文标题:[GeekBand][C++ STL与泛型编程]第七周笔记

          本文链接:https://www.haomeiwen.com/subject/yvqcjxtx.html