C++ Primer Plus 笔记第十六章

string 和标准模板库

 本章内容包括:

    标准C++ string类

    模板 auto_ptr、unique_ptr 和 shared_ptr

    标准模板库(STL)

    容器类

    迭代器

    函数对象

    STL算法

    模板 initializer_list

16.1 string 类

   string 类由头文件 string 支持;

   头文件 cstring 支持对 C-风格字符串进行操纵的 C 库字符串函数,但不支持 string 类

16.1.1 构造字符串

   string类的构造函数:

    string ( const char * s )  

      // 将 string 初始化为 s 指向的NBTS;

    string ( size_type n, char c )   

      // 创建一个包含 n 个元素的 string 对象,其中每一个元素都被初始化为字符 c;

    string ( const string & str )  

      // 将一个 string 对象初始化为 string 对象 str (复制构造函数);

    string ( )  

      // 创建一个默认的 string 对象,长度为 0(默认构造函数);

    string ( const char * s, size_type n )  

      // 将 string 对象初始化为 s 指向的 NBTS 的前 n 个字符,即使超过了NBTS 结尾;

    template< class Iter >  string ( Iter begin, Iter end ):

      // 将 string 对象初始化为区间 [ begin, end )内的字符,其中 begin 和 end 就像指针,用于指定位置

    string ( const string & str, string size_type pos = 0, size_type n = npos ):

      // 将一个 string 对象初始化为对象 str 中从位置 pos 开始到结尾的字符,或从位置 pos 开始的 n 个字符

    string ( string && str ) noexcept:  

      // C++ 11 新增的,将一个 string 对象初始化为 string 对象 str,并可能修改 str (移动构造函数)

    string ( initializer_list il )  

      // 这是C++11 新增的,将一个 string 对象初始化为初始化列表 il 中的字符

   string 类重载

    += 运算符:用于将一个字符串附加到另一个字符串后面;

    =  运算符: 用于将一个字符串赋给另一个字符串;

    << 运算符:用于显示 string 对象;

    [ ] 运算符: 用于访问字符串中各个字符

16.1.2 string 类输入

   C- 风格字符串输入方式:

    char  info[100];

    cin >> info;        // read a word

    cin.getline ( info,100 );   // read a line, discard \n

    cin.get (info, 100);    // read a iline, leave \n in queue

   string 对象输入方式: 

    string stuff;

    cin >> stuff;      // read a word

    getline ( cin, stuff );  // read a line, discard \n

   两个版本的 getline () 都可以有一个可选的参数,用于指定使用哪个字符来确定输入的边界:

    cin.getline ( info, 100, ':' ); // read up to :,discard :

    getline ( stuff, ':' );                // read up to :,discard :   

   string 版本的 getline() 将自动调整目标 string 对象的大小,使之能够存储输入字符:

    读取 C-风格字符串的函数是 istream 类的方法,而 string 版本是独立的函数;

    C- 风格字符串输入, cin 是调用对象;  // cin.getline()

    string 对象输入, cin 是一个函数参数 ;  // getline( cin, str )

   string 输入函数自动调整目标 string 大小,使之与输入匹配,但也存在一些限制:

    1. string 对象的最大允许长度,由 string :: npos 指定,通常是最大的 unsigned int;

    2. 程序可以使用的内存量

   string 版本 getline ( ) 函数从输入中读取字符,并将其存储到目标 string 中,直到发生三种情况:

    1. 到达文件尾,输入流的 eofbit 将被设置,方法 fail() 和 eof() 都将返回 true;

    2. 遇到分界符(默认为 \n),把分界符从输入流中删除,但不存储它;

    3. 读取的字符串数达到最大允许值(string :: npos 和可供分配的内存字节数较小的一个):

      在这种情况下,将设置输入流的failbit,fail () 将返回 true

    输入流对象有一个统计系统,用于跟踪流的错误状态:

    检查到文件尾后将设置 eofbit 寄存器;

    检查到输入错误时将设置 failbit 寄存器;

    出现无法识别的故障(如硬盘故障)时将设置 badbit 寄存器;

    一切顺利时将设置 goodbit 寄存器

   string 版本的 operator >> () 函数的行为为,读取直到遇到空白字符并将其留在输入队列中:

    空白字符包括,空格、换行符和制表符

