C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览

文章目录

      • STL六大部件
      • 容器的使用
        • Array
        • vector
        • list
        • deque
        • mutiset
        • multimap
        • unordered_multiset/set

使用一个东西,却不明白它的道理,不高明。

STL六大部件

  • 容器 Containers 用来存放数据
  • 分配器 Allocators 为容器内的数据分配存储空间
  • 算法 Algorithms 计算数据
  • 迭代器 Iterators 访问数据的式(算法使用其访问容器内大数据)
  • 适配器 Adapters
  • 仿函数 Functors 类似于函数的功能

C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览_第1张图片
六大组件之间的关系示意图如下:
C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览_第2张图片
容器和容器之间的实现关系图如下(这个图是候捷老师总结的):
大体意思是,左侧的序列式容器中,heap相关的算法实现使用的是上一层的vector,而priority_queue优先级队列的实现使用的是上一层的heap。
缩进的容器代表的是被实现的容器,之上未缩进的代表的是底层结构。像stack和queue(缩进)了容器适配器底层是用deque实现的。

同时对于关联式容器,set,map,mutiset,multimap(缩紧)的底层实现都是rb_tree(未缩进);且hash_set,hash_map等容器的底层是hashtable。

图中显示的是C++11 版本之前的容器内容,当C++11推出之后,slist就变成了forward_list,hash_set和hash_map就变成了unordered_setunordered_map;hash_multiset 和 hash_multimap就变成了unordered_multisetunordered_multimapC++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览_第3张图片

C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览_第4张图片

举例如下代码:
核心功能是计算数组arr中大于40的元素的个数
C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览_第5张图片
所有的容器区间都是 前闭后开区间[ ),所以遍历容器的形态可以如下

Containter<T> c;
...
Container<T>::iterator ite = c.begin();
for(; ite != c.end(); ++ ite)
	...

C++11 的新特性,提供range-based for statement,来简化以上的容器遍历形态

for ( dec1 : coll ) {
	statement
}
for ( int i : {2,3,4,5,7,8}) {
	std::cout << i << std::endl;
}
std::vector <double> vec;
...
for ( auto elem : vec) {
	std::cout << elem << std::endl;
}

for (auto &elem :vec) {
	elem *= 3;
}

容器的使用

Array

静态数组,空间大小是直接分配好的
基本接口如下

  • size()
  • front() 返回第一个元素
  • back() 返回最后一个元素
  • data() 返回第一个元素的首地址
#include 
#include 

using namespace std;

int main()
{
        array<int,100> arr_test;
        for(int i = 0;i < 100; ++i) {
                arr_test[i]=i;
        }
        cout << arr_test.size() << endl;
        cout << arr_test.data() << endl;

        return 0;
}

vector

基本接口如下:

  • push_back()
  • size()
  • front() 第一个元素
  • back() 最后一个元素
  • data() 内存中的起始地址

在这里插入图片描述
vector的内存增长是两倍的方式,即 push_back 第一个元素时发现内存空间不够,allocator分配器会分配能够存放2个vector元素的内存空间;放第三个元素的时候,又会分配4个元素的内存空间;放第五个的时候又会分配 8个元素的存储空间。。。

同时每次分配新的大小的内存空间的时候会做一个元素的整体拷贝,即将之前的空间元素整体拷贝到新的扩容后的内存空间。
利用data()函数验证如下:

#include  // vector
#include 

using namespace std;

int main()
{
	vector<int> test_arr;
	for(int i = 0; i<100;++i) {
		test_arr.push_back(i);
		if(i % 2 == 0) 
			cout << test_arr.data() << endl;
	} 

	return 0;
}

可以看到最后的输出,当分配空间之后内存中的起始地址都会发生变化:

0x1885050 #两个元素空间
0x1885050
0x1885090 #四个元素空间
0x1885090
0x18850c0 #八个元素空间
0x18850c0
0x18850c0
0x18850c0
...

后续会进行相关的源码实现的分析,为什么要这样做?

list

基本操作如下

  • push_back()
  • size()
  • max_size() 存放最多的元素的个数,只要内存足够,就能够一直分配
  • front()
  • back()

在这里插入图片描述

deque

双端队列
基本操作如下:

  • push_back()
  • size()
  • front()
  • back()
  • max_size()

内存分配的示意图如下(元素内存中 的存放方式并不是连续的):
C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览_第6张图片
deque内部每一个区域存储一个指针,指针指向的是内存一段buffer区域,buffer里面存储的是一个个具体元素。当需要扩容的时候,会分配一个新的指针区域,并为这个指针区域分配对应的buffer。

mutiset

multiset和set的区别就是 muti允许元素重复,来体现multi的作用。

  • insert() 插入元素
  • size()
  • max_size()

底层数据结构是红黑树的实现
这里它和multimap的基本是一致的,只是multimap容器的对外使用是允许key-value不同,而multiset的key-value是一样的。且元素插入本身有序,因为底层红黑树的结构就是有序的。
C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览_第7张图片
一些测试代码如下:

#include 
#include 
#include 
#include 
#include 

#define MAX_LEN 1000000
using namespace std;

int main()
{

	multiset<string> c;
	for(int i = 0; i<MAX_LEN ;++i) {
		try {
			c.insert(to_string(i));
		}
		catch(exception &p) {
			cout << "i = " << i << " " << p.what() << endl;
			abort();
		}
	} 

	cout << "c.max_size: " << c.max_size() << endl; 
	cout << "c.size: " << c.size() << endl; 

	string target = to_string(23456);
	cout << "find target is: " << target << endl;	
	
	/*对比全局的find函数和mutiset自带的函数查询效率*/
	clock_t timestart = clock();
	auto item = ::find(c.begin(),c.end(),target);
	cout << "::find,milli-seconds: " << clock() - timestart << endl;
	if (item != c.end()) cout << "found " << endl;
	else cout << "not found" << endl;

	timestart = clock();
	auto pItem = c.find(target);
	cout << "c.find,milli-seconds: " << clock() - timestart << endl;
	if(pItem != c.end()) cout << "found" << endl;
	else cout << "not found" << endl;	

	return 0;
}

