STL入门基础(二)

来源:天极网

2 牛刀小试:且看一个简单例程

大致功能是:从标准输入设备(一般是键盘)读入一些整型数据,然后对它们进行排序,最终将结果输出到标准输出设备(一般是显示器屏幕)。这是一种典型的处理方式,程序本身具备了一个系统所应该具有的几乎所有的基本特征:输入 + 处理 + 输出。你将会看到三个不同版本的程序。第一个是没有使用STL的普通C++程序,你将会看到完成这样看似简单的事情,需要花多大的力气,而且还未必没有一点问题(真是吃力不讨好)。第二个程序的主体部分使用了STL特性,此时在第一个程序中所遇到的问题就基本可以解决了。同时,你会发现采用了STL之后,程序变得简洁明快,清晰易读。第三个程序则将STL的功能发挥到了及至,你可以看到程序里几乎每一行代码都是和STL相关的。

2.2.1 第一版:史前时代--转木取火

在STL还没有降生的"黑暗时代",C++程序员要完成前面所提到的那些功能,需要做很多事情(不过这比起C程序来,似乎好一点),程序大致是如下这个样子的:

 

// name:example2_1.cpp
// alias:Rubish

#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没有足够的智力预知你传给它的数组的实际大小),第三个参数给出了数组中每个元素以字节为单位的大小。以下是某次运行的结果:

输入:0 9 2 1 5

输出:0 1 2 5 9有一个问题,这个程序并不像看起来那么健壮(Robust)。如果我们输入的数字个数超过max_size所规定的上限,就会出现数组越界问题。如果你在Visual C++的IDE环境下以控制台方式运行这个程序时,会弹出非法内存访问的错误对话框。这个问题很严重,严重到足以使你开始重新审视这个程序的代码。为了弥补程序中的这一缺陷。我们不得不考虑采用如下三种方案中的一种:

  1. 采用大容量的静态数组分配。
  2. 限定输入的数据个数。
  3. 采用动态内存分配。

第一种方案比较简单,你所做的只是将max_size改大一点,比如:1000或者10000。但是,严格讲这并不能最终解决问题,隐患仍然存在。假如有人足够耐心,还是可以使你的这个经过纠正后的程序崩溃的。此外,分配一个大数组,通常是在浪费空间,因为大多数情况下,数组中的一部分空间并没有被利用。

再来看看第二种方案,通过在第一个for循环中加入一个限定条件,可以使问题得到解决。比如:for (int n = 0; cin >> num[n] && n < max_size; n ++); 但是这个方案同样不甚理想,尽管不会使程序崩溃,但失去了灵活性,你无法输入更多的数。

看来只有选择第三种方案了。是的可以利用指针,以及动态内存分配妥善的解决上述问题,并且使程序具有良好的灵活性。这需要用到new,delete操作符,或者古老的malloc(), realloc()和free()函数。但是为此,你将牺牲程序的简洁性,使程序代码陡增,代码的处理逻辑也不再像原先看起来那么清晰了。一个 compare函数或许就已经令你不耐烦了,更何况要实现这些复杂的处理机制呢?很难保证你不会在处理这个问题的时候出错,很多程序的bug往往就是这样产生的。同时,你还应该感谢stdlib.h,它为你提供了qsort函数,否则,你还需要自己实现排序算法。如果你用的是冒泡法排序,那效率就不会很理想。……,问题真是越来越让人头疼了!

2.2.2 第二版:工业时代--组件化大生产

试着使用一下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;

// 从标准输入设备读入整数,
// 直到输入的是非整型数据为止
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++特性(包括输入输出处理,STL中的容器和算法)。不必在意那个.h,并不是我的疏忽,程序保证可以编译通过,只要你的C++编译器支持标准C++规范的相关部分。

第四行加入那个声明只是为了表明程序引用到了std这个标准名字空间(namespace),因为STL中的那些玩意儿全都包含在那里面。只有通过这行声明,编译器才能允许你使用那些有趣的特性。

程序中用到了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似乎要简单了许多。

程序的最后是输出部分,在这里vector完全可以以假乱真了,它所提供的对元素的访问方式简直和普通的C++内建数组一模一样。那个size函数用来返回vector中的元素个数,就相当于第一个程序中的变量n。这两行代码直观的不用我再多解释了。

2.2.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"));
}
2.3 如何运行 
选用了目前在Windows平台下较为常见的Microsoft Visual C++ 6.0和Borland C++ Builder 6.0作为例子。
尽管Visual C++ 6.0对最新的ANSI/ISO C++标准支持的并不是很好。不过据称Visual C++ .NET(也就是VC7.0)
在这方面的性能有所改善。 
你可以选用多种方式运行前面的程序,比如在Visual C++下,你可以直接在DOS命令行状态下编译运行,也可以在VC的
IDE下采用控制台应用程序(Console Application)的方式运行。对于C++ Builder,情况也类似。 
对于Visual C++而言,如果是在DOS命令行状态下,你首先需要找到它的编译器。假定你的Visual C++装在
C:/Program Files/Microsoft Visual Studio/VC98下面,则其编译器所在路径应该是
C:/Program Files/Microsoft Visual Studio/VC98/Bin,
在那里你可以找到cl.exe文件。编译时请加上/GX和/MT参数。如果一切正常,结果就会产生一个可执行文件。如下所示: 
cl /GX /MT example2_2.cpp 
前一个参数用于告知编译器允许异常处理(Exception Handling)。在P. J. Plauger STL中的很多地方使用了
异常处理机制(即try…throw…catch语法),所以应该加上这个参数,否则会有如下警告信息: 
warning C4530: C++ exception handler used, but unwind semantics are not enabled. 
后一个参数则用于使程序支持多线程,它需要在链接时使用LIBCMT.LIB库文件。不过P. J. Plauger STL
并不是线程安全的(thread safety)。如果你是在VC环境下使用像STLport这样的STL实现版本,
则需要加上这个参数,因为STLport是线程安全的。 
如果在IDE环境下,可以在新建工程的时候选择控制台应用程序.

至于那些参数的设置,则可以通过在Project功能菜单项中的Settings功能【Alt+F7】中设置编译选项来完成。 
有时,在IDE环境下编译STL程序时,可能会出现如下警告信息(前面那几个示例程序不会出现这种情况): 
warning C4786: '……' : identifier was truncated to '255' characters in the debug information 
这是因为编译器在Debug状态下编译时,把程序中所出现的标识符长度限制在了255个字符范围内。如果超过最大长度,这些标识
符就无法在调试阶段查看和计算了。而在STL程序中大量的用到了模板函数和模板类,编译器在实例化这些内容时,展开之后所产
生的标识符往往很长(没准会有一千多个字符!)。如果你想认识一下这个warning的话,很简单,在程序里加上如下一行代码: 
vector<string> string_array; // 类似于字符串数组变量 
对于这样的warning,当然可以置之不理,不过也是有解决办法的。 你可以在文件开头加入下面这一行
#pragma warning(disable: 4786)。
它强制编译器忽略这个警告信息,这种做法虽然有点粗鲁,但是很有效。
 
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值