C++STL详解一:六大组成部件之间的关系

C++STL详解一:六大组成部件之间的关系

文章内容参考自侯捷C++STL和泛型系列教程以及《STL源码剖析》


文章目录

  • C++STL详解一:六大组成部件之间的关系
  • 前言
  • 一、六大部件之间的关系
  • 二、容器和分配器
    • 1.分配器的作用
    • 2.容器
  • 三、迭代器和泛型算法
  • 四、标准库中对于容器区间的界定


前言

作为标准库的重要组成部分,STL占据了标准库一半以上的内容,它由六个部分组成的:

分配器(Allocators)
容器(Containers)
泛型算法(Algorithms)
迭代器(Iterators)
仿函数(Functors)
适配器(Adapters)

在本文中,我将主要讲述六大部件的功能和他们之间的联系,以及在了解STL的实现代码之前必须知道的一些问题,而各个部件的详细描述我会在后边的专题文章中进行描述。


一、六大部件之间的关系

C++STL详解一:六大组成部件之间的关系_第1张图片
上图描述了STL中六大部件之间的关系:

1、容器作为STL的主体,是许多不同的数据结构
2、分配器为容器的实现分配应有的空间
3、泛型算法用来处理容器中的数据
4、迭代器是泛型算法和容器之间的粘合剂
5、仿函数使得算法可以有更加灵活的自定义模式
6、适配器保证了自定义的功能可以和STL中现有的功能相融合

我们用一段简单的程序来体会一下六大部件的作用:

#include 
#include //count_if
#include //less	bind
#include 

using namespace std;

int main(void)
{
	//创建一个容器
	int buf[] = { 27,210,12,47,109,83 };
	//这里刻意写出第二模板参数,是为了解释分配器的作用
	vector< int, allocator<int> > v(buf, buf + 6);
	
	using namespace std::placeholders;//使用 占位符_1所在的 命名空间
	//输出vector中 小于40的 元素的个数
	cout << count_if(v.begin(), v.end(),  bind( less<int>(), _1, 40 ) ) << endl;
	//输出2
	
	return 0;
}

这里的vector是一个容器,用来储存数据,它的第二模板参数allocator是为当前容器分配内存的分配器(一般情况下省略不写,默认就会使用allocator< T >)。

count_if( )是一个泛型算法,他的声明如下:

//返回范围 [first, last) 中,对于判定标准p返回true的元素的个数
//这里的p应该是一个一元谓词,对需要进行计数的元素返回为true
template< class InputIt, class UnaryPredicate >//模板参数
typename iterator_traits<InputIt>::difference_type//返回值
count_if( InputIt first, InputIt last, UnaryPredicate p );//函数名及参数列表

bind( )是一个适配器,声明如下:

//函数模板 bind 生成 f 的转发调用适配器。调用此适配器等价于以一些绑定到 args 的参数调用 f 。
//这里暂时可以理解为,将Args中的元素作为参数传递给仿函数f,不需要绑定的元素使用占位符_1等代替。
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );

less( )是一个仿函数,可能定义如下:

//进行比较的函数对象。调用类型 T 上的 operator< ,除非特化。
template< class T = void >
struct less
{
	constexpr bool operator()(const T &lhs, const T &rhs) const 
	{//可能实现方式
	    return lhs < rhs;
	}
}

这里我们分析一下最后一句调用的层次:

//输出区间 [ v.begin(),v.end() ) 中小于40的元素的个数
 count_if(v.begin(), v.end(),  bind( less<int>(), _1, 40 ) )

这里count_if( )需要两个迭代器和一个一元谓词,两个迭代器自然没问题,v.begin()和v.end()直接就满足调用的要求,但是对于最后一个参数p却不能直接满足需求。

我们想要统计小于40的元素,所以这个谓词需要对小于40的元素返回true。标准库中的仿函数less< int >是一个二元谓词,用来比较两个元素的大小,所以我们就需要提前绑定他的第二参数为40,让输入的数据和40去比较,适配器bind( )就是用来满足这个需求的。


二、容器和分配器

1.分配器的作用

如果看过我之前的文章,对于分配器的概念应该不会陌生:他是用来分配内存空间的。

但是和在内存分配中描述的分配器目标有些许不同:

1. 在内存分配中,分配器的主要目的是为了降低malloc产生的内存开销
2. 在STL中,分配器的主要目的是用来为容器存储的数据提供内存空间

关于内存分配和内存分配中的分配器,如果有兴趣可以参考我之前的文章:

C++内存分配详解三:内存分配模型
C++内存分配详解五:std::alloc源码剖析
C++内存分配详解六:malloc()详解

在后边的文章中,我会展示出一个简单的STL分配器,虽然它并不符合STL的标准,但是可以用来作为我们自己的STL的分配器去使用。


2.容器

首先,容器分为三大类:顺序容器、关联式容器、无序容器。我个人比较倾向于分为两类,因为关联式容器和无需容器都是关联式容器,只是实现方法上有些许的不同,这在后边的文章中会详细讲到。

顺序容器:进行顺序存储的容器
array(数组)
vector(向量)
deque(双向队列)
list(双向链表)
解过的人可能会问static和heap呢?其实这两个严格意义上不能算是容器,而应该是适配器

关联式容器: 主要用来查询的容器,创建之后自带顺序
set/multiset(集合)
map/multiset(表)
关联式容器底层使用红黑树实现

无序容器: 主要用来查询的容器,创建之后自带顺序
unordered set/multiset(无序集合)
unordered map/multiset(无序表)
无序容器底层使用hashTable实现

至于各个容器之间的区别,我会在后边的文章中慢慢讲到,同时我也会简单的仿写几个容器大致的功能。


三、迭代器和泛型算法

泛型算法之所以说是泛型算法,因为它并不是单独的为某一种容器去设计的,而是可以共多种不同的容器去使用的算法。为了满足这个需求,就需要对于算法来说,屏蔽掉容器的实现方法,但是却可以按照同一种顺序去访问大多数的容器,这就是迭代器的作用。

迭代器实际上是一个泛化的指针,或者说是一种智能指针,是一个类模板。它通过重载对于本身的++和- -等操作,使泛型算法可以很简单的使用迭代器完成对不同容器的访问。而这种方式就要求对于每一种不同的容器,我们都需要设计不同的迭代器。标准库也正是这么做的。


四、标准库中对于容器区间的界定

在STL中,对于每一次取得的容器区间总是前闭后开区间 也就是说:
通过begin( )取得的迭代器是指向第一个有效的元素
通过end( )取得的迭代器是指向最后一个有效元素之后的元素
所以,对于end( )取得的迭代器进行赋值操作是非法的!

这种规定对于STL的泛型算法和仿函数也同样有效,也就意味着,传递给泛型算法或是仿函数的参数区间也应该是前闭后开区间。

你可能感兴趣的:(STL详解,c++,stl,容器)