C++标准库分析总结(四)——<容器分类、List容器设计原则>

目录

1 容器分类(结构性分类)

1.1 容器分类简介

1.2 各个容器的结构关系

1.3 序列式容器(Sequence Container)

1 Array

2 Vector

3 Deque

4 List

5 Forward_List(C++11新增)

1.4 关联式容器(Associative Container)-- 有序(元素key是有序)

1 Set/MultSet

2 Map/Multimap

1.5  无序容器(Unordered Container)-- 无序(元素的key是无序的)

1 unordered_multiset

2 unordered_multimap

3 unordered_set

4 unordered_map

2 List容器结构分析

2.1 基本结构

2.2 迭代器操作方式

2.3 多版本对比介绍






1 容器分类(结构性分类)

1.1 容器分类简介

容器分为序列式容器关联式容器(分为有序和无序),红色部分是C++2.0引入的

      

  • 序列式容器(Sequence Container)元素都是有序的里面的空间可能是连续的,也可能是由指针一个个串起来的。序列式容器的特点是将数据放进容器之后,会按照用户放进去的顺序依次排列。

序列式容器特点额外学习材料
array一段连续空间,不论是否使用,都会全部占用array
vector尾部可进可出,当空间不够时会自动扩充vector
deque双向都可扩充,两端都可进可出deque
stack-
queue队列-
priority_queue优先级队列-
list一个双向环状链表,有向前后和向后两个指针list
forward_list一个单向链表,仅有向后一个指针forward_list
  • 关联式容器(Associative Container)元素是由key和value组成的当然用key能很快找value,适用于查找操作(查找很快)。

关联式容器名特点实现注释额外学习材料
set/multisetkey和value是同一个,BST存储是有序的红黑树加上multi意味着可以重复键值对setmultiset
map/multimap每一个key对应一个value,BST存储是有序的红黑树加上multi意味着可以重复键值对mapmultimap
unordered_set/unordered_multiset相对于set/multiset,存储是无序的哈希表加上multi意味着可以重复键值对unordered_setunordered_multiset
unordered_map/unordered_multimap相对于map/multimap,存储是无序的哈希表加上multi意味着可以重复键值对unordered_mapunordered_multimap

测试程序之辅助函数:

//--------------------------------------------------------------------
// 从键盘输入获取一个long类型。
long get_a_target_long()
{
long target=0;

	cout << "target (0~" << RAND_MAX << "): ";
	cin >> target;
	return target;
}

// 从键盘输入获取一个string类型。
string get_a_target_string()
{
long target=0;
char buf[10];

	cout << "target (0~" << RAND_MAX << "): ";
	cin >> target;
	snprintf(buf, 10, "%d", target);
	return string(buf);
}

// 比较两个long
int compareLongs(const void* a, const void* b)
{
  return ( *(long*)a - *(long*)b );
}

// 比较两个string
int compareStrings(const void* a, const void* b)
{
  if ( *(string*)a > *(string*)b )
     	return 1;
  else if ( *(string*)a < *(string*)b )
     	return -1;  
  else      	
        return 0;  
}

1.2 各个容器的结构关系

容器间的关系,以及各个容器所占用的字节大小如下所示:

1.3 序列式容器(Sequence Container)

1 Array

对比于c语言的数组,标准库进行封装,初始化的时候就要确定大小,不能扩展(注意在栈上有大小限制,不同的机器内存限制不一样)。

使用容器array:

#include <array>
#include <iostream>
#include <ctime> 
#include <cstdlib> //qsort, bsearch, NULL

namespace jj01
{
void test_array()
{
	cout << "\ntest_array().......... \n";
     
array<long,ASIZE> c;  	
			
clock_t timeStart = clock();									
    for(long i=0; i< ASIZE; ++i) {
        c[i] = rand(); 
    }
	cout << "milli-seconds : " << (clock()-timeStart) << endl;	//
	cout << "array.size()= " << c.size() << endl;		
	cout << "array.front()= " << c.front() << endl;	
	cout << "array.back()= " << c.back() << endl;	
	cout << "array.data()= " << c.data() << endl;	
	
long target = get_a_target_long();

	timeStart = clock();
    ::qsort(c.data(), ASIZE, sizeof(long), compareLongs);
long* pItem = (long*)::bsearch(&target, (c.data()), ASIZE, sizeof(long), compareLongs); 
	cout << "qsort()+bsearch(), milli-seconds : " << (clock()-timeStart) << endl;	//    
  	if (pItem != NULL)
    	cout << "found, " << *pItem << endl;
  	else
    	cout << "not found! " << endl;	
}
}

2 Vector

一种可以动态扩充的数组,只能在容器的末端进行扩充,当容量不足时会自动扩充,扩充规则为当前空间的2倍,内存由Vector背后的分配器进行,使用者不用关心内存分配问题

使用容器vector:

