二、C++、STL标准模板库和泛型编程——分配器、序列式容器 (侯捷)

侯捷 C++八部曲笔记汇总 - - - 持续更新 ! ! !
一、C++ 面向对象高级开发
1、C++面向对象高级编程(上)
2、C++面向对象高级编程(下)
二、STL 标准库和泛型编程
1、分配器、序列式容器
2、关联式容器
3、迭代器、 算法、仿函数
4、适配器、补充
三、C++ 设计模式
四、C++ 新标准
五、C++ 内存管理机制
六、C++ 程序的生前和死后

二、C++、STL标准模板库和泛型编程——分配器、序列式容器 (侯捷)

使用一个东西,却不明白它的道理,不高明!—— 林语堂

阶段学习
使用C++标准库
认识C++标准库(胸中自有丘壑!)
良好使用C++标准库
扩充C++标准库

所谓 Generic ProgrammingGP,泛型编程),就是使用 template (模板)为主要工具来编写程序。

  • GP 是将 datasmethods 分开来;

    • ContainersAlgorithms可各自闭门造车﹐其间以Iterator连通即可·
    • Algorithms通过Iterators确定操作范围﹐也通过Iterators取用 Container元素。
  • OOP(Object-Oriented Programming),企图将 datasmethods 关联在一起。

六大部件

C++标准模板库Standard Template 最重要的六大部件(Components):容器算法仿函数迭代器适配器分配器

  • 容器(Containers)是class template
  • 算法(Algorithms)是function template其内最终涉及元素本身的操作,无非就是比大小!)
  • 迭代器(Iterators)是class template
  • 仿函数(Functors)是class template
  • 适配器(Adapters)是class template
  • 分配器(Allocators)是class template

关系图:

在这里插入图片描述
使用例子:

#include <iostream>
#include <vector>
#include <functional>

using namespace std;//打开std这个命名空间

int main()
{
	int ia[6] = { 27, 210, 12, 47, 109, 83 };

	//vector 为容器 ,allocator为分配器,可以不写
	vector<int, allocator<int>> vi(ia, ia + 6);

	//connt_if 为算法, vi.begin() 和 vi.end()为迭代器
	cout << count_if(vi.begin(), vi.end(), 
		not1(bind2nd(less<int>(),40)));
	//not1 和 bind2nd 为仿函数适配器,less为仿函数

	return 0;

}

容器分类大致分为两种容器:序列容器,关联容器(key、value)

  • 序列容器:arrayvectordequelistforward-list
  • 关联容器:setmultisetmapmultimap,实现是使用 红黑树(弱严格平衡二叉树)

在这里插入图片描述

  • C++11中有Unordered容器,但他也属于关联容器,实现使用hash table做的

在这里插入图片描述

分配器allocators

  • 容器需要一个东西来支持它对内存的使用,这个东西就是分配器,最好的情况下,我们不需要知道这个东西,所以需要一个默认的分配器
  • 比如说vector的模版定义如下,会有一个默认的分配器std::allocator<_Tp>,如果不指定分配器,就会默认使用这一个
template<typename _Tp, typename _Alloc = std::allocator<_Tp>>
class vector : protected _Vector_base<_Tp, _Alloc>

有以下分配器(不在标准库里面):

array_allocator
__mt_allocator //多线程
debug_allocator
__pool_allocator//内存池
bitmap_allocator
malloc_allocator
new_allocator

一般不推荐自己指定分配器,如果自己指定了不好的分配器,会影响容器的性能!

operator new & malloc

C++层面用operator new() ,C 层面用malloc()

  • allocator只是以::opreator new::opreator delete 完成 allocate()deallocate(),没有任何特殊设计;
  • allocate()会调用malloc()
  • deallocate()调用free()
    在这里插入图片描述
    我们需要的size是青色部分空间的大小,但是malloc会在青色部分外包裹其他东西。因此,会产生一些额外开销(如果要分配的区块小,那么额外开销就相对较大,不能忍受)。

16条链表,负责不同大小的内存分配。8字节对齐。每个内存块不会都带cookie。只会在链表的头尾有cookie
在这里插入图片描述

下图以 缩排 形式表达 “ 基层和衍生层 ” 的关系:

  • 这里所谓衍生,并非 继承(inheritence) 而是 复合(composition)

操作符重载

  • 运算符出现在表达式中,并且其至少一个操作数具有类类型枚举类型时,重载解析用于确定签名与以下内容匹配的所有函数中要调用的用户定义函数:

在这里插入图片描述

序列式容器

array 容器

是C语言本来就有的东西,为什么要把他包装成容器?

  • 因为要让他有容器的行为,要有迭代器,才能配合算法
  • 必须指定大小,不能拓展。
  • 没有构造函数,没有析构函数