16.1.3 使用字符串

   创建 string 对象、显示 string 对象内容、将数据读取和附加到 string 对象中、给string 对象赋值、将两个 string 对象连结起来;

   可以比较字符串:

    string 类对全部 6 个关系运算符进行了三种方式重载:

      能够将 string 对象与另一个 string 比较;

      能够将 string 对象与C-风格字符串进行比较;

      能够将 C-风格字符串与 string 对象进行比较

   可以确定字符串长度, size() 和 length()成员函数都返回字符串中的字符数;

   可以以多种不同的方式在字符串中搜索给定的子字符串或字符:  

    rfind ( )、find_first_of ( )、find_last_of ( )、find_first_not_of ( ) 和 find_last_not_of ( );

16.1.4 string 还提供了哪些功能

   string 库提供了很多其他的工具,包括:

    删除字符串部分或全部内容;

    用一个字符串的部分或全部内容替换另一个字符串的部分或全部内容;

    将数据插入到字符串中或删除字符串中的数据;

    将一个字符串中的部分或全部内容与另一个字符串的部分或全部内容进行比较;

    从字符串中提取子字符串;

    将一个字符串中的内容复制到另一个字符串中;

    交换另两个字符串中的内容

   c_str ( ) 方法返回一个指向 C-风格字符串的指针,该 C-风格字符串的内容与调用 c_str ( ) 方法的 string 对象相同;

16.1.5 字符串种类

   string 库实际上是基于一个模板类的:

    temlate < class charT, class traits = char_traits, class Allocator = allocator >

    basic_string { .  .  . };

    // traits 类描述关于选定字符类型的特定情况; Allocator 是一个管理内存分配的类

   模板 basic_string 有4 个具体化,每个具体化都有一个 typedef 名称:

    typedef basic_string string;

    typedef basic_string wstring;

    typedef basic_string u16string;  // C++11

    typedef basic_string u32string;  // C++11 

16.2 智能指针

   问题:  

     常规指针占据内存被释放,但是指针指向的内存未被释放

   智能指针作为对象,可以在对象过期时,让他的析构函数删除指向的内存

   三种智能指针模板(auto_ptr、unique_ptr 和 shared_ptr)都定义了类似指针的对象:

    可以将 new 获得(直接或间接)的地址赋给这种对象;

    当智能指针过期时,其析构函数将使用 delete 来释放内存;

   创建智能指针 包含头文件 memory,该文件模板定义

   模板 auto_ptr包含如下构造函数 :

    template < class X >

    class auto_ptr

    {

    public:

      explicit auto_ptr (X * p = 0)  throw ( );

    .  .  .

    };

    请求 X 类型的 auto_ptr 将获得一个指向 X 类型的 auto_ptr:

      auto_ptr pd( new double ); 

      auto_ptr ps( new string );   

    new double 是 new 返回的指针,指向新分配的内存块,它是构造函数 auto_ptr 的参数,对应原型中 p;

    所有智能指针类都定义一个 explicit 构造函数,该构造函数将指针作为参数,因此不需要自动将指针转换为智能指针对象:

      shared_ptr pd;

      double *p_reg = new double;

      pd = p_reg;               // not allowed ( implicit conversion )

      pd = shared_ptr (p_reg);        // allowed ( explicit conversion )

      shared_ptr pshared = p_reg; // not allowed ( implicit conversion )

      shared_ptr pshared(p_reg);   // allowed ( explicit conversion )

   智能指针对象的很多方面都类似于常规指针:

    ps是一个智能指针对象,可以对他执行解除引用操作符(*ps);

    用它来访问结构成员(ps->puffindex);

    将它赋给指向同类型的常规指针;

    还可以将智能指针对象赋给同类型的智能指针对象(会出现问题)

16.2.2 有关智能指针的注意事项

   对于智能指针之间的赋值,避免出现常规指将同一个对象 delete 两次:

    1. 定义赋值运算符,执行深复制,这样两个智能指针将指向不同的对象;

    2. 建立所有权概念

      对于特定的对象只能有一个智能指针可拥有它,只有拥有对象的指针的析构函数才能删除该对象;

      让赋值转让所有权 —— auto_ptr 和 unique_ptr 的策略(unique_ptr 的策略更加严格)

    3. 创建智能指针的概念

      跟踪引用特定对象的智能指针数 —— 引用计数;

      这是 shared_ptr 的采用的策略

