简单了解STL

来源:互联网 发布:网络人 编辑:程序博客网 时间:2024/06/07 12:33
STL是StandardTemplate Library(标准模板库)的缩写,是一个高效的C++程序库,它被容纳于C++标准程序库(C++ StandardLibrary)中,是ANSI/ISOC++标准的一部分。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。另外,string类型用于处理字符串,也算作是STL的一部分。

STL的代码从广义上讲分为三类:container(容器)iterator(迭代器)algorithm(算法),几乎所有的代码都采用了模板(类模板和函数模板)的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。在C++标准中,STL被组织为下面的13个头文件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack>和<utility>。

1、容器

从实现的角度看,STL容器是一种类模板。STL是经过精心设计的,为了减小我们使用容器的难度,大多数容器都提供了相同的成员函数,尽管一些成员函数的实现是不同的。STL中的常用容器包括:顺序性容器(vector、deque、list)、关联容器(map、set)、容器适配器(queue、stack)

容器是容纳、包含一组元素或元素集合的对象。不管是C++内建的基本数据类型或是用户自定义的类类型的数据,都可以存入STL的容器中。

注意:如果存储在容器中的元素的类型是用户自定义的类类型,那么至少要为该类提供默认的构造函数、析构函数和赋值运算符函数。一些编译器还需要重载一些关系操作符函数(至少需要重载==和<,还可能需要重载!=和>),即使程序并不需要用到它们。另外,如果用户自定义的类中有指针数据成员,还必须提供复制构造函数和函数operator=,因为插入操作使用的是插入元素的一个副本,而不是元素本身

STL容器按存取顺序大致分为两种:

    序列(sequence)容器关联(associative)容器

序列容器主要包括vector(向量)、list(表)、deque(双端队列)、stack(栈)、queue(队列)、priority_queue(优先队列)等。其中,stack和queue由于只是将deque改头换面,其实是一种容器适配器,但它的用途在软件领域比deque广泛。priority_queue也是一种容器适配器。序列容器中只能包含一种类型的数据元素,而且各元素的排列顺序完全按照元素插入时的顺序。
    关联容器主要包括set(集合)、multiset(多重集合)、map(映射)、multimap(多重映射),可以存储值的集合或键值对。键是关联容器中存储在有序序对中的特定类型的值。map和multimap存储和操作的是键和与键相关的值,其元素是有关联的<键,值>数据对。set和multiset存储和操作的只是键,其元素是由单个数据构成。

1.1 vector(向量)

vector类似于数组,它存储具有相同数据类型的一组元素,可以从后面快速地插入与删除元素,可以快速地随机访问元素,但是在序列中间插入、删除元素较慢,因为需要移动插入或删除处后面的所有元素。向量能够动态改变自身大小,当要将一个元素插入到一个已满的向量时,会为向量分配一个更大的内存空间,将向量中的元素复制到新的内存空间中,然后释放旧的内存空间。但是重新分配更大空间需要进行大量的元素复制,从而增大了性能开销。

#include <iostream>using namespace std;#include <vector>#include <string>class Person           //声明Person类{public:   Person(char *Name, char Sex, int Age )  //构造函数   {  strcpy(name, Name); sex = Sex; age = Age;  }   ~Person( ){ }       //析构函数    void Show( )    {  cout << " The person’s name: " << name << endl;       cout << "             sex: " << sex << endl;       cout << "             age: " << age << endl;    }private:                  //私有数据成员   char name[11];  //姓名,不超过5个汉字   char sex;           //性别,M:男,F:女   int age;              //年龄};int main( ){  vector<Person> v;     //构造空向量v   Person person1("Tom", 'M', 18);   v.push_back(person1);  //在向量v的尾部插入person1   Person person2("Mary", 'F', 19);    v.push_back(person2);  //在向量v的尾部插入person2   Person person3("Mike", 'M', 19);    v.push_back(person3);  //在向量v的尾部插入person3   for ( int i = 0; i < 3; i++ )   v[i].Show();   return 0;  }

程序运行结果如下:

Theperson’s name: Tom

                      sex: M

                       age: 18

Theperson’s name: Mary

                    sex: F

                      age: 19

Theperson’s name: Mike

                      sex: M

                       age: 19

1.2 list(表)

STL中的list是一个双向链表,可以从头到尾或从尾到头访问链表中的节点,节点可以是任意数据类型。链表中节点的访问常常通过迭代器进行。它的每个元素间用指针相连,不能速记访问元素。为了访问表容器中指定的元素,必须从第一个位置(表头)开始,随着指针从一个元素到下一个元素,直到找到要找的元素。但插入元素比vector快,对每个元素分别分配空间,不存在空间不够、重新分配的情况。