在这里插入图片描述

array<long, msize> c;
c.size();
c.front();
c.back();
c.data();//返回array的起点地址
qsort(c.data(), msize, sizeof(long), compareLong); // 快速排序,指定地址,多少个元素,每个元素大小是多少,比较方法是怎样
bsreach(&target, c.data(), msize, sizeof(long), compareLong); //二分查找,指定查找对象

vector 容器

动态增长数组:

  • 容器容量按2倍增长,扩充的时候需要找到和当前容量两倍大的连续的内存才行!
  • 在进行增长的时候,会调用大量的构造函数。
    有三个迭代器,所以sizeof(vector)的大小为12。

在这里插入图片描述

vector<int> c;
c.push_back(1);
c.size();
c.capacity();
c.front();
c.back();
c.data();//起始地址
auto it = ::find(c.begin(), c.end(), target);//顺序查找

在这里插入图片描述

list 容器

  • 双向链表:看源码,只有一个protected类型的数据node,这是一个__list_node结构的指针,这个结构中包含list节点的,指向 前一个节点后一个节点 的指针。

在这里插入图片描述

上面这个图的设计有这么两个问题:(针对这个两个问题,GNU4.9作出了改进!)

  1. __list_nodeprevnext使用的是void*类型的指针,而不是一个确切的__list_node类型,传入的值会做一个类型转化;
  2. iterator的定义有三个模板参数,即T, T&, T*,这个显然是传一个 T 就够了,只要在模板类进行推导就行。

一定要有5个typedef,也就是下图中括号行。这就牵扯到了迭代器的Traits设计。

迭代器需要遵循的原则:迭代器是连接容器和算法的中间件,因此迭代器必须能够回答算法的一些提问,这样算法才能更好的对容器进行操作。这 5种迭代器 关联类型分别为:iterator_category(迭代器类型)、value_type(值存放类型)、difference_type(容器两个元素之间的距离类型)、pointerreference【后两种很少用!】。

如果一个迭代器不是一个

  • 会引入Traits(特征、特性、特值)机作为中间层,接收类迭代器和指针迭代器,会做相应的工作(模板偏特化),得到指针的5种迭代器关联类型。

在这里插入图片描述

  • 还包含一个迭代器,这个迭代器就是一个pointer like,重载了一些操作符,包括:前++后++->*
    • 这些容器都会模拟 int 型的操作方法,所以 list 不允许 后++两次;
    • 因此,后++的重载是返回self类型,而不是self&

在这里插入图片描述

forward_list 容器

单向链表,只提供头插法,尾插效率太低,计算size效率太低,寻找back效率太低,不提供
在这里插入图片描述

forward_list<int> c;
c.push_front(); //头插
c.max_size();
c.front();
//无c.back()
//无c.size()

deque 容器

类似vector,两边都能扩充,逻辑上是连续的,但是 物理上是分段连续的

  • 连续是假象,分段是事实
  • 有一个map来存放这些段。
  • 当一个buffer使用完的之后,会有一个新的buffer,这个buffer的地址放在map中。
  • 这个buffer一次分配多大呢?这会影响到这个容器的效率~

在这里插入图片描述
map会将这些分段的node给串起来;node指向在map的位置:

  • firstlast指的是node的边界
  • cur指向当前node的元素

还会有一个startfinish的迭代器,保存双向队列的两端头元素。
map不够大,也会二倍扩充。

在这里插入图片描述
deque还需要指定buffer_size,也就是每一个buffer容纳的元素个数,默认是0,就会做相应的操作来存放默认数量的元素,但是肯定不会是让一个buffer存放0个元素的。迭代器类型用的是随机存取(也就是连续的)是deque类做的伪装。迭代器做了模拟连续空间的操作。

在这里插入图片描述

deque<int> c;
c.push_back(1);
c.pop_back();
c.push_front(1);
c.pop_front();
c.max_size();
c.front();
c.back();

queue 容器

queuestack没有自己的数据结构,是借用deque,是容器适配器,内涵一个deque,并封住某些功能。

  • 不提供遍历操作, 所有不提供迭代器:

在这里插入图片描述

queue<int> c;
c.push(1);
c.pop();
c.front();
c.back();
c.size();

stack 容器

stack是容器适配器, 内涵一个deque,并封住某些功能。
在这里插入图片描述

stack<int> c;
c.push(1);
c.pop();
c.top();
c.size();

源码中queuestack提供第二个模板参数来指定实现stackqueue的容器,可以使用listdeque
不能够使用vectorsetmap实现,因为不允许任意放元素 ,转调用也不能调用不到正确的函数。

注:个人学习笔记,如有不足,欢迎指正!

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷酷的懒虫

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值