《C++ Primer Plus》16.3 标准模板库 学习笔记

STL提供了一组表示容器、迭代其、函数对象和算法的模板。容器是一个与数组类似的单元,可以存储若干个值。STL容器是同质的,即存储的值的类型相同;算法是完成特定任务(如对数组进行排序或在链表中查找特定值)的处方;迭代其能够用来遍历容器的对象,与能够便利数组的指针类似,是广义指针;函数对象是类似于函数的对象,可以使类对象或函数指针(包括函数名,因为函数名被用作指针)。STL使得能够构造各种容器(包括数组、队列和链表)和执行各种操作(包括搜索、排序和随机排列)。
Alex Stepanov和Meng Lee在Hewlett-Packard实验室开发了STL,并于1994年发布其实现。ISO/ANSI C++委员会投票同意将其作为C++标准的组成部分。STL不知面向对象的编程,而是一种不同的编程模式——泛型编程(generic programming)。

16.3.1 模板类vector
在计算中,矢量(vector)对应数组,而不是数学中的矢量。
vector类提供了与第14章valarray和ArrayTP以及第4章介绍的array类似的操作,即可以创建vector对象,将一个vector对象赋给另一个对象,使用[]运算符来访问vector元素。要使类称为通用的,应将它设计为模板类,STL正是这样做的——在头文件vector(以前为vector.h)中定义了一个vector模板。
要创建vector模板对象,可使用通常的<type>表示法来指出要使用的类型。另外,vector模板使用动态内存分配,因此可以用初始化来指出需要多少矢量:
#include <vector>
using namespace std;
vector<int> ratings(5); // a vector of 5 ints
int n;
cin >> n;
vector<double> scores(n);   // a vector of n doubles
由于运算符[]被重载,因此创建vector对象后,可以使用通常的数组表示法来访问各个元素:
ratings[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。
--------------------------------------------------*/

16.3.2 可对矢量执行的操作
所有的STL容器都提供了一些基本方法,其中包括:
size()    —— 返回容器中元素数目
swap()    —— 交换量割绒其的内容
begin()    —— 返回一个指向容器中第一个元素的迭代器
end()    —— 返回一个表示超过容器尾的迭代器
迭代器:是一个广义指针。事实上,它可以是指针,也可以是一个可对其执行类似指针的操作——如解除引用(如operator*())和递增(如operator++())——的对象。通过将指针广义化为迭代其,让STL能够为各种不同的容器类(包括那些简单指针无法处理的类)提供统一的接口。每个容器类都定义了一个合适的迭代器,该迭代其的类型是一个名为iterator的typedef,其作用域为整个类。例如,要为vector的double类型规范声明一个迭代其,可以这样做:
vector<double>::iterator pd;    // pd an iterator
假设scores是一个vector<double>对象:
vector<double> scores;
则可以使用迭代其pd执行这样的操作:
pd = scores.begin();    // have pd point to the first element
*pd = 22.3;             // dereference pd and assign value to first element
++pd;                   // make pd point to the next element
可以看到,迭代器的行为就像指针。
还有一个C++11自动类型推断很有用的地方。例如,可以不这样做:
vector<double>::iterator pd = scores.begin();
而这样做:
auto pd = scores.begin();    // C++11 automatic type deduction
超过结尾(past-the-end)是一种迭代器,指向容器最后一个元素后面的哪个元素。这与C-风格字符串最后一个字符后面的空字符串类似,指示空字符是一个值,而“超过结尾”是一个指向元素的指针(迭代器)。end()成员函数标识超过结尾的位置。如果将迭代其设置为容器的第一个元素,并不断地递增,则最终它降到大容器结尾,从而遍历整个容器的内容。因此,如果scores和pd的定义与前面的示例中相同,则可以用下面的代码来显式容器的内容:
for (pd = scores.legin(); pd != scores.end(); pd ++)
    cout << *pd << endl;
所有容器都包含刚才讨论的那些方法。vector模板类也包含了一些只有某些STL容器才有的方法。push_back()是一个方便的方法,它将元素添加到矢量末尾。这样做时,它将负责内存管理,增加矢量的长度,使之呢狗狗容纳新的成员,这意味着可以编写这样的代码:
vector<double> scores;  // create an empty vector
double temp;
while (cin >> temp && temp >= 0)
    scores.push_back(temp);
cout << "You entered " << scores.size() << " scores.\n";
erase()方法删除矢量中给定区间的元素。它接受来那个哥迭代其参数,这些参数定义了要删除的区间。了解STL如何使用两个迭代其来定义区间至关重要。第一个迭代其指向区间的起始处,第二个迭代其位于区间终止除的后一个位置。例如,下述代码删除第一个和第二个元素,即删除begin()和begin()+1指向的元素(由于vector提供了随机访问功能,因此vector类迭代其定义了诸如begin()+2等操作):
scores.erase(scores.begin(), scores.begin() + 2);
如果it1和it2是迭代器,则STL文档使用[p1,p2)来表示p1到p2(不包括p2)的区间。
insert()方法的功能与erase()相反。它接受3个迭代其参数,第一个参数制定了新元素的插入位置,第二个和第三个迭代其参数定义了被插入区间,该区间通常是另一个容器对象的一部分。例如,下面的代码将矢量new_v中除第一个元素外的所有元素插入到old_v矢量的第一个元素前面:
vector<int> old_v;
vector<int> new_v;
...
old_v.insert(old_v.begin(), new_v.begin() + 1, new_v.end());
顺便说一下,对于这种情况,拥有超微元素是非常方便的,因为这使得在矢量尾部附加元素非常简单。下面的代码将新元素插入到old.end()前面,即矢量最后一个元素的后面。
old_v.insert(old_v.end(), new_v.begin() + 1, new_v.end());

16.3.3 对矢量可执行的其他操作
程序员通常要对数组指向很多操作,如搜索、排序、随机排序等。矢量模板类包含了执行这些常见操作的方法吗?答案是没有!STL从更广泛的角度定义了非成员(non-member)函数来执行这些操作,即不是为每个容器定义find()成员函数,而是定义了一个适用于所有容器类的非成员函数find()。这种设计理念省去了大量重复的工作。例如,假设有8个容器类,需要支持10中操作。如果每个类都有自己的成员函数,则需要定义80(8*10)个成员函数。但采用STL方式时,只需要定义10个非成员函数即可。在定义新的容器类时,只要遵循正确的指导思想,则它也可以使用已有的10个非成员函数来执行查找、排序等操作。
另一方面,即使有执行相同任务的非成员函数,STL有时也会定义一个成员函数。这时因为对有些操作来说,类特定算法的效率比通用算法高,因此,ector的成员函数swap()的效率比非成员函数swap()高,但非成员函数让您能够交换两个类型不同的容器的内容。
下面来看3个具有代表性的STL函数:for_each()、random_shuffle()和sort()。for_each()函数可用于很多容器类,它接受3个参数。前两个是定义容器中区间的迭代其,最后一个是指向函数的指针(更普遍地说,最后一个参数是一个函数对象,函数对象将稍候介绍)。for_each()函数将被指向的函数应用于容器区间中的各个元素。被指向的函数不能修改容器元素的值。可以用for_each()函数来代替for循环。例如,可以将代码:
vector<Review>::iterator pr;
for (pr = books.begin(); pr != books.end(); pr ++)
    ShowReview(*pr);
替换为:
for_each(books.begin(), books.end(), ShowReview);
这样可以避免显式地调用迭代其变量。
Random_suhffle()函数接受两个指定区间的迭代其参数,并随机排列该区间中的元素。例如,下面的语句随机排列books矢量中所有元素:
random_shuffle(books,begin(), books.end());
与可用于任何容器类的for_each不同,该函数要求容器类允许随机访问,vector类可以做到这一点。
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对象(如books)的矢量进行排序了:
sort(books.begin(), books.end());
上述版本的operator<()函数按title成员的字幕顺序排序。如果title成员相同,则按照rating排序。然而,如果想按降序或是按rating(而不是title)排序,该如何办呢?可以使用另一种格式的sort()。它接受3个参数,前两个参数也是指定区间的迭代其,最后一个参数是指向要使用的函数的指针(函数对象),而不是用于比较的operator<()。返回值可转换为bool,false表示两个参数的顺序不正确。下面是一个例子:
bool WorseThan(cosnt Review & r1, const Review & r2)
{
    if (r1.rating < r2.rating)
        return true;
    else
        return false;
}
有了这个函数后,就可以使用下面的语句将包含Review对象的books矢量按在升序排列:
sort(books.begin(), books.end(), WorseThan);

16.3.4 基于范围的for循环(C++11)
第5章说过,基于范围的for循环是为用于STL而设计的。为复习该循环,下面是第5章的第一个示例:
double prices[5] = {4.99, 10.99, 6.87, 7.99, 8.49};
for (double x : prices)
    cout << x << std::endl;
在这种for循环中,括号内的代码声明一个类型与容器存储的内容相同的变量,然后指出了容器的名称。接下来,循环体使用指定的变量一次访问同期的每个元素。例如,对于下述摘自程序清单16.9的语句:
for_each(books.begin(), books.end(), ShowReview);
可将其替换为下述基于范围的for循环:
for(auto x : books) ShowReview(x);
根据book的类型(vector<Review>),编译器将推断出x的类型为Review,而循环将一次将books中的每个Review对象传递给ShowReview()。
不同于for_each(),基于范围的for循环可修改容器的内容,诀窍是制定一个引用参数。例如,假设有如下函数:
void InflateReview(Review &r) {r.rating++;}
可使用如下循环对books的每个元素执行该函数:
for (auto & x : books) InflateReview(x);

posted @ 2016-07-16 23:34  月光诗人  阅读(243)  评论(0编辑  收藏  举报