一个简单的List容器使用的例子
#include<iostream>#include<list>         //链表头文件using namespace std;int main(){    int i;    list<int> L1,L2;     int a1[]={100,90,80,70,60};    int a2[]={30,40,50,60,60,60,80};     for(i=0;i<5;i++)  L1.push_back(a1[i]);     for(i=0;i<7;i++)  L2.push_back(a2[i]); L1.reverse();                   //将L1链表倒序    L1.merge(L2);                //将L2合并到L1链表中    cout<<"L1的元素个数为:"<<L1.size()<<endl;     L1.unique(); //删除L1中相邻位置的相同元素,只留1个    while(!L1.empty()){         cout<<L1.front()<<"\t";          L1.pop_front();      //删除L1的链首元素    }    cout<<endl;    return 0;} 

程序运行结果如下:

L1的元素个数为:12

30    40   50   60    70   80   90    100


1.3 stack(堆栈)

STL中的stack容器不是重新创建的,它只是对已有容器做适当的调整。默认情况下,双端队列(deque)是基础容器,但是我们可以用下面的声明选择向量或链表。
stack<int>stack1;                       //默认为双端队列
stack<int,vector<int>> stack2; //向量
stack<int,list<int>> stack3;      //链表
stack是一种较简单的操作受限的容器,只允许在一端存取元素,后进栈的元素先出栈,即LIFO(lastin first out)。

1.4 set和multiset

set和multiset都属于关联容器,他们提供了控制数字(包括字符及串)集合的操作,集合中的数字称为关键字(也称为),不需要有另一个值与关键字相关联。set和multiset会根据特定的排序准则,自动将元素排序,两者提供的操作方法基本相同,只是multiset允许元素重复set不允许重复

#include<iostream>#include<set>using namespace std;int main(){   set<int> s;    set<int>::iterator ps;    multiset <int> ms;    multiset<int>::iterator pms;    s.insert(1);  s.insert(8);  s.insert(2);   s.insert(1);    ms.insert(1); ms.insert(8); ms.insert(2); ms.insert(1);cout<<" the set:"<<endl;    for(ps=s.begin();ps!=s.end();ps++)        cout<<*ps<<" ";    cout<<endl;    cout<<" the multiset:"<<endl;    for(pms=q.begin();pms!=q.end();pms++)         cout<<*pms<<" ";    cout<<endl;    return 0;}

程序运行结果如下:

theset

1  2  8

themultiset:

1  1 2  8  


1.5 map和multimap

它们也属于关联容器,都是映射类的模板。映射是实现关键字与值关联的存储结构,可以使用一个关键字来访问相应的数值。关键字可以是数值,如学号070203200,它对应的名字为“张三”,班级为“0707班”。map中的元素不允许重复,而multimap中的元素是可以重复的。它们的迭代器提供了两个数据成员:一个是first,用于访问关键字;另一个是second,用于访问值。

1.6 string

STL中的string是一种特殊类型的容器,原因是它除了可作为字符类型的容器外,更多的是作为一种数据类型——字符串,可以像int、double之类的基本数据类型那样定义string类型的数据,并进行各种运算。
string容器在STL中被实现为一个类——string类,该类提供了用于字符串赋值(assign)、比较(compare)的函数,同时还重载了赋值和比较运算符。表8-6列出了string类重载的运算符。

注意:与char*字符串不同的是,string类型的字符串不一定以“\0”终止,其长度可用成员函数length读取,可用下标运算符[]访问其中的单个字符,起始下标为0,终止下标是字符串长度减1。


string类提供的常用成员函数如下:
出于介绍成员函数的需要,我们事先定义两个string类型变量s1、s2。
strings1="ABCDEFG";
strings2="0123456123";

substr(n1,n):取子串函数,从当前字符串的n1下标开始,取出n个字符。如“s=s1.substr(2,3)”的结果为:s="CDE"
swap(s):将当前字符串与s交换。如“s1.swap(s2)”的结果为:s1="0123456123",s2="ABCDEFG"
size()/length():计算当前字符串中目前存放的字符个数。如“s1.length()”的结果为:7

capacity():计算字符串的容量(即在不增加内存的情况下,可容纳的字符个数)。如“s1.capacity()”的结果为:31
max_size():计算string类型数据的最大容量。如“s1.max_size()”的结果为:4294967293
find(s):在当前字符串中查找子串s,如找到,就返回s在当前字符串中的起始位置;如没找到,返回常数string::npos。如“s1.find("EF")”的结果为:4

