zoukankan      html  css  js  c++  java
  • 《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. ";
    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);

  • 相关阅读:
    论面向服务架构(SOA)设计及其应用
    论MVC架构设计模式分析
    软件架构理论与实践读后感(一)
    视频全量分析规划书
    架构实战—软件架构设计的过程读后感(三)
    架构实战—软件架构设计的过程读后感(二)
    第8周周总结
    Refined Architecture阶段阅读笔记
    visual studio2010编译过程中出现COFF文件损坏的原因和方法总结
    解决visual studio 2013编译过程中存在的无法打开kernel.lib问题
  • 原文地址:https://www.cnblogs.com/moonlightpoet/p/5677530.html
Copyright © 2011-2022 走看看