16.2.3 unique_ptr 为何优于 auto_ptr

   unique_ptr 更安全;  

   程序试图将一个 unique_ptr 赋给另一个时:

    如果源 unique_ptr 是个临时右值,编译器允许这样做;

    如果源 unique_ptr 将存在一段时间,编译器禁止这样做;

    不会留下悬挂的 unique_ptr 指针

   相比 auto_ptr ,unique_ptr有一个可用于数组的变体;

   警告: 使用 new 分配内存时,才能使用auto_ptr 和 shared_ptr,使用 new [ ] 分配内存时,不能使用它们;

       不使用 new 分配内存时,不能使用auto_ptr 或 shared_ptr;

       不使用 new 或 new [ ] 分配内存时,不能使用 unique_ptr

16.2.4 选择智能指针

   如果程序使用多个指向同一个对象的指针,应选择shared_ptr:

    1. 有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;

    2. 两个对象包含都指向第三个对象的指针;

    3. STL容器包含指针:

      很多STL算法都支持复制和赋值操作,这些操作可用于 shared_ptr,但不能用于 unique_ptr 和 auto_ptr;

   如果程序不需要多个指向同一个对象的指针,则可以使用 unique_ptr:

    如果函数使用 new 分配内存,并返回指向该内存的指针,将其返回类型声明为 unique_ptr;

    这样,所有权将转让给接受返回值的 unique_ptr,而智能指针将负责调用 delete;

    可以将 unique_ptr 存储到 STL 容器中,只要不调用将一个 unique_ptr 复制或赋给另一个方法或算法

   模板 shared_ptr 包含一个显示构造函数,可用于将右值 unique_ptr 转换为 shared_ptr,shared_ptr 将接管原来归unique_ptr所有的对象;

   在满足 unique_ptr 要求的条件时,也可以使用 auto_ptr,但 unique_ptr 是更好的选择

16.3 STL

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

   容器是一个与数组类似的单元,可以存储若干个值,STL 容器是同质的,存储值的类型相同;

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

   迭代器能够用来遍历容器对象与能够遍历数组指针类似,是广义指针;

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

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

16.3.1 vector 模板类

   头文件 vector 中定义了一个 vector 模板,可以创建vector对象,将一个vector对象赋给另一个对象,使用 [ ] 操作符访问vector元素;

   创建 vector 模板对象,可使用通常的表示法来指出要使用的类型,vector模板使用动态内存分配,可以使用初始化参数来指出需要多少矢量;  

    #include

    using namespace  std;

    vector rating(5);

    int n;

    cin >> n;

    vector scores(n);

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

   分配器:

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

      template < class T, class Allocator = allocator  >

      class vector { .  .  . }

      默认使用 allocator 类,这个类使用 new 和 delete