rfind(s):同find,但从后向前进行查找。如“s1.rfind("BCD")”的结果为:1
find_first_of(s):在当前串中查找子串s第一次出现的位置。如“s2.find_first_of("123")”的结果为:1
find_last_of(s):在当前串中查找子串s最后一次出现的位置。如“s2.find_last_of("123")”的结果为:9

replace(n1,n, s):替换当前字符串中的字符,n1是替换的起始下标,n是要替换的字符个数,s是用来替换的字符串。
replace(n1,n, s, n2, m):替换当前字符串中的字符,n1是替换的起始下标,n是要替换的字符个数,s是用来替换的字符串,n2是s中用来替换的起始下标,m是s中用于替换的字符个数。如“s1.replace (2, 3, s2, 2, 3)”的结果为:s1="AB234FG"
insert(n,s):在当前串的下标位置n之前,插入s串。
insert(n1,s, n2, m):在当前串的n1下标之后插入s串,n2是s串中要插入的起始下标,m是s串中要插入的字符个数。

2、迭代器(iterator)

迭代器是STL中算法和容器的粘合剂,用来将算法和容器联系起来,起到一种关联的作用。迭代器类似于指针,但它是基于模板的“功能更强大、更智能、更安全的指针”,用于指示容器中的元素位置,通过迭代器能够遍历容器中的每个元素。STL算法利用它们对容器中的对象序列进行遍历。

迭代器提供的主要操作如下:
operator*: 返回当前位置上的元素值
operator++:将迭代器前进到下一个元素位置
operator--:将迭代器后退到前一个元素位置
operator=:为迭代器赋值
begin():      指向容器起点(即第一个元素)位置
end():指向容器的结束点(最后一个元素之后)位置
rbegin():   指向按反向顺序的第一个元素位置
rend():      指向按反向顺序的最后一个元素后的位置

链表中迭代器的应用例子

#include<iostream>#include<list>using namespace std;int main(){int i;//L1、L2为空链表,L3为有10个元素的链表list<int> L1, L2, L3(10); list<int>::iterator iter;  //定义迭代器iter int a1[]={100,90,80,70,60};  int a2[]={30,40,50,60,60,60,80};
//插入L1链表元素,在表尾插入    for(i=0;i<5;i++)        L1.push_back(a1[i]);     //插入L2链表元素,在表头插入    for(i=0;i<7;i++)         L2.push_front(a2[i]);     //通过迭代器顺序输出L1的所有元素    for(iter=L1.begin();iter!=L1.end();iter++)          cout<<*iter<<"\t";    cout<<endl;int sum=0;    //通过迭代器反向输出L2的所有元素    for(iter=--L2.end();iter!=L2.begin();iter--){          cout<<*iter<<"\t";          sum+=*iter;  //计算L2所有链表节点的总和    }    cout<<"\nL2: sum="<<sum<<endl;    int data=0;    //通过迭代器修改L3链表的内容    for(iter=L3.begin();iter!=L3.end();iter++)         *iter=data+=10;     //通过迭代器输出L3的内容    for(iter=L3.begin();iter!=L3.end();iter++)         cout<<*iter<<"\t";    cout<<endl;//通过迭代器反序输出L3的所有元素    for ( iter = --L3.end( ); iter != L3.begin( ); iter-- )     cout << *iter << "\t";    cout << *L3.begin( ) << endl;    //通过逆向迭代器反序输出L3的所有元素    for ( rsiter = --L3.rend( ); rsiter != L3.rbegin( ); rsiter-- )        cout << *rsiter << "\t";      cout << *L3.rbegin( ) << endl;    return 0;}

3、算法

STL算法用于操控各种容器,同时也可以操控内建数组。比如
find算法用于在容器中查找等于某个特定值的元素,
for_each算法用于将某个函数应用到容器中的各个元素上,
sort算法用于对容器中的元素排序等。这样一来,只要熟悉了STL,许多代码可以被大大的简化,只要通过调用一两个算法,就可以完成所需要的功能并大大地提高效率。

STL算法主要由头文件<algorithm><numeric><functional>组成:
<algorithm>是所有STL头文件中最大的一个,它是由很多函数模板组成的。可以认为每个函数模板在很大程度上都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历、赋值、修改、移动、移除、反转、排序、合并等。如果使用这些函数模板,要在头文件中包含<algorithm>。

<numeric>体积很小,只包括几个在序列上面进行简单数学运算的函数模板,包括加法和乘法在序列上的一些操作。
<functional>中则定义了一些类模板,用以声明函数对象

