常用容器
一、string 容器
1.1 string容器基本概念
C风格字符串(以空字符结尾的字符数组)太过复杂化难于掌握,不适合大程序的开发,所以C++标准库定义了一种string类,定义在头文件。
string 和 C 风格的字符串对比:
char *是一个指针,string是一个类,string 封装了char,管理这个字符串,是一个char型的容器
string封装了很多实用的成员方法,查找find、拷贝copy、删除delete、替换replace、插入insert
不用考虑内存释放和越界,string管理char *所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界的取值越界等。
1.2 string容器常用操作
1.2.1 string构造函数
1 2 3 4 string ();string (const string &str);string (const char *s);string (int n,char c);
1.2.2 string基本赋值操作
1 2 3 4 5 6 7 8 string& operator =(const char * s); string& operator =(const string &s); string& operator =(char c); string& assing (const char * s) ;string& assing (const char * s,int n) string& assign (const string &s) ;string& assign (int n,char c) ;string& assign (const string &s,int start,int n) ;
1.2.3 string存取字符操作
1 2 char & operator [](int n);char & at (int n) ;
[] 和 at 的区别:如果越界[]不会抛出异常,at会抛出异常
1.2.4 string拼接操作
1 2 3 4 5 6 7 8 string& operator +=(const string &str); string& operator +=(const char *str); string& operator +=(const char c); string& append (const char *s) ;string& append (const char *s,int n) ;string& append (const string &s) ;string& append (const string &s,int pos,int n) ;string& append (int n,char c) ;
1.2.5 string查找和替换
1 2 3 4 5 6 7 8 9 10 int find (const string &str,int pos = 0 ) const ;int find (const char *s,int pos = 0 ) const ;int find (const char *s,int pos = 0 ,int n) const ;int find (const char c,int pos = 0 ) const ;int rfind (const string &str,int pos = npos) const ;int rfind (const char *s,int pos = npos) const ;int rfind (const char *s,int pos, int n) const ;int rfind (const char c,int pos = 0 ) const ;string &replace (int pos,int n,const string &str) ;string &replace (int pos,int n,const char *s) ;
1.2.6 string 比较操作
1 2 3 4 5 6 7 int compare (const string &str) const ;int compare (const char *s) const ;
C语言中的比较函数:
1.2.7 string 子串
1 string substr (int pos = 0 , int n = npos) const ;
strtok:C语言字符串切割。
可以利用substr实现C语言的字符串切割
1.2.8 string的插入和删除
1 2 3 4 string& insert (int pos,const char *s) ;string& insert (int pos,const string &str) ;string& insert (int pos,int n, char c) ;string& erase (int pos,int n = npos) ;
1.2.9 string 和 C风格字符串的转换
1 2 3 4 5 6 7 string str = "hello world" ; const char *cstr = str.c_str ();char *s = "hello world" ;string str1 (s) ;string str2 = s;
在C++中存在一个从const char到string的隐式类型转换,却不存在从一个string对象到Cstring的自动类型转换。对于string类型的字符串,可以通过c_str()函数返回string对象对象的Cstring。通常,程序员在整个程序中应坚持使用string类对象,直到必须将内容转化为 char 时,才将其转换为Cstring。
提示:伪类修改string字符串的内容,下标操作符[]和at都会返回字符的引用。但当字符串的内存被重新分配之后,可能会发生错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 string s = "abcdefg" ; char &a = s[2 ];char &b = s[3 ];a = '1' ; b = '2' ; cout<<s<<endl; cout<<(int *)s.c_str ()<<endl; s = "ppppppppppppppppp" ; cout<<s<<endl; cout<<(int *)s.c_str ()<<endl;
二、vector容器(单端动态数组)
vector 的数据安排以及操作方式,与array非常相似,两者的唯一差别在于空间的运用的灵活性。array是静态空间,一旦配置了就不能改变,更换大一点或者小一点的空间,可以,但是一切琐碎的操作得由自己来,首先配置一块新的空间,然后将旧空间的数据搬进新空间,再释放原来的空间。vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新的元素。因此vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必害怕空间不足而一开始就要求一个大块头的array了。vector的实现技术,关键在于对其大小的控制以及重新配置时数据移动效率,一旦vector旧空间满了,如果客户每增加一个元素,vector内部只是扩充一个元素的空间,实为不智,因为所谓的扩充空间(不论多大),一如刚所说,是“配置新空间–数据移动–释放旧空间”的大工程,时间成本很高,应该加入某种未雨绸缪的考虑,稍后我们便可以看到vector的空间配置策略。
2.1 vector迭代器
vector维护一个线性空间,所以元素的性别如何,普通指针都可以作为vector的迭代器,因为vector迭代器所需要的的操作行为,如operator->、operator++、operator–、operator+、operator-、operator+=、operator-=,普通指针天生具备。vector支持随机存取,而普通指针正有着这样的能力,所以vector提供的是随机访问迭代器(Random Access Iterators)。
vector容器的迭代器:随机访问迭代器
随机访问迭代器:迭代器+n 可以通过编译就是随机访问迭代器
2.1.1 vector的容量(capacity)和大小(size)的区别
容量:空间能容纳元素最大个数
大小:空间实际存放元素个数
1 2 3 4 5 6 7 8 9 void test01 () { vector<int > v; for (int i = 0 ; i < 100 ; i++){ v.push_back (i); } cout<<"v的容量:" <<v.capacity ()<<endl; cout<<"v的大小:" <<v.size ()<<endl; }
运行结果:
分析:为什么容量大于大小,因为vector有未雨绸缪机制,每次分配的空间会大于实际所需要的空间,后面会详细分析的。
2.1.2 vector 的数据结构
vector所采用的数据结构非常简单,线性连续空间,它以两个迭代器myfirst和mylast分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器_myend指向整块连续内存空间的尾端。为了降低空间配置时的速度成本,vector实际配置的大小可能会比客户端需求带一些,以备将来可能的扩充,这边是容量的概念。换句话说,一个vector的容量永远大于或等于其大小,一旦容量等于大小,便是满载,下次再有新增元素,整个vector容器就得另觅居所。
注意: 所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空间),而是重新申请一块更大的内存空间,然后将原数据拷贝到新空间中,并释放原空间。因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就都失效了。这是程序员容易犯的一个小错误,务必小心。
2.1.3 vector另寻空间的次数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void test02 () { vector<int > v; int *p = nullptr ; int count = 0 ; for (int i = 0 ; i < 1000 ; i++){ v.push_back (i); if (p != &v[0 ]){ count++; p = &v[0 ]; } } cout<<"v的容量:" <<v.capacity ()<<endl; cout<<"v的大小:" <<v.size ()<<endl; cout<<"v另寻空间次数:" <<count<<endl; }
运行结果:
只要空间满了 就会另寻空间
2.1.4 vector的未雨绸缪机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void test03 () { vector<int > v; int *p = nullptr ; int count = 0 ; for (int i = 0 ; i < 1000 ; i++){ if (p != &v[0 ]){ count++; p = &v[0 ]; cout<<"--------" <<count<<"--------" <<endl; } v.push_back (i); cout<<"v的容量:" <<v.capacity ()<<",v的大小:" <<v.size ()<<endl; } }
运行结果:
2.1.5 vector常用api操作
vector构造函数
1 2 3 4 vector<T> v; vector (v.begin (),v.end ());vector (n,elem);vector (const vector &vec);
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void printVectorInt (vector<int > v) { for (vector<int >::iterator it = v.begin ();it!=v.end ();it++){ cout<<*it<<" " ; } cout<<endl; } void test04 () { vector<int > v1; v1.push_back (1 ); v1.push_back (2 ); v1.push_back (3 ); v1.push_back (4 ); v1.push_back (5 ); printVectorInt (v1); cout<<"*********" <<endl; vector<int > v2 = vector<int >(v1.begin ()+1 ,v1.end ()-2 ); printVectorInt (v2); int arr[]={12 ,13 ,14 ,15 ,16 }; vector<int > v3 (arr,arr+sizeof (arr)/sizeof (int )) ; printVectorInt (v3); cout<<"*********" <<endl; vector<int > v4 (5 ,59 ) ; printVectorInt (v4); cout<<"*********" <<endl; vector<int > v5 = v4; vector<int > v6 (v4) ; printVectorInt (v5); printVectorInt (v6); }
运行结果:
vector常用赋值操作
1 2 3 4 assign (beg,end);assign (n,elem);vector& operator =(const vector &vec); swap (vec);
vector大小操作
1 2 3 4 5 6 size ();empty ();resize (int num);resize (int num,elem);capacity ();reserve (int len);
注意:resize只是作用于容器的size,不会更改容器的容量capacity,除非resize大于原来容器的容量。
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void test01 () { vector<int > v (5 ,10 ) ; printVectorInt (v); cout<<"size:" <<v.size ()<<",capacity:" <<v.capacity ()<<endl; cout<<"***********************" <<endl; v.resize (10 ); printVectorInt (v); cout<<"size:" <<v.size ()<<",capacity:" <<v.capacity ()<<endl; cout<<"***********************" <<endl; v.resize (12 ,59 ); printVectorInt (v); cout<<"size:" <<v.size ()<<",capacity:" <<v.capacity ()<<endl; cout<<"***********************" <<endl; v.resize (7 ); printVectorInt (v); cout<<"size:" <<v.size ()<<",capacity:" <<v.capacity ()<<endl; cout<<"***********************" <<endl; }
运行结果:
vector v(5,10); v.resize();这两个函数vector容器容量的增长规律好像不是2的n次方了。反正容量肯定比大小大。
利用resize和swap收缩容器容量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void test02 () { vector<int > v; for (int i=0 ;i<1000 ;i++){ v.push_back (i); } cout<<"size:" <<v.size ()<<",capacity:" <<v.capacity ()<<endl; v.resize (12 ); cout<<"size:" <<v.size ()<<",capacity:" <<v.capacity ()<<endl; v.resize (13 ); vector<int >(v).swap (v); cout<<"size:" <<v.size ()<<",capacity:" <<v.capacity ()<<endl; }
运行结果:
分析:vector<int>(v).swap(v);
——> vector(v)是一个匿名对象,调用拷贝构造,因为v的size为13,所以匿名对象的size和capacity都为13,然后匿名对象调用swap与v交换,所以最后的v变为了匿名对象,即最后的v的size和capacity都为13,起到了收缩容量的功能。
reserve预留空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void test03 () { vector<int > v; int count = 0 ; int *p = NULL ; for (int i=0 ;i<1000 ;i++){ v.push_back (i); if (p!=&v[0 ]){ count++; p=&v[0 ]; } } cout<<"另寻空间次数为:" <<count<<endl; vector<int > v1; int count1 = 0 ; int *p1 = NULL ; v1.reserve (1000 ); for (int i=0 ;i<1000 ;i++){ v1.push_back (i); if (p1!=&v1[0 ]){ count1++; p1=&v1[0 ]; } } cout<<"另寻空间次数为:" <<count1<<endl; }
运行结果:
分析:如果不预留空间,当空间不足时,会自动另寻空间;而我要插入1000个元素,开始我就直接预留了1000个元素,告诉编译器我等下要插入1000个数据,你不用一个试探我要开多少,我就是要开1000,你直接给我开辟1000个元素所需空间,所以寻空间次数为1;
vector数据存取操作
1 2 3 4 at (int idx);operator [];front ();back ();
与string容器类似,下标越界,at会抛出异常,而[]不会抛出异常。
vector 插入和删除操作
1 2 3 4 5 6 insert (const_iterator pos,int count,elem);push_back (ele);pop_back ();erase (const_iterator start,const_iterator end);erase (const_iterator pos);clear ();
pop_back erase clear 不会更改容器的容量,只会更改容器的大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 void test04 () { vector<int > v1 (5 ,9 ) ; printVectorInt (v1); cout<<"size:" <<v1.size ()<<",capacity:" <<v1.capacity ()<<endl; cout<<"***********************" <<endl; v1.push_back (12 ); printVectorInt (v1); cout<<"size:" <<v1.size ()<<",capacity:" <<v1.capacity ()<<endl; cout<<"***********************" <<endl; v1.pop_back (); printVectorInt (v1); cout<<"size:" <<v1.size ()<<",capacity:" <<v1.capacity ()<<endl; cout<<"***********************" <<endl; v1.erase (v1.begin ()+1 ,v1.end ()-2 ); printVectorInt (v1); cout<<"size:" <<v1.size ()<<",capacity:" <<v1.capacity ()<<endl; cout<<"***********************" <<endl; v1.erase (v1.begin ()+1 ); printVectorInt (v1); cout<<"size:" <<v1.size ()<<",capacity:" <<v1.capacity ()<<endl; cout<<"***********************" <<endl; v1.clear (); cout<<"#" ; printVectorInt (v1); cout<<"#" <<endl; cout<<"size:" <<v1.size ()<<",capacity:" <<v1.capacity ()<<endl; cout<<"***********************" <<endl; }
运行结果:
三、deque容器(双端动态数组)
3.1 deque容器基本概念
vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间。所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率极差,无法被接受。
deque容器和vector容器最大差异,一在于deque允许使用常数项时间(固定的时间,不会因为数据的多少而变化)对头部进行元素的插入和删除。二在于deque没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新空间并连接起来,换句话说,像vector那样,“旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque身上是不会发生的。也因此,deque没有必须要提供所谓的空间预留(reserve)功能,虽然deque容器也提供了Random Access Iterator(随机访问迭代器),但是它的迭代器并不是普通的指针,其复杂度和vector不是一个量级,这当然影响各个运算的层面。因此,除非有必要,我们应该尽可能的使用vector,而不是deque。对deque进行的排序操作,为了最高效率,可将deque先完整的复制到vector中,对vector容器进行排序,在复制回deque。
3.2 deque容器实现原理
deque容器是连续的空间,至少逻辑上看来如此,连续线性空间总是令我们联想到array和vector,array是无法成长的,vector虽可成长,却只能像尾端成长,而且其成长其实是一个假象,事实上是三大步骤:
1、申请更大空间
2、原数据复制到新空间
3、释放原空间
如果不是vector每次配置新的空间时都留有余裕,其成长所带来的代价是非常大的。deque是由一段一段的定量的连续空间构成的。一旦有必要在deque容器的前端或者尾端增加新的空间,便配置一段连续定量的空间,串联在deque的头端或者尾端。deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机读取的接口,避开了重新配置空间、复制、释放的轮回,代价就是复杂的迭代器架构。既然deque是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。deque代码实现远比vector或list多得多。deque采取一块所谓的map(注意,不是STL的map容器)作为主控,这里所谓的map是一小块连续的内存空间,其中每一个元素(此处称为一个节点)都是一个指针,指向另一端连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。
3.3 deque常用API
3.3.1 deque构造函数
1 2 3 4 deque<T> deqT; deque (beg,end);deque (n,elem);deque (const deque &deq);
3.3.2 deque赋值操作
1 2 3 4 assign (beg,end);assign (n,elem);deque& operator =(const deque &deq); swap (deq);
3.3.3 deque大小操作
1 2 3 4 size ();empty ();resize (int num);resize (int num,elem);
3.3.4 deque双端插入和删除操作
1 2 3 4 push_back (elem);push_front (elem);pop_back (elem);pop_front (elem);
3.3.5 deque数据存取
1 2 3 4 at (idx);operator [];front ();back ();
3.3.6 deque插入操作
1 2 3 insert (pos,elem);insert (pos,n,elem);insert (pos,beg,end);
3.3.7 deque删除操作
1 2 3 clear ();erase (beg,end);erase (pos);
3.4 案例:
有5名选手,选手A、B、C、D、E,10个评委分别对每一个选手打分,去除最高分,去除评委中最低分,取平均分。
创建无名选手,放到vector容器中
遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中
sort算法对deque容器中的分数排序,pop_back pop_front 去除最高和最低分
deque容器遍历一遍,累加分数,累加分数/d.size()
person.score = 平均分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 #include <iostream> #include <string> #include <vector> #include <deque> #include <time.h> #include <algorithm> using namespace std;class Person {private : string name; int score; public : friend void printVectorPerson (vector<Person> &v) ; friend void playGame (vector<Person> &v) ; Person (string name,int score){ this ->name = name; this ->score = score; } }; void createPerson (vector<Person> &v) { string nameTmp="ABCDE" ; for (int i=0 ;i<5 ;i++){ string name = "选手" ; name+=nameTmp[i]; v.push_back (Person (name,0 )); } } void printVectorPerson (vector<Person> &v) { for (vector<Person>::iterator it=v.begin ();it!=v.end ();it++){ cout<<(*it).name<<"," <<(*it).score<<endl; } cout<<endl; } void playGame (vector<Person> &v) { srand (time (NULL )); for (vector<Person>::iterator it=v.begin ();it!=v.end ();it++){ deque<int > d; for (int i=0 ;i<10 ;i++){ d.push_back (rand ()%41 +60 ); } sort (d.begin (),d.end ()); d.pop_back (); d.pop_front (); int sum=accumulate (d.begin (),d.end (),0 ); (*it).score = sum/d.size (); } } int main (int argc, char *argv[]) { vector<Person> v; createPerson (v); playGame (v); printVectorPerson (v); return 0 ; }
运行结果:
四、stack容器(栈特点)
4.1 stack容器基本概念
stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口,形式如图所示。stack容器允许新增元素,移除元素,取得栈顶元素,但是除了最顶端外,没有任何其他方法可以存取stack的其他元素。换言之,stack不允许有遍历行为。有元素推入栈的操作称为:push,将元素退出stack的操作称为pop。top()永远指向栈顶元素。
白画了一张图-_- 提供图片了….
4.2 stack没有迭代器
stack所有元素的进出都必须符合“先进后出”的条件,只有stack顶端的元素才有机会被外界取用。stack不提供遍历功能,也不提供迭代器。
4.3 stack常用API
4.3.1 stack构造函数
1 2 stack<T> stkT; stack (const stack &stk);
4.3.2 stack赋值操作
1 stack& operator =(const stack &stk);
4.3.3 stack数据存取操作
1 2 3 push (elem);pop ();top ();
4.3.4 stack大小操作
案例:打印栈中所有元素
1 2 3 4 5 6 7 8 9 10 11 void test01 () { stack<int > s; s.push (12 ); s.push (32 ); s.push (36 ); s.push (66 ); while (!s.empty ()) { cout<<s.top ()<<endl; s.pop (); } }
五、queue 队列容器
5.1 queue容器基本概念
queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素。
5.2 queue没有迭代器
queue所有元素的进出都必须符合“先进先出”的条件,只有queue的顶端元素,才有机会被外界取用。queue不提供遍历功能,也不提供迭代器。
5.3 queue常用API
5.3.1 queue构造函数
1 2 queue<T> queT; queue (const queue &que);
5.3.2 queue存取、插入和删除操作
1 2 3 4 push (elem);pop ();back ();front ();
5.3.3 queue赋值操作
1 queue& operator =(const queue &que);
5.3.4 queue大小操作
六、list容器(链表)
6.1 list容器的概念
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。链表由一系列节点(链表中每一个元素称为节点)组成,节点可以在运行时动态生成、每个节点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个节点地址的指针域。相较于vector的连续线性空间,list就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或释放一个元素空间,因此,对于任何位置的元素插入或元素的移除,list永远是常数项时间。list和vector是两个最常被使用的容器。list容器是一个双向链表。
采用动态存储分配,不会造成内存浪费和溢出,链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素,链表灵活,但是空间和时间额外耗费较大。
6.2 list容器的迭代器
list容器不能像vector一样以普通指针作为迭代器,因为其节点不能保证在同一块连续的内存空间上,list迭代器必须有能力指向list的节点,并有能力进行正确的递增、递减、取值、成员存取操作。所谓“list递增、递减、取值、成员取用”是指,递增时指向下一个节点、递减时指向上一个节点、取值时取得是节点的数据值、成员取用时取的是节点的成员。由于list是一个双向链表,迭代器必须能够具备前移、后移的能力,所以list容器提供的是Bidirectional Iterator(双向迭代器)。list有一个重要的性质,插入操作和删除操作都不会造成原来的迭代器失效。这在vector是不成立的,因为vector的插入操作可能造成内存的重新配置,导致原有的迭代器全部失效,甚至list元素的删除,也只有被删除的那个元素的迭代器失效,其他迭代器不受影响。
注意:list迭代器不支持+,但是支持++,++被重载了p=p->next;
6.3 list容器的数据结构
list容器不仅是一个双向链表,还是一个循环的双向链表
6.4 list常用API
6.4.1 list构造函数
1 2 3 4 list<T> lstT; list (beg,end);list (n,elem);list (const list &lst);
6.4.2 list数据元素插入和删除操作
1 2 3 4 5 6 7 8 9 10 11 push_back (elem);pop_back ();push_front (elem);pop_front ();insert (pop,elem);insert (pop,n,elem);insert (pos,beg,end);clear ();erase (beg,end);erase (pos);remove (elem);
6.4.3 list大小操作
1 2 3 4 size ();empty ();resize (num);resize (num,elem);
6.4.4 list赋值操作
1 2 3 4 assign (beg,end);assign (n,elem);list& operator =(const list &lst); swap (lst);
6.4.5 list数据的存取
6.4.6 list反转排序
注意:list 不支持系统提供的排序算法sort,系统提供的排序算法sort只支持随机访问迭代器。
6.5 list使用remove()删除自定义数据
如果使用remove删除某个节点必须重载==运算符,因为对于自定义数据类型,是不知道如何进行比较的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Person {private : string name; int age; public : friend void printListPerson (list<Person> &L) ; Person (string name,int age){ this ->name = name; this ->age = age; } bool operator ==(const Person &ob){ if (this ->name == ob.name && this ->age == ob.age) return true ; return false ; } }; void printListPerson (list<Person> &L) { for (list<Person>::iterator it=L.begin ();it!=L.end ();it++){ cout<<(*it).name<<" " <<(*it).age<<endl; } } void test02 () { list<Person> L; L.push_back (Person ("xiaohua" ,18 )); L.push_back (Person ("xiaoming" ,19 )); L.push_back (Person ("lucy" ,23 )); L.push_back (Person ("jack" ,22 )); printListPerson (L); L.remove (Person ("lucy" ,23 )); cout<<"--------------" <<endl; printListPerson (L); }
运行结果:
6.6 list对自定义数据进行sort()排序
1、方法一:必须重载<运算符。因为对于自定义数据,sort不知道如何进行大小比较。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Person {private : string name; int age; public : friend void printListPerson (list<Person> &L) ; Person (string name,int age){ this ->name = name; this ->age = age; } bool operator <(const Person &ob){ return this ->age < ob.age; } }; void printListPerson (list<Person> &L) { for (list<Person>::iterator it=L.begin ();it!=L.end ();it++){ cout<<(*it).name<<" " <<(*it).age<<endl; } } void test03 () { list<Person> L; L.push_back (Person ("xiaohua" ,18 )); L.push_back (Person ("xiaoming" ,19 )); L.push_back (Person ("lucy" ,23 )); L.push_back (Person ("jack" ,22 )); printListPerson (L); L.sort (); cout<<"--------------" <<endl; printListPerson (L); }
运行结果:
2、方法二:指定排序规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Person {private : string name; int age; public : friend void printListPerson (list<Person> &L) ; friend bool myComparePerson (const Person &ob1,const Person &ob2) ; Person (string name,int age){ this ->name = name; this ->age = age; } }; bool myComparePerson (const Person &ob1,const Person &ob2) { return ob1.age < ob2.age; } void printListPerson (list<Person> &L) { for (list<Person>::iterator it=L.begin ();it!=L.end ();it++){ cout<<(*it).name<<" " <<(*it).age<<endl; } } void test04 () { list<Person> L; L.push_back (Person ("xiaohua" ,18 )); L.push_back (Person ("xiaoming" ,19 )); L.push_back (Person ("lucy" ,23 )); L.push_back (Person ("jack" ,22 )); printListPerson (L); L.sort (myComparePerson); cout<<"--------------" <<endl; printListPerson (L); }
vector容器进行自定义数据排序,也是一样的。要么重载运算符,要么指定排序规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Person {private : string name; int age; public : friend bool myComparePerson (const Person &ob1,const Person &ob2) ; friend void printVectorPerson (vector<Person> &v) ; Person (string name,int age){ this ->name = name; this ->age = age; } }; bool myComparePerson (const Person &ob1,const Person &ob2) { return ob1.age < ob2.age; } void printVectorPerson (vector<Person> &v) { for (vector<Person>::iterator it=v.begin ();it!=v.end ();it++){ cout<<(*it).name<<" " <<(*it).age<<endl; } } void test05 () { vector<Person> v; srand (time (NULL )); for (int i=0 ;i<5 ;i++){ string str="ABCDEFGHIJKL" ; string name="成员" ; name += str[i]; v.push_back (Person (name,rand ()%20 +17 )); } printVectorPerson (v); sort (v.begin (),v.end (),myComparePerson); cout<<"--------------" <<endl; printVectorPerson (v); }
运行结果:
6.7 仿函数指定排序规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 class Person {public : string name; int age; public : Person (string name,int age){ this ->name = name; this ->age = age; } }; void printVectorPerson (vector<Person> &v) { for (vector<Person>::iterator it=v.begin ();it!=v.end ();it++){ cout<<(*it).name<<" " <<(*it).age<<endl; } } class myComparePerson1 {public : bool operator () (const Person &ob1,const Person &ob2) { return ob1.age < ob2.age; } }; void test06 () { vector<Person> v; srand (time (NULL )); for (int i=0 ;i<5 ;i++){ string str="ABCDEFGHIJKL" ; string name="成员" ; name += str[i]; v.push_back (Person (name,rand ()%20 +17 )); } printVectorPerson (v); myComparePerson1 ob1; sort (v.begin (),v.end (),ob1); cout<<"--------------" <<endl; printVectorPerson (v); }
运行结果:
6.8 lambda表达式
lambda表达式是一个匿名函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void test07 () { vector<Person> v; srand (time (NULL )); for (int i=0 ;i<5 ;i++){ string str="ABCDEFGHIJKL" ; string name="成员" ; name += str[i]; v.push_back (Person (name,rand ()%20 +17 )); } printVectorPerson (v); sort (v.begin (),v.end (),[](const Person &ob1,const Person &ob2){ return ob1.age < ob2.age; }); cout<<"--------------" <<endl; printVectorPerson (v); }
运行结果:
类似js中,传入匿名函数(即没有名字的函数)作为回调函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function doSomething (args,callback ) { somethingComplicated(args); callback(); } function onEnd ( ) {} doSomething(null , onEnd); doSomething(null ,function ( ) { compute(); });
七、set/multiset容器
7.1 set/multiset容器基本概念
set的特性是,所有元素都会根据元素的键值自动被排序。set的元素不像map那样可以同时拥有实值和键值,set元素即是键值又是实值。set不允许两个元素有相同的键值。我们可以通过set的迭代器改变set元素的值吗?不行,因为set元素值就是其键值,关系到set元素的排序规则。如果任意改变set元素值,会严重破坏set组织。换句话说,set的iterator是一种const_iterator。set拥有和list某些相同的性质,当对容器中的元素进行插入操作或者删除操作的时候,操作之前所有的迭代器,在操作完成之后依然有效,被删除的那个元素的迭代器必然是一个例外。
multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。set和multiset的底层实现是红黑树,红黑树为平衡二叉树的一种。
树的简单知识:二叉树就是任何节点最多只允许有两个子节点,分别是左子节点和右子节点。
set容器的键值不允许相同;set容器提供的迭代器是只读迭代器(不允许修改元素的值)
7.2 set容器常用API
7.2.1 set构造函数
1 2 3 set<T> st; multiset<T> mst; set (const set &st);
7.2.2 set赋值操作
1 2 set& operator =(const set &st); swap (st);
7.2.3 set大小操作
7.2.4 set插入和删除操作
1 2 3 4 5 insert (elem);clear ();erase (pos);erase (beg,end);erase (elem);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 void test08 () { set<int > s; srand (time (NULL )); for (int i=0 ;i<5 ;i++){ s.insert (rand ()%101 ); } s.insert (59 ); for_each(s.begin (),s.end (),[](int val){ cout<<val<<" " ; }); cout<<endl; s.erase (s.begin ()); for_each(s.begin (),s.end (),[](int val){ cout<<val<<" " ; }); cout<<endl; s.erase (59 ); for_each(s.begin (),s.end (),[](int val){ cout<<val<<" " ; }); cout<<endl; }
运行结果:
7.2.5 set查找操作
1 2 3 4 5 find (key);count (key);lower_bound (keyElem);upper_bound (keyElem);equal_range (keyElem);
end();指向最后一个元素的下一个位置
案例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void test09 () { set<int > s; srand (time (NULL )); for (int i=0 ;i<5 ;i++){ s.insert (rand ()%101 ); } s.insert (59 ); for_each(s.begin (),s.end (),[](int val){ cout<<val<<" " ; }); cout<<endl; set<int >::iterator ret = s.find (59 ); if (ret == s.end ()){ cout<<"没有查到" <<endl; }else { cout<<"查到元素:" <<*ret<<endl; } if (s.find (100 ) == s.end ()){ cout<<"没有查到" <<endl; }else { cout<<"查到元素:" <<*(s.find (100 ))<<endl; } cout<<"count(59)=" <<s.count (59 )<<endl; cout<<"count(100)=" <<s.count (100 )<<endl; }
运行结果:
案例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 void test10 () { set<int > s; s.insert (20 ); s.insert (10 ); s.insert (40 ); s.insert (60 ); for_each(s.begin (),s.end (),[](int val){ cout<<val<<" " ; }); cout<<endl; cout<<"*******20********" <<endl; set<int >::const_iterator ret1 = s.lower_bound (20 ); if (ret1 == s.end ()){ cout<<"没有查到下限" <<endl; }else { cout<<"查到下限:" <<*ret1<<endl; } set<int >::const_iterator ret2 = s.upper_bound (20 ); if (ret2 == s.end ()){ cout<<"没有查到上限" <<endl; }else { cout<<"查到上限:" <<*ret2<<endl; } cout<<"*******25********" <<endl; ret1 = s.lower_bound (25 ); if (ret1 == s.end ()){ cout<<"没有查到下限" <<endl; }else { cout<<"查到下限:" <<*ret1<<endl; } ret2 = s.upper_bound (25 ); if (ret2 == s.end ()){ cout<<"没有查到上限" <<endl; }else { cout<<"查到上限:" <<*ret2<<endl; } cout<<"*******60********" <<endl; ret1 = s.lower_bound (60 ); if (ret1 == s.end ()){ cout<<"没有查到下限" <<endl; }else { cout<<"查到下限:" <<*ret1<<endl; } ret2 = s.upper_bound (60 ); if (ret2 == s.end ()){ cout<<"没有查到上限" <<endl; }else { cout<<"查到上限:" <<*ret2<<endl; } cout<<"*******80********" <<endl; ret1 = s.lower_bound (80 ); if (ret1 == s.end ()){ cout<<"没有查到下限" <<endl; }else { cout<<"查到下限:" <<*ret1<<endl; } ret2 = s.upper_bound (80 ); if (ret2 == s.end ()){ cout<<"没有查到上限" <<endl; }else { cout<<"查到上限:" <<*ret2<<endl; } }
运行结果:
案例3: equal_range()
存在返回上下限,对组pair<set::const_iterator,set::const_iterator>
不存在返回end();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 void test11 () { set<int > s; s.insert (20 ); s.insert (10 ); s.insert (40 ); s.insert (60 ); for_each(s.begin (),s.end (),[](int val){ cout<<val<<" " ; }); cout<<endl; cout<<"*******20********" <<endl; pair<set<int >::const_iterator,set<int >::const_iterator> ret = s.equal_range (20 ); if (ret.first == s.end ()){ cout<<"没有查到下限" <<endl; }else { cout<<"查到下限:" <<*ret.first<<endl; } if (ret.second == s.end ()){ cout<<"没有查到上限" <<endl; }else { cout<<"查到上限:" <<*ret.second<<endl; } cout<<"*******25********" <<endl; ret = s.equal_range (25 ); if (ret.first == s.end ()){ cout<<"没有查到下限" <<endl; }else { cout<<"查到下限:" <<*ret.first<<endl; } if (ret.second == s.end ()){ cout<<"没有查到上限" <<endl; }else { cout<<"查到上限:" <<*ret.second<<endl; } cout<<"*******60********" <<endl; ret = s.equal_range (60 ); if (ret.first == s.end ()){ cout<<"没有查到下限" <<endl; }else { cout<<"查到下限:" <<*ret.first<<endl; } if (ret.second == s.end ()){ cout<<"没有查到上限" <<endl; }else { cout<<"查到上限:" <<*ret.second<<endl; } cout<<"*******80********" <<endl; ret = s.equal_range (80 ); if (ret.first == s.end ()){ cout<<"没有查到下限" <<endl; }else { cout<<"查到下限:" <<*ret.first<<endl; } if (ret.second == s.end ()){ cout<<"没有查到上限" <<endl; }else { cout<<"查到上限:" <<*ret.second<<endl; } }
运行结果:
7.3 对组(pair)
对组将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用pair的两个公有属性first和second访问。
类模板:template<class T1,class T2> struct pair;
如何创建对组?
1 2 3 4 5 6 pair<string,int > pair (string("name" ),20 ) ;pair<string,int > pair = make_pair ("name" ,30 ); pair<string,int > pair3 = pair2;
7.4 仿函数修改set容器排序规则
因为set<>中<>只能写类型,所以只能使用仿函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class mySort {public : bool operator () (const int &a,const int &b) { return a>b; } }; void test12 () { set<int ,mySort> s; srand (time (NULL )); for (int i=0 ;i<7 ;i++){ s.insert (rand ()%101 ); } for_each(s.begin (),s.end (),[](int val){ cout<<val<<" " ; }); cout<<endl; }
运行结果:
7.5 set容器存放自定义数据
1、必须指定排序规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class mySortPerson {public : bool operator () (const Person &ob1,const Person &ob2) { return ob1.age > ob2.age; } }; void test13 () { set<Person,mySortPerson> s; s.insert (Person ("xiaohua" ,18 )); s.insert (Person ("xiaoming" ,23 )); s.insert (Person ("jack" ,20 )); s.insert (Person ("lucy" ,25 )); s.insert (Person ("bob" ,19 )); for_each(s.begin (),s.end (),[](const Person &ob){ cout<<"name:" <<ob.name<<",age:" <<ob.age<<endl; }); cout<<endl; }
运行结果:
2、重载<运算符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Person {public : string name; int age; public : Person (string name,int age){ this ->name = name; this ->age = age; } bool operator <(const Person &ob)const { return this ->age < ob.age; } }; void test14 () { set<Person> s; s.insert (Person ("xiaohua" ,18 )); s.insert (Person ("xiaoming" ,23 )); s.insert (Person ("jack" ,20 )); s.insert (Person ("lucy" ,25 )); s.insert (Person ("bob" ,19 )); for_each(s.begin (),s.end (),[](const Person &ob){ cout<<"name:" <<ob.name<<",age:" <<ob.age<<endl; }); cout<<endl; }
运行结果:
7.6 multiset允许存放重复的键值
1、set容器不允许插入重复的键值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void test15 () { set<int > s; pair<set<int >::const_iterator,bool > ret1,ret2; ret1 = s.insert (10 ); if (ret1.second == true ){ cout<<"第一次插入成功" <<endl; }else { cout<<"第一次插入失败" <<endl; } ret2= s.insert (10 ); if (ret2.second == true ){ cout<<"第二次插入成功" <<endl; }else { cout<<"第二次插入失败" <<endl; } for_each(s.begin (),s.end (),[](const int val){ cout<<val<<" " ; }); cout<<endl; }
运行结果:
分析:第一次插入10成功第二次插入10失败,最后遍历出来也只有一个10;
2、multiset容器允许插入重复的键值
1 2 3 4 5 6 7 8 9 10 void test16 () { multiset<int > s; s.insert (10 ); s.insert (10 ); s.insert (10 ); for_each(s.begin (),s.end (),[](const int val){ cout<<val<<" " ; }); cout<<endl; }
运行结果:
八、map/multimap容器
8.1 map/multimap容器基本概念
map的特性是,所有元素都会根据元素的键值自动排序。map所有的元素都是pair,同时拥有实值和键值,pair的第一元素被视为键值,第二元素被视为实值,map不允许两个元素有相同的键值。我们可以通过map的迭代器改变map的键值吗?答案是不行的,因为map的键值关系到map元素的排序规则,任意改变map键值将会严重破坏map组织。如果要修改元素的实值,那么是可以的。map和list拥有相同的某些性质,当对它的容器进行新增操作或者删除操作时,操作之前的所有迭代器在操作完成之后依然有效,当然被删除的那个元素的迭代器必然是个例外。multimap和map的操作类似,唯一区别multimap键值可以重复。
map和multimap都是以红黑树为底层实现机制。
总结:
所有元素都会根据元素的键值自动排序
所有元素都是pair,同时拥有实值和键值,第一个参数是键值,第二个元素是实值。
map容器的键值唯一不可变,实值可以变
迭代器是只读迭代器
8.2 map容器常用API
8.2.1 map构造函数
1 2 map<T1,T2> mapTT; map (const map &mp);
8.2.1 map赋值操作
1 2 map& operator =(const map &mp); swap (mp);
8.2.2 map大小操作
8.2.3 map插入数据元素操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 map.insert (); map<int ,string> mapStu; mapStu.insert (pair<int ,string>(3 ,"lucy" )); mapStu.insert (make_pair (5 ,"bob" )); mapStu.insert (map<int ,string>::value_type (4 ,"jack" )); mapStu[6 ] = "mick" ;
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void test01 () { map<int ,string> m; m.insert (pair<int ,string>(1 ,"小明" )); m.insert (make_pair (2 ,"小花" )); m.insert (map<int ,string>::value_type (3 ,"lucy" )); m[4 ]="bob" ; for_each(m.begin (),m.end (),[](const pair<int ,string> &ob){ cout<<ob.first<<" " <<ob.second<<endl; }); }
运行结果:
8.2.4 map删除操作
1 2 3 4 clear ();erase (pos);erase (beg,end);erase (keyElem);
8.2.5 map查找操作
1 2 3 4 5 find (key);count (keyElem);lower_bound (keyElem);upper_bound (keyElem);equal_range (keyElem);
8.3 map和vector容器配合使用
四个队伍参加比赛,随机产生出场顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void test02 () { map<int ,string> m;; m.insert (make_pair (1 ,"队伍A" )); m.insert (make_pair (2 ,"队伍B" )); m.insert (make_pair (3 ,"队伍C" )); m.insert (make_pair (4 ,"队伍D" )); vector<int > v; v.push_back (1 ); v.push_back (2 ); v.push_back (3 ); v.push_back (4 ); srand (time (NULL )); random_shuffle (v.begin (),v.end ()); cout<<"出场顺序:" ; for (vector<int >::iterator it=v.begin ();it!=v.end ();it++){ cout<<m[*it]<<" " ; } cout<<endl; cout<<"出场顺序:" ; for_each(v.begin (),v.end (),[&](int val){ cout<<m[val]<<" " ; }); cout<<endl; }
8.4 multimap允许相同的键值
1 2 3 4 5 6 7 8 9 10 void test03 () { multimap<int ,string> mlmp; mlmp.insert (make_pair (1 ,"lucy" )); mlmp.insert (make_pair (1 ,"bob" )); mlmp.insert (make_pair (1 ,"mick" )); for_each(mlmp.begin (),mlmp.end (),[](const pair<int ,string> &ob){ cout<<ob.first<<" " <<ob.second<<endl; }); }
运行结果:
8.4 五个员工加入三个不同部门
公司招聘了5名员工,5名员工进入公司之后,需要指派员工在哪个部门工作,人员信息有:姓名、年龄、电话等组成,通过multimap进行信息的插入保存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 class Person {public : string name; int age; long int tel; public : Person (string name,int age,long int tel){ this ->name = name; this ->age = age; this ->tel = tel; } }; void test04 () { vector<Person> v; v.push_back (Person ("员工A" ,18 ,1234567 )); v.push_back (Person ("员工B" ,19 ,1124252 )); v.push_back (Person ("员工C" ,21 ,4231455 )); v.push_back (Person ("员工D" ,20 ,4213584 )); v.push_back (Person ("员工E" ,25 ,3672548 )); multimap<int ,Person> mlmp; cout<<"1:编辑部 2:研究部 3:售后部" <<endl; for (vector<Person>::iterator it=v.begin ();it!=v.end ();it++){ int n=0 ; cout<<"请输入" <<(*it).name<<"要指派的部门:" ; cin>>n; mlmp.insert (make_pair (n,*it)); } for_each(mlmp.begin (),mlmp.end (),[](const pair<int ,Person> &ob){ cout<<ob.first<<" " <<ob.second.name<<" " <<ob.second.age<<" " \ <<ob.second.tel<<endl; }); cout<<"请输入要查询的部门:" <<endl; int n1=0 ; cin>>n1; int count=mlmp.count (n1); multimap<int ,Person>::const_iterator ret = mlmp.find (n1); for (int i=0 ;i<count;i++,ret++){ cout<<(*ret).first<<" " <<(*ret).second.name<<" " <<(*ret).second.age<<" " \ <<(*ret).second.tel<<endl; } }
运行结果:
九、STL容器使用时机
vector容器的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次记录,上上次记录,但却不会去删除记录,因为记录是事实的描述。
deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如采用vector,则头端移除时,会移动大量的数据,速度慢。
vector和deque的比较:
vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却不是固定的。
如果有大量释放操作的话,vector花的时间更少,这跟两者的内部实现有关
deque支持头部的快速插入和快速移除,这是deque的优点。
list使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置的移除和插入。
set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
map的使用场景:比如按ID号存储十万个用户,想要快速通过ID查找对应的用户。二叉树的查找效率,这时候就体现出来了。如果是vector容器,最坏情况下可能要遍历完整个容器才能找到该用户。
于2021.1.27 22:50 写完