C++STL学习第二讲上(从源代码角度讲解STL)

第二讲 从源代码角度讲解STL(上)

1. OOP(面向对象编程)vs. GP(泛型编程)

1. OOP(Object-Oriented programming)
  • OOP企图把datas和methods关联在一起;
  • 例如STL中的list有自带的sort函数;
    • 为什么list不能使用全局的sort函数?
    • 因为::sort()函数要求的迭代器类型是RandomAccessIterator(随机访问迭代器。支持O(1)时间复杂度对元素的随机位置访问,支持对元素的读取。),迭代器可以随意移动
    • 而list的迭代器是BidirectionalIterator(双向迭代器。支持向前向后逐个遍历元素,可以对元素读取。);
    • 所以list不能使用::sort()函数,要自定义sort()函数。
      在这里插入图片描述
2. GP(Generic Programming)
  • GP总是将datas和methods分开来;

    • SLT中的datas(Containers)和methods(Algorithm)需要借助Iterators才能相互联系;
      在这里插入图片描述
  • algorithms(算法)的本质:

    • 有两个版本的max函数;
      在这里插入图片描述

2. allocators(分配器)

  • allocator class最重要的部分:
    • allocate()函数:用于分配空间;
    • deallocate()函数:用于释放空间;
1. operator new() 和 malloc()、operator delete() 和 free()
  • 任何allocators进行分配空间都会执行operator new(),最终执行malloc()函数;
    • malloc()函数属于C标准库函,底层会调用操作系统的相关API;
    • 右图是malloc()实际分配的空间,会有一些额外内容开销overhead
  • 而释放空间都会执行operator delete(),最终执行free()函数;
    • 同样free()函数属于C标准库函,底层会调用操作系统的相关API;
      在这里插入图片描述
2. 不同版本标准库的allocator
1. VC6(Visual C++ 6)的std::allocator
  • VC6 Containers 使用的默认allocator是 std::allocator
  • VC6的allocator只是给 ::operator new(malloc) 和 ::operator delete(free) 简单地套一层外壳 allocate 和 deallocate ,没有特殊设计
  • 直接使用allocator class,释放也需要指定大小,这种设计不好(没人会记住当初分配了多大的空间);
  • 注意:
    • allocate函数有一个没有名字的形参,这种写法表示这个参数在这个函数里是没有用的(因为没有名字,不知道这种写法有什么用);
      在这里插入图片描述
2. BC5(Borland C++ 5)的std::allocator
  • BC5 Containers 使用的默认allocator是 std::allocator
  • 同样没有特殊设计
    在这里插入图片描述
3. 没有特殊设计allocator带来的影响
  • 由前面第一节,可以知道malloc分配的内存会有额外overhead
  • 我们关心的是,这个overhead占总数据大小的比例,而不是overhead本身的大小;
    • 至少首尾表示空间大小的cookie,是肯定不能少的;
    • 如果现在需要申请1000000个小数据(比较小的类),只是简单包装的allocator就会调用了1000000次malloc,这样会导致实际数据占用总空间的比例要远小于overhead的比例,这是不能容忍的;
  • 因此如果能设计一个allocator,尽可能减少调用malloc的次数,就能降低overhead的比例。
4. G2.9(GUN)的std::allocator
  • 同样没有特殊设计

    • 但右边的注释说明了,G2.9并没有使用这个std::allocator;
      在这里插入图片描述
  • 注意:G2.9 Containers 使用的默认allocator是std::alloc,不是 std::allocator ;
    在这里插入图片描述

  • std::alloc的实现结构:

    • 结构:
      • 定义一个16长度的指针数组:每个指针指向了8~128个字节大小的单向链表;
      • 例如:0索引对应的大小 == 8个字节,1索引对应的大小 == 16个字节,…,15索引对应的大小 == 128个字节;
      • 即0索引指向的单向链表,每块大小都是8个字节;
    • 作用:
      • 尽可能减少调用malloc的次数;因为每次malloc得到的内存都需要overhead;
      • 这些空间块都可以直接使用,不需要调用malloc向操作系统申请内存;如果空间块不足,才会调用malloc操作;
    • 具体的分配过程在内存管理视频中,这里不详谈:
      在这里插入图片描述
5. G4.9(GUN)的std::allocator:
  • G4.9 std::allocator 使用了继承;

  • std::allocator 没有特殊设计
    在这里插入图片描述

  • 注意:G4.9 Containers 使用的默认allocator又用回了没有特殊设计,只是做包装的std::allocator
    在这里插入图片描述

  • 为什么不用 G2.9 的 std::alloc,std::alloc到哪去了?

    • 之前谈到除了标准库的std::allocator,还有几个额外非标准的分配器;
    • G2.9 的 std::alloc 变成了 pool_alloc(内存池分配器)
      在这里插入图片描述

3. 容器之间的关系

1. C++11之前的容器关系
  • 下图中的缩进表示复合关系(composition);【例如set拥有rb_tree(红黑树);】
  • 下图中的heap是堆数据结构,不是系统的heap内存区;
  • 左右两侧是G2.9和G4.9,对应容器的sizeof()大小;
2. C++11之后的容器关系
  • 从C++11开始,STL引入了大量的继承关系;
  • C++11容器修改(图的下方方框):
    • 图中的slist改名forward_list;
    • hash_set,hash_map改为unordered_set,unordered_map;
    • hash_multiset,hash_multimap改为unordered_multiset,unordered_multimap;
    • 新增array;
      在这里插入图片描述

4. Containers 为什么要使用 Iterators

  • array和vector这种内存空间分布连续的容器,它的 iterator 直接使用指针就可以满足需求,iterator 的行为就是指针的行为;
  • 而对于其他的容器,它的 iterator 必须是一个class,并且要使 iterator 的行为像 pointer;
    • 比如 pointer 的++操作:
      • 对于连续空间分布的容器,可以很容易使用 pointer(也是迭代器)访问元素;
      • 对于离散空间分布的容器,直接使用 pointer 根本不能访问其他的元素,所以需要对应的 iterator 实现这个操作;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值