首先要理解什么是容器,在C++中容器被定义为:在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对象的指针,这种对象类型就叫做容器。简单来说
容器就是包含其他类的对象们的对象,当然这种(容器)对象中还包含了一系列处理其所包含对象以及要包含对象的处理。
其次介绍一下STL,它由容器算法迭代器组成;
STL可以实现方便容易的搜索数据或对数据排序等一系列的算法。
STL 对定义的通用容器分三类:顺序性容器、关联式容器和容器适配器。
顺序容器:一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。
这个位置和 元素本身无关,而和操作的时间和地点有关,顺序性容器不会根据元素的特点排序而是直接保存了元素操作时的逻辑顺序。比如我们一次性对一个顺序
性容器追加三 个元素,这三个元素在容器中的相对位置和追加时的逻辑次序是一致的。
关联容器:他和顺序容器不同关联容器是一种非线型的二叉树结构,各元素之间没有严格的物理顺序关系,也就是说元素在容器中并没有保存元素置 入容器先后的的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。
顺序容器中常用的主要有vector,list,deque等。
容器适配器 :是一个比较抽象的概念, C++的 解释是:适配器是使一事物的行为类似于另一事物的行为的一种机制。容器适配器是让一种已存在的容器类型采用另一种不同的抽象类型的工作方式来实现的一种机 制。其实仅是发生了接口转换。那么你可以把它理解为容器的容器,它实质还是一个容器,只是他不依赖于具体的标准容器类型,可以理解是容器的模版。或者把它 理解为容器的接口,而适配器具体采用哪种容器类型去实现,在定义适配器的时候可以由你决定。
为了定义一个容器类型的对象必须包含相关的头文件:
1 定义vector: #include <vector> 2 定义list: #include <list> 3 定义deque: #include <deque> 4 定义queue: #include <queue> 5 定义stack: #include <stack> //注意:stack和queue都是基于deque实现的,但是stack和queue比较常用
1 vector<int>vec;//定义vector,常用 2 list<int>lis; 3 deque<int>deq; 4 stack<int>sta;//定义栈,常用 5 queue<int>que;//定义栈,常用
顺序容器中vector最为常用,它表示一段连续的内存,基于数组实现:
1 vector<int> vec1; //默认初始化,vec1为空 2 3 vector<int> vec2(vec1); //使用vec1初始化vec2 4 5 vector<int> vec3(vec1.begin(),vec1.end());//使用vec1初始化vec2 6 7 vector<int> vec4(10); //10个值为的元素 8 9 vector<int> vec5(10,4); //10个值为的元素
1 //vector的定义 2 vector 创建一个空的vector。 3 vector c1(c2) 复制一个vector 4 vector c(n) 创建一个vector,含有n个数据,数据均已缺省构造产生 5 vector c(n, elem) 创建一个含有n个elem拷贝的vector 6 vector c(beg,end) 创建一个含有n个elem拷贝的vector 7 8 //vector的操作 9 c.assign(beg,end) 将[beg; end)区间中的数据赋值给c。 10 c.assign(n,elem) 将n个elem的拷贝赋值给c。 11 c.at(idx) 传回索引idx所指的数据,如果idx越界,抛出out_of_range。 12 c.back() 传回最后一个数据,不检查这个数据是否存在。 13 c.begin() 传回迭代器中的第一个数据地址。 14 c.capacity() 返回容器中数据个数。 15 c.clear() 移除容器中所有数据。 16 c.empty() 判断容器是否为空。 17 c.end() 指向迭代器中末端元素的下一个,指向一个不存在元素。 18 c.erase(pos) 删除pos位置的数据,传回下一个数据的位置。 19 c.erase(beg,end) 删除[beg,end)区间的数据,传回下一个数据的位置。 20 c.front() 传回第一个数据。 21 get_allocator 使用构造函数返回一个拷贝。 22 c.insert(pos,elem) 在pos位置插入一个elem拷贝,传回新数据位置。 23 c.max_size() 返回容器中最大数据的数量。 24 c.insert(pos,n,elem) 在pos位置插入n个elem数据。无返回值。 25 c.insert(pos,beg,end) 在pos位置插入在[beg,end)区间的数据。无返回值。 26 c.pop_back() 删除最后一个数据。 27 c.push_back(elem) 在尾部加入一个数据。 28 c.rbegin() 传回一个逆向队列的第一个数据。 29 c.rend() 传回一个逆向队列的最后一个数据的下一个位置。 30 c.resize(num) 重新指定队列的长度。 31 c.reserve() 保留适当的容量。 32 c.size() 返回容器中实际数据的个数。 33 c1.swap(c2) 34 swap(c1,c2) 将c1和c2元素互换。同上操作。 35 operator[] 返回容器中指定位置的一个引用。
1 //下标法 2 3 int length = vec1.size(); 4 5 for(int i=0;i<length;i++) 6 7 { 8 9 cout<<vec1[i]; 10 11 } 12 13 cout<<endl<<endl; 14 15 //迭代器法 16 17 vector<int>::const_iterator iterator = vec1.begin(); 18 19 for(;iterator != vec1.end();iterator++) 20 21 { 22 23 cout<<*iterator; 24 25 }
vector 在需要的时候会扩容,在 VS 下是 1.5倍,在 GCC 下是 2 倍。
因为如果采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度,因此,使用成倍的方式扩容。
显然,增长的倍数不可能很大,也不会比 1 小,那么,它的最佳上限是多少呢?如果以 大于2 倍的方式扩容,下一次申请的内存会大于之前
分配内存的总和,导致之前分配的内存不能再被使用。所以,最好的增长因子在 (1,2)之间。
vector 的优点:
(1) 指定一块如同数组一样的连续存储,但空间可以动态扩展。即它可以像数组一样操作,并且可以进行动态操作。通常体现在push_back() pop_back() 。
(2) 随机访问方便,它像数组一样被访问,即重载了[ ] 操作符和vector.at()
(3) 节省空间,因为它是连续存储,在存储数据的区域都是没有被浪费的,但是要明确一点vector 大多情况下并不是满存的,在未存储的区域实际是浪费的。
vector的缺点:
(4) 在内部进行插入、删除操作效率非常低,这样的操作基本上是被禁止的。Vector 被设计成只能在后端进行追加和删除操作,其原因是vector 内部的实现是按照顺序表的原理。
(5) 只能在vector 的最后进行push 和pop ,不能在vector 的头进行push 和pop 。
(6) 当动态添加的数据超过vector 默认分配的大小时要进行内存的重新分配、拷贝与释放,这个操作非常消耗性能。 所以要vector 达到最优的性能,最好在创建vector 时就指定其空间大小。
Vector内部数据结构:数组。
随机访问每个元素,所需要的时间为常量。
在末尾增加或删除元素所需时间与元素数目无关,在中间或开头增加或删除元素所需时间随元素数目呈线性变化。
可动态增加或减少元素,内存管理自动完成,但程序员可以使用reserve()成员函数来管理内存。
vector的迭代器在内存重新分配时将失效(它所指向的元素在该操作的前后不再相同)。当把超过capacity()-size()个元素插入vector中时,内存会重新分配,所有的迭代器都将失效;否则,指向当前元素以后的任何元素的迭代器都将失效。当删除元素时,指向被删除元素以后的任何元素的迭代器都将失效
关联容器的特点是明显的,相对于顺序容器,有以下几个主要特点:
1, 其内部实现是采用非线性的二叉树结构,具体的说是红黑树的结构原理实现的;
2, set 和map 保证了元素的唯一性,mulset 和mulmap 扩展了这一属性,可以允许元素不唯一;
3, 元素是有序的集合,默认在插入的时候按升序排列。
关联容器中map最为常用,它提供一个键值对(key/value)容器:
1, 关联容器对元素的插入和删除操作比vector 要快,因为vector 是顺序存储,而关联容器是链式存储;比list 要慢,是因为即使它们同是链式结构,但list 是线性的,而关联容器是二叉树结构,其改变一个元素涉及到其它元素的变动比list 要多,并且它是排序的,每次插入和删除都需要对元素重新排序;
2, 关联容器对元素的检索操作比vector 慢,但是比list 要快很多。vector 是顺序的连续存储,当然是比不上的,但相对链式的list 要快很多是因为list 是逐个搜索,它搜索的时间是跟容器的大小成正比,而关联容器 查找的复杂度基本是Log(N) ,比如如果有1000 个记录,最多查找10 次,1,000,000 个记录,最多查找20 次。容器越大,关联容器相对list 的优越性就越能体现;
3, 在使用上set 区别于vector,deque,list 的最大特点就是set 是内部排序的,这在查询上虽然逊色于vector ,但是却大大的强于list 。
4, 在使用上map 的功能是不可取代的,它保存了“键- 值”关系的数据,而这种键值关系采用了类数组的方式。数组是用数字类型的下标来索引元素的位置,而map 是用字符型关键字来索引元素的位置。在使用上map 也提供了一种类数组操作的方式,即它可以通过下标来检索数据,这是其他容器做不到的,当然也包括set 。(STL 中只有vector 和map 可以通过类数组的方式操作元素,即如同ele[1] 方式)。
5.如果迭代器所指向的元素被删除,则该迭代器失效。其它任何增加、删除元素的操作都不会使迭代器失效。
1 //1.定义和初始化 2 3 map<int,string> map1; //空map 4 5 6 7 //2.常用操作方法 8 9 map1[3] = "Saniya"; //添加元素 10 11 map1.insert(map<int,string>::value_type(2,"Diyabi"));//插入元素 12 13 //map1.insert(pair<int,string>(1,"Siqinsini")); 14 15 map1.insert(make_pair<int,string>(4,"V5")); 16 17 string str = map1[3]; //根据key取得value,key不能修改 18 19 map<int,string>::iterator iter_map = map1.begin();//取得迭代器首地址 20 21 int key = iter_map->first; //取得eky 22 23 string value = iter_map->second; //取得value 24 25 map1.erase(iter_map); //删除迭代器数据 26 27 map1.erase(3); //根据key删除value 28 29 map1.size(); //元素个数 30 31 map1.empty(); //判断空 32 33 map1.clear(); //清空所有元素
1 /3.遍历 2 3 for(map<int,string>::iterator iter = map1.begin();iter!=map1.end();iter++) 4 5 { 6 7 int keyk = iter->first; 8 9 string valuev = iter->second; 10 11 }
在开发过程中我比较常用的就是这两种容器,今天把他们从底层了解了一下,对我今后对容器的选择和使用有了一定的帮助,吧今天了解到的整理下来希望可以用到。