2008年(35)
分类: C/C++
2008-03-28 17:17:41
虽然,STL的优点甚多,但是STL的语法实在令初学者人头疼,许多人望而却步。可是STL是每个C++程序设计者迟早都要啃的一块骨头。因为越来越多的C++代码是用STL编写的,看不懂麻烦就大鸟。越来越多的人在用STL,不懂就无法和别人一起合作了。好事多磨嘛,早点学习早点解脱。
下面让我们来看几段代码吧:(你觉得头疼就不要看了)
//stl_cpp_1.cpp #include好懂吧,除了那个std有点让人不舒服以外?这是一段普通的没有使用STL的C++代码。再看下面一段:double mean(double *array, size_t n) { double m=0; for(size_t i=0; i
//stl_cpp_2.cpp #include如果你真的没有接触过STL的话,你会问,呀,vector 是啥呀?我会告诉你,那是一排美女。嘎嘎。这可不是个比喻,表想歪鸟。这是一段纯种的STL代码,看到尖括号了吧,知道那是模板了吧。看到a.push_back(5),a.begin(),a.end()你不感觉奇怪么?可是我们并没有定义这些函数啊。#include int main(void) { std::vector a; std::vector ::const_iterator i; a.push_back(1); a.push_back(2); a.push_back(3); a.push_back(4); a.push_back(5); for(i=a.begin(); i!=a.end(); ++i){ std::cout<<(*i)<
//stl_cpp_3.cpp #include这一段你又看到了新东西了吧,iterator???不罗嗦了,等你看完这篇文章,回头再看就简单了。在正式介绍STL之前,我们需要花点时间来了解一下模板和命名空间。#include int main(void) { std::vector q; q.push_back(10); q.push_back(11); q.push_back(12); std::vector v; for(int i=0; i<5; ++i){ v.push_back(i); } std::vector ::iterator it = v.begin() + 1; it = v.insert(it, 33); v.insert(it, q.begin(), q.end()); it = v.begin() + 3; v.insert(it, 3, -1); it = v.begin() + 4; v.erase(it); it = v.begin() + 1; v.erase(it, it + 4); v.clear(); return 0; }
//stl_cpp_4.cpp #include下面再看看,类模板:#include //定义函数模板 template //template 是关键字,T 表示一种待实例化的类型 //template 也是对的 T max(T a, T b)//函数模板,函数名为 max,此函数有2个T类型的参数,返回类型为T { return (a>b)?a:b; } //在此例实例化的时候,T可以是多种类型的,int,char,string… int main(void) { int x=2,y=6; double x1=9.123,y1=12.6543; cout<<"把T实例化为int:"<
//stl_cpp_5.cpp #include命名空间(名字空间)//定义名为ex_class的类模板 template < typename T> class ex_class { T value; public: ex_class(T v) { value=v; } void set_value(T v) { value=v; } T get_value(void) {return value;} }; //main()函数中测试ex_class类模板 int main(void) { //测试int类型数据 ex_class a(5),b(10); cout<<"a.value:"< ch(''A''); cout<<"ch.value:"< x(5.5); cout<<"x.value:"<
//stl_cpp_6.cpp #include一个namespace是指一个具名的范围(named scope)。namespace被用来将相关的声明划归在一起,将不相关的代码部分隔开。命名空间只是命名了一个特殊的作用域,当程序很大,而且需要多人合作的时候,命名空间就显得特别的重要。比如2个程序员A,B 在同一个程序中定义了函数 pop(),如果没有使用命名空间,则会出错,而且这种错误难以检测出来。为了安全起见,他们可以定义不同的命名空间 A和B,在用的时候可以使用A::pop()和B::pop()来区分。using namespace std; namespace printA { print() {cout<<"using namespace printA….."<
#include第二种方法是:using namespace std;
#include动态绑定和静态绑定using std::vector;
对存储于容器中的数据进行处理时,迭代器能从一个成员移向另一个成员。他能按预先定义的顺序在某些容器中的成员间移动。对普通的一维数组、向量、双端队列和列表来说,迭代器是一种指针。
知道了吧?嘎嘎,当然了,你猜到了,那是我在瞎扯蛋。
下面让我们来看看专家是怎么说的:
下面我将依次介绍STL的这三个主要组件。
容器
STL中的容器有队列容器和关联容器,容器适配器(congtainer adapters:stack,queue,priority queue),位集(bit_set),串包(string_package)等等。
在本文中,我将介绍list,vector,deque等队列容器,和set和multisets,map和multimaps等关联容器,一共7种基本容器类。
队列容器(顺序容器):队列容器按照线性排列来存储T类型值的集合,队列的每个成员都有自己的特有的位置。顺序容器有向量类型、双端队列类型、列表类型三种。
基本容器——顺序容器
向量(vector容器类):#include
下面一个例子用的是第四种构造方法,其它的方法读者可以自己试试。
//stl_cpp_7.cpp //程序:初始化演示 #include为了帮助理解向量的概念,这里写了一个小例子,其中用到了vector的成员函数:begin(),end(),push_back(),assign(),front(),back(),erase(),empty(),at(),size()。#include #include using namespace std; int ar[10] = { 12, 45, 234, 64, 12, 35, 63, 23, 12, 55 }; char* str = "Hello World"; int main(void) { vector vec1(ar, ar+10); //first=ar,last=ar+10,不包括ar+10 vector vec2(str, str+strlen(str)); //first=str,last= str+strlen(str),不包括最后一个 cout<<"vec1:"< ::const_iterator p=vec1.begin();p!=vec1.end(); ++p) cout<<*p; cout<<''\n''<<"vec2:"< ::const_iterator p1=vec2.begin();p1!=vec2.end(); ++p1) cout<<*p1; getchar(); return 0; }
//stl_cpp_8.cpp #includepush_back()是将数据放入vector(向量)或deque(双端队列)的标准函数。Insert()是一个与之类似的函数,然而它在所有容器中都可以使用,但是用法更加复杂。end()实际上是取末尾加一,以便让循环正确运行--它返回的指针指向最靠近数组界限的数据。#include using namespace std; typedef vector INTVECTOR;//自定义类型INTVECTOR //测试vector容器的功能 void main(void) { //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():"<
//stl_cpp_9.cpp #include上面我们演示了deque如何进行插入删除等操作,像erase(),assign()是大多数容器都有的操作。关于deque的其他操作请参阅附录。#include using namespace std; typedef deque INTDEQUE;//有些人很讨厌这种定义法,呵呵 //从前向后显示deque队列的全部元素 void put_deque(INTDEQUE deque, char *name) { INTDEQUE::iterator pdeque;//仍然使用迭代器输出 cout << "The contents of " << name << " : "; for(pdeque = deque.begin(); pdeque != deque.end(); pdeque++) cout << *pdeque << " ";//注意有 "*"号哦,没有"*"号的话会报错 cout<
//stl_cpp_10.cpp #include上面并没有演示splice()函数的用法,这是一个拗口的函数。用起来有点麻烦。图3所示是splice函数的功能。将一个列表插入到另一个列表当中。list容器类定义了splice()函数的3个版本:#include #include using namespace std; void PrintIt(list
n) { for(list ::iterator iter=n.begin(); iter!=n.end(); ++iter) cout<<*iter<<" ";//用迭代器进行输出循环 } int main(void) { list listn1,listn2; //给listn1,listn2初始化 listn1.push_back(123); listn1.push_back(0); listn1.push_back(34); listn1.push_back(1123); //now listn1:123,0,34,1123 listn2.push_back(100); listn2.push_back(12); //now listn2:12,100 listn1.sort(); listn2.sort(); //给listn1和listn2排序 //now listn1:0,34,123,1123 listn2:12,100 PrintIt(listn1); cout<
splice(position,list_value); splice(position,list_value,ptr); splice(position,list_value,first,last);list_value是一个已存在的列表,它将被插入到源列表中,position是一个迭代参数,他当前指向的是要进行拼接的列表中的特定位置。
listn1:123,0,34,1123 listn2:12,100执行listn1.splice(find(listn1.begin(),listn1.end(),0),listn2);之后,listn1将变为:123,12,100,34,1123。即把listn2插入到listn1的0这个元素之前。其中,find()函数找到0这个元素在listn1中的位置。值得注意的是,在执行splice之后,list_value将不复存在了。这个例子中是listn2将不再存在。
listn1.splice(find(listn1.begin(),listn1.end(),0),++listn2.begin(),--listn2.end()); listn1:123,43,87,0,34,1123 listn2:12,100以上,我们学习了vector,deque,list三种基本顺序容器,其他的顺序容器还有:slist,bit_vector等等。
template第一个参数key是所存储的键的类型,第二个参数是为排序值而定义的比较函数的类型,第三个参数是被实现的存储分配符的类型。在有些编译器的具体实现中,第三个参数可以省略。第二个参数使用了合适形式的迭代器为键定义了特定的关系操作符,并用来在容器中遍历值时建立顺序。集的迭代器是双向,同时也是常量的,所以迭代器在使用的时候不能修改元素的值。
explicit set(const Compare&=compare()); 如:setless> set1;
templateset(InputIterator, InputIterator,\ const Compare&=compare()); 如:set >set2(vector1.begin(),vector1.end());
复制构造函数:
set(const set); 如:set >set3(set2);
下面我们来看一个简单的集和多集的插入例程:
//stl_cpp_11.cpp #include在集之间可以进行并集(set_union())、交集(set_intersection())、差集(set_diffrence())d等操作,功能强大。#include using namespace std; int main(void) { set set1; for(int i=0; i<10; ++i) set1.insert(i); for(set ::iterator p=set1.begin();p!=set1.end();++p) cout<<*p<<""; if(set1.insert(3).second)//把3插入到set1中 //插入成功则set1.insert(3).second返回1,否则返回0 //此例中,集中已经有3这个元素了,所以插入将失败 cout<<"set insert success"; else cout<<"set insert failed"; int a[] = {4, 1, 1, 1, 1, 1, 0, 5, 1, 0}; multiset A; A.insert(set1.begin(),set1.end()); A.insert(a,a+10); cout< ::iterator p=A.begin();p!=A.end();++p) cout<<*p<<" "; cin.get(); return 0; }
//stl_cpp_12.cpp #include从以上例程中,我们可以看到map对象的行为和一般数组的行为类似。Map允许两个或多个值使用比较操作符。下面我们再看看multimap:#include
//stl_cpp_13.cpp #include在map中是不允许一个键对应多个值的,在multimap中,不支持operator[],也就是说不支持map中允许的下标操作。#include
注:STL的算法并不只是针对STL容器,对一般容器也是适用的。
变序型队列算法(mutating algorithms):
又叫可修改的序列算法。这类算法有复制(copy)算法、交换(swap)算法、替代(replace)算法、删除(remove)算法,移动(transfer)算法、翻转(reverse)算法等等。这些算法可以改变容器中的数据(数据值和值在容器中的位置)。下面介绍2个比较常用的算法reverse()和copy()。
//stl_cpp_14.cpp #includerevese()的功能是将一个容器内的数据顺序翻转过来,它的原型是:#include #include //下面用到了输出迭代器ostream_iterator using namespace std; int main(void) { 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: "< (cout, " "));//复制到输出迭代器 swap_ranges(arr,arr+6,arr2);//交换arr和arr2序列 cout<<''\n''<<"arr swaped to arr2,now arr:"< (cout, " ")); cout<<''\n''<<"arr2:"< (cout, " ")); cin.get(); return 0; }
template将first和last之间的元素翻转过来,上例中你也可以只将arr中的一部分进行翻转:void reverse(Bidirectional first, Bidirectional last);
reverse(arr+3,arr+6);这也是有效的。First和last需要指定一个操作区间。Copy()是要将一个容器内的数据复制到另一个容器内,它的原型是:
Template它把[first,last-1]内的队列成员复制到区间[result,result+(last-first)-1]中。泛型交换算法:Swap()操作的是单值交换,它的原型是:OutputIterator copy(InputIterator first, InputIterator last, OutputIterator result);
templateswap_ranges()操作的是两个相等大小区间中的值,它的原型是:void swap(T& a,T& b);
template交换区间[first1,last1-1]和[first2, first2+(last1-first1)-1]之间的值,并假设这两个区间是不重叠的。ForwardIterator2 swap_ranges(ForwardIterator1 first1,ForwardIterator1 last1, \ ForwardIterator1 first2);
//stl_cpp_15.cpp #includefind()的原型是:#include #include using namespace std; int main(void) { int a[10]={12,31,5,2,23,121,0,89,34,66}; vector v1(a,a+10); vector ::iterator result1,result2;//result1和result2是随机访问迭代器 result1=find(v1.begin(),v1.end(),2); //在v1中找到2,result1指向v1中的2 result2=find(v1.begin(),v1.end(),8); //在v1中没有找到8,result2指向的是v1.end() cout< v2(a+2,a+8); vector v3(b,b+4); result1=search(v1.begin(),v1.end(),v2.begin(),v2.end()); cout<<*result1< v4(b,b+9); int i=count(v4.begin(),v4.end(),5); int j=count(v4.begin(),v4.end(),2); cout<<"there are "<
template其功能是在序列[first,last-1]中查找value值,如果找到,就返回一个指向value在序列中第一次出现的迭代,如果没有找到,就返回一个指向last的迭代(last并不属于序列)。 search()的原型是:InputIterator find(InputIterator first, InputIterator last,\ const EqualityComparable& value);
template其功能是在源序列[first1,last1-1]查找目标序列[first2,last2-1]如果查找成功,就返回一个指向源序列中目标序列出现的首位置的迭代。查找失败则返回一个指向last的迭代。 Count()的原型是:ForwardIterator1 search(ForwardIterator1 first1, ForwardIterator1 last1,\ ForwardIterator2 first2, ForwardIterator2 last2);
template其功能是在序列[first,last-1]中查找出等于value的成员,返回等于value得成员的个数。iterator_traits ::difference_type count(InputIterator first,\ InputIterator last, const EqualityComparable& value);
//stl_cpp_16.cpp #includesort()的原型是:#include using namespace std; int main(void) { 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<<" "<
template功能是对[first,last-1]区间内的元素进行排序操作。与之类似的操作还有:partial_sort(), stable_sort(),partial_sort_copy()等等。 merge()的原型是:void sort(RandomAccessIterator first, RandomAccessIterator last);
template将有序区间[first1,last1-1]和[first2,last2-1]合并到[result, result + (last1 - first1) + (last2 - first2)-1]区间内。OutputIterator merge(InputIterator1 first1, InputIterator1 last1,\ InputIterator2 first2, InputIterator2 last2,OutputIterator result);
template其功能是检查有序区间[first2,last2-1]内元素是否都在[first1,last1-1]区间内,返回一个bool值。bool includes(InputIterator1 first1, InputIterator1 last1,\ InputIterator2 first2, InputIterator2 last2);
以上我们学习了STL容器和算法的概念,以及一些简单的STL容器和算法。在使用算法处理容器内的数据时,需要从一个数据成员移向另一个数据成员,迭代器恰好实现了这一功能。下面我们来学习STL迭代器 。
迭代器(itertor):#include
迭代器实际上是一种泛化指针,如果一个迭代器指向了容器中的某一成员,那么迭代器将可以通过自增自减来遍历容器中的所有成员。迭代器是联系容器和算法的媒介,是算法操作容器的接口。在运用算法操作容器的时候,我们常常在不知不觉中已经使用了迭代器。
STL中定义了6种迭代器:
实际上,在前面的例子中,我们不停的在用迭代器。下面我们用几个例子来帮助理解这些迭代器的用法。
下面的例子用到了输入输出迭代器:
// stl_cpp_17.cpp #include这里用到了输入迭代器istream_iterator,输出迭代器ostream_iterator。程序完成了将一个文件输出到屏幕的功能,先将文件读入,然后通过输入迭代器把文件内容复制到类型为字符串的向量容器内,最后由输出迭代器输出。Inserter是一个输入迭代器的一个函数(迭代器适配器),它的使用方法是:#include #include #include #include using namespace std; int main(void) { vector v1; ifstream file("Text1.txt"); if(file.fail()) { cout<<"open file Text1.txt failed"< (file),istream_iterator (),inserter(v1,\ v1.begin())); copy(v1.begin(),v1.end(),ostream_iterator (cout," ")); cout<
inserter (container ,pos);
congtainer是将要用来存入数据的容器,pos是容器存入数据的开始位置。上例中,是把文件内容存入(copy())到向量v1中。
现在我们已经对STL的三大基本组件有了一个大概的了解,下面让我们一起来看看STL的其他标准组件。
函数对象(functor或者funtion objects):#include
函数对象又称之为仿函数。函数对象将函数封装在一个对象中,使得它可作为参数传递给合适的STL算法,从而使算法的功能得以扩展。可以把它当作函数来使用。用户也可以定义自己的函数对象。下面让我们来定义一个自己的函数对象。
// stl_cpp_18.cpp #include这里的int_max()就是一个函数对象,struct关键字也可以用class来代替,只不过struct默认情况下是公有访问权限,而class定义的是默认私有访问权限。下面我们来定义一个STL风格的函数对象:using namespace std; struct int_max{ int operator()(int x,int y){return x>y?x:y; } };//operator() 重载了"()", (int x,int y)是参数列表 int main(void) { cout<
// stl_cpp_19.cpp #include在这里,我们定义了一个函数对象adder(),这也是一个类,它的基类是unary_function函数对象。unary_function是一个空基类,不包涵任何操作或变量。只是一种格式说明,它有两个参数,第一个参数是函数对象的使用数据类型,第二个参数是它的返回类型。基于它所定义的函数对象是一元函数对象。(注:用关键字struct或者class定义的类型实际上都是"类")#include using namespace std; struct adder : public unary_function { adder() : sum(0) {} double sum; void operator()(double x) { sum += x; } }; int main(void) { double a[5]={0.5644,1.1,6.6,8.8,9.9}; vector V(a,a+5); adder result = for_each(V.begin(), V.end(), adder()); cout << "The sum is " << result.sum << endl; cin.get(); return 0; }
结束语
如果你理解了算法、迭代器、容器,那么你几乎就了解了 STL。关于STL的其他方面,新手都是不常用的,可以暂时以理解STL的组成的编程思想为主。这篇文章里用到了19个cpp代码,每个代码都在Windows 2000+ Dev-C++ 4.9.9.0和windows 2000+VC环境下通过编译运行。读者可以通过copy/paste到任何一款C++编译器中运行。无论你想不想学STL,先运行一下STL代码吧。编程快乐,好好学习,天天向上。
我是新手,欢迎高手批评,欢迎STL学习者交流: Email:taohanjunjiang@yahoo.com.cn QQ:370679790
【附录】
附录一:Dev-C++ 和VC
在Dev-C++ 4.9.9.0 +windows 2000下,不允许出现,void main(){} 而必须为 int main() {return 0;} ,或者 int main(){},奇怪的是VC对 int main(){} 的写法会提出警告,而必须为 int main() {return 0;}。建议使用标准格式 int main(void) {……return 0;}
Dev-C++而且不支持头文件方式,#include
如果用纯C/C++编程,建议使用比较简单的C++编译器,甚至是命令行下的编译方式。功能强大的IDE甚至会误导编程者。还要花费不少的时间来学习使用IDE开发环境。Dev比VC要简单一些,只是不适合做大型工程,而且编译时间比VC稍慢一点,纯C++编程建议使用。他有插入时间和头文件注释模板的功能,作者可以方便地插入编程、程序更改时间,方便地填写程序说明信息。
在Dev下可以导入VC工程,而且保持原来的VC工程文件,但是Dev对VC的支持还不够,在编译的时候会遇到错误。
在编写dos程序的时候,建议在程序的末尾return之前加上:getchar();cin.get();之类的代码,以方便看运行结果。
附录二:中小型程序段的编辑工具
在编辑程序过程中并不一定非要在特定的IDE环境中,在有些专业化的文本编辑工具中编辑代码会更有利于代码的修改和编辑。这里介绍几种更能比较强大的文本编辑器。