16.3.2 可对矢量执行的操作

   STL 容器提供了一些基本方法:

    size(): 返回容器中元素数目;

    swap(): 交换两个容器的内容;

    begin(): 返回一个指向容器中第一个元素的迭代器;

    end(): 返回一个超过容器尾的迭代器

   什么是迭代器:

    迭代器是一个广义指针;

    可以是指针,也可以是一个可对其执行类似指针的操作;

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

   每个容器类都定义了一个合适的迭代器,该迭代器的类型名为 iterator 的typedef,作用域为整个类:

    vector :: iterator pd;       // pd an itertor

    vector scores;

    pd = scores.begin();      // 迭代器就像指针

     可以使用下面代码显示容器内容:

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

        cout << *pd << endl;

   vector 模板类也包含一些只有某些STL容器才有的方法:

    1. push_back() : 将元素添加到矢量末尾

      double temp;

      scores.push_back(temp);

    2. erase() 方法删除给定区间的元素

      scores.erase (scores.begin(),scores.begin + 2);   // 删除 begin() 和 bigin() + 1指向的元素

    3. insert() 方法接收3个迭代器参数,第一个参数指定新元素插入位置,第二个和第三个指定迭代器参数插入区间

      vector old;

      vector new;

      old.insert( old.begin(),new.begin() + 1,new.end() ); // 将new除第一个元素外所有元素插入到old矢量的第一个元素的前面

  例程 16.6 演示 size()、begin()、end()、push_back()、erase() 和 insert() 的用法:

 1 #include
 2 #include<string>
 3 #include
 4 using namespace std;
 5 
 6 struct Review
 7 {
 8     string title;
 9     int rating;
10 };
11 
12 bool FillReview(Review & rr);
13 void ShowReview(const Review & rr);
14 
15 int main()
16 {
17     vector books;
18     Review temp;
19     while (FillReview(temp))
20         books.push_back(temp);
21     int num = books.size();
22     if (num > 0)
23     {
24         cout << "Thank you. You entered the following: \n"
25             << "Rating\tBook\n";
26         for (int i = 0; i < num; i++)
27             ShowReview(books[i]);
28 
29         cout << "Reprising:\n"
30             << "Rating\tBook\n";
31         vector::iterator pr;
32         for (pr = books.begin(); pr != books.end(); pr++)
33             ShowReview(*pr);
34 
35         vector oldlist(books);        // copy constructor used
36         if (num > 3)
37         {
38             books.erase(books.begin() + 1, books.begin() + 3);
39             cout << "After erasure:\n";
40             for (pr = books.begin(); pr != books.end(); pr++)
41                 ShowReview(*pr);
42 
43             books.insert(books.begin(), oldlist.begin() + 1, oldlist.begin() + 2);
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 
59 bool FillReview(Review & rr)
60 {
61     cout << "Enter book title (quit to quit): ";
62     getline(cin, rr.title);
63     if (rr.title == "quit")
64         return false;
65     cout << "Enter book rating: ";
66     cin >> rr.rating;
67     if (!cin)
68         return false;
69     cin.get();
70     return true;
71 }
72 void ShowReview(const Review & rr)
73 {
74     cout << rr.rating << "\t" << rr.title << endl;
75 }

16.3.3 对矢量可执行的其他操作

   STL 定义了非成员函数来执行操作如:搜索、排序、随机排序等;

   代表性的STL函数: for_each()、random_shuff()、sort():

    1. for_each() 接受3个参数,前两个是定义容器中区间的迭代器,最后一个是函数指针(函数对象)

      将被指向的函数应用于容器区间中的各个元素,被指向的函数不能修改容器元素的值;

      for_each (books.begin(), books.end(), ShowReview);    // 代替for循环显示容器元素值

    2. random_shuff() 函数接受两个指定区间的迭代器参数,并随机排列该区间中的元素(要求容器类允许随机访问)

      random_shuff ( books.begin(),books.end() );   // 随机排列books矢量中的所有元素

    3. sort() 两个版本(要求容器支持随机访问):

      (一)接受两个定义区间的迭代器参数,使用为存储容器中元素的类型元素定义 < 操作符,对区间元素操作

          sort (books.begin(), books.end() );    // 排序使用内置的 < 操作符对值进行比较

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

            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 < () ,而是自定义函数:

            bool WorseThan(const Review & r1,cosnt Review & r2)

            {

              if (r1.rating < r2.rating)

                return true;

              else 

                return false;

            }

          有了这样的函数,就可以将包含Review对象的books矢量按rating 升序排列:

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

 16.4 通用编程技术

   STL 是一种通用编程技术,旨在编写独立于数据类型的代码;

   C++ 中,完成通用程序的工具是模板,模板使得能够按照通用类型定义函数或类,STL 通过通用算法更进一步;

16.4.1 为何使用迭代器

   模板使得算法独立于存储的数据类型,而迭代器使得算法独立于使用的容器类型:

    1. 在double数组中搜索特定值函数:

      double * find_ar (double *ar, int n, const double & val)

      {

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

          if ( ar[i] == val )

            return &ar[i];

        return 0;

      } 

      可以用模板扩展到任意类型的数组,但是仍与一种特定的数据结构(数组)关联在一起

    2. 链表又被链接在一起的 Node 结构组成

      struct node

      {

        double item;

        Node *p_next;

      }

      Node * find_l1 ( Node * head, const double & val )

      {

        Node * start;

        for ( start = head; start != 0; start = start->p_next )

          if ( start->item == val )

            return start;

        return 0;

      }

      可以使用模板将这种算法推广到支持任何数据类型的链表,不过这种算法也是与特定的数据结构(链表)关联在一起

   通用编程技术旨在使用同一个find函数来处理数组、链表或任何其他容器类型;

   要实现 find 函数,迭代器应具备特征:

    1. 应能够对迭代器使用解除引用操作,以便能够访问它的引用值,即p是迭代器,则应对 *p 定义;

    2. 应能够将一个迭代器赋给另一个, p = q;

    3. 应能够将一个迭代器与另一个进行比较;

    4. 应能够使用迭代器遍历容器中的所有元素,这可以通过为迭代器 p 定义 ++p 和 p++ 来实现

   每个容器类都定义了相应的迭代器类型,都有begin() 和end() 方法,都使用 ++ 操作符

16.4.2 迭代器类型

   STL 定义了5种迭代器:输入迭代器、输出迭代器、正向迭代器、双向迭代器和随机访问迭代器;

   1. 输入迭代器

    来自容器的信息被视为输入,输入迭代器可被程序用来读取容器中的信息;

    输入迭代器的算法不会修改容器中的值,输入迭代器是单向迭代器,可以递增不能倒退

   2. 输出迭代器

    将信息从程序输出给容器的迭代器,程序的输出是容器的输入;

    对于单通行、只写算法,则使用输出

   3. 正向迭代器

    与输入迭代器和输出迭代器相似,正向迭代器只使用 ++ 操作符遍历容器;

    与输入输出迭代器不同的是,它总是按照相同的顺序遍历一系列值;

    将正向迭代器递增后,仍然可以对前面的迭代器解除引用

   4. 双向迭代器

    双向迭代器具有正向迭代器的所有 特性,同时支持两种(前缀后缀)递减操作符

   5. 随机访问迭代器

    有些算法(标准排序和二分法)要求能够直接掉到容器中的任何一个元素——随机访问;

    随机访问迭代器具有双向迭代器的所有特性,同时支持随机访问的操作和对元素排序的关系操作符

16.4.3 迭代器测次结构

   迭代器类型形成了一个层次结构:

    正向迭代器具有输入迭代器和输出迭代器的全部功能,同时还有自己的功能;

    双向迭代器具有正向迭代器的全部功能,同时还具有自己的功能;

    随机访问迭代器具有双向迭代器的全部功能,同时还有自己的功能

   在编写算法时尽可能使用要求最低的迭代器,并让他适用于容器的最大区间;

   每个容器类都定义了一个类级 typedef 名称—— iterator:

    vector 类的迭代器类型为 vector :: interator;    // 矢量迭代器是随机访问迭代器,允许使用基于任何迭代器类型的算法;

    list 类的迭代器类型为 list :: interator;    

    STL 实现了一个双向链表,它使用双向迭代器,因此不能使用基于随机访问迭代器算法,但可以使用基于要求较低的迭代器算法;

16.4.4 概念、改进和模型

   1. 将指针用于迭代器

    迭代器是广义指针,指针满足所有迭代器的要求;

    迭代器是STL算法的接口,而指针是迭代器,因此STL算法可以使用指针来对基于指针的非STL容器进行操作

    可将STL算法用于数组:

      const int SIZE = 100;

      double Receipts [SIZE];

      sort (Receipts, Receipts + SIZE);     // STL sort () 函数指向容器第一个元素的迭代器和指向超尾的迭代器作为参数

    同样可以将STL算法用于自己设计的数组形式,只要提供适当的迭代器和超尾指示器即可;

    STL 提供了一些预定义迭代器;

    一种算法copy () 可以将数据从一个容器复制到另一个容器中:

      算法是以迭代器方式实现的,可以从一个容器到另一个容器复制;

      也可以在数组之间进行复制,因为可以将指向数组的指针用作迭代器;

      代码将一个数组复制到一个矢量中:

        int casts[10] = { 6, 7, 2, 9, 4, 11, 8, 7, 10, 5 };

        vector dice(10);

        copy ( casts, casts + 10, dice.begin() );           // 前两个迭代器参数表示要复制的范围,

         最后一个表示要将第一个参数复制到什么位置,前两个是输入迭代器,最后一个是输出迭代器

    将信息复制到显示器上,需要一个输出流迭代器使用 copy():

      STL 为输出流迭代器提供了 ostream_iterator 模板;

      该模板是输出流迭代器概念的一个模型,也是一个适配器——一个类或函数;

      可以通过包含头文件 iterator 和相关声明创建这种迭代器:

        #include

        .  .  .

        ostream_iterator out_iter ( cout, " " );

        则 out_iter 迭代器是一个接口,可以使用cout来显示信息;

        第一个模板参数指出被发送给输出流的数据类型,第二个模板参数指出输出流使用的字符类型;

        构造函数的第一个参数(cout)指出了要使用输出流,也可用于文件的输出流;

        构造函数的第二个参数是在给发送输出流的每个数据项后显示的分隔符:

          *out_iter++ = 15;  // 将15 和空格组成的字符串发送到 cout 管理的输出流中,并为下一次操作做好了准备

      可以将 copy ()  用于迭代器:

        copy ( dice.begin(), dice.end(), out_iter );    // copy vector to output stream 即显示容器内容

        copy ( dice.begin(), dice.end(), ostream_iterator (cout," ") );

   2. 其他有用的迭代器

    头文件 iterator 还提供了其他一些专用的预定义迭代器类型:

      reverse_iterator: 执行递增操作将导致递减

      back_insert_iterator:  将元素插入到容器尾部

      front_insert_iterator:  将元素插入到容器前端

      insert_iterator:  将元素插入到构造函数的参数指定位置前面

    后3种迭代器(back_insert_iterator  front_insert_iterator  insert_oterator)可以将元素插入而不会导致覆盖问题

    copy ( dice.rbegin(), dice.rend(), out_iter );    //  display in reverse order

    记住: rbegin() 与end() 返回相同的值(超尾),但类型不同;

    例程: 

 1 #include
 2 #include
 3 #include
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     int casts[10] = { 6,7,2,9,4,11,8,7,10,5 };
 9     vector<int> dice(10);
10     // copy from array to vector
11     copy(casts, casts + 10, dice.begin());
12 
13     cout << "Let the dice be cast!\n";
14     // creat an ostream iterator
15     ostream_iterator <int, char> out_iter(cout, " ");
16 
17     // copy from ostream to output
18     copy(dice.begin(), dice.end(), out_iter);
19     cout << endl;
20 
21     cout << "Implicit use of reverse iterator.\n";
22     copy(dice.rbegin(), dice.rend(), out_iter);
23     cout << endl;
24 
25     cout << "Emplicit use of reverse iterator.\n";
26     vector<int>::reverse_iterator ri;
27     for (ri = dice.rbegin(); ri != dice.rend(); ++ri)
28         cout << *ri << ' ';
29     cout << endl;
30     return 0;
31 }

    可以用insert_iterator 将复制数据的算法转换为插入数据的算法:

      back_insert_iterator< vector > back_iter (dice);// 容器类型作为模板参数,容器标识符作为构造函数参数

      insert_iterator< vector > back_iter ( dice, dice.begin() );  // 还需要指定一个插入位置构造函数参数

   copy() 不仅可以将信息从一个容器复制到另一个容器,还可将信息从容器复制到输出流,从输入流复制到容器;

   copy() 还可以将信息插入到另一个容器

16.4.5 容器种类  

   STL 具有容器概念和容器类型;

    概念是具有名称的通用类别: 容器、序列容器、联合容器等;

    容器类型:

      deque、list、queue、priority_queue、stack、vector、map、multimap、set、multist和bitset;

      C++11 新增了 forward_list、unordered_map、 unordered_multimap、 unordered_set 和 unordered_multoset;

   1. 容器概念:

    容器概念指定了所有STL容器类都必须满足一系列要求;

    容器是存储其他对象的对象,被存储的对象必须是同一种类型,可以是OOP意义上的对象,也可以是内置类型值;

    存储在容器中的对象类型,必须是可复制构造的和可赋值的:

      基本类型满足要求;

      只要类定义没有将复制构造函数和赋值操作符声明为私有或保护的,也满足要求

    复杂度:

      编译时间: 操作在编译时执行,执行时间为 0;

      固定时间: 操作发生在运行阶段,独立于对象中的元素数目;

      线性时间: 时间与元素数目成正比;

   2. 序列

    STL 6 种容器类型(deque、list、queue、priority_queue、stack和vector)都是序列:

      队列能够在队尾添加元素,在队首删除元素,Deque表示的双端队列允许在两端添加和删除;

      序列还要求其元素按严格的线性顺序排列,即存在第一个元素、最后一个元素;

      序列具有确定的顺序,可以执行将值插入到特定位置、删除特定区间等操作;

    (1)vector

      vector模板在 vector 头文件中声明;

      vector 是数组的一种表示,提供了自动内存管理功能,可以动态改变vector对象的长度;

      提供了对元素的随机访问,在尾部添加和删除元素的时间是固定的,在头部或中部插入和删除元素复杂度为线性时间;

      vector 还是反转容器概念的模型:

        两个类方法: rbegin() 和 rend();

        返回迭代器都是类级类型 reverse_iterator

    (2)deque

      deque模板类(deque头文件)是双端队列(double-ended queue);

      支持随机访问;

      从deque对象的开始位置插入和删除元素时间是固定的;

      多数操作发生在序列的其实和结尾处,应考虑使用 deque 数据结构;

      deque对象设计比vector对象设计更为复杂一些

    (3)list

      list 模板类(list头文件)表示双向链表,可以双向遍历链表;

      list 在链表中任意位置进行插入和删除的时间都是固定的;

      vector 强调的是通过随机访问快速访问,list 强调的是元素的快速插入和删除;

      list 是可反转容器,list 不支持数组表示法和随机访问;

      list 工具箱:

        list 方法组成了一个方便的工具箱;

        sort()、merge()、unique() 方法各自拥有接受另一个参数的版本,参数用来比较元素的函数

    (4)queue

      queue 模板类(queue头文件)是一个适配器类,让底层类展示典型的队列接口;

      不能随机访问队列元素,甚至不能遍历队列,使用限制在定义队列的基本操作上;

      可以将元素添加到队尾、从队首删除元素、查看队首和队尾的值,检查元素数目和测试队列是否为空

    (5)priority_queue

      与queue主要区别: 在priority_queue中,最大元素被移到队首,内部区别在于,默认的底层是 vector;

      可以修改确定哪个元素放在队首的比较方式,方法提供一个可选的构造函数:

        priority_queue  pg1;         // default version

        priority_queue  pg2( greater );   // use greater to order 

    (6)stack

      与queue相似,stack也是一个适配器类,给底层类提供了典型的堆栈接口;

      不允许随机访问堆栈元素,甚至不允许遍历堆栈;

      可以将压入堆到栈顶,从栈顶弹出元素,查看栈顶的值,检查元素数目和测试堆栈是否为空

16.4.6 联合容器

   联合容器是对容器概念的另一个改进,联合容器将值与关键字关联在一起,使用关键字来查找值;

   联合容器提供了对元素的快速访问,与序列相似联合容器也允许插入新元素,通常包含确定数据放置位置的算法;

   STL提供四种联合容器:set、multiset、map、multimap;

   set 值与键相同,键是唯一的

   multiset 类型类似于 set ,但multiset 可能有多个值的键相同;

   map 值的类型与键字不同,键唯一,每个键对应一个值;

   multimap 类型类似于 set ,但multimap 可能有多个值的键相同;

   程序清单16.14 multimap.cpp

 1 #include
 2 #include<string>
 3 #include
 4 #include
 5 
 6 typedef int KeyType;
 7 typedef std::pair<const KeyType, std::string> Pair;
 8 typedef std::multimapstring> MapCode;
 9 
10 int main()
11 {
12     using namespace std;
13     MapCode codes;
14     codes.insert(Pair(415, "San Francisco"));
15     codes.insert(Pair(510, "Oakland"));
16     codes.insert(Pair(718, "Brooklyn"));
17     codes.insert(Pair(718, "Statenlyn"));
18     codes.insert(Pair(415, "San Rafael"));
19     codes.insert(Pair(510, "Berkeley"));
20 
21     cout << "Number of cities with area code 415: " << codes.count(415) << endl;
22     cout << "Number of cities with area code 718: " << codes.count(718) << endl;
23     cout << "Number of cities with area code 510: " << codes.count(510) << endl;
24     cout << "Area Code\n";
25 
26     MapCode::iterator it;
27     for (it = codes.begin(); it != codes.end(); ++it)
28         cout << "        " << (*it).first << "      " << (*it).second << endl;
29     pair range = codes.equal_range(718);
30     cout << "Cities with area code 718:\n";
31     for (it = range.first; it != range.second; ++it)
32         cout << (*it).second << endl;
33 
34     return 0;
35 
36 }
View Code

 

16.4.7 无序关联容器(C++ 11)

   无序关联容器也将值与键关联起来,并使用键来查找值;

   关联容器基于树结构,无序关联容器是基于数据结构的哈希表:

    旨在提高添加和删除元素的速度以及提高查找算法的效率

   有 4 种无序关联容器:

    unordered_set、unordered_multiset、unordered_map 和 unordered_multimap

16.5 函数对象

   很多STL算法都使用函数对象——函数符;

   函数符是可以以函数方式与 () 结合使用的任意对象:

    函数名、指向函数的指针、重载了 ( ) 运算符的类对象(即定义了函数 operator() ()的类)

   for_each的模板原型:

    template < class InputIterator, class Function > 

    Function for_each ( InputIterator first, InputIterator last, Function f );

16.5.1 函数符概念

   正如STL定义了容器和迭代器的概念一样,它也定义了函数符的概念:

    生成器是不用参数就可以调用的函数符;

    一元函数是用一个参数调用的函数符;

    二元函数是用两个参数可以调用的函数符;

    返回bool值的一元函数是谓词;

    返回bool值的二元函数是二元谓词

16.5.2 预定义函数符

   STL定义了多个基本函数符,它们执行诸如将两个函数相加、比较两个值是否相等操作;

   提供这些函数对象是为了支持将函数作为参数的STL容器;

   头文件functional定义了多个模板类函数对象;

   对于所有的内置的算数运算符、关系运算符和逻辑运算符,STL都提供了等价的函数符:

 

16.5.3 自适应函数符和函数适配器

   函数符自适应性意义在于: 函数适配器对象可以使用函数对象,并认为存在这些typedef成员

 

16.6 算法

    STL包含很多非成员函数:sort(),copy(),find(),random_shuffle()等:

    总体设计相同,都是使用迭代器来标识要处理的数据区间和结果的放置位置

   对于算法函数设计:

    1. 都使用模板来提供泛型;

    2. 都使用迭代器来提供访问容器中数据的通用表示

      因此,copy()函数可以用于将double值存储在数组中的容器、将string值存储在链表中的容器,

      也可以用于将用户定义的对象存储在树结构中容器,指针是一种特殊的迭代器,因此诸如copy()

      等STL容器可以用于常规数组

   统一的容器设计使得不同类型的容器之间具有明显的关系:

    可以使用copy():

      将常规数组中的值复制到vector对象中;

      将vector对象中的值复制到list对象中;

      将list对象中的值复制到set对象中

    可以使用 == 来比较不同类型的容器,如deque何vector

16.6.1 算法组

   STL将算法库分成4组:

    非修改式序列操作;

    修改式序列操作;

    排序和相关操作;

    通用数字运算;

   前3组在头文件algorithm中描述,第4组是专用于数值数据的,头文件numeric

   非修改式序列操作:

    这些操作不修改容器的内容。例如,find()和for_each()

   修改式序列操作:

    他们可以修改容器内容,可以修改值,也可以修改值的排列顺序;

    transform()、random_shuffle()和copy()

   排序和相关操作:

    包括多个排序函数(包括 sort() )和其他各种函数,包括集合操作

   数字操作:

    包括将区间内容累积、计算两个容器的内部乘积、计算相邻对象差的函数

    通常,都是数组的操作特性,因此vector是最有可能使用这些操作的容器

16.6.2 算法的通用特性

   对于算法进行分类的方式之一是按结果放置位置进行分类:

    有些算法就地完成工作;  // sort()

    有些则创建拷贝;       // copy()

   有些算法有两个版本:就地版本和复制版本;

   STL约定复制版本的名称以_copy结尾, 复制版本将接受一个额外的输出迭代器参数,该参数指定结果的放置位置

16.6.3 STL和string类

   string 类虽然不是STL的组成部分,但设计时考虑了STL;

   包含 begin()、end()、rbegin()、rend()等成员,因此可以使用STL接口;

16.6.4 函数和容器方法

   有时可以使用STL方法或STL函数,通常方法是更好的选择;

   首先方法更适合特定的容器,其次作为成员函数,方法可以使用模板类的内存管理工具,从而在需要时调整容器的长度;

   尽管方法通常更合适,但非方法函数通常更通用:

    可以将非方法函数用于数组、string对象、STL容器,还可以用它们来处理混合的容器类型  

   

      

       

    

 

                              

                              

 

    

   

 

 

 

 

 

  

 

转载于:https://www.cnblogs.com/kidycharon/p/9756484.html

你可能感兴趣的:(C++ Primer Plus 笔记第十六章)