一、概述
- STL是什么
- 为什么需要学习STL
- 初识STL
- STL的组成
二、容器
三、算法
概述
1 #include <iostream> 2 int main(void){ 3 double a[] = {1, 2, 3, 4, 5}; 4 std::cout<<mean(a, 5)<<std::endl; // will print 3 5 return 0; 6 }
除了那个std有点让人不舒服以外,这是一段普通的没有使用STL的C++代码。再来看下一段,
1 #include <vector> 2 #include <iostream> 3 int main(){ 4 std::vector<double> a; 5 a.push_back(1); 6 a.push_back(2); 7 a.push_back(3); 8 a.push_back(4); 9 a.push_back(5); 10 for(int i = 0; i < a.size(); ++i){ 11 std::cout<<a[i]<<std::endl; 12 } 13 return 0; 14 }
如果你真的没有接触过STL的话,你会问,呀,vector 是啥呀?这是一段纯种的STL代码,看到尖括号<double>了吧,知道那是模板了吧。看到a.push_back(5)、a.size()你不感觉奇怪么?可是我们并没有定义这些函数啊。再继续看下面的代码
1 #include <vector>
2 #include <iostream>
3 int main(){
4 std::vector< int > q;
5 q.push_back(10);
6 q.push_back(11);
7 q.push_back(12);
8 std::vector< int > v;
9 for(int i=0; i<5; ++i){
10 v.push_back(i);
11 }
12 std::vector<int>::iterator it = v.begin() + 1;
13 it = v.insert(it, 33);
14 v.insert(it, q.begin(), q.end());
15 it = v.begin() + 3;
16 v.insert(it, 3, -1);
17 it = v.begin() + 4;
18 v.erase(it);
19 it = v.begin() + 1;
20 v.erase(it, it + 4);
21 v.clear();
22 return 0;
23 }
这一段你又看到了新东西了吧:iterator、insert、erase、clear。关于模板的其他细节,读者可以参阅《C++ Templates 中文版》。在这里,仅简单地介绍一下模板类和函数模板的概念。模板是C++中实现代码重用机制的一种工具,可以实现类型参数化,把类型定义为参数。函数模板和类模板允许用户构造模板函数和模板类。下面再来看一段函数模板的例子:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 //定义函数模板 5 template<class T> //template 是关键字,T 表示一种待实例化的类型 6 //template<typename T> 也是对的 7 T MAX(T a, T b){ //函数模板,函数名为 max,此函数有2个T类型的参数,返回类型为T 8 return (a>b)?a:b; 9 } 10 11 //在此例实例化的时候,T可以是多种类型的,int,char,string… 12 int main(){ 13 int x=2,y=6; 14 double x1=9.123,y1=12.6543; 15 cout<<"把T实例化为int:"<<MAX(x,y)<<endl; //实例化函数模板,把T实例化为int 16 cout<<"把T实例化为double:"<<MAX(x1,y1)<<endl; //把T实例化为double 17 }
注意:在此例实例化的时候,T 可以是多种类型的,如int,char,string等。
- 容器:装东西的东西,装水的杯子,装咸水的大海,装人的教室……STL里的容器是可容纳一些数据的模板类。
- 算法:就是往杯子里倒水,往大海里排污,从教室里撵人……STL里的算法,就是处理容器里面数据的方法、操作。
- 迭代器:往杯子里倒水的水壶,排污的管道,撵人的那个物业管理人员……STL里的迭代器:遍历容器中数据的对象。对存储于容器中的数据进行处理时,迭代器能从一个成员移向另一个成员。他能按预先定义的顺序在某些容器中的成员间移动。对普通的一维数组、向量、双端队列和列表来说,迭代器是一种指针。
- 容器(container):容器是数据在内存中组织的方法,例如,数组、堆栈、队列、链表或二叉树(不过这些都不是STL标准容器)。STL中的容器是一种存储T(Template)类型值的有限集合的数据结构,容器的内部实现一般是类。这些值可以是对象本身,如果数据类型T代表的是Class的话。
- 算法(algorithm):算法是应用在容器上以各种方法处理其内容的行为或功能。例如,有对容器内容排序、复制、检索和合并的算法。在STL中,算法是由模板函数表现的。这些函数不是容器类的成员函数。相反,它们是独立的函数。令人吃惊的特点之一就是其算法如此通用。不仅可以将其用于STL容器,而且可以用于普通的C++数组或任何其他应用程序指定的容器。
- 迭代器(iterator):一旦选定一种容器类型和数据行为(算法),那么剩下唯一要他做的就是用迭代器使其相互作用。可以把达代器看作一个指向容器中元素的普通指针。可以如递增一个指针那样递增迭代器,使其依次指向容器中每一个后继的元素。迭代器是STL的一个关键部分,因为它将算法和容器连在一起。
2.1 向量(vector)
向量(vector容器类):#include <vector>,vector是一种动态数组,是基本数组的类模板。其内部定义了很多基本操作。既然这是一个类,那么它就会有自己的构造函数和方法。具体如何使用请参照C PLus Plus。如果对于Vector有一些不明白可以参考这里。
下面关于构造函数的演示:
#include <cstring> #include <vector> #include <iostream> using namespace std; int ar[10] = { 12, 45, 234, 64, 12, 35, 63, 23, 12, 55 }; char* str = "Hello World"; int main() { vector <int> vec1(ar, ar+10); //first=ar,last=ar+10,不包括ar+10 vector < char > vec2(str,str+strlen(str)); //first=str,last= str+strlen(str), cout<<"vec1:"<<endl; //打印vec1和vec2,const_iterator是迭代器,后面会讲到 //当然,也可以用for (int i=0; i<vec1.size(); i++)cout << vec[i];输出 //size()是vector的一个成员函数 for(vector<int>::const_iterator p=vec1.begin();p!=vec1.end(); ++p) cout<<*p; cout<<'\n'<<"vec2:"<<endl; for(vector< char >::const_iterator p1=vec2.begin();p1!=vec2.end(); ++p1) cout<<*p1; cout<<'\n'; return 0; }
为了帮助理解向量的概念,这里写了一个小例子,其中用到了vector的成员函数:begin(),end(),push_back(),assign(),front(),back(),erase(),empty(),at(),size()。
#include <iostream> #include <vector> using namespace std; typedef vector<int> INTVECTOR;//自定义类型INTVECTOR //测试vector容器的功能 int main() { //vec1对象初始为空 INTVECTOR vec1; //vec2对象最初有10个值为6的元素 INTVECTOR vec2(10,6); //vec3对象最初有3个值为6的元素,拷贝构造 INTVECTOR vec3(vec2.begin(),vec2.begin()+3); //声明一个名为i的双向迭代器 INTVECTOR::iterator i; //从前向后显示vec1中的数据 cout<<"vec1.begin()--vec1.end():"<<endl; for (i =vec1.begin(); i !=vec1.end(); ++i) cout << *i << " "; cout << endl; //从前向后显示vec2中的数据 cout<<"vec2.begin()--vec2.end():"<<endl; for (i =vec2.begin(); i !=vec2.end(); ++i) cout << *i << " "; cout << endl; //从前向后显示vec3中的数据 cout<<"vec3.begin()--vec3.end():"<<endl; for (i =vec3.begin(); i !=vec3.end(); ++i) cout << *i << " "; cout << endl; //测试添加和插入成员函数,vector不支持从前插入 vec1.push_back(2);//从后面添加一个成员 vec1.push_back(4); vec1.insert(vec1.begin()+1,5);//在vec1第一个的位置上插入成员5 //从vec1第一的位置开始插入vec3的所有成员 vec1.insert(vec1.begin()+1,vec3.begin(),vec3.end()); cout<<"after push() and insert() now the vec1 is:" <<endl; for (i =vec1.begin(); i !=vec1.end(); ++i) cout << *i << " "; cout << endl; //测试赋值成员函数 vec2.assign(8,1); // 重新给vec2赋值,8个成员的初始值都为1 cout<<"vec2.assign(8,1):" <<endl; for (i =vec2.begin(); i !=vec2.end(); ++i) cout << *i << " "; cout << endl; //测试引用类函数 cout<<"vec1.front()="<<vec1.front()<<endl;//vec1第零个成员 cout<<"vec1.back()="<<vec1.back()<<endl;//vec1的最后一个成员 cout<<"vec1.at(4)="<<vec1.at(4)<<endl;//vec1的第五个成员 cout<<"vec1[4]="<<vec1[4]<<endl; //测试移出和删除 vec1.pop_back();//将最后一个成员移出vec1 vec1.erase(vec1.begin()+1,vec1.end()-2);//删除成员 cout<<"vec1.pop_back() and vec1.erase():" <<endl; for (i =vec1.begin(); i !=vec1.end(); ++i) cout << *i << " "; cout << endl; //显示序列的状态信息 cout<<"vec1.size(): "<<vec1.size()<<endl;//打印成员个数 cout<<"vec1.empty(): "<<vec1.empty()<<endl;//清空 }
一个集合(#include<set>)是一个容器,它其中所包含的元素的值是唯一的。这在收集一个数据的具体值的时候是有用的。集合中的元素按一定的顺序排列,并被作为集合中的实例。如果你需要一个键/值对(pair)来存储数据,map(也是一个关联容器,后面将马上要讲到)是一个更好的选择。一个集合通过一个链表来组织,在插入操作和删除操作上比向量(vector)快,但查找或添加末尾的元素时会有些慢。我们来看一个简单的例子
1 #include <iostream> 2 #include <set> 3 using namespace std; 4 5 int main() 6 { 7 set<int> set1; 8 for(int i=0; i<10; ++i) 9 set1.insert(i); 10 for(set<int>::iterator p=set1.begin();p!=set1.end();++p) 11 cout<<*p<<""; 12 if(set1.insert(3).second)//把3插入到set1中 13 //插入成功则set1.insert(3).second返回1,否则返回0 14 //此例中,集中已经有3这个元素了,所以插入将失败 15 cout<<"set insert success"; 16 else 17 cout<<"set insert failed"; 18 int a[] = {4, 1, 1, 1, 1, 1, 0, 5, 1, 0}; 19 multiset<int> A; 20 A.insert(set1.begin(),set1.end()); 21 A.insert(a,a+10); 22 cout<<endl; 23 for(multiset<int>::iterator p=A.begin();p!=A.end();++p) 24 cout<<*p<<" "; 25 return 0; 26 }
1 #include <iostream> 2 #include <set> 3 using namespace std; 4 5 int GetMasses(int *n){ 6 int sum[2], tmp; 7 set <int> s; 8 for(int i=0; i<2; i++) 9 sum[i] = 0; 10 for(int i=0; i<3; i++){ 11 s.insert(n[i]); 12 for(int j=0; j<3; j++){ 13 if(i != j){ 14 sum[0] = n[i] + n[j]; 15 s.insert(sum[0]); 16 if(n[i]>n[j]){ 17 sum[1] = n[i]-n[j]; 18 s.insert(sum[1]); 19 } 20 else{ 21 sum[1] = n[j]-n[i]; 22 if(sum[1] > 0) 23 s.insert(sum[1]); 24 } 25 for(int k=0; k<3; k++){ 26 if(k != i && k != j){ 27 for(int m=0; m<2; m++){ 28 s.insert(sum[m]+n[k]); 29 tmp =sum[m] - n[k]; 30 if (tmp >0) 31 s.insert(tmp); 32 } 33 } 34 } 35 } 36 } 37 } 38 return s.size(); 39 } 40 41 int main() 42 { 43 int T, sum, n[3]; 44 cin >> T; 45 while(T--){ 46 int x, y, max, ans; 47 cin >> x >> y; 48 sum = x+y; 49 50 ans = 0; 51 for(int i=1; i<=x/2; i++){ 52 n[0] = i; 53 n[1] = x-i; 54 n[2] = y; 55 max = GetMasses(n); 56 if(max > ans) 57 ans = max; 58 } 59 60 for(int i=1; i<= y/2; i++){ 61 n[0] = i; 62 n[1] = y-i; 63 n[2] = x; 64 max = GetMasses(n); 65 if(max > ans) 66 ans = max; 67 } 68 cout << ans << endl; 69 } 70 return 0; 71 }
映射和多重映射(#include<map>)基于某一类型Key的键集的存在,提供对T类型的数据进行快速和高效的检索。对map而言,键只是指存储在容器中的某一成员。Map不支持副本键,multimap支持副本键。Map和multimap对象包涵了键和各个键有关的值,键和值的数据类型是不相同的,这与set不同。set中的key和value是Key类型的,而map中的key和value是一个pair结构中的两个分量。Map支持下表运算符operator[],用访问普通数组的方式访问map,不过下标为map的键。在multimap中一个键可以对应多个不同的值。
#include <iostream> #include <map> using namespace std; int main() { map<char,int,less<char> > map1; map<char,int,less<char> >::iterator mapIter; //char 是键的类型,int是值的类型 //下面是初始化,与数组类似 //也可以用map1.insert(map<char,int,less<char> >::value_type(''c'',3)); map1['c']=3; map1['d']=4; map1['a']=1; map1['b']=2; for(mapIter=map1.begin();mapIter!=map1.end();++mapIter) cout<<" "<<(*mapIter).first<<": "<<(*mapIter).second; //first对应定义中的char键,second对应定义中的int值 //检索对应于d键的值是这样做的: map<char,int,less<char> >::const_iterator ptr; ptr=map1.find('d'); cout<<'\n'<<" "<<(*ptr).first<<" 键对应于值:"<<(*ptr).second; return 0; }
#inlcude <algorithm>
STL中算法的大部分都不作为某些特定容器类的成员函数,他们是泛型的,每个算法都有处理大量不同容器类中数据的使用。值得注意的是,STL中的算法大多有多种版本,用户可以依照具体的情况选择合适版本。中在STL的泛型算法中有4类基本的算法:
变序型队列算法:可以改变容器内的数据;
非变序型队列算法:处理容器内的数据而不改变他们;
排序值算法:包涵对容器中的值进行排序和合并的算法,还有二叉搜索算法、通用数值算法。(注:STL的算法并不只是针对STL容器,对一般容器也是适用的。)
变序型队列算法:又叫可修改的序列算法。这类算法有复制(copy)算法、交换(swap)算法、替代(replace)算法、删除(clear)算法,移动(remove)算法、翻转(reverse)算法等等。这些算法可以改变容器中的数据(数据值和值在容器中的位置)。
#include <iostream> #include <algorithm> #include <iterator> //下面用到了输出迭代器ostream_iterator using namespace std; int main() { int arr[6]={1,12,3,2,1215,90}; int arr1[7]; int arr2[6]={2,5,6,9,0,-56}; copy(arr,(arr+6),arr1);//将数组aar复制到arr1 cout<<"arr[6] copy to arr1[7],now arr1: "<<endl; for(int i=0;i<7;i++) cout<<" "<<arr1[i]; reverse(arr,arr+6);//将排好序的arr翻转 cout<<'\n'<<"arr reversed ,now arr:"<<endl; copy(arr,arr+6,ostream_iterator<int>(cout, " "));//复制到输出迭代器 swap_ranges(arr,arr+6,arr2);//交换arr和arr2序列 cout<<'\n'<<"arr swaped to arr2,now arr:"<<endl; copy(arr,arr+6,ostream_iterator<int>(cout, " ")); cout<<'\n'<<"arr2:"<<endl; copy(arr2,arr2+6,ostream_iterator<int>(cout, " ")); return 0; }
3.2 查找(find()/Search())
Find() 其功能是在序列[first,last-1]中查找value值,如果找到,就返回一个指向value在序列中第一次出现的迭代,如果没有找到,就返回一个指向last的迭代(last并不属于序列)。
Search() 其功能是在源序列[first1,last1-1]查找目标序列[first2,last2-1]如果查找成功,就返回一个指向源序列中目标序列出现的首位置的迭代。查找失败则返回一个指向last的迭代。
3.3 排序
排序算法(sort algorithm):这一类算法很多,功能强大同时也相对复杂一些。这些算法依赖的是关系运算。在这里我只介绍其中比较简单的几种排序算法:sort(),merge(),includes()
#include <iostream> #include <algorithm> using namespace std; int main() { int a[10]={12,0,5,3,6,8,9,34,32,18}; int b[5]={5,3,6,8,9}; int d[15]; sort(a,a+10); for(int i=0;i<10;i++) cout<<" "<<a[i]; sort(b,b+5); if(includes(a,a+10,b,b+5)) cout<<'\n'<<"sorted b members are included in a."<<endl; else cout<<"sorted a dosn`t contain sorted b!"; merge(a,a+10,b,b+5,d); for(int j=0;j<15;j++) cout<<" "<<d[j]; return 0;
例3:Applications
#include <iostream> #include <cstring> #include <iomanip> #include <algorithm> using namespace std; struct TEAM{ string name; int rank; } team[505]; struct MAN{ string name, team; char sex; double score; int nP, nC; } man[505]; int mao[505], sur[505], r, s; bool IsPrime(int num){ for(int i=2; i*i<=num; i++){ if(num%i==0) return 0; } return 1; } double Max(double a,double b){ return (a>b)?a:b; } double jamcal(int r){ return Max(0,((r-1200)/100.0)*1.5); } double GetScoreProblem(int num){ for(int i=0; i<r; i++) if(num == mao[i]) return 2.5; for(int i=0; i<s; i++) if(num == sur[i]) return 1.5; if(IsPrime(num)) return 1; return 0.3; } bool cmpScore(MAN x, MAN y){ return x.score > y.score; } bool cmpRate(int x, int y){ return x > y; } int main() { int T, n, m, q, tmp, c[1005]; cin >> T; while(T--){ cin >> n >> m; cin >> r; for(int i=0; i<r; i++) cin >> mao[i]; cin >> s; for(int i=0; i<s; i++) cin >> sur[i]; cin >> q; for(int i=0; i<q; i++) cin >> team[i].name >> team[i].rank; for(int i=0; i<n; i++){ man[i].score = 0; cin>>man[i].name>>man[i].team>>man[i].sex>>man[i].nP>>man[i].nC; for(int j=0; j<man[i].nP; j++){ cin >> tmp; man[i].score += GetScoreProblem(tmp); } //Get some pts from the competition for(int j=0; j<q; j++){ if(man[i].team == team[j].name){ if(team[j].rank == 1) man[i].score += 36; else if(team[j].rank == 2) man[i].score += 27; else if(team[j].rank == 3) man[i].score += 18; } } //Get some pts form JapanJam memset(c, 0, sizeof(c)); for(int j=0; j<man[i].nC; j++){ cin >> c[j]; } sort(c, c+man[i].nC, cmpRate); man[i].score += jamcal(c[2]); //man[i].score += std::max(0, (c[2]-1200)/100)*1.5; //for girl if(man[i].sex == 'F') man[i].score += 33; } sort(man,man+n, cmpScore); //output for(int i=0; i<m; i++){ cout<<man[i].name<<" "<<setiosflags(ios::fixed)<<setprecision(3)<<man[i].score<<endl; } } return 0; }
2.1 基本容器——向量(vector)