vc的STL

原文
http://blog.sina.com.cn/s/blog_4bc7c789010162aq.html


译者注
这是一篇指导您如何在Microsoft Visual Studio下学习STL并进行实践的文章。这篇文章从STL的基础知识讲起,循序渐进,逐步深入,涉及到了STL编写代码的方法、STL代码的编译和调试、命名空间(namespace)、STL中的ANSI / ISO字符串、各种不同类型的容器(container)、模板(template)、游标(Iterator)、算法(Algorithms)、分配器(Allocator)、容器的嵌套等方面的问题,作者在这篇文章中对读者提出了一些建议,并指出了使用STL时应该注意的问题。这篇文章覆盖面广,视角全面。不仅仅适合初学者学习STL,更是广大读者使用STL编程的实践指南。
STL简介
STL (标准模版库,Standard Template Library)是当今每个从事C++编程的人需要掌握的一项不错的技术。我觉得每一个初学STL的人应该花费一段时间来熟悉它,比如,学习STL时会有急剧升降的学习曲线,并且有一些命名是不太容易凭直觉就能够记住的(也许是好记的名字已经被用光了),然而如果一旦你掌握了STL,你就不会觉得头痛了。和MFC相比,STL更加复杂和强大。
STL有以下的一些优点:
可以方便容易地实现搜索数据或对数据排序等一系列的算法;
调试程序时更加安全和方便;
即使是人们用STL在UNIX平台下写的代码你也可以很容易地理解(因为STL是跨平台的)。

背景知识
写这一部分是让一些初学计算机的读者在富有挑战性的计算机科学领域有一个良好的开端,而不必费力地了解那无穷无尽的行话术语和沉闷的规则,在这里仅仅把那些行话和规则当作STLer们用于自娱的创造品吧。
使用代码
本文使用的代码在STL实践中主要具有指导意义。
一些基础概念的定义
模板(Template)——类(以及结构等各种数据类型和函数)的宏(macro)。有时叫做甜饼切割机(cookie cutter),正规的名称应叫做范型(generic)——一个类的模板叫做范型类(generic class),而一个函数的模板也自然而然地被叫做范型函数(generic function)。
STL——标准模板库,一些聪明人写的一些模板,现在已成为每个人所使用的标准C++语言中的一部分。
容器(Container)——可容纳一些数据的模板类。STL中有vector,set,map,multimap和deque等容器。
向量(Vector)——基本数组模板,这是一个容器。
游标(Iterator)——这是一个奇特的东西,它是一个指针,用来指向STL容器中的元素,也可以指向其它的元素。
Hello World程序
我愿意在我的黄金时间在这里写下我的程序:一个hello world程序。这个程序将一个字符串传送到一个字符向量中,然后每次显示向量中的一个字符。向量就像是盛放变长数组的花园,大约所有STL容器中有一半是基于向量的,如果你掌握了这个程序,你便差不多掌握了整个STL的一半了。

//程序:vector演示一
//目的:理解STL中的向量
// #include “stdafx.h” -如果你使用预编译的头文件就包含这个头文件

include // STL向量的头文件。这里没有”.h”。

include // 包含cout对象的头文件。

using namespace std; //保证在程序中可以使用std命名空间中的成员。
char* szHW = “Hello World”;
//这是一个字符数组,以”\0”结束。
int main(int argc, char* argv[])
{
vector vec; //声明一个字符向量vector (STL中的数组)
//为字符数组定义一个游标iterator。
vector ::iterator vi;
//初始化字符向量,对整个字符串进行循环,
//用来把数据填放到字符向量中,直到遇到”\0”时结束。
char* cptr = szHW; // 将一个指针指向“Hello World”字符串
while (*cptr != ‘\0’)
{ vec.push_back(*cptr); cptr++; }
// push_back函数将数据放在向量的尾部。
// 将向量中的字符一个个地显示在控制台
for (vi=vec.begin(); vi!=vec.end(); vi++)
// 这是STL循环的规范化的开始——通常是 “!=” , 而不是 “<”
// 因为”<” 在一些容器中没有定义。
// begin()返回向量起始元素的游标(iterator),end()返回向量末尾元素的游标(iterator)。
{ cout << vi; } // 使用运算符 “” 将数据从游标指针中提取出来。
cout << endl; // 换行
return 0;
}
push_back是将数据放入vector(向量)或deque(双端队列)的标准函数。Insert是一个与之类似的函数,然而它在所有容器中都可以使用,但是用法更加复杂。end()实际上是取末尾加一(取容器中末尾的前一个元素),以便让循环正确运行——它返回的指针指向最靠近数组界限的数据。就像普通循环中的数组,比如for (i=0; i<6; i++) {ar[i] = i;} ——ar[6]是不存在的,在循环中不会达到这个元素,所以在循环中不会出现问题。
STL的烦恼之一——初始化
STL令人烦恼的地方是在它初始化的时候。STL中容器的初始化比C/C++数组初始化要麻烦的多。你只能一个元素一个元素地来,或者先初始化一个普通数组再通过转化填放到容器中。我认为人们通常可以这样做:

