zoukankan      html  css  js  c++  java
  • 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
    "<<NUM<<" book titles and your ratings (0-10).
    "; 
    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:
    "<<"Rating	Book
    ";
    27     for (i=0; i<NUM; i++)
    28     {
    29         cout<<ratings[i]<<"	"<<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. ";

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

    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:
    "
    26                <<"Rating	Book
    ";
    27         for (int i =0; i<num; i++)
    28             ShowReview(books[i]);
    29         cout<<"Reprising:
    "<<"Rating	Books
    ";
    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:
    ";
    39             for (pr = books.begin(); pr != books.end(); pr++)
    40                  ShowReview(*pr);
    41            
    42             //insert 1 item
    43             books.insert();
    44             cout<<"After insertion:
    ";
    45             for (pr =books.begin(); pr != books.end(); pr++)
    46                 ShowReview(*pr);
    47         }
    48         books.swap(oldlist);
    49         cout<<"Swapping oldlist with books:
    ";
    50         for(pr = books.begin(); pr != books.end();pr++)
    51             ShowReview(*pr);
    52     }
    53     else
    54         cout<<"Nothing entered, nothing gained.
    ";
    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() != '
    ')
    69         continue;
    70     return true;
    71 }
    72 
    73 void ShowReview()
    74 {
    75     std::cout<<rr.rating<<"	"<<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.
    ";
    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() != "
    ")
    82         continue;
    83     return true;
    84 }
    85 
    86 void ShowReview(const Review & rr)
    87 {
    88     std::cout<<rr.rating<<"	"<<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);

  • 相关阅读:
    解读setTimeout, promise.then, process.nextTick, setImmediate的执行顺序
    规范git commit提交记录和版本发布记录
    《Javascript设计模式与开发实践》--读书笔记
    一个简洁明了的表达式拼接例子.md
    前端知识分享.md
    PHP常用框架.md
    关于软件版本以及VS版本扩展介绍
    WPF 优秀控件.md
    Deepin 常见问题锦集
    一些面向对象的设计法则
  • 原文地址:https://www.cnblogs.com/grooovvve/p/10467794.html
Copyright © 2011-2022 走看看