STL(Standard Template Library),即标准模板库,是一个具有工业强度的,高效的C++程序库。它被容纳于C++标准程序库(C++ Standard Library)中,是ANSI/ISO C++标准中最新的也是极具革命性的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性,这种现象有些类似于Microsoft Visual C++中的MFC(Microsoft Foundation Class Library)。STL的一个重要特点是数据结构和算法的分离,另一个重要特性是它不是面向对象的。
STL六大组件:容器(Container)、算法(Algorithm)、迭代器(Iterator)、仿函数(Function object)、适配器(Adaptor)、空间配置器(allocator)
迭代器提供了访问容器中对象的方法迭代器就如同一个指针。事实上,C++的指针也是一种迭代器。但是,迭代器不仅仅是指针,因此你不能认为他们一定具有地址值。例如,一个数组索引,也可以认为是一种迭代器。迭代器有各种不同的创建方法。程序可能把迭代器作为一个变量创建。一个STL容器类可能为了使用一个特定类型的数据而创建一个迭代器。作为指针,必须能够使用*操作符类获取数据。你还可以使用其他数学操作符如++。典型的,++操作符用来递增迭代器,以访问容器中的下一个对象。如果迭代器到达了容器中的最后一个元素的后面,则迭代器变成past-the-end值。使用一个past-the-end值得指针来访问对象是非法的,就好像使用NULL或为初始化的指针一样。
迭代器的声明与赋值:
vector<int>::iterator first;
first = intVector.begin();
*first = 123;
例如:用迭代器访问向量中的元素
vector<int> s;
for (int i=0; i<10; i++) s.push_back(i);
for (vector<int>::iterator it = s.begin(); it != s.end(); it++)
cout << *it << " ";
cout<<endl;
vector的begin()和end()方法都会返回一个vector::iterator对象,分别指向vector的首元素位置和尾元素的下一个位置(我们可以称之为结束标志位置)。
容器是一种数据结构,提供对一个容器中的对象的访问方法,并且定义了容器中对象的范围。
1.向量(vector)
头文件:#include <vector>
常用的定义:
定义一个空的vector对象,存储的是int类型的元素:vector<int> s;
定义一个含有n个int元素的vector对象:
定义一个vector对象,并从由迭代器first和last定义的序列[first, last)中复制初值:vector<int> s(first, last);
vector的基本操作有:
直接以下标方式访问容器中的元素:
返回首元素:s.front()
返回尾元素:
向表尾插入元素x:
返回表长:
当表空时,返回真,否则返回假:s.empty()
删除表尾元素:
返回指向首元素的随机存取迭代器:
返回指向尾元素的下一个位置的随机存取迭代器:s.end()
向迭代器it指向的元素前插入新元素val:
向迭代器it指向的元素前插入n个x:
将由迭代器first和last所指定的序列[first, last)插入到迭代器it指向的元素前面:s.insert(it, first, last)
删除由迭代器first和last所指定的序列[first, last):s.erase(first, last)
删除由迭代器it所指向的元素: s.erase(it)
预分配缓冲空间,使存储空间至少可容纳n个元素 : s.reserve(n)
改变序列的长度,超出的元素将会被删除,如果序列需要扩展(原空间小于n),元素默认值将填满扩展出的空间:
改变序列的长度,超出的元素将会被删除,如果序列需要扩展(原空间小于n),将用val填满扩展出的空间:s.resize(n, val)
删除容器中的所有的元素:
将s与另一个vector对象v进行交换:s.swap(v)
要注意的是,resize操作和clear操作都是对表的有效元素进行的操作,但并不一定会改变缓冲空间的大小。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> L;
int x;
while (cin>>x) L.push_back(x);
for (int i=L.size()-1; i>=0; i--)
cout << L[i] << " ";
cout << endl;
return 0;
}
2.映象(map)、多重映象(multimap)
头文件:#include<map>
介绍:用有序二叉树来存贮类型为pair<const Key, T>的元素对序列。序列中的元素以const Key部分作为标识,map中所有元素的Key值都必须是唯一的,multimap则允许有重复的Key值。可以将map看作是由Key标识元素的元素集合,这类容器也被称为“关联容器”,可以通过一个Key值来快速确定一个元素,因此非常适合于需要按照Key值查找元素的容器。map模板类需要四个模板参数,第一个是键值类型,第二个是元素类型,第三个是比较算子,第四个是分配器类型。其中键值类型和元素类型是必要的。
map的基本操作有:
定义map对象,例如:map<string, int> m;
向map中插入元素对,有多种方法,例如:m[key] = value;
[key]操作是map很有特色的操作,如果在map中存在键值为key的元素对,则返回该元素对的值域部分,否则将会创建一个键值为key的元素对,值域为默认值。所以可以用该操作向map中插入元素对或修改已经存在的元素对的值域部分。
也可以直接调用insert方法插入元素对,insert操作会返回一个pair,当map中没有与key相匹配的键值时,其first是指向插入元素对的迭代器,其second为true;若map中已经存在与key相等的键值时,其first是指向该元素对的迭代器,second为false。
查找元素对,例如:int i = m[key];
要注意的是,当与该键值相匹配的元素对不存在时,会创建键值为key的元素对。
map<string, int>::iterator it = m.find(key);
如果map中存在与key相匹配的键值时,find操作将返回指向该元素对的迭代器,否则,返回的迭代器等于map的end()(参见vector中提到的begin和end操作)。
删除元素对,例如:m.erase(key);
删除与指定key键值相匹配的元素对,并返回被删除的元素的个数。
m.erase(it);
删除由迭代器it所指定的元素对,并返回指向下一个元素对的迭代器。
#include <map>
using namespace std;
int main()
{
map<string, int> m;
m["one"] = 1;
m["two"] = 2;
// 几种不同的insert调用方法
m.insert(make_pair("three", 3));
m.insert(map<string, int>::value_type("four", 4));
m.insert(pair<string, int>("five", 5));
string key;
while (cin>>key)
{
map<string, int>::iterator it = m.find(key);
if (it == m.end())
{
cout << "No such key!" << endl;
}
else
{
cout<< key << " is " << it->second << endl;
cout<< "Erased " << m.erase(key) << endl;
}
}
return 0;
}
map练习题目:魔咒词典
3.集合(set)、多重集合(multiset)set集合容器实现了红黑树的平衡二叉检索树的数据结构,在插入元素时,它会自动调整二叉树的排列,把该元素放到适当的位置,以确保每个子树根节点的键值大于左子树所有节点的键值,而小于右子树所有节点的键值;另外,还得确保根节点左子树的高度与右子树的高度相等,因为二叉树高度最小,检索速度最快。这里要注意的是,set容器不会插入相同键值的元素。平衡二叉检索树的检索使用中序遍历算法,检索效率高于vector、deque和list等容器。另外,采用中序遍历算法可将键值由小到大遍历出来,所以,可以理解为平衡二叉检索树在插入元素时,就会自动将元素按键值由小到大的顺序排列。由于构造set集合的主要目的就是为了快速检索,对于set容器中的键值,不可直接去修改。multiset、map和multimap的内部结构也是平衡二叉检索树
定义:set<int> s1;
基本操作:扫描整一个set所有的元素,用迭代器
for (set<int>::iterator i=s1.begin(); i!=s1.end(); i++)
cout<<(*i)<<endl;
元素插入:(常用)insert,插入value,返回pair配对对象,可以根据.second判断是否插入成功。(提示:value不能与set容器内元素重复)
s1.insert(2);//插入2
元素删除:1,(常用)size_type erase(value) 移除set容器内元素值为value的所有元素,返回移除的元素个数
void erase(&pos) 移除pos位置上的元素,无返回值
void erase(&first, &last) 移除迭代区间[&first, &last)内的元素,无返回值
void clear(), 移除set容器内所有元素
s1.erase(2); //删除2元素查找(常用)iterator find(value)返回value所在位置,找不到value将返回end()
count(value)返回set对象内元素值为value的元素个数
if (s1.find(2)!=s1.end()) cout<<"get!"<<endl; //找到2时输出get!
multiset与set一样,也是使用红黑树来组织元素数据的,唯一不同的是,multiset允许重复的元素键值插入,而set则不允许。multiset也需要声明头文件包含“#include <set>”,由于它包含重复元素,所以在插入元素、删除元素、查找元素上较set有差别。
4.队列(queue)、双端队列(deque)、优先队列(priority_queue)
queue队列容器是一个先进先出的线性存储表,元素的插入只能在队尾,元素的删除只能在队首。使用queue需要声明头文件包含语句“#include <queue>” 。
与stack模板类很相似,queue模板类也需要两个模板参数,一个是元素类型,一个容器类型,元素类型是必要的,容器类型是可选的,默认为deque类型。
定义queue对象:
queue<int> q1;
queue<double> q2;
queue的基本操作有:
入队,如例:q.push(x); 将x接到队列的末端。
出队,如例:q.pop(); 弹出队列的第一个元素,注意,并不会返回被弹出元素的值。
访问队首元素,如例:q.front(),即最早被压入队列的元素。
访问队尾元素,如例:q.back(),即最后被压入队列的元素。
判断队列空,如例:q.empty(),当队列空时,返回true。
访问队列中的元素个数,如例:q.size()
deque双端队列容器与vector一样,采用线性表顺序存储结构。但与vector唯一不同的是,deque采用分块的线性存储结构来存储数据,每块的大小一般为512字节,称为一个deque块,所有的deque块使用一个Map块进行管理,每个Map数据项纪录每个deque块的首地址。这样,deque块在头部和尾部都可插入和删除元素,而不需移动其他元素。一般来说,当考虑到容器元素的内存分配策略和操作的性能时,deque相对于vector会更有优势。使用deque需要声明头文件包含“#include <deque>”。
基本操作
q.begin() 开头迭代器
q.end() 队尾迭代器,依旧是左闭右开,所以这个是空的
q.push_back(v) 在队尾加入v
q.push_front(v) 在队头加入v
q.front() 查询队头元素
q.back() 查询队尾元素
q.pop_front() 删掉队头元素
q.pop_back() 删掉队尾元素
q.begin() 未必和静态数组一样,不是说你在前面插了个元素q.push_front(v),之后q.begin()就往左移一个(--)了,它指不定跑哪儿去了,所以要重新取
在<queue>头文件中,还定义了另一个非常有用的模板类priority_queue(优先队列)。优先队列与队列的差别在于优先队列不是按照入队的顺序出队,而是按照队列中元素的优先权顺序出队(默认为大者优先,也可以通过指定算子来指定自己的优先顺序)。
priority_queue模板类有三个模板参数,第一个是元素类型,第二个容器类型,第三个是比较算子。其中后两个都可以省略,默认容器为vector,默认算子为less,即小的往前排,大的往后排(出队时序列尾的元素出队)。
定义priority_queue对象:
priority_queue<int> q1;
priority_queue< pair<int, int> > q2; // 注意在两个尖括号之间一定要留空格。
priority_queue<int, vector<int>, greater<int> > q3; // 定义小的先出队
priority_queue的基本操作与queue相同。
empty() 如果队列为空返回真
pop() 删除对顶元素
push() 加入一个元素
size() 返回优先队列中拥有的元素个数
top() 返回优先队列对顶元素
在默认的优先队列中,优先级高的先出队。在默认的int型中先出队的为较大的数。
如果是基本数据类型,或已定义了比较运算符的类,可以直接用STL的less算子和greater算子——默认为使用less算子,即小的往前排,大的先出队。
如果要定义自己的比较算子,方法有多种,这里介绍其中的一种:重载比较运算符。优先队列试图将两个元素x和y代入比较运算符(对less算子,调用 x<y,对greater算子,调用x>y),若结果为真,则x排在y前面,y将先于x出队,反之,则将y排在x前面,x将先出队。
优先队列练习题目:Windows Message Queue
5.栈(stack)
stack堆栈是一个后进先出的线性表,插入和删除元素都只能在表的一端进行。插入元素的一端称为栈顶,另一端称为栈底。插入元素叫入栈,元素的删除称为出栈。要使用stack必须声明头文件包含语句“#include <stack>”。
定义stack对象:
stack<int> s1;
stack<string> s2;
stack的基本操作有:
入栈,如例:s.push(x);
出栈,如例:s.pop();注意,出栈操作只是删除栈顶元素,并不返回该元素。
访问栈顶,如例:s.top()
判断栈空,如例:s.empty(),当栈空时,返回true。
访问栈中的元素个数,如例:s.size()
6.字符串(string)
C++ STL提供了string基本字符序列容器来处理字符串,可以把string理解为字符串类,它提供了添加、删除、替换、查找和比较等丰富方法。使用string容器需要包含头文件声明“#include <string>”。
基本操作:
s.empty() 判断是否为空,bool型
s.size() 或 s.length() 返回字符的个数
s[n] 返回位置为n的字符,从0开始计数
s1+s2 连接
7.双向链表(list)
list容器实现了双向链表的数据结构,数据元素是通过链表指针串连成逻辑意义上的线性表,这样,对链表的任一位置的元素进行插入、删除和查找都是极快速的。由于list对象的节点并不要求在一段连续的内存中,所以对于迭代器,只能通过“++”或“--”的操作将迭代器移动到后继/前驱节点元素处。而不能对迭代器进行+n或-n的操作,这点是与vector等不同的地方。使用list需要声明头文件包含“#include <list>”。
使用方法:list myList;
list.push_back(val);在后面加入元素
list.push_front(vla);在前面加入元素
list.begin();返回头指针
list.end();返回尾部的指针,但是不指向元素,需要往前挪一个位置才能指向最后面的元素
#include <stdio.h>
#include <list>
#include <string.h>
using namespace std;
int main()
{
int n,m,a[10005],b[10005],i,j;
while(~scanf("%d%d",&n,&m))
{
for(i = 0; i<n; i++)
scanf("%d",&a[i]);
for(i = 0; i<m; i++)
scanf("%d",&b[i]);
list<int> la;
list<int> lb;
for(i = 0; i<n; i++)
la.push_back(a[i]);
for(i = 0; i<m; i++)
lb.push_back(b[i]);
la.merge(lb);
la.sort();
la.unique();
int cnt = 0;
while(!la.empty())
{
if(!cnt)
printf("%d",la.front());
else
printf(" %d",la.front());
cnt++;
la.pop_front();
}
printf("\n");
}
return 0;
}
算法是用来操作容器中的数据的模板函数,函数本身与他们操作的数据的结构和类型无关,因此他们可以在从简单数组到高度复杂容器的任何数据结构上使用。
常用算法:
1.min_element/max_element,找出容器中的最小/最大值
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
vector<int> L;
for (int i=0; i<10; i++)
L.push_back(i);
vector<int>::iterator min_it = min_element(L.begin(), L.end());
vector<int>::iterator max_it = max_element(L.begin(), L.end());
cout << "Min is " << *min_it << endl;
cout << "Max is " << *max_it << endl;
return 0;
}
2.sort对容器进行排序
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void Print(vector<int> &L)
{
for (vector<int>::iterator it=L.begin(); it!=L.end(); it++)
cout << *it << " ";
cout << endl;
}
int main()
{
vector<int> L;
for (int i=0; i<5; i++)
L.push_back(i);
for (int i=9; i>=5; i--)
L.push_back(i);
Print(L);
sort(L.begin(), L.end());
Print(L);
sort(L.begin(), L.end(), greater<int>()); // 按降序排序
Print(L);
return 0;
}
3.copy在容器间复制元素
#include <algorithm>
#include <iostream>
using namespace std;
int main( )
{
// 先初始化两个向量v1和v2
vector <int> v1, v2;
for (int i=0; i<=5; i++)
v1.push_back(10*i);
for (int i=0; i<=10; i++)
v2.push_back(3*i);
cout << "v1 = ( " ;
for (vector <int>::iterator it=v1.begin(); it!=v1.end(); it++)
cout << *it << " ";
cout << ")" << endl;
cout << "v2 = ( " ;
for (vector <int>::iterator it=v2.begin(); it!=v2.end(); it++)
cout << *it << " ";
cout << ")" << endl;
// 将v1的前三个元素复制到v2的中间
copy(v1.begin(), v1.begin()+3, v2.begin()+4);
cout << "v2 with v1 insert = ( " ;
for (vector <int>::iterator it=v2.begin(); it!=v2.end(); it++)
cout << *it << " ";
cout << ")" << endl;
//在v2内部进行复制,注意参数2表示结束位置,结束位置不参与复制
copy(v2.begin()+4, v2.begin()+7, v2.begin()+2);
cout << "v2 with shifted insert = ( " ;
for (vector <int>::iterator it=v2.begin(); it!=v2.end(); it++)
cout << *it << " ";
cout << ")" << endl;
return 1;
}
4.全排列函数next_permutation
5.返回一个迭代器,指向键值>= key的第一个元素:lower_bound(int *array, int size, int key)
6.返回一个迭代器,指向键值> key的第一个元素:upper_bound(int *array, int size, int key)