STL学习笔记

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;

以上区别在于后者在访问越界时会抛出异常,而前者不会。

例:

  1. int     intarray[10];     
  2. vector<int>         first_vector(intarray,         intarray         +         10);     
  3. vector<int>         second_vector(first_vector.begin(),first_vector.end());   
  4. class         man     
  5. {     
  6.     public:     
  7.     AnsiStirng         id;     
  8.     AnsiString         mc;     
  9. }     
  10. vector<man>         manList;     
  11. man         thisman;     
  12. thisman.id="2001";     
  13. thisman.name="yourname";     
  14. manList.push_back (thisman);           //加入第一个元素     
  15. thisman.id="2002";     
  16. thisman.name="myname";     
  17. manList.push_back         thisman;           //加入第二个元素      
  18. manList.clear();           //清空      

(3). 遍历

  1. for(vector<datatype>::iterator it=a.begin(); it!=a.end();it++)  
  2. {  
  3.        cout<<*it<<endl;  
  4. }  
  1. for(int i=0;i<a.size;i++)  
  2. {  
  3.      cout<<a[i]<<endl;  
  4. }  

(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数据

  1.  map<int, string> mapStudent;  
  2.  mapStudent.insert(pair<int, string>(1, “student_one”));  

第二种:用insert函数插入value_type数据

  1. map<int, string> mapStudent;  
  2. mapStudent.insert(map<int, string>::value_type (1, “student_one”));  

第三种:用数组方式插入数据

  1. map<int, string> mapStudent;  
  2. mapStudent[1] = “student_one”;  
  3. mapStudent[2] = “student_two”;  

(3).map的大小

在往map里面插入了数据,我们怎么知道当前已经插入了多少数据呢,可以用size函数:

  1. int nSize = mapStudent.size();  

(4).数据的遍历

第一种:应用前向迭代器

  1. map<int, string>::iterator iter;  
  2. for(iter = mapStudent.begin(); iter != mapStudent.end(); iter++)  
  3. {  
  4.     Cout<<iter->first<<” ”<<iter->second<<end;  
  5. }  

第二种:应用反相迭代器

  1. map<int, string>::reverse_iterator iter;  
  2. for(iter = mapStudent.rbegin(); iter != mapStudent.rend(); iter++)  
  3. {  
  4.   Cout<<iter->first<<” ”<<iter->second<<end;  
  5. }  

第三种:用数组方式

  1.  int nSize = mapStudent.size()  
  2. for(int nIndex = 1; nIndex <= nSize; nIndex++)   
  3. {  
  4.    Cout<<mapStudent[nIndex]<<end;  
  5. }  

(5).数据的查找(包括判定这个关键字是否在map中出现)

这里给出三种数据查找方法

第一种:用count函数来判定关键字是否出现,但是无法定位数据出现位置

第二种:用find函数来定位数据出现位置它返回的一个迭代器,

当数据出现时,它返回数据所在位置的迭代器,如果map中没有要查找的数据,它返回的迭代器等于end函数返回的迭代器

  1. int main()  
  2. {  
  3.         map<int, string> mapStudent;  
  4.         mapStudent.insert(pair<int, string>(1, “student_one”));  
  5.         mapStudent.insert(pair<int, string>(2, “student_two”));  
  6.         mapStudent.insert(pair<int, string>(3, “student_three”));  
  7.         map<int, string>::iterator iter;  
  8.         iter = mapStudent.find(1);  
  9.         if(iter != mapStudent.end())  
  10.         {  
  11.              Cout<<”Find, the value is ”<<iter->second<<endl;  
  12.          }  
  13.         else  
  14.         {  
  15.            Cout<<”Do not Find”<<endl;  
  16.         }  
  17. }  

第三种:这个方法用来判定数据是否出现

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中不出现这个关键字,程序说明

  1. mapPair = mapStudent.equal_range(2);  
  2. if(mapPair.first == mapPair.second)  
  3. {  
  4.    cout<<”Do not Find”<<endl;  
  5. }  

(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()

集合的并,交和差

  1. set_union(a.begin(),a.end(),b.begin(),b.end(),insert_iterator<set<int> >(c,c.begin()));  
  2. set_intersection(a.begin(),a.end(),b.begin(),b.end(),insert_iterator<set<int> >(c,c.begin()));  
  3. 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就是一个列表

  1. include <list>  
  2. using namespace std;  
  3. typedef list<stUserListNode *> UserList; //存储用户信息  
  4. ClientList.push_back(currentuser);//添加  
  5. ClientList.remove(*removeiterator);//删除  
  6. //查找  
  7. stUserListNode GetUser(char *username) //根据用户名获取用户信息  
  8. {  
  9. for(UserList::iterator UserIterator=ClientList.begin();  
  10.       UserIterator!=ClientList.end();  
  11.         ++UserIterator)  
  12. {  
  13.    if( strcmp( ((*UserIterator)->userName), username) == 0 )  
  14.     return *(*UserIterator);  
  15. }  
  16. 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 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值