C++_标准模板库STL概念介绍1-建立感性认知

标准模板库的英文缩写是STL,即Standard Template Library。

STL里面有什么呢?

  它提供了一组表示容器、迭代器、函数对象和算法的模板。

  容器是一个与数组类似的单元,可以存储若干值。

  STL容器是同质的,即存储的值的类型相同。

  算法是完成特定任务的处方(例如对数组进行排序或在链表中查找特定的值)。

  迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,属于广义指针。

  函数对象是类似于函数的对象,可以是类对象或函数指针(包括函数名,因为函数名被用作指针)。

 

STL使得能够构造各种容器(数组、队列、链表)和执行各种操作(搜索、排序和随机排列)。

 

STL是C++标准的一部分。

 

STL不是面向对象的编程,而是一种不同的编程模式——泛型编程(generic programming)

这使得STL在功能和方法上都很有趣。

这篇文章主要通过介绍例子,来对容器、迭代器和算法有感性的认识;领会泛型编程的精神;介绍底层的设计理念,简要地介绍STL。

==================================

一、模板类vector

在计算机中,矢量对应于数组,而不是数学矢量。

在数学中,可以使用N个分量来表示N维数学矢量

因此从这方面讲,数学矢量类似于一个N维数组。

当然,数学矢量还有一些计算机矢量不具备的特征,例如内乘积和外乘积。

 

接下来看一下计算矢量有哪些特征:

1、存储了一组可随机访问的值;

2、可以使用索引来直接访问矢量的第n个元素;

3、可以创建一个vector对象,将对象赋给另一个对象,使用[ ]运算符来访问vector元素。

 

要使类成为通用的,应将它设计为模板类。

STL正是这样做的——在头文件vector中定义了一个vector模板。

 

要创建vector模板对象,可使用通常的<type>表示法来指出要使用的类型

另外,vector模板使用动态内存分配,因此可以用初始化参数来指出需要多少矢量:

 

#include vector

using namespace std;

vector <int> rating(5);   //a vector of 5 ints

int n;

cin >> n;

vector<double> scores(n);  //a vector of n doubles

由于运算符[ ]被重载,因此创建vector对象后,可以使用通常的数组表示法来访问各个元素:

rating [0] = 9;

for (int i =0; i<n; i++)

  cout<< scores[i] <<endl;

 

分配器

与string类似,各种STL容器模板都接受一个可选的模板参数,该参数指定使用哪个分配器对象来管理内存

例如,vector模板的开头与下面类似:

template <class T, class Allocator = allocator<T>>

  class vector {...

如果省略该模板参数的值,则容器模板将默认使用allocator<T>类。这个类使用new和delete。

 

接下来有个示例程序,它使用了这个类。该程序创建了vector对象,一个是int规范、一个是string规范,它们都包含5个元素。

 

 1 // vect1.cpp  -- introducing the vector template
 2 #include <iostream>
 3 #include <string>
 4 #include <vector>
 5 
 6 const int NUM = 5;
 7 int main()
 8 {
 9     using std::vector;
10     using std::string;
11     using std::cin;
12     using std::cout;
13 
14     vector<int> ratings(NUM);
15     vector<string> titles(NUM);
16     cout<<"You will do exactly as told.Your will enter\n"<<NUM<<" book titles and your ratings (0-10).\n"; 
17     int i;
18     for (i=0; i<NUM;i++)
19     {
20         cout << "Enter title #"<<i+1<<": ";
21         getline(cin,titles[i]);
22         cout<<"Enter your rating (0-10):";
23         cin >>ratings[i];
24         cin.get();
25     }
26     cout<<"Thank you. You entered the following:\n"<<"Rating\t\Book\n";
27     for (i=0; i<NUM; i++)
28     {
29         cout<<ratings[i]<<"\t"<<titles[i]<<endl;
30     }
31     return 0;     
32 }

 

 

 

==================================

二、可对矢量执行的操作

 除了分配空间外,还可以对模板完成一些基本的操作;

STL容器都提供了一些基本方法,其中包括size()——返回容器中元素数目、swap()——交换两个容器的内容、begin()——返回一个指向容器中第一个元素的迭代器、end()——返回一个表示超过容器尾的迭代器。

 

那么什么是迭代器呢?它实际上是一个广义指针。它可以是指针,也可以是一个可对其执行类似指针的操作——如解除引用和递增。

通过将指针广义化为迭代器,让STL能够为各种不同的容器类提供统一的接口。

该迭代器的类型是一个名为iterator的typedef,其作用域为整个类。

 

要为vector的double类型规范声明一个迭代器,可以这样做:

vector<double>::iterator pd;   //pd an iterator

假设scores是一个vector<double>类型的对象;

vector<double> scores;

则可以使用迭代器pd执行这样的操作:

pd = scores.begin();

*pd = 22.3;

++pd;

可以看到迭代器的行为像一个指针;

 

另外C++11还有一个自动类型推断的功能如下:

auto pd = scores.begin();

 

接下来讨论什么是超过结尾(past-to-end)?它是一种迭代器,指向容器最后一个元素后面的一个元素。

这与C风格字符串的最后一个字符空字符类似,空字符是一个值。

而“超过结尾”是一个指向元素的指针(迭代器)。

end()成员函数标识超过结尾的位置。

如果将迭代器设置为容器的第一个元素,并不断地递增,最终它将到达容器的末尾,从而遍历整个容器的内容。

则可以使用下列代码来显示容器的内容:

for(pd = scores.begin(); pd != scores.end(); pd++)

  cout<<*pd<< endl;

 

另外push-back()是一个方便的方法,它将元素添加到容器的末尾。这样做的时候,它将负责管理内存,增加矢量的长度,使之能容纳新的成员。

可以这样写代码:

vector<double> scores;

double temp;

while(cin>>temp && temp>=0)

  socres.push_back(temp);

cout<<"Your entered"<<scores.size()<<"scores.\n";

每次循环都给对象添加元素,无需了解元素的数目,只要有内存,就能添加元素。

 

earse()方法删除容器中指定区间的元素。它接受两个迭代器参数,这些参数定义了要删除的区间。

了解STL如何使用两个迭代器来定义区间非常重要。

第一个迭代器指向区间的起始位置,第二个迭代器指向位于区间终止处的后一个位置。

代码如下,该代码表示删除begin()和begin()+1指向的元素:

scores.erase(scores.begin(), scores.begin()+2);

我们发现STL文档中会使用[ )方法来表示区间。注意,这种不是C++的标准,仅用于文档表达;

[p1, p2)用来表示p1到p2(不包括p2)的区间。这是一种前闭后开的区间。STL容器就是根据这个约定来定义区间范围的。

[begin(), end()]表示集合的所有内容。

 

insert()方法的功能和erase()相反。它接受3个迭代器参数,第一个参数指定了新元素的插入位置,第二个和第三个参数定义了要插入的属于另一个对象的新元素区间,该区间通常是另一个容器对象的一部分。

代码如下:

vector<int> old_v;

vector<int> new_v;

...

old_v.insert(old_v.begin(), new_v.begin()+1, new_v.end());

上述代码表示将矢量new_v中的除第一个元素以外的所有元素插入到old_v矢量的第一个元素前面;

 

另外,对于上面这种情况来说,拥有超尾元素(past-to-end)带来了方便。因为这使得在矢量尾部添加元素非常简单。

old_v.insert(old_v.end(), new_v.begin()+1, new_v.end());

 

接下来有个示例程序,演示了各个容器方法的使用。

 1 //vect2.cpp  -- methods and iterators
 2 #include <iostream>
 3 #include <string>
 4 #include <vector>
 5 
 6 struct Review {
 7     std::string title;
 8     int rating;
 9 };
10 
11 bool FillReview(Review & rr);
12 void ShowReivew(const Review & rr);
13 
14 int main()
15 {
16     using std::cout;
17     using std::vector;
18     vector<Review> books;
19     Review temp;
20     while (FillReview(temp))
21         book.push_back(temp);
22     int num = books.size();
23     if (num >0)
24     {
25         cout<<"Thank you. Your entered the following:\n"
26                <<"Rating\tBook\n";
27         for (int i =0; i<num; i++)
28             ShowReview(books[i]);
29         cout<<"Reprising:\n"<<"Rating\tBooks\n";
30         vector<Review>::iterator pr;
31         for (pr = books.begin(); pr != books.end(); pr++) 
32             ShowReview(*pr);
33         vector<Review>oldlist(books);
34         if (num >3)
35         {
36             //remove 2 items
37             books.erase(begin.begin()+1, books.begin()+3)
38             cout<< "After erasure:\n";
39             for (pr = books.begin(); pr != books.end(); pr++)
40                  ShowReview(*pr);
41            
42             //insert 1 item
43             books.insert();
44             cout<<"After insertion:\n";
45             for (pr =books.begin(); pr != books.end(); pr++)
46                 ShowReview(*pr);
47         }
48         books.swap(oldlist);
49         cout<<"Swapping oldlist with books:\n";
50         for(pr = books.begin(); pr != books.end();pr++)
51             ShowReview(*pr);
52     }
53     else
54         cout<<"Nothing entered, nothing gained.\n";
55     return 0;      
56 }
57 
58 bool FillReview(Review & rr)
59 {
60     std:;cout<<"Enter book title(quit to quit):";
61     std::getline(std::cin, rr.title);
62     if (rr.title == "quit")
63         return false;
64     std::cout<<"Enter book rating: ";
65     std::cin >> rr.rating;
66     if(!std::cin)
67         return false;
68     while(std::cin.get() != '\n')
69         continue;
70     return true;
71 }
72 
73 void ShowReview()
74 {
75     std::cout<<rr.rating<<"\t"<<rr.title<<std::endl;
76 }

 

==================================

三、对矢量可执行的其他操作

 程序员通常要对数组执行很多操作,如搜索、排序、随机排序等;

矢量模板包含了执行这些常见的操作方法吗?没有!

STL从更广泛的角度定义了非成员函数来执行这些操作,既不是为每个容器类定义find()成员函数,而是定义了一个适用于所有容器类的非成员函数find()。

这种设计理念省去了大量的重复工作。

举个例子,假设有8个容器类,需要支持10种操作。如果每个类都有自己的成员函数,则需要定义80个成员函数。

但是采用STL的方法,只需要定义10个非成员函数即可。

在定义新的容器类时,只要遵循正确的指导思想,则它也可以使用已有的10个非成员函数来执行查找、排序等操作。

 

当然有时候,即使有执行相同任务的非成员函数,STL有时也会定义一个成员函数。这是因为对有些操作来讲使用类的特定算法的效率比通用算法高。

因此,vector的成员函数swap()的效率比非成员函数swap()高,但非成员函数让您能够交换两个类型不同的容器的内容。

 

接下来讨论3个具有代表性的STL非成员函数:for_each()、random_shuffle()和sort()。

for_each()函数可用于很多函数类,它接受3个参数。前两个是迭代器用于定义容器中的区间,最后一个是指向函数的指针(更普遍地说,最后一个参数是一个函数对象,函数对象将稍后介绍)。

可以将代码:

vector<Review>::iterator pr;

for (pr = books.begin(); pr != books.end(); pr++)

  ShowReview(*pr);

替换为:

for_each(books.begin(),books.end(), ShowReview);

这样可以避免显式地使用迭代器变量。

 

Random_shuffle()函数接受两个指定区间的迭代器参数,并随机排列该区间中的元素。

例如,下面的语句随机排列books矢量中的所有元素:

random_shuffle(books.begin(), books.end());

 

sort()函数也要求容器支持随机访问。该函数有两个版本。

第一个版本接受两个定义区间的迭代器参数,并使用为存储在容器中的类型元素定义的<运算符,以对区间中的元素进行操作。

下面的语句按照升序对coolstuff的内容进行排序,排序时使用内置的<运算符对值进行比较:

vector<int> coolstuff;

...

sort(coolstuff.begin(), coolstuff.end());

如果容器元素是用户定义的对象,则要使用sort(),必须定义能够处理该类型对象的operator<()函数。

例如,如果为Review提供了成员函数或非成员函数operator<(),则可以对包含Review对象的矢量进行排序。

由于Review是一个结构,因此其成员是公有的,这样的非成员函数将为:

bool operator<(const Review & r1, const Review & r2)

{

  if(r1.title <r2.title)

    return true;

  else if (r1.title == r2.title && r1.rating<r2.rating)

    return true;

  else

    return false;

}

有了这样的函数,就可以对包含Review对象的矢量进行排序了;

sort(books.begin(), books.end());    //这两个参数是指定容器区间的迭代器;

 

上述的排序是按照升序的方式进行,使用内置的<运算符进行排序;

如果想要按照降序的方式的话,可以自己定义一个函数WorseThan();

有了这个函数后,就可以对包含Review对象的book矢量进行降序排序:

sort(books.begin(), books.end(),WorseThan());

 

接下来这段程序将演示STL非成员函数的用法:

 1 //vect3.cpp -- using STL functions
 2 #include <iostream>
 3 #include <string>
 4 #include <vector>
 5 #include <algorithm>
 6 
 7 struct Review {
 8     std::string title;
 9     int rating;
10 }
11 
12 bool operator<(const Review & r1, const Review & r2);
13 bool worsethan(const Review & r1, const Review & r2);
14 bool FillReview(Review & rr);
15 void ShowReview(const Review & rr);
16 
17 int main()
18 {
19     using namespace std;
20 
21     vector<Review> books;
22     Review temp;
23     while(FillReview(temp))
24         books.push_back(temp);
25     
26     if(books.size()>0)
27     {
28         cout<<""<<books.size()<<""<<"";
29         for_each(books.begin(), books.end(), ShowReview);
30 
31         sort(books.begin(), books.end());
32         cout<<"";
33         for_each(books.begin(), books.end(), ShowReview);
34 
35         sort(books.begin(), books.end(),worseThan);
36         cout<<"";
37         for_each(books.begin(), books.end(), ShowReview);
38 
39         random_shuffle(books.begin(), books.end());
40         cout<<"";
41         for_each(books.begin(), books.end(), ShowReview);        
42     }
43     else
44     {
45         cout<<"No entries";
46     }
47     cout<<"Bye.\n";
48     return 0;
49 }
50 
51 bool operator<(const Review & r1, const Review & r2)
52 {
53     if(r1.title < r2.title)
54         return true;
55     else(r1.title == r2.title && r1.rating <r2.rating)
56         return true;
57     else
58         return false;
59 }
60 
61 bool worsethan(const Review & r1, const Review & r2)
62 {
63     if(r1.rating < r2.rating)
64         return true;
65     else
66         return false;
67 }
68 
69 
70 bool FillReview(Review & rr)
71 {
72     std::cout<<"Enter book title(quit to quit)";
73     std::getline(std::cin, rr.title);
74     if(rr.title == "quit")
75         return false;
76     std::cout<<"Enter book rating:";
77     std::cin>>rr.rating;
78     if(!std::cin)
79         return false;
80     //get rid of rest of input line
81     while(std::cing.get() != "\n")
82         continue;
83     return true;
84 }
85 
86 void ShowReview(const Review & rr)
87 {
88     std::cout<<rr.rating<<"\t"<<rr.title<<std::endl;
89 } 

 

 

==================================

四、基于范围的for循环(C++11)

接下来介绍一种新型的for循环方式,叫做基于范围的for循环;

这种for循环其实是为用于STL而设计的;

下面有个示例:

double price[5] = {4.99, 10.99, 6.87, 7.99, 8.49};

for (double x : prices)

  cout<<x<<std::endl;

首先在for循环的括号中,声明一个变量,该变量的类型与容器中存储的内容相同。然后指出容器的名称。

接下来,循环体使用指定的变量依次访问容器的每个元素。

由此我们可以改写代码:

将代码:for_each(books.begin(), books.end(), ShowReview);

改写成:for(auto x : books) ShowReview(x);

注意这里修饰x变量的关键字是auto。自动变量,这是因为根据books的类型vector<Review>,编译器可以推断出x的类型为Review,而循环将依次将books中的每个Review对象传递给ShowReview()。

这个是自动类型推断功能。

 

基于范围的for循环还可以修改容器的内容,诀窍是使用一个引用参数。

void InflateReview(Review &r){r.rating++}

接下来:

for(auto x:books) InflateReview(x);

posted @ 2019-03-03 21:33  Grooovvve  阅读(374)  评论(0编辑  收藏  举报