我选用了一个稍稍复杂一点的例子,它的大体功能是:从标准输入设备(通常是键盘)读入一些整型数据,而后对它们进行排序,最终将结果输出到标准输出设备(通常是显示器屏幕)。这是一种典型的处理方式,程序自己具有了一个系统所应该具备的几乎全部的基本特征:输入 + 处理 + 输出。你将会看到三个不一样版本的程序。第一个是没有使用STL的普通C++程序,你将会看到完成这样看似简单的事情,须要花多大的力气,并且还未必没有一点问题(真是吃力不讨好)。第二个程序的主体部分使用了STL特性,此时在第一个程序中所遇到的问题就基本能够解决了。同时,你会发现采用了STL以后,程序变得简洁明快,清晰易读。第三个程序则将STL的功能发挥到了及至,你能够看到程序里几乎每一行代码都是和STL相关的。这样的机会并不老是随处可见的,它展示了STL中的几乎全部的基本组成部分,尽管这看起来彷佛有点过度了。html
有几点是须要说明的:ios
这个例程的目的,在于向你演示如何在C++程序中使用STL,同时但愿经过实践,证实STL所带给你的确确实实的好处。程序中用到的一些STL基本组件,好比:vector(一种容器)、sort(一种排序算法),你只须要有一个大体的概念就能够了,这并不影响阅读代码和理解程序的含义。c++
1.初版:史前时代--转木取火程序员
在STL尚未降生的"黑暗时代",C++程序员要完成前面所提到的那些功能,须要作不少事情(不过这比起C程序来,彷佛好一点),程序大体是以下这个样子的:算法
// name:example2_1.cpp #include <stdlib.h> #include <iostream.h> int compare(const void *arg1, const void *arg2); void main(void) { const int max_size = 10; // 数组容许元素的最大个数 int num[max_size]; // 整型数组 // 从标准输入设备读入整数,同时累计输入个数, // 直到输入的是非整型数据为止 int n; for (n = 0; cin >> num[n]; n ++); // C标准库中的快速排序(quick-sort)函数 qsort(num, n, sizeof(int), compare); // 将排序结果输出到标准输出设备 for (int i = 0; i < n; i ++) cout << num[i] << "\n"; } // 比较两个数的大小, // 若是*(int *)arg1比*(int *)arg2小,则返回-1 // 若是*(int *)arg1比*(int *)arg2大,则返回1 // 若是*(int *)arg1等于*(int *)arg2,则返回0 int compare(const void *arg1, const void *arg2) { return (*(int *)arg1 < *(int *)arg2) ? -1 : (*(int *)arg1 > *(int *)arg2) ? 1 : 0; }
这是一个和STL没有丝毫关系的传统风格的C++程序。由于程序的注释已经很详尽了,因此不须要我再作更多的解释。总的说来,这个程序看起来并不十分复杂(原本就没有太多功能)。只是,那个compare函数,看起来有点费劲。指向它的函数指针被做为最后一个实参传入qsort函数,qsort是C程序库stdlib.h中的一个函数。如下是qsort的函数原型:数组
void qsort(void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
看起来有点使人做呕,尤为是最后一个参数。大概的意思是,第一个参数指明了要排序的数组(好比:程序中的num),第二个参数给出了数组的大小(qsort没有足够的智力预知你传给它的数组的实际大小),第三个参数给出了数组中每一个元素以字节为单位的大小。最后那个长长的家伙,给出了排序时比较元素的方式(仍是由于qsort的智商问题)。函数
如下是某次运行的结果:
输入:0 9 2 1 5 输出:0 1 2 5 9组件化
有一个问题,这个程序并不像看起来那么健壮(Robust)。若是咱们输入的数字个数超过max_size所规定的上限,就会出现数组越界问题。这个问题很严重,严重到足以使你开始从新审视这个程序的代码。为了弥补程序中的这一缺陷。咱们不得不考虑采用以下三种方案中的一种:
采用大容量的静态数组分配。限定输入的数据个数。采用动态内存分配。ui
第一种方案比较简单,你所作的只是将max_size改大一点,好比:1000或者10000。可是,严格讲这并不能最终解决问题,隐患仍然存在。假若有人足够耐心,仍是可使你的这个通过纠正后的程序崩溃的。此外,分配一个大数组,一般是在浪费空间,由于大多数状况下,数组中的一部分空间并无被利用。spa
再来看看第二种方案,经过在第一个for循环中加入一个限定条件,可使问题获得解决。好比:for (int n = 0; cin >> num[n] && n < max_size; n ++); 可是这个方案一样不甚理想,尽管不会使程序崩溃,但失去了灵活性,你没法输入更多的数。
看来只有选择第三种方案了。是的,你能够利用指针,以及动态内存分配妥善的解决上述问题,而且使程序具备良好的灵活性。这须要用到new,delete操做符,或者古老的malloc(),realloc()和free()函数。可是为此,你将牺牲程序的简洁性,使程序代码陡增,代码的处理逻辑也再也不像原先看起来那么清晰了。一个compare函数或许就已经令你不耐烦了,更况且要实现这些复杂的处理机制呢?很难保证你不会在处理这个问题的时候出错,不少程序的bug每每就是这样产生的。同时,你还应该感谢stdlib.h,它为你提供了qsort函数,不然,你还须要本身实现排序算法。若是你用的是冒泡法排序,那效率就不会很理想。……,问题真是愈来愈让人头疼了!
2.第二版:工业时代--组件化大生产
咱们应该庆幸本身所生活的年代。工业时代,科技的发展所带来的巨大便利已经影响到了咱们生活中的每一个细节。若是你还在以原始人类的方式生活着,那我真该怀疑你是否属于某个生活在非洲或者南美丛林里的原始部落中的一员了,难道是玛雅文明又重现了?
STL即是这个时代的产物,正如其余科技成果同样,C++程序员也应该努力使本身适应并充分利用这个"高科技成果"。让咱们从新审视初版的那个破烂不堪的程序。试着使用一下STL,看看效果如何。
// name:example2_2.cpp // alias:The first STL program #include <iostream> #include <vector> #include <algorithm> using namespace std; void main(void) { vector<int> num; // STL中的vector容器 int element; // 从标准输入设备读入整数, // 直到输入的是非整型数据为止(无效输入)或者遇到文件结束符(ctrl+z) while (cin >> element) num.push_back(element); // STL中的排序算法 sort(num.begin(), num.end()); // 将排序结果输出到标准输出设备 for (int i = 0; i < num.size(); i ++) cout << num[i] << "\n"; }
这个程序的主要部分改用了STL的部件,看起来要比第一个程序简洁一点,你已经找不到那个讨厌的compare函数了。它真的能很好的运行吗?你能够试试,由于程序的运行结果和前面的大体差很少,因此在此略去。我能够向你保证,这个程序是足够健壮的。不过,可能你尚未彻底看明白程序的代码,因此我须要为你解释一下。毕竟,这个戏法变得太快了,较之第一个程序,一眨眼的功夫,那些老的C++程序员所熟悉的代码都不见了,取而代之的是一些新鲜玩意儿。
程序中用到了vector,它是STL中的一个标准容器,能够用来存放一些元素。你能够把vector理解为int [?],一个整型的数组。之因此大小未知是由于,vector是一个能够动态调整大小的容器,当容器已满时,若是再放入元素则vector会悄悄扩大本身的容量。push_back是vector容器的一个类属成员函数,用来在容器尾端插入一个元素。main函数中第一个while循环作的事情就是不断向vector容器尾端插入整型数据,同时自动维护容器空间的大小。
sort是STL中的标准算法,用来对容器中的元素进行排序。它须要两个参数用来决定容器中哪一个范围内的元素能够用来排序。这里用到了vector的另两个类属成员函数。begin()用以指向vector的首端,而end()则指向vector的末端。这里有两个问题,begin()和end()的返回值是什么?这涉及到STL的另外一个重要部件--迭代器(Iterator),不过这里并不须要对它作详细了解。你只须要把它看成是一个指针就能够了,一个指向整型数据的指针。相应的sort函数声明也能够看做是void sort(int* first, int* last),尽管这实际上很不精确。另外一个问题是和end()函数有关,尽管前面说它的返回值指向vector的末端,但这种说法不能算正确。事实上,它的返回值所指向的是vector中最末端元素的后面一个位置,即所谓pass-the-end value。这听起来有点费解,不过没必要在乎,这里只是稍带一提。总的来讲,sort函数所作的事情是对那个准整型数组中的元素进行排序,一如第一个程序中的那个qsort,不过比起qsort来,sort彷佛要简单了许多。
我想个人耐心讲解应该可使你大体看懂上面的程序了,事实上STL的运用使程序的逻辑更加清晰,使代码更易于阅读。试问,有谁会不明白begin、end、size这样的字眼所表达的含义呢(除非他不懂英语)?试着运行一下,看看效果。再试着多输入几个数,看看是否会发生数组越界现象。实践证实,程序运行良好。是的,因为vector容器自行维护了自身的大小,C++程序员就不用操心动态内存分配了,指针的错误使用毕竟会带来不少麻烦,同时程序也会变得冗长无比。这正是前面第三种方案的缺点所在。
3.第三版:惟美主义的杰做
事态的发展有时候总会趋向极端,这在那些惟美主义者当中犹是如此。首先声明,我并非一个惟美主义者,提供第二版程序的改进版,彻底是为了让你更深入的感觉到STL的魅力所在。在看完第三版以后,你会强烈感觉到这一点。或许你也会变成一个惟美主义者了,至少在STL方面。这应该不是个人错,由于决定权在你手里。下面咱们来看看这个绝版的C++程序。
// name:example2_3.cpp // alias:aesthetic version #include <iostream> #include <vector> #include <algorithm> #include <iterator> using namespace std; void main(void) { typedef vector<int> int_vector; typedef istream_iterator<int> istream_itr; typedef ostream_iterator<int> ostream_itr; typedef back_insert_iterator< int_vector > back_ins_itr; // STL中的vector容器 int_vector num; // 从标准输入设备读入整数, // 直到输入的是非整型数据为止 copy(istream_itr(cin), istream_itr(), back_ins_itr(num)); // STL中的排序算法 sort(num.begin(), num.end()); // 将排序结果输出到标准输出设备 copy(num.begin(), num.end(), ostream_itr(cout, "\n")); }
在这个程序里几乎每行代码都是和STL有关的(除了main和那对花括号,固然还有注释),而且它包含了STL中几乎全部的各大部件(容器container,迭代器iterator, 算法algorithm, 适配器adaptor),惟一的遗憾是少了函数对象(functor)的身影。
还记得开头提到的一个典型系统所具备的基本特征吗?--输入+处理+输出。全部这些功能,在上面的程序里,仅仅是经过三行语句来实现的,其中每一行语句对应一种操做。对于数据的操做被高度的抽象化了,而算法和容器之间的组合,就像搭积木同样轻松自如,系统的耦合度被降到了极低点。这就是闪耀着泛型之光的STL的伟大力量。如此简洁,如此巧妙,如此神奇!就像魔术通常,以致于再一次让你摸不着头脑。怎么实现的?为何在看第二版程序的时候如此清晰的你,又坠入了五里雾中(窃喜)。
前面提到的迭代器能够对容器内的任意元素进行定位和访问。在STL里,这种特性被加以推广了。一个cin表明了来自输入设备的一段数据流,从概念上讲它对数据流的访问功能相似于通常意义上的迭代器,可是C++中的cin在不少地方操做起来并不像是一个迭代器,缘由就在于其接口和迭代器的接口不一致(好比:不能对cin进行++运算,也不能对之进行取值运算--即*运算)。为了解决这个矛盾,就须要引入适配器的概念。istream_iterator即是一个适配器,它将cin进行包装,使之看起来像是一个普通的迭代器,这样咱们就能够将之做为实参传给一些算法了(好比这里的copy算法)。由于算法只认得迭代器,而不会接受cin。对于上面程序中的第一个copy函数而言,其第一个参数展开后的形式是:istream_iterator(cin),其第二个参数展开后的形式是:istream_iterator()(若是你对typedef的语法不清楚,能够参考有关的c++语言书籍)。其效果是产生两个迭代器的临时对象,前一个指向整型输入数据流的开始,后一个则指向"pass-the-end value"。这个函数的做用就是将整型输入数据流从头到尾逐一"拷贝"到vector这个准整型数组里,第一个迭代器从开始位置每次累进,最后到达第二个迭代器所指向的位置。或许你要问,若是那个copy函数的行为真如我所说的那样,为何不写成以下这个样子呢?
copy(istream_iterator<int>(cin), istream_iterator<int>(), num.begin());
你确实能够这么作,可是有一个小小的麻烦。还记得初版程序里的那个数组越界问题吗?若是你这么写的话,就会遇到相似的麻烦。缘由在于copy函数在"拷贝"数据的时候,若是输入的数据个数超过了vector容器的范围时,数据将会拷贝到容器的外面。此时,容器不会自动增加容量,由于这只是简单地拷贝,并非从末端插入。为了解决这个问题,适配器back_insert_iterator登场了,它的做用就是引导copy算法每次在容器末端插入一个数据。程序中的那个back_ins_itr(num)展开后就是:back_insert_iterator(num),其效果是生成一个这样的迭待器对象。
至于第三句,ostream_itr(cout, "\n")展开后的形式是:ostream_iterator(cout, "\n"),其效果是产生一个处理输出数据流的迭待器对象,其位置指向数据流的起始处,而且以"\n"做为分割符。第二个copy函数将会从头到尾将vector中的内容"拷贝"到输出设备,第一个参数所表明的迭代器将会从开始位置每次累进,最后到达第二个参数所表明的迭代器所指向的位置。
这就是所有的内容。转自:http://www.cnblogs.com/cutepig/archive/2007/07/15/818957.html