//程序:初始化演示
//目的:为了说明STL中的向量是怎样初始化的。

include // 和

include

using namespace std;
int ar[10] = { 12, 45, 234, 64, 12, 35, 63, 23, 12, 55 };
char* str = “Hello World”;
int main(int argc, char* argv[])
{
vector vec1(ar, ar+10);
vector vec2(str, str+strlen(str));
return 0;
}
在编程中,有很多种方法来完成同样的工作。另一种填充向量的方法是用更加熟悉的方括号,比如下面的程序:
//程序:vector演示二
//目的:理解带有数组下标和方括号的STL向量

include

include

include

using namespace std;
char* szHW = “Hello World”;
int main(int argc, char* argv[])
{
vector vec(strlen(sHW)); //为向量分配内存空间
int i, k = 0;
char* cptr = szHW;
while (*cptr != ‘\0’)
{ vec[k] = *cptr; cptr++; k++; }
for (i=0; i

pragma warning(disable: 4786)

另一条需要注意的是,你必须确保在两个尖括号之间或尖括号和名字之间用空格隔开,因为是为了避免同“>>”移位运算符混淆。比如
vector

include

include

include

using namespace std;
int main(int argc, char* argv[])
{
set strset;
set ::iterator si;
strset.insert(“cantaloupes”);
strset.insert(“apple”);
strset.insert(“orange”);
strset.insert(“banana”);
strset.insert(“grapes”);
strset.insert(“grapes”);
for (si=strset.begin(); si!=strset.end(); si++)
{ cout << *si << ” “; }
cout << endl;
return 0;
}
// 输出: apple banana cantaloupes grapes orange
//注意:输出的集合中的元素是按字母大小顺序排列的,而且每个值都不重复。
如果你感兴趣的话,你可以将输出循环用下面的代码替换:
copy(strset.begin(), strset.end(), ostream_iterator(cout, ” “));
.集合(set)虽然更强大,但我个人认为它有些不清晰的地方而且更容易出错,如果你明白了这一点,你会知道用集合(set)可以做什么。
所有的STL容器
容器(Container)的概念的出现早于模板(template),它原本是一个计算机科学领域中的一个重要概念,但在这里,它的概念和STL混合在一起了。下面是在STL中出现的7种容器:
vector(向量)——STL中标准而安全的数组。只能在vector 的“前面”增加数据。
deque(双端队列double-ended queue)——在功能上和vector相似,但是可以在前后两端向其中添加数据。
list(列表)——游标一次只可以移动一步。如果你对链表已经很熟悉,那么STL中的list则是一个双向链表(每个节点有指向前驱和指向后继的两个指针)。
set(集合)——包含了经过排序了的数据,这些数据的值(value)必须是唯一的。
map(映射)——经过排序了的二元组的集合,map中的每个元素都是由两个值组成,其中的key(键值,一个map中的键值必须是唯一的)是在排序或搜索时使用,它的值可以在容器中重新获取;而另一个值是该元素关联的数值。比如,除了可以ar[43] = “overripe”这样找到一个数据,map还可以通过ar[“banana”] = “overripe”这样的方法找到一个数据。如果你想获得其中的元素信息,通过输入元素的全名就可以轻松实现。
multiset(多重集)——和集合(set)相似,然而其中的值不要求必须是唯一的(即可以有重复)。
multimap(多重映射)——和映射(map)相似,然而其中的键值不要求必须是唯一的(即可以有重复)。
注意:如果你阅读微软的帮助文档,你会遇到对每种容器的效率的陈述。比如:log(n*n)的插入时间。除非你要处理大量的数据,否则这些时间的影响是可以忽略的。如果你发现你的程序有明显的滞后感或者需要处理时间攸关(time critical)的事情,你可以去了解更多有关各种容器运行效率的话题。
怎样在一个map中使用类?
Map是一个通过key(键)来获得value(值)的模板类。
另一个问题是你希望在map中使用自己的类而不是已有的数据类型,比如现在已经用过的int。建立一个“为模板准备的(template-ready)”类,你必须确保在该类中包含一些成员函数和重载操作符。下面的一些成员是必须的:
缺省的构造函数(通常为空)
拷贝构造函数
重载的”=”运算符

你应该重载尽可能多的运算符来满足特定模板的需要,比如,如果你想定义一个类作为 map中的键(key),你必须重载相关的运算符。但在这里不对重载运算符做过多讨论了。
//程序:映射自定义的类。
//目的:说明在map中怎样使用自定义的类。

include

include

include

include

using namespace std;
class CStudent
{
public :
int nStudentID;
int nAge;
public :
//缺省构造函数——通常为空
CStudent() { }
// 完整的构造函数
CStudent(int nSID, int nA) { nStudentID=nSID; nAge=nA; }
//拷贝构造函数
CStudent(const CStudent& ob)
{ nStudentID=ob.nStudentID; nAge=ob.nAge; }
// 重载“=”
void operator = (const CStudent& ob)
{ nStudentID=ob.nStudentID; nAge=ob.nAge; }
};
int main(int argc, char* argv[])
{
map

include //如果要使用算法函数,你必须要包含这个头文件。

include // 包含accumulate(求和)函数的头文件

include

include

using namespace std;
int testscore[] = {67, 56, 24, 78, 99, 87, 56};
//判断一个成绩是否通过了考试
bool passed_test(int n)
{
return (n >= 60);
}
// 判断一个成绩是否不及格
bool failed_test(int n)
{
return (n < 60);
}
int main(int argc, char* argv[])
{
int total;
// 初始化向量,使之能够装入testscore数组中的元素
vector vecTestScore(testscore,
testscore + sizeof(testscore) / sizeof(int));
vector ::iterator vi;
// 排序并显示向量中的数据
sort(vecTestScore.begin(), vecTestScore.end());
cout << “Sorted Test Scores:” << endl;
for (vi=vecTestScore.begin(); vi != vecTestScore.end(); vi++)
{ cout << *vi << “, “; }
cout << endl;
// 显示统计信息
// min_element 返回一个 iterator 类型的对象,该对象指向值最小的那个元素。
//“*”运算符提取元素中的值。
vi = min_element(vecTestScore.begin(), vecTestScore.end());
cout << “The lowest score was ” << *vi << “.” << endl;
//与min_element类似,max_element是选出最大值。
vi = max_element(vecTestScore.begin(), vecTestScore.end());
cout << “The highest score was ” << *vi << “.” << endl;
// 使用声明函数(predicate function,指vecTestScore.begin()和vecTestScore.end())来确定通过考试的人数。
cout << count_if(vecTestScore.begin(), vecTestScore.end(), passed_test) <<
” out of ” << vecTestScore.size() <<
” students passed the test” << endl;
// 确定有多少人考试挂了
cout << count_if(vecTestScore.begin(),
vecTestScore.end(), failed_test) <<
” out of ” << vecTestScore.size() <<
” students failed the test” << endl;
//计算成绩总和
total = accumulate(vecTestScore.begin(),
vecTestScore.end(), 0);
// 计算显示平均成绩
cout << “Average score was ” <<
(total / (int)(vecTestScore.size())) << endl;
return 0;
}
Allocator(分配器)
Allocator用在模板的初始化阶段,是为对象和数组进行分配内存空间和释放空间操作的模板类。它在各种情况下扮演着很神秘的角色,它关心的是高层内存的优化,而且对黑盒测试来说,使用Allocator是最好的选择。通常,我们不需要明确指明它,因为它们通常是作为不用添加的缺省的参数出现的。如果在专业的测试工作中出现了Allocator,你最好搞清楚它是什么。
Embed Templates(嵌入式模版)和Derive Templates(基模板)

每当你使用一个普通的类的时候,你也可以在其中使用一个STL类。它是可以被嵌入的:
class CParam
{
string name;
string unit;
vector vecData;
};
或者将它作为一个基类:
class CParam : public vector
{
string name;
string unit;
};
STL模版类作为基类时需要谨慎。这需要你适应这种编程方式。
模版中的模版
为构建一个复杂的数据结构,你可以将一个模板植入另一个模板中(即“模版嵌套”)。一般最好的方法是在程序前面使用typedef关键字来定义一个在另一个模板中使用的模版类型。
// 程序:在向量中嵌入向量的演示。
//目的:说明怎样使用嵌套的STL容器。

include

include

using namespace std;
typedef vector VEC_INT;
int inp[2][2] = {{1, 1}, {2, 0}};
// 要放入模板中的2x2的正则数组
int main(int argc, char* argv[])
{
int i, j;
vector vecvec;
// 如果你想用一句话实现这样的嵌套,你可以这样写:
// vector

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值