1. STL容器简介
STL(Standard Template Library,标准模板库)从根本上说,是一些算法、容器的集合,STL可以让你重复运用既有的算法,而不必在环境类似的情况下再撰写相同的代码。STL算法是泛型的,不与任何特定数据结构或对象型别系缚在一起,但是却像为你量身定做的一样,有很高的效率。STL甚至是可扩充的,就像STL组件彼此之间可以相互配合运用一样,STL组件也可以和你写的组件水乳交融的搭配运用。
STL被内建在编译器中, 在C++标准中,STL被组织为下面的13个头文件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack>和<utility>。
2. STL各个容器的实现
Ø vector
内部数据结构:数组。
随机访问每个元素,所需要的时间为常量。
在末尾增加或删除元素所需时间与元素数目无关,在中间或开头增加或删除元素所需时间随元素数目呈线性变化。
可动态增加或减少元素,内存管理自动完成,但程序员可以使用reserve()成员函数来管理内存。
vector的迭代器在内存重新分配时将失效(它所指向的元素在该操作的前后不再相同)。当把超过capacity()-size()个元素插入vector中时,内存会重新分配,所有的迭代器都将失效;否则,指向当前元素以后的任何元素的迭代器都将失效。当删除元素时,指向被删除元素以后的任何元素的迭代器都将失效。
总结
需要经常随机访问请用vector
Ø deque
内部数据结构:数组。
随机访问每个元素,所需要的时间为常量。
在开头和末尾增加元素所需时间与元素数目无关,在中间增加或删除元素所需时间随元素数目呈线性变化。
可动态增加或减少元素,内存管理自动完成,不提供用于内存管理的成员函数。
增加任何元素都将使deque的迭代器失效。在deque的中间删除元素将使迭代器失效。在deque的头或尾删除元素时,只有指向该元素的迭代器失效。
Ø list
内部数据结构:双向环状链表。
不能随机访问一个元素。
可双向遍历。
在开头、末尾和中间任何地方增加或删除元素所需时间都为常量。
可动态增加或减少元素,内存管理自动完成。
增加任何元素都不会使迭代器失效。删除元素时,除了指向当前被删除元素的迭代器外,其它迭代器都不会失效。
Ø slist
内部数据结构:单向链表。
不可双向遍历,只能从前到后地遍历。
其它的特性同list相似。
Ø stack
适配器,它可以将任意类型的序列容器转换为一个堆栈,一般使用deque作为支持的序列容器。
元素只能后进先出(LIFO)。
不能遍历整个stack。
Ø queue
适配器,它可以将任意类型的序列容器转换为一个队列,一般使用deque作为支持的序列容器。
元素只能先进先出(FIFO)。
不能遍历整个queue。
Ø priority_queue
适配器,它可以将任意类型的序列容器转换为一个优先级队列,一般使用vector作为底层存储方式。
只能访问第一个元素,不能遍历整个priority_queue。
第一个元素始终是优先级最高的一个元素。
Ø set
键和值相等。
键唯一。
元素默认按升序排列。
如果迭代器所指向的元素被删除,则该迭代器失效。其它任何增加、删除元素的操作都不会使迭代器失效。
Ø multiset
键可以不唯一。
其它特点与set相同。
Ø hash_set
与set相比较,它里面的元素不一定是经过排序的,而是按照所用的hash函数分派的,它能提供更快的搜索速度(当然跟hash函数有关)。
其它特点与set相同。
Ø hash_multiset
键可以不唯一。
其它特点与hash_set相同。
Ø map
键唯一。
元素默认按键的升序排列。
如果迭代器所指向的元素被删除,则该迭代器失效。其它任何增加、删除元素的操作都不会使迭代器失效。
Ø multimap
键可以不唯一。
其它特点与map相同。
Ø hash_map
与map相比较,它里面的元素不一定是按键值排序的,而是按照所用的hash函数分派的,它能提供更快的搜索速度(当然也跟hash函数有关)。
其它特点与map相同。
Ø hash_multimap
键可以不唯一。
其它特点与hash_map相同。
3. Stl基本容器string、vector、map、set 、list、deque用法
set 和map都是无序的保存元素,只能通过它提供的接口对里面的元素进行访问
set:集合, 用来判断某一个元素是不是在一个组里面
map:映射,相当于字典,把一个值映射成另一个值,如果想创建字典的话使用它好了
string、 vector、list、deque是有序容器
Ø String
string 是basic_string<char> 的实现,在内存中是连续存放的.为了提高效率,都会有保留内存,如string s= "abcd",这时s使用的空间可能就是255, 当string再次往s里面添加内容时不会再次分配内存.直到内容>255时才会再次申请内存,因此提高了它的性能.
当内容>255 时,string会先分配一个新内存,然后再把内容复制过去,再复制先前的内容.
对string的操作,如果是添加到最后时,一般不需要 分配内存,所以性能最快;
如果是对中间或是开始部分操作,如往那里添加元素或是删除元素,或是代替元素,这时需要进行内存复制,性能会降低.
如 果删除元素,string一般不会释放它已经分配的内存,为了是下次使用时可以更高效.
由于string会有预保留内存,所以如果大量使 用的话,会有内存浪费,这点需要考虑.还有就是删除元素时不释放过多的内存,这也要考虑.
string中内存是在堆中分配的,所以串的长 度可以很大,而char[]是在栈中分配的,长度受到可使用的最大栈长度限制.
如果对知道要使用的字符串的最大长度,那么可以使用普通的 char[],实现而不必使用string.
string用在串长度不可知的情况或是变化很大的情况.
如果string已经经历 了多次添加删除,现在的尺寸比最大的尺寸要小很多,想减少string使用的大小,可以使用:
string s = "abcdefg";
string y(s); // 因为再次分配内存时,y只会分配与s中内容大一点的内存,所以浪费不会很大
s.swap(y); // 减少s使用的内存
如 果内存够多的话就不用考虑这个了
capacity是查看现在使用内存的函数
大家可以试试看string分配一个一串后 的capacity返回值,还有其它操作后的返回值
Ø Vector
(1).声明:
一个vector类似于一个动态的一维数组。
vector<int> a; //声明一个元素为int类型的vector a
vectot<MyType> a; //声明一个元素为MyType类型的vector a
这里的声明的a包含0个元素,既a.size()的值为0,但它是动态的,其大小会随着数据的插入和删除改变而改变。
vector<int> a(100, 0); //这里声明的是一已经个存放了100个0的整数vector
(2).向量操作
常用函数:
size_t size(); // 返回vector的大小,即包含的元素个数
void pop_back(); // 删除vector末尾的元素,vector大小相应减一
void push_back(); //用于在vector的末尾添加元素
T back(); // 返回vector末尾的元素
void clear(); // 将vector清空,vector大小变为0
其他访问方式:
cout<<a[5]<<endl;
cout<<a.at(5)<<endl;
以上区别在于后者在访问越界时会抛出异常,而前者不会。
例:
- int intarray[10];
- vector<int> first_vector(intarray, intarray + 10);
- vector<int> second_vector(first_vector.begin(),first_vector.end());
- class man
- {
- public:
- AnsiStirng id;
- AnsiString mc;
- }
- vector<man> manList;
- man thisman;
- thisman.id="2001";
- thisman.name="yourname";
- manList.push_back (thisman); //加入第一个元素
- thisman.id="2002";
- thisman.name="myname";
- manList.push_back thisman; //加入第二个元素
- manList.clear(); //清空
(3). 遍历
- for(vector<datatype>::iterator it=a.begin(); it!=a.end();it++)
- {
- cout<<*it<<endl;
- }
- for(int i=0;i<a.size;i++)
- {
- cout<<a[i]<<endl;
- }
(4). 排序
C++中当 vector 中的数据类型为基本类型时我们调用std::sort函数很容易实现 vector中数据成员的升序和降序排序,然而当vector中的数据类型为自定义结构体类型时,我们可以自定义排序方式对vector中的数据类型按照某个字段排序,排序方法请参考:http://blog.csdn.net/freeboy1015/article/details/7258772
Ø Map
Map是STL的一个关联容器,它提供一对一(其中第一个可以称为关键字,每个关键字只能在map中出现一次,第二个可能称为该关键字的值)的数据处理能力,由于这个特性,map内部的实现自建一颗红黑树(一种非严格意义上的平衡二叉树),这颗树具有对数据自动排序的功能。
下面举例说明什么是一对一的数据映射。比如一个班级中,每个学生的学号跟他的姓名就存在着一一映射的关系,这个模型用map可能轻易描述,
很明显学号用int描述,姓名用字符串描述(本篇文章中不用char *来描述字符串,而是采用STL中string来描述),
下面给出map描述代码:
(1).声明方式:
Map<int, string> mapStudent;
(2).数据的插入
在构造map容器后,我们就可以往里面插入数据了。这里讲三种插入数据的方法:
第一种:用insert函数插入pair数据
- map<int, string> mapStudent;
- mapStudent.insert(pair<int, string>(1, “student_one”));
第二种:用insert函数插入value_type数据
- map<int, string> mapStudent;
- mapStudent.insert(map<int, string>::value_type (1, “student_one”));
第三种:用数组方式插入数据
- map<int, string> mapStudent;
- mapStudent[1] = “student_one”;
- mapStudent[2] = “student_two”;
(3).map的大小
在往map里面插入了数据,我们怎么知道当前已经插入了多少数据呢,可以用size函数:
- int nSize = mapStudent.size();
(4).数据的遍历
第一种:应用前向迭代器
- map<int, string>::iterator iter;
- for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)
- {
- Cout<<iter->first<<” ”<<iter->second<<end;
- }
第二种:应用反相迭代器
- map<int, string>::reverse_iterator iter;
- for(iter = mapStudent.rbegin(); iter != mapStudent.rend(); iter++)
- {
- Cout<<iter->first<<” ”<<iter->second<<end;
- }
第三种:用数组方式
- int nSize = mapStudent.size()
- for(int nIndex = 1; nIndex <= nSize; nIndex++)
- {
- Cout<<mapStudent[nIndex]<<end;
- }
(5).数据的查找(包括判定这个关键字是否在map中出现)
这里给出三种数据查找方法
第一种:用count函数来判定关键字是否出现,但是无法定位数据出现位置
第二种:用find函数来定位数据出现位置它返回的一个迭代器,
当数据出现时,它返回数据所在位置的迭代器,如果map中没有要查找的数据,它返回的迭代器等于end函数返回的迭代器
- int main()
- {
- map<int, string> mapStudent;
- mapStudent.insert(pair<int, string>(1, “student_one”));
- mapStudent.insert(pair<int, string>(2, “student_two”));
- mapStudent.insert(pair<int, string>(3, “student_three”));
- map<int, string>::iterator iter;
- iter = mapStudent.find(1);
- if(iter != mapStudent.end())
- {
- Cout<<”Find, the value is ”<<iter->second<<endl;
- }
- else
- {
- Cout<<”Do not Find”<<endl;
- }
- }
第三种:这个方法用来判定数据是否出现
Lower_bound函数用法,这个函数用来返回要查找关键字的下界(是一个迭代器)
Upper_bound函数用法,这个函数用来返回要查找关键字的上界(是一个迭代器)
例如:map中已经插入了1,2,3,4的话,如果lower_bound(2)的话,返回的2,而upper-bound(2)的话,返回的就是3
Equal_range函数返回一个pair,pair里面第一个变量是Lower_bound返回的迭代器,pair里面第二个迭代器是Upper_bound返回的迭代器,如果这两个迭代器相等的话,则说明map中不出现这个关键字,程序说明
- mapPair = mapStudent.equal_range(2);
- if(mapPair.first == mapPair.second)
- {
- cout<<”Do not Find”<<endl;
- }
(6).数据的清空与判空
清空map中的数据可以用clear()函数,判定map中是否有数据可以用empty()函数,它返回true则说明是空map
(7).数据的删除
这里要用到erase函数,它有三个重载了的函数
迭代器删除
iter = mapStudent.find(1);
mapStudent.erase(iter);
用关键字删除
Int n = mapStudent.erase(1);//如果删除了会返回1,否则返回0
用迭代器,成片的删除
一下代码把整个map清空
mapStudent.earse(mapStudent.begin(), mapStudent.end()); //成片删除要注意的是,也是STL的特性,删除区间是一个前闭后开的集合
(8). 其他一些函数用法
这里有swap,key_comp,value_comp,get_allocator等函数
Ø Set
set是集合,set中不会包含重复的元素,这是和vector的区别。
定义:
定义一个元素为整数的集合a,可以用
set<int> a;
基本操作:
对集合a中元素的有
插入元素:a.insert(1);
删除元素(如果存在):a.erase(1);
判断元素是否属于集合:if (a.find(1) != a.end()) ...
返回集合元素的个数:a.size()
将集合清为空集:a.clear()
集合的并,交和差
- set_union(a.begin(),a.end(),b.begin(),b.end(),insert_iterator<set<int> >(c,c.begin()));
- set_intersection(a.begin(),a.end(),b.begin(),b.end(),insert_iterator<set<int> >(c,c.begin()));
- set_difference(a.begin(),a.end(),b.begin(),b.end(),insert_iterator<set<int> >(c,c.begin()));
(注意在此前要将c清为空集)。
注意:很重要的一点,为了实现集合的快速运算,set的实现采用了平衡二叉树,因此,set中的元素必须是可排序的。如果是自定义的类型,那在定义类型的同时必须给出运算符<的定义
Ø List
slist容器跟vector、deque容器都属于序列容器(sequence containers), 所以大部分的操作接口还是类似的,像创建(create)、复制(copy)、销毁(destroy)、大小(size)、比较(compare)、赋值(assignment)、迭代器(iterator)等都基本类似。
list就是一个列表
- include <list>
- using namespace std;
- typedef list<stUserListNode *> UserList; //存储用户信息
- ClientList.push_back(currentuser);//添加
- ClientList.remove(*removeiterator);//删除
- //查找
- stUserListNode GetUser(char *username) //根据用户名获取用户信息
- {
- for(UserList::iterator UserIterator=ClientList.begin();
- UserIterator!=ClientList.end();
- ++UserIterator)
- {
- if( strcmp( ((*UserIterator)->userName), username) == 0 )
- return *(*UserIterator);
- }
- throw Exception("not find this user");
如果你喜欢经常添加删除大对象的话,那么请使用list
要保存的 对象不大,构造与析构操作不复杂,那么可以使用vector代替
list<指针>完全是性能最低的做法,这种情况下还是使用 vector<指针>好,因为指针没有构造与析构,也不占用很大内存
Ø deque
双端队列, 也是在堆中保存内容的.它的保存形式如下:
[堆1]
...
[堆2]
...
[堆3]
每个 堆保存好几个元素,然后堆和堆之间有指针指向,看起来像是list和vector的结合品,不过确实也是如此
deque可以让你在前面快速地添加 删除元素,或是在后面快速地添加删除元素,然后还可以有比较高的随机访问速度
vector是可以快速地在最后添加删除元素,并可以快速地 访问任意元素
list是可以快速地在所有地方添加删除元素,但是只能快速地访问最开始与最后的元素
deque在开始和最后添加元素都一样 快,并提供了随机访问方法,像vector一样使用[]访问任意元素,但是随机访问速度比不上vector快,因为它要内部处理堆跳转
deque 也有保留空间.另外,由于deque不要求连续空间,所以可以保存的元素比vector更大,这点也要注意一下.还有就是在前面和后面添加元素时都不需要 移动其它块的元素,所以性能也很高
4. stl容器性能比较
关于性能问题推荐一篇很不错的文章:http://www.360doc.com/content/12/0812/23/3398926_229844858.shtml
参考资料:
1. http://www.360doc.com/content/12/0812/23/3398926_229844858.shtml
2. http://www.deuxmille.org/archives/1470
3. http://ewangplay.appspot.com/?p=31001
4. http://www.360doc.com/content/10/1212/12/4780948_77347715.shtml