输出如下,结果非常明显了:

c.max_size: 288230376151711743
c.size: 1000000
find target is: 23456
::find,milli-seconds: 6629
found 
c.find,milli-seconds: 5
found

multimap

基本接口如下:

  • insert() 插入元素
  • size()
  • max_size()
  • multiset 不允许使用 [ ] 进行insert

同样使用红黑树实现,底层数据按key有序。
C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览_第8张图片
简单应用代码:

#include 
#include  //to_string()
#include  //clock()
#include 
#include  // find()

#define MAX_LEN 1000000
using namespace std;

int main()
{

	multimap<long, string> c;
	for(int i = 0; i<MAX_LEN ;++i) {
		try {
			c.insert(make_pair(i,to_string(i*10)));
		}
		catch(exception &p) {
			cout << "i = " << i << " " << p.what() << endl;
			abort();
		}
	} 

	cout << "c.max_size: " << c.max_size() << endl; 
	cout << "c.size: " << c.size() << endl; 

	long target = 23456;
	cout << "find target is: " << target << endl;	
	
	clock_t timestart = clock();
	auto pItem = c.find(target);
	cout << "c.find,milli-seconds: " << clock() - timestart << endl;
	if(pItem != c.end()) cout << "found" << endl;
	else cout << "not found" << endl;	

	return 0;
}

输出如下:

c.max_size: 256204778801521550 #怀疑这里的max_size与系统内存大小有关系
c.size: 1000000
find target is: 23456
c.find,milli-seconds: 5
found

unordered_multiset/set

基本操作接口:

  • insert()
  • size()
  • max_size()
  • bucket_count()
  • load_factor()
  • max_load_factor()
  • max_bucket_count()

数据结构如下:
C++ STL: 基本六大部件概览 及 各个容器使用方式和底层实现概览_第9张图片
unordered_multiset/set 以及unordered_multimap/map的底层实现都是hash,为了解决hash冲突,也使用hash 链。为了防止链过长,使用链hash bucket的方式,当链的元素个数达到一定程度会分配一个双倍的存储空间,但是该链上的元素会打rehash重新分配。

简单应用代码:

#include 
#include  // to_string()
#include  // clock()
#include  // unordered_multiset 
#include  //::find()

#define MAX_LEN 1000000
using namespace std;

int main()
{

	unordered_multiset<string> c;
	for(int i = 0; i<MAX_LEN ;++i) {
		try {
			c.insert(to_string(i));
		}
		catch(exception &p) {
			cout << "i = " << i << " " << p.what() << endl;
			abort();
		}
	} 

	cout << "c.max_size: " << c.max_size() << endl; 
	cout << "c.size: " << c.size() << endl; 
	cout << "c.bucket_count: " << c.bucket_count() << endl; 
	cout << "c.load_factor(): " << c.load_factor() << endl; 
	cout << "c.max_load_factor(): " << c.max_load_factor() << endl; 
	cout << "c.max_bucket_count: " << c.max_bucket_count() << endl; 

	string target = to_string(23456);
	cout << "find target is: " << target << endl;	
	
	/*对比全局::find函数提供的查找效率和容器本身提供的find函数查找效率的差异*/
	clock_t timestart = clock();
	auto item = ::find(c.begin(),c.end(),target);
	cout << "::find,milli-seconds: " << clock() - timestart << endl;
	if (item != c.end()) cout << "found " << endl;
	else cout << "not found" << endl;

	timestart = clock();
	auto pItem = c.find(target);
	cout << "c.find,milli-seconds: " << clock() - timestart << endl;
	if(pItem != c.end()) cout << "found" << endl;
	else cout << "not found" << endl;	

	for(int i = 0;i < 20; ++i) 
		cout << "bucket " << i << "'s element is: " << c.bucket_size(i) << endl;

	return 0;
}

输出如下:

c.max_size: 384307168202282325
c.size: 1000000
c.bucket_count: 1056323
c.load_factor(): 0.94668
c.max_load_factor(): 1
c.max_bucket_count: 384307168202282325
find target is: 23456
::find,milli-seconds: 75920
found 
c.find,milli-seconds: 2
found
bucket 0's element is: 0
bucket 1's element is: 1
bucket 2's element is: 0
bucket 3's element is: 1
bucket 4's element is: 2
bucket 5's element is: 2
bucket 6's element is: 3
bucket 7's element is: 2
bucket 8's element is: 2
bucket 9's element is: 2
bucket 10's element is: 1
bucket 11's element is: 3
bucket 12's element is: 0
bucket 13's element is: 1
bucket 14's element is: 1
bucket 15's element is: 3
bucket 16's element is: 1
bucket 17's element is: 0
bucket 18's element is: 1
bucket 19's element is: 0

以上的结果中bucket_count竟然比size的元素个数多,这个原理就是前面说到unordered_multiset的实现过程中对单个hash链上的元素个数超过一定的限制之后进行rehash,同时分配double的空间导致的。且bucket_count统计的是分配链空间的bucket,即使它没有存放实际的元素也会被统计到该数值之内。

后续会从源码层面分析以上容器各个操作接口及对应存储结构的外在表现,一群顶尖的工程师用优秀的代码风格开发了这样一批使用简单,内部逻辑复杂有趣的STL,学习无止境,向大佬看齐!

你可能感兴趣的:(#,编程语言:C++,#,编程语言C)