//---------------------------------------------------
#include <vector>
#include <stdexcept>
#include <string>
#include <cstdlib> //abort()
#include <cstdio>  //snprintf()
#include <iostream>
#include <ctime> 
#include <algorithm> 	//sort()
namespace jj02
{
void test_vector(long& value)
{
	cout << "\ntest_vector().......... \n";
     
vector<string> c;  	
char buf[10];
			
clock_t timeStart = clock();								
    for(long i=0; i< value; ++i)
    {
    	try {
    		snprintf(buf, 10, "%d", rand());
        	c.push_back(string(buf));     		
		}
		catch(exception& p) {
			cout << "i=" << i << " " << p.what() << endl;	
			     //曾經最高 i=58389486 then std::bad_alloc
			abort();
		}
	}
	cout << "milli-seconds : " << (clock()-timeStart) << endl;	
	cout << "vector.max_size()= " << c.max_size() << endl;	//1073747823
	cout << "vector.size()= " << c.size() << endl;		
	cout << "vector.front()= " << c.front() << endl;	
	cout << "vector.back()= " << c.back() << endl;	
	cout << "vector.data()= " << c.data() << endl;
	cout << "vector.capacity()= " << c.capacity() << endl << endl;		

																			
string target = get_a_target_string();
    {
	timeStart = clock();
auto pItem = find(c.begin(), c.end(), target);
	cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;  
	 
  	if (pItem != c.end())
    	cout << "found, " << *pItem << endl << endl;
  	else
    	cout << "not found! " << endl << endl;
    }


	{
	timeStart = clock();
    sort(c.begin(), c.end());
	cout << "sort(), milli-seconds : " << (clock()-timeStart) << endl; 
	
	timeStart = clock();	    
string* pItem = (string*)::bsearch(&target, (c.data()), 
                                   c.size(), sizeof(string), compareStrings); 
	cout << "bsearch(), milli-seconds : " << (clock()-timeStart) << endl; 
	   
  	if (pItem != NULL)
    	cout << "found, " << *pItem << endl << endl;
  	else
    	cout << "not found! " << endl << endl;	
	}
	
	c.clear();
	test_moveable(vector<MyString>(),vector<MyStrNoMove>(), value);	
}	
}

push_back()了一百万个元素,用了大概3000ms,和array比起来,慢了好多。(可能因为vector的扩容)

(估计不是因为扩容,而是因为每次需要开辟空间,因为array的空间是静态分配的。因为后面显示list插入一百万个元素耗时和vector差不多)

使用::find()几乎不费时,使用先排序再二分用时还挺长的。(可能是运气好,毕竟只找了一次,不能得出很严谨的结论)

3 Deque

双端队列,容器两端均可进行数据扩充;

deque的两端扩容是怎么实现的?按理说的话,vector的扩容是在另一个地方开辟足够的空间,然后再把老地方里面的元素copy过去,因为原地扩容的话可能地方不够用,但是deque竟然可以两端扩容,那么它是怎么实现的呢?deque的底层结构示意图如下:

deque其实是由一段一段的buffer组成的,每段buffer有一个固定的长度,deque维护的只是指向每个buffer的指针,当deque需要扩容时,就在前面或者后面新增一个buffer。也就是deque其实是分段连续的,但是deque却表现为整段连续,这当然是通过重载迭代器的相关操作符实现的。

下面是deque的测试情况,和之前容器表现都类似:

容器适配器stack和queue内部就是有一个deque,从而能实现栈和普通队列的行为。

4 List

双向环状链表,内存占用比Forward_List多;

list测试:

list有一个max_size()的成员函数,返回的值很大,好几亿。list自己有一个sort()的成员函数。

5 Forward_List(C++11新增)

单向链表,只能从尾部扩充

Forward_List测试:

测试结果和list的相似。但是单链表没有back()和size()的成员函数

1.4 关联式容器(Associative Container)-- 有序(元素key是有序

关联式容器可以理解为一种小型的关系型数据库,所以查找起来非常快。

1 Set/MultSet

  目前各大厂商编译器都是由红黑树(RBtree)实现,保证每个子树根节点键值大于左子树所有节点的键值,小于右子树所有节点的键值,左右是高度平衡的防止某一个分支比较长,每个元素由key和value组成set中key和value是同一个值,key和value是不分的,元素不可重复,当重复放入时容器会反弹回来;

  MultSet表示元素可以重复。

multiset测试:

注意容器本身的find()和全局的find()的速度对比

 set测试:

2 Map/Multimap

  目前各大厂商编译器也都是由红黑树实现,区别于set,每个元素分为key和value,同样元素不可重复,当重复放入时容器会反弹回来;

  Multimap表示元素可重复,也就是key-value可重复。

注意multimap不能用[ ]做插入。

 map测试:

1.5  无序容器(Unordered Container)-- 无序(元素的key是无序的

  Unordered setUnordered  map底层是由哈希表(Hashtable)实现用哈希表必然存在元素碰撞问题,所以各大编译器都采用Separate Chaining哈希表

1 unordered_multiset

有一个bucket_count()的成员函数,能看到桶的数量。有一个bucket_size(i)的成员函数,能看到第i个桶的元素个数。

2 unordered_multimap

3 unordered_set

4 unordered_map

2 List容器结构分析

2.1 基本结构

  G2.9版本,list的设计是双向环状链表,每个节点是一个指针,这个节点又由两个指针(一个向前指一个向后指)和数据部分data组成;

  list整体数据部分是node,而node是list_node*类型,list_node是list的一个节点,所以list占用的内存大小是4(在32位机器上),当list从allocate拿内存时是以节点为单位的,也就是说除了数据data部分,还要有两个指针;

2.2 迭代器操作方式

  迭代器的操作要回到节点内部,而不是直接跳到下一个地址,比如迭代器++操作,就是要回到节点内部找到next指针指到的位置,然后再去挪动迭代器,迭代器的操作,都是一堆运算符重载

注意:c++语法不允许后++加两次,因为后加加操作符重载后返回的不是引用(后加加里面有临时变量)

2.3 多版本对比介绍

注意:最新版本中,list数据部分(node节点)拆成了两个指针,list大小为两个指针的大小,为8(32位系统下)

  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值