C++顺序容器

一 容器是什么

在C++中,容器被定义为:在数据存储上,有一种对象类型,它可以持有其他对象或指向其他对象的指针,这种对象类型就叫做容器。简单理解,即容器就是保存其他对象的对象。而且,这种“对象”还有处理“其他对象”的方法。
容器是随着面向对象语言的诞生而提出的,它甚至被认为是早期面向对象语言的基础。现在几乎所有面向对象语言中都伴随着一个容器,C++中则是标准模版库(STL)。

二 容器的分类

STL 对定义的通用容器分三类:顺序性容器、关联式容器和容器适配器

  1. 顺序性容器
    是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。这个位置和元素本身无关,而和操作的时间和地点有关,顺序性容器不会根据元素的特点排序而是直接保存了元素操作时的逻辑顺序。比如我们一次性对一个顺序性容器追加三个元素,这三个元素在容器中的相对位置和追加时的逻辑次序是一致的。
  2. 关联式容器
    和顺序性容器不一样,关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。关联式容器另一个显著的特点是它是以键值的方式来保存数据,就是说它能把关键字和值关联起来保存,而顺序性容器只能保存一种(可以认为它只保存关键字,也可以认为它只保存值)。
  3. 容器适配器 是一个比较抽象的概念,C++的解释是:适配器是使一事物的行为类似于另一事物的行为的一种机制。容器适配器是让一种已存在的容器类型采用另一种不同的抽象类型的工作方式来实现的一种机制。其实仅是发生了接口转换。那么你可以把它理解为容器的容器,它实质还是一个容器,只是他不依赖于具体的标准容器类型,可以理解是容器的模版。或者把它理解为容器的接口,而适配器具体采用哪种容器类型去实现,在定义适配器的时候可以由你决定。

三 顺序容器

1 vector
常用接口:

	//初始化
	vector vec; //默认初始化,空
	vector vec2(vec); //使用vec初始化vec2
	vector vec3(3); //初始化3个值为0的元素
	vector vec4(4, 1); //初始化4个值为1的元素
	vector vec5(5, "null"); //初始化5个值为null的元素

	//常用的操作方法
	vec.push_back(3); //向末尾添加元素3
	vec.begin();//返回指向初始位置的迭代器
	vec.end(); //返回指向末尾下一位置的迭代器
	vec.size(); //当前vector容器真实占用的大小
	vec.capacity();//capacity是指能允许的最大元素数,即预分配的内存空间。
	vec.empty(); //判断是否为空

	cout << vec[0] << endl; //取得第一个元素 支持[]
	vec.insert(vec.end(), 5, 3); //从末尾下一位置插入5个值为3的元素
	vec.pop_back(); //删除末尾元素
	vec.erase(vec.begin(), vec.end());//删除之间的元素,其他元素前移
	cout << (vec == vec2) ? true : false; //判断是否相等==、!=、>=、<=...
	vec.clear(); //清空元素


	vector vs1(3); 
	vector vs2(5); 
	vs1.swap(vs2); //执行后,vs1中5个元素,而vs2则存3个元素

要点

  1. 可以直接访问任何元素。
  2. 线性顺序结构。可以指定一块连续的空间,也可以不预先指定大小,空间可自动扩展,也可以像数组一样被操作,即支持[]操作符和vector.at(),因此可看做动态数组,通常体现在追加数据push_back()和删除末尾数据pop_back()。
  3. 当分配空间不够时,vector会申请一块更大的内存块(以2的倍数增长,vs是这样),然后将原来的数据拷贝到新内存块中并将原内存块中的对象销毁,最后释放原来的内存空间。因此如果vector保存的数据量很大时会很消耗性能,因此在预先知道它大小时性能最优。
  4. 节省空间。因为它是连续存储,在存储数据的区域是没有浪费的,但实际上大多数时候是存不满的,因此实际上未存储的区域是浪费的。
  5. 在内部进行插入和删除的操作效率低。由于vector内部按顺序表结构设计,因此这样的操作基本上是被禁止的,它被设计成只能在后端进行追加和删除操作。

2 list
常用接口:

	//初始化
	list lst1; //创建空list
	list lst2(3); //创建含有三个元素的list
	list lst3(3, 2); //创建含有三个元素的值为2的list
	list lst4(lst2); //使用lst2初始化lst4

	//常用的操作方法
	lst1.assign(lst2.begin(), lst2.end()); //分配值
	lst1.push_back(10); //添加值
	lst1.pop_back(); //删除末尾值
	lst1.begin(); //返回首值的迭代器
	lst1.end(); //返回末尾位置下一位的迭代器
	lst1.clear();//清空值
	bool isEmpty1 = lst1.empty(); //判断为空
	lst1.erase(lst1.begin(), lst1.end()); //删除元素
	lst1.front(); //返回第一个元素的引用
	lst1.back(); //返回最后一个元素的引用
	lst1.insert(lst1.begin(), 3, 2); //从指定位置插入3个值为2的元素
	lst1.remove(2); //相同的元素全部删除
	lst1.reverse(); //反转
	lst1.size(); //含有元素个数
	lst1.sort(); //排序,需要自己写重载操作符<
	lst1.unique(); //删除相邻重复元素

要点

  1. 线性链表结构。
  2. 其数据由若干个节点构成,每个节点包括一个信息块(即实际存储的数据)、一个前驱指针和一个后驱指针。无需分配指定的内存大小且可任意伸缩,因此它存储在非连续的内存空间中,并且由指针将有序的元素链接起来。因而相比vector它也占更多的内存。
  3. 根据其结构可知随机检索的性能很差,vector是直接找到元素的地址,而它需要从头开始按顺序依次查找,因此检索靠后的元素时非常耗时。即不支持[]操作符和.at()。
  4. 由于list每个节点保存着它在链表中的位置,插入或删除一个元素仅对最多三个元素有所影响,因此它可以迅速在任何节点进行插入和删除操作。

3 deque
是一种优化了的、对序列两端元素进行添加和删除操作的基本序列容器。它允许较为快速地随机访问,但它不像vector 把所有的对象保存在一块连续的内存块,而是采用多个连续的存储块,并且在一个映射结构中保存对这些块及其顺序的跟踪。向deque 两端添加或删除元素的开销很小。它不需要重新分配空间,所以向末端增加元素比vector 更有效。
实际上,deque 是对vector 和list 优缺点的结合,它是处于两者之间的一种容器。

常用接口与vector和list相似

要点:
(1) 随机访问方便,即支持[ ] 操作符和vector.at() ,但性能没有vector 好;
(2) 可以在内部进行插入和删除操作,但性能不及list ;
(3) 可以在两端进行push 、pop ;

四 比较

vector 是一段连续的内存块,而deque 是多个连续的内存块, list 是所有数据元素分开保存,可以是任何两个元素没有连续。

vector 的查询性能最好,并且在末端增加数据也很好,除非它重新申请内存段;适合高效地随机存储。若已经知道需要存储元素的数目, 则选择vector。

list 是一个链表,任何一个元素都可以是不连续的,但它都有两个指向上一元素和下一元素的指针。所以它对插入、删除元素性能是最好的,而查询性能非常差;适合 大量地插入和删除操作而不关心随机存取的需求。若需要随机插入/删除(不仅仅在两端),则选择list

deque 是介于两者之间,它兼顾了数组和链表的优点,它是分块的链表和多个数组的联合。 如果你需要随机存取又关心两端数据的插入和删除,那么deque 是最佳之选。

你可能感兴趣的:(C++)