STL中的copy算法和sort算法的应用
#include <iostream>#include <string>             //用于人机界面交互#include <vector>   //为了使用vector容器#include <algorithm>      //为了使用sort算法#include <iterator>  //为了使用输入输出迭代器using namespace std;int main(void) { typedef vector<int> IntVector;typedef istream_iterator<int> IstreamItr;typedef ostream_iterator<int> OstreamItr;typedef back_insert_iterator< IntVector > BackInsItr;// STL中的vector容器IntVector num;//从标准输入设备读入整数,直到输入的是非整型数据为止cout << "请输入整数序列,按任意非数字键并回车结束输入\n";copy(IstreamItr(cin), IstreamItr(), BackInsItr(num));//提示程序状态cout << "排序中……\n";// STL中的排序算法sort(num.begin(), num.end());cout<<"排序完毕的整数序列:\n";copy(num.begin(), num.end(), OstreamItr(cout, "\n"));//使输出窗口暂停以观察结果system("pause");return 0;}
4、函数对象
v利用STL中的算法进行编程可以减轻编程人员的许多负担,但大多数STL算法都需要函数或函数对象作为参数,比如用于排序的sort算法,其函数模板定义如下。

   template<class RandomAccessIterator>

   voidsort(RandomAccessIterator first, RandomAccessIterator last);

   template<class RandomAccessIterator, class Compare>

   voidsort(RandomAccessIterator first, RandomAccessIterator last, Compare comp);


#include <iostream>using namespace std; #include <algorithm>#include <functional>int main( ){   int a[] = {1,3,2,4,5,7};      sort(&a[0], &a[6]);  //递增排序    for(int i = 0; i < 6; i++)  cout<<a[i]<<' ';    cout<<endl;     sort(&a[0], &a[6], greater<int>()); //递减排序    for(int i = 0; i < 6; i++)  cout<<a[i]<<' ';      cout<<endl;    return 0; }

函数对象究竟是什么?

    简单地说,函数对象其实就是一些使用起来像调用函数一样的对象,如:一般的函数,函数指针,或重载了函数调用运算符()的类的实例等,使用这些对象的方式好像调用函数一样,所以称这些对象为函数对象。

函数对象定义的示例代码
#include <iostream>#include <algorithm>#include <functional>#include <vector>using namespace std;void Print(int x) //普通函数{  if(x>=0&&x<=100) cout<<x<< " ";  }typedef void(*ptPrint)(int x);ptPrint prt=Print; //函数对象指针class cPrint //重载了( )运算符的类cPrint{public:     void operator () (int val)     {   if(val>=0&&val<=100)  cout<<val<< " ";  }};int main( ){   int a[] = {1,3,2,4,5,7};    const int N=sizeof(a)/sizeof(int);    vector<int> vectorA(a,a+N);     for_each(vectorA.begin(), vectorA.end(), Print);    cout<<endl;    for_each(vectorA.begin(), vectorA.end(), prt);    cout<<endl;    for_each(vectorA.begin(), vectorA.end(), cPrint( ) );    cout<<endl;    return 0; }
在实际中,往往会根据调用函数对象时参数的多少将函数对象分为3种形式:无参函数对象,一元函数对象和二元函数对象。无参函数对象与一元、二元函数对象有很大的区别,STL中定义了以下两个函数对象的基类unary_function和binary_function,分别用于设计一元、二元函数对象。

template <class Arg, class Result>struct unary_function {    typedef Arg argument_type;    typedef Result result_type;};template <class Arg1, class Arg2, class Result>struct binary_function {     typedef Arg1 first_argument_type;     typedef Arg2 second_argument_type;     typedef Result result_type;};

有许多学生的成绩以对象的形式存在于一个向量vector中,学生成绩类有学号、姓名、成绩3个数据成员。请使用标准模板库的sort算法对这些学生成绩进行排序。
#include <vector>    #include <algorithm>    #include <functional>    #include <string>    #include <iostream>   using namespace std;    #pragma argsusedclass Grade{public:    Grade(int id,string name,int score)    {   ID=id;  Name=name;  Score=score;  }int ID;    string Name;    int Score;};void printScore(Grade grade) //打印学生成绩{    cout<<grade.ID<< " "              <<grade.Name<< " "             <<grade.Score<<endl; }//函数对象类class gradeCompare:binary_function<Grade,Grade, bool>{public:    bool operator () (Grade X,Grade Y) const   {  return X.Score>Y.Score;   }}; int main( ){   vector<Grade> finalGrade;    finalGrade.push_back(Grade(1,"A",56));    finalGrade.push_back(Grade(2,"B",57));    finalGrade.push_back(Grade(3,“C”,58));sort(finalGrade.begin(),finalGrade.end(),gradeCompare());for_each(finalGrade.begin(),finalGrade.end(),printScore);    return 0;}



更多与STL相关参阅《C++标准程序库》《STL源码剖析》

0 0
原创粉丝点击