【读书笔记:C++ primer plus 第六版 中文版】第16章 string类和标准模板库

转载请注明出处:http://blog.csdn.net/enyusmile/article/details/48677679

本章内容包括:

  • 标准C++ string类
  • 模板auto_ptr,unique_ptr和shared_ptr
  • 标准模板库(STL)
  • 容器类
  • 迭代类
  • 函数对象(functor)
  • STL算法
  • 模板initializer_list

16.1 string类

  • string类是由头文件string支持的(注意,头文件string.h和cstring支持对C-风格字符串进行操作的C库字符串函数,但 不支持string类).

16.1.1 构造字符串

  • 使用构造函数时都进行了简化,即隐藏了这样一个事实:string实际上是模板具体化basic_string的一个typedef,同时省略了与内存管理相关的参数.string类将string::npos定义为字符串的最大长度,通常为unsigned int的最大值.另外,表各种使用缩写NBTS(null-terminated string)来表示以空字符结束的字符串–传统的C字符串.
  • 表16.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结尾
    templatestring(Iter begin,Iter end) 将string对象初始化为区间[begin,end]内的字符,其中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中的字符
  • 程序清单16.1 str1.cpp

    1. 程序说明
    2. C++11新增的构造函数

16.1.2 string类输入

  • C和C++的string输入,它们之间的主要区别在于,string版本的getline()将自动调整目标string对象的大小,使之刚好能够存储输入的字符;
  • 在设计方面一个区别是,读取C-风格字符串的函数是istream类的方法,而string版本是独立的函数.这就是对于C-风格字符串输入,cin是调用对象;而对于string独享输入,cin是一个函数参数的原因.
  • string版本的getline()函数从输入中读取字符,并将其存储到目标string中,直到发生下列三种情况之一:
    • 到达文件为,在这种情况下,输入流的eofbit将被设置,这意味着方法fail()和eof()都将返回true;
    • 遇到分界字符(默认为\n),在这种情况下,将把分界字符从输入流中删除,但不存储它;
    • 读取的字符数达到最大允许值(string::npos和可供分配的内存字节数中较小的一个),在这种情况下,将设置输入流的failbit,这意味着方法fail()将返回true.
  • 程序清单16.2 strfile.cpp
    • 注意,将:制定为分界字符后,换行符将被视为常规字符.

16.1.3 使用字符串

  • size()和length()成员函数都返回字符串中的字符数.为什么这两个函数完成相同的任务呢?length()成员来自焦躁版本的string类,而size()则是为提供STL兼容性而添加的.
// hangman.cpp -- some string methods
#include 
#include 
#include 
#include 
#include 
using std::string;
const int NUM = 26;
const string wordlist[NUM] = {"apiary", "beetle", "cereal",
    "danger", "ensign", "florid", "garage", "health", "insult",
    "jackal", "keeper", "loaner", "manage", "nonce", "onset",
    "plaid", "quilt", "remote", "stolid", "train", "useful",
    "valid", "whence", "xenon", "yearn", "zippy"};
int main()
{
    using std::cout;
    using std::cin;
    using std::tolower;
    using std::endl;

    std::srand(std::time(0));
    char play;
    cout << "Will you play a word game?  ";
    cin >> play;
    play = tolower(play);
    while (play == 'y')
    {
        string target = wordlist[std::rand() % NUM];
        int length = target.length();
        string attempt(length, '-');
        string badchars;
        int guesses = 6;
        cout << "Guess my secret word. It has " << length
            << " letters, and you guess\n"
            << "one letter at a time. You get " << guesses
            << " wrong guesses.\n";
        cout << "Your word: " << attempt << endl;
        while (guesses > 0 && attempt != target)
        {
            char letter;
            cout << "Guess a letter: ";
            cin >> letter;
            if (badchars.find(letter) != string::npos
                || attempt.find(letter) != string::npos)
            {
                cout << "You already guessed that. Try again.\n";
                    continue;
            }
            int loc = target.find(letter);
            if (loc == string::npos)
            {
                cout << "Oh, bad guess!\n";
                --guesses;
                badchars += letter; // add to string
            }
            else
            {
                cout << "Good guess!\n";
                attempt[loc]=letter;
                // check if letter appears again
                loc = target.find(letter, loc + 1);
                while (loc != string::npos)
                {
                    attempt[loc]=letter;
                    loc = target.find(letter, loc + 1);
                }
           }
            cout << "Your word: " << attempt << endl;
            if (attempt != target)
            {
                if (badchars.length() > 0)
                    cout << "Bad choices: " << badchars << endl;
                cout << guesses << " bad guesses left\n";
            }
        }
        if (guesses > 0)
            cout << "That's right!\n";
        else
            cout << "Sorry, the word is " << target << ".\n";
        cout << "Will you play another?  ";
        cin >> play;
        play = tolower(play);
    }
    cout << "Bye\n";
    return 0; 
}

16.1.4 string还提供了哪些功能

  • 方法capacity()返回当前分配给字符串的内存块的大小,而reserve()方法让您能够请求内存块的最小长度.
  • 程序清单16.4 str2.cpp

16.1.5 字符串种类

  • 模板basic_string有4个具体化,每个具体化都有一个typedef名称:
typedef basic_string<char> string;
typedef basic_string<wchar_t> wstring;
typedef basic_string<char16_t> u16string;//C++11
typedef basic_string<char32_t> u32string;//C++11
  • 这让您能够使用基于类型wchar_t,char16_t,char32_t和char的字符串.
  • Allocator是一个管理内存分配的类.对于各种字符类型,都有预定义的allocator模板具体化,它们都是默认的.它们使用new和delete.

16.2 智能指针模板类

  • 智能指针是行为类似于指针的类对象,本节介绍三个可帮助管理动态内存分配的智能指针模板.
  • 但凡涉及”别忘了”的解决方法,很少是最佳的.
  • 模板auto_ptr是C++98提供的解决方案,C++11已将其摒弃,并提供了另外两种解决方案.然而,虽然auto_ptr被摒弃,但它已使用了多年;同时,如果您的编译器不支持其他两种解决方案,auto_ptr将是唯一的选择.

16.2.1 使用智能指针

  • 这三个智能指针模板(auto_ptr,unique_ptr和shared_ptr)都定义了类似指针的对象,可以将new获得(直接或简介)的地址赋给这种对象.当智能指针过期时,其析构函数将使用delete来释放内存.因此,如果将new返回的地址赋给这些对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动被释放.
  • 要创建智能指针对象,必须包含头文件memory,该文件模板定义.
  • 程序清单16.5 smrtptrs.cpp
    • 所有智能指针类都一个explicit构造函数,该构造函数将指针作为参数.因此不需要自动将指针转换为智能指针对象.
  • 对全部三种智能指针都应避免的一点:
    • string vaction(“I wandered lonely as a cloud.”);
    • shared_ptr pvac(&vacation);//NO!
    • pvac过期时,程序将把delete运算符用于非堆内存,这是错误的.

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

  • 为何有三种智能指针呢?实际上有4种,但本书不讨论weak_ptr.
  • 为什么摒弃auto_ptr呢?
  • 程序清单16.6 fowl.cpp
// fowl.cpp  -- auto_ptr a poor choice
#include 
#include 
#include 
int main()
{
    using namespace std;
    auto_ptr<string> films[5] =
    {
        auto_ptr<string> (new string("Fowl Balls")),
        auto_ptr<string> (new string("Duck Walks")),
        auto_ptr<string> (new string("Chicken Runs")),
        auto_ptr<string> (new string("Turkey Errors")),
        auto_ptr<string> (new string("Goose Eggs"))
    };
    auto_ptr<string> pwin;
    pwin = films[2];   // films[2] loses ownership
    cout << "The nominees for best avian baseball film are\n";
    for (int i = 0; i < 5; i++)
        cout << *films[i] << endl;
    cout << "The winner is " << *pwin << "!\n";
    // cin.get();
    return 0;
}
  • 消息core dumped表明,错误地使用auto_ptr可能导致问题(这种代码的行为是不确定的,其行为可能随系统而异).
  • 如果在程序清单16.6中使用shared_ptr代替auto_ptr(者要求编译器支持C++11新增的shared_ptr类),则程序将正常运行
  • 如果使用unique_ptr,结果将如何呢?与auto_ptr一样,unique_ptr也采用所有权模型.但使用unique_ptr时,程序不会等到运行阶段崩溃,而在编译器因下述代码行出现错误pwin = films[2];

16.2.3 unique_ptr为何由于auto_ptr

  • unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全).
  • unique_ptr如何能够区分安全和不安全的用法呢?答案是它使用了C++11新增的移动构造函数和右值引用.
  • 相比于auto_ptr,unique_ptr还有另一个优点.它有一个可用于数组的辩题.别忘了,必须将delete和new配对,将delete[]和new[]配对.模板auto_ptr使用delete而不是delete[],因此只能与new一起使用,二不能与new[]一起使用.但unique_ptr有使用new[]和delete[]的版本:
std::unique_ptrpad(new doule(5));//will use delete [] 
  • 警告:使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[]分配内存式,不能使用它们.不使用new分配内存时,不能使用auto_ptr,不能使用auto_ptr或shared_ptr;不使用new或new[]分配内存时,不能使用unique_ptr.

16.2.4 选择智能指针

  • 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr.但不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定).如果您的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr.
  • 如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr.如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择.

16.3 标准模板库

  • STL提供了一组表示容器,迭代器,函数对象和算法的模板.STL不是面向对象的编程,而是一种不同的编程模式—泛型编程(generic programming).

16.3.1 模板类vector

  • 要创建vector模板对象,可使用通常的表示法来指出要使用的类型.另外,vector模板使用动态内存分配,因此可以用初始化参数来指出需要多少矢量.
  • 分配器:与string类相似,各种STL容器模板都接收一个可选的模板参数,该参数制定使用哪个分配器对象来管理内存.例如,vector模板的开头与下面类似:
template T, class Allocator = allocator<T>>
    class vector{...
  • 如果省略该模板参数的值,则容器模板将默认使用allocator类.这个类使用new和delete.
  • 程序清单16.7 vect1.cpp

16.3.2 可对矢量执行的操作

  • 每个容器类都定义了一个何时的迭代器,该迭代器的类型是一个名为iterator的typedef,其作用域为整个类.
  • 可以vector::iterator pd = scores.begin();,也可以使用C++11中的auto pd = scores.begin();//C++11 automatic type deduction
  • 注意:区间[it1,it2)由迭代器it1和it2指定,其范围为it1和it2(不包括it2).
  • 程序清单16.8 vect2.cpp

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

  • 因为对有些操作来说,类特定算法的效率比通用算法高,因此,vector的成员函数swap()的效率比非成员函数swap()高,但非成员函数让您能够交换两个类型不同的容器的内容.
  • Random_shuffle()函数接受两个制定区间的迭代器参数,并随机排列该区间中的元素.
  • 程序清单16.9 vect3.cpp

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

  • 不同于for_ecah(),基于范围的for循环可修改容器的内容,诀窍是制定一个引用参数.例如,假设有如下函数:
  • void InflateReview(Review &r){r.rating++;}可使用如下循环对books的每个元素执行该函数:for(auto & x :books) InflateReview(x);

16.4 泛型编程

  • STL是一种泛型编程(generic programming).面向对象编程关注的是编程的数据方面,而泛型编程关注的是算法.它们之间的共同点是抽象和创建可重用代码,但它们的理念绝然不同.
  • 泛型编程旨在编写独立于数据类型的代码.在C++中,完成通用程序的工具是模板.当然,模板使得能够按泛型定义函数或类,而STL通过通用算法更进了一步.模板让这一切称为可能,但必须对元素进行仔细地设计.为解模板和设计是如何协同工作的,来看一看需要迭代器的原因.

16.4.1 为何使用迭代器

  • 理解迭代器是理解STL的关键所在.模板使得算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型.
  • 泛型编程旨在使用同一个find函数来处理数组,链表或任何其他容器类型.
  • 首先是处理容器的算法,应尽可能用通用的术语来表达算法,使之独立于数据类型和容器类型.为使通用算法能够适用于具体情况,应定义能够满足算法需求的迭代器,并把要求加到容器设计上.即基于算法的要求,设计基本迭代器的特征和容器特征.

16.4.2 迭代器类型

  • STL定义了5种迭代器,并根据所需的迭代器类型对算法进行了描述.这5种迭代器分别是输入迭代器,输出迭代器,正向迭代器,双向迭代器和随机访问迭代器.
  • 1.输入迭代器
    • 输入迭代器可被程序用来读取容器中的信息.
    • 并不能保证输入迭代器第二次遍历容器时,顺序不变.另外,输入迭代器被递增后,也不能保证其先前的值仍然可以被解除引用.基于输入迭代器的任何算法都应当是单通行(single-pass)的,不依赖于钱一次遍历时的迭代器值,也不依赖于本次遍历汇总前面的迭代器值.
    • 注意,输入迭代器是单向迭代器,可以递增,但不能倒退.
  • 2.输出迭代器
    • 输出迭代器与输入迭代器相似,知识解除引用让程序能修改容器值,而不能读取.
    • 简而言之,对于单通行,只读算法,可以使用输入迭代器;而对于单通行,只写算法,则可以使用输出迭代器.
  • 3.正向迭代器
    • 与输入和输出迭代器不同的是,它总是按相同的顺序遍历一系列值.另外,将正向迭代器递增后,仍然可以对前面的迭代器值解除引用(如果保存了它),并可以得到相同的值.这些特征使得多次通行算法称为可能.
  • 4.双向迭代器
    • 双向迭代器具有正向迭代器的所有特性,同时支持两种(前缀和后缀)递减运算符.
  • 5.随机访问迭代器
    • 像a+n这样的表达式仅当a和a+n都位于容器区间(包括超尾)内时才合法.

16.4.3 迭代器层次结构

  • 正向迭代器具有输入迭代器和输出迭代器的全部功能,同时还有自己的功能;双向迭代器具有正向迭代器的全部功能,同时还有自己的功能;随机访问迭代器具有正向迭代器的全部功能,同时还有自己的功能.
  • 为何需要这么多迭代器呢?目的是为了在编写算法尽可能使用要求最低的迭代器,并让它适用于容器的最大区间.
  • 注意,各种迭代器的类型并不是确定的,而只是一种概念性描述.

16.4.4 概念,改进和模型

  • STL文献使用术语概念concept来描述一系列的要求.
  • 如果所设计的容器类需要去哦迭代器,可考虑STL,它包含用于标准种类的迭代器模板.
  • 概念可以具有类似继承的关系.然而,不能将C++继承机制用于迭代器.有些STL文献使用术语改进refinement来表示这种概念上的继承,因此,双向迭代器是对正向迭代器概念的一种改进.
  • 概念的具有实现被称为模板model.
  • 1.将指针用作迭代器
    • 迭代器是广义指针,而指针满足所有的迭代器要求.迭代器是STL算法的接口,而指针是迭代器,因此STL算法可以使用指针来对基于指针的非STL容器进行操作.
    • C++支持将超尾概念用于数组,使得可以将STL算法用于常规数组.由于指针是迭代器,而算法是基于迭代器的,这使得可将STL算法用于常规数组.
  • 2.其他有用的迭代器
    • 注意:rbegin()和end()返回相同的值(超尾),但类型不同(reverse_iterator和iterator).同样,rend()和begin()也返回相同的值(指向地i铬元素的迭代器),但类型不同.
    • 程序清单16.10 copyit.cpp
// copyit.cpp -- copy() and iterators
#include 
#include 
#include 
int main()
{
    using namespace std;
    int casts[10] = {6, 7, 2, 9 ,4 , 11, 8, 7, 10, 5};
    vector<int> dice(10);
    // copy from array to vector
    copy(casts, casts + 10, dice.begin());
    cout << "Let the dice be cast!\n";
    // create an ostream iterator
    ostream_iterator<int, char> out_iter(cout, " ");
    // copy from vector to output
    copy(dice.begin(), dice.end(), out_iter);
    cout << endl;
    cout <<"Implicit use of reverse iterator.\n";
    copy(dice.rbegin(), dice.rend(), out_iter);
    cout << endl;
    cout <<"Explicit use of reverse iterator.\n";
   // vector::reverse_iterator ri;  // use if auto doesn't work
    for (auto ri = dice.rbegin(); ri != dice.rend(); ++ri)
        cout << *ri << ' ';
    cout << endl;
    // cin.get();
    return 0; 
}
  • 提示:可以用insert_iterator将复制数据的算法转换为插入数据的算法.
  • 程序清单16.11 inserts.cpp
// inserts.cpp -- copy() and insert iterators
#include 
#include 
#include 
#include 
#include 
void output(const std::string & s) {std::cout << s << " ";}
int main()
{
    using namespace std;
    string s1[4] = {"fine", "fish", "fashion", "fate"};
    string s2[2] = {"busy", "bats"};
    string s3[2] = {"silly", "singers"};
    vector<string> words(4);
    copy(s1, s1 + 4, words.begin());
    for_each(words.begin(), words.end(), output);
    cout << endl;

// construct anonymous back_insert_iterator object
    copy(s2, s2 + 2, back_insert_iterator<vector<string> >(words));
    for_each(words.begin(), words.end(), output);
    cout << endl;
// construct anonymous insert_iterator object
    copy(s3, s3 + 2, insert_iterator<vector<string> >(words, words.begin()));
    for_each(words.begin(), words.end(), output);
    cout << endl;
    // cin.get();
    return 0; 
}
  • 对于这些迭代器,请记住,只要使用就会熟悉它们.另外还请记住,这些预定义迭代器提供了STL算法的通用性.

16.4.5 容器种类

  • STL具有容器概念和容器类型.概念是具有名称的通用类别;容器类型是可用于创建具体容器对象的模板.以前的11个容器类型分别是deque,list,queue,priority_queue,stack,vector,map,multimap,set,multiset和bitset(它是在比特级处理数据的容器);C++11新增了forward_list,unordered_map,unordered_multimap,unordered_set和unordered_multiset,且不讲bitset视为容器,而将其视为一种独立的类别.
  • 1.容器概念
    • 容器概念指定了所有STL容器类都必须满足的一系列要求.
    • 存储在容器中的数据为容器所有,这意味着当容器过期时,存储在容器中的数据也将过期(然而,如果数据是指针的化,则它指向的数据并不一定过期).
    • 不能将任何类型的对象存储在容器中,具体地说,类型必须是可复制构造的和可赋值的.基本类型满足这些要求;只要类定义没有将复制构造函数和赋值运算符声明为私有或保护的,则也满足这种要求.C++改进了这些概念,添加了术语可复制插入copyInsertable和可移动插入MoveInsertable.
  • 表16.5 一些基本的容器特征
  • 其中,X表示容器类型,a和b表示类型为X的值;r表示类型为X&的值;u表示类型为X的标识符.
  • 表达式 返回类型 说明 复杂度
    X::iterator 指向T的迭代器类型 满足正向迭代器要求的任何迭代器 编译时间
    X::value_type T T的类型 编译时间
    X u; 创建一个名为u的空容器 固定
    X(); 创建一个匿名的空容器 固定
    X u(a); 调用复制构造函数后u==a 线性
    X u=a; 作用同X u(a); 线性
    r=a; X& 调用赋值运算符后r==a 线性
    (&a)->~X(); void 对容器中每个元素应用析构函数 线性
    a.begin() 迭代器 返回指向容器第一个元素的迭代器 固定
    a.end() 迭代器 返回超尾值迭代器 固定
    a.size() 无符号整型 返回元素个数,等价于a.end()-a.begin() 固定
    a.swap(b) void 交换a和b的内容 固定
    a==b 可转换为bool 如果a和b的长度相同,且a中每个元素都等于(==为真)b中相应的元素,则为帧 线性
    a!=b 可转换bool 返回!(a==b) 线性
    • ”复杂度”一列描述了执行操作所需的时间.这个表列出了3种可能性,从快到慢依次为:编译时间;固定时间;线性时间,如果复杂度为编译时间,则操作将在编译时执行,执行时间为0.固定复杂度以为这操作发生在运行阶段,但独立于对象中的元素数目.线性复杂度意味着时间与元素数目成正比.即如果a和b都是容器,则a==b具有线性复杂度,因为==操作必须用于容器中的每个元素.实际上,这是最糟糕的情况.如果两个容器的长度不同,则不需要做任何的单独比较.
    • 复杂度要求是STL特征,虽然实现细节可以隐藏,但性能规格应公开,以便程序员能够指导完成特定操作的计算成本.
  • 2.C++11新增的容器要求

    • 表16.6 C++11新增的基本容器要求,在这个表中,rv表示类型为X的非常量右值,如函数的返回值.
    • 表达式 返回类型 说明 复杂度
      X u(rv); 调用移动构造函数后,u的值与rv的原始值相同 线性
      X u=rv; 作用同Xu(rv);
      a=rv; X& 调用移动赋值运算符后,u的值与rv的原始值相同 线性
      a.cbegin() const_iterator 返回指向容器第一个元素的const迭代器 固定
      a.cend const_iterator 返回超尾值const迭代器 固定
    • 如果源对象是临时的,移动操作的效率将高于常规复制.

  • 3.序列
    • 序列概念增加了迭代器至少是正向迭代器这样的要求,这保证了元素将按特定顺序排列,不会在两次迭代之间发生变化.
    • 介绍这7种序列容器类型:
      1. vector
        • vector是数组的一种类表示,它提供了自动内存管理功能,可以动态地改变vector对象的长度,并随着元素的添加和删除而增大和缩小.它提供了对元素的随机访问.在尾部添加和删除元素的时间是固定的,但在头部或中间插入和删除元素的复杂度为线性时间.
        • 除序列外,vector还是可反转容器reversible container概念的模型.
      2. deque
        • deque模板类(在deque头文件中声明)表示双端队列double-ended queue,通常被简称为deque.
      3. list
        • list模板类(在list头文件中声明)表示双向链表.
        • 与vector不同的是,list不支持数组表示法和随机访问.
        • 程序清单 16.12 list.cpp
      4. 程序说明
        • insert()和splice()之间的主要区别在于:insert()将原始区间的副本插入到目标地址,而splice()则将原始区间移到目标地址.
        • 注意,unique()只能将相邻的相同值压缩为单个值.
      5. list工具箱
      6. forward_list(C++11)
        • C++11新增了容器类forward_list,它实现了单链表.
      7. queue
        • queue模板类(在头文件queue(以前为queue.h)中声明)是一个适配器类.
        • queue模板的限制比deque更多.它不仅不允许随机访问队列元素,甚至不允许遍历队列.
      8. priority_queue
        • 和queue的区别在于,在priority_queue中,最大的元素被移到队首(生活不总是公平的,队列也一样).内部区别在于,默认的底层类是vector.
      9. stack
        • 它不仅不允许随机访问栈元素,甚至不允许遍历栈.它把使用限制在定义栈的基本操作上,即可以将俨如推导栈顶,从栈顶弹出元素,查看栈顶的值,检查元素数目和测试栈是否为空.
      10. array(C++11)

16.4.4 关联容器

  • 关联容器associative constainer是对容器概念的另一个改进.
  • STL提供了4种关联容器:set,multiset,map和multimap.
  • 1.set示例
    • 程序清单16.13 setops.cpp
  • 2.multimap示例
    • 程序清单16.14 multmap.cpp

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

  • 无序关联容器是对容器概念的另一种改进,底层的差别在于,关联容器是基于树结构的,而无序关联容器是基于数据结构哈希表的.
  • 有4种无序关联容器,它们是unordered_set,unordered_multiset,unordered_map和unordered_multimap.

16.5 函数对象

  • 函数对象—也叫函数符functor.函数符是可以以函数方式与()结合使用的任意对象.

16.5.1 函数符概念

  • 生成器generator是不用参数就可以调用的函数符.
  • 一元函数unary function是用一个参数可以调用的函数符
  • 二元函数binary function是用两个参数可以调用的函数符.
  • 当然,这些概念都有相应的改进版:
    • 返回bool值的医院函数是为此predicate
    • 返回bool值的二元函数是二元为此binary predicate
  • 程序清单16.15 functor.cpp

16.5.2 预定义的函数符

  • 头文件functional(以前为function.h)定义了多个模板类函数对象,其中包括plus<>().
  • 对于所有内置的算术运算符,关系运算符和逻辑运算符,STL都提供了等价的函数符.
  • 警告:老式C++实现使用函数符名times,而不是multiplies.
    16.5.3 自适应函数符和函数适配器

  • STL有5个相关的概念:自适应生成器adaptable generator,自适应一元函数adaptable unary function,自适应二元函数adaptable binary function,自适应为此adaptable predicate和自适应二元为此adaptable binary predicate.

  • 使用函数符称为自适应的原因是,它携带了标识参数类型和返回类型的typedef成员.这些成员分别是result_type,first_argument_type和second_argument_type.
  • 函数符自适应性的意义在于:函数适配器对象可以使用函数对象,并认为存在这些typedef成员.
  • 程序清单16.16 funadap.cpp

16.6 算法

  • 对于算法函数设计,有两个主要的通用部分.首先,它们都使用模板来提供泛型;其次,它们都使用迭代器来提供访问容器中数据的通用表示.

16.6.1 算法组

  • STL将算法库分成4组(前3组在头文件algorithm(以前为algo.h)中描述,第4组是专用于数值数据的,有自己的头文件,称为numeric(以前它们也位于algol.h中)):
    • 非修改式序列操作;
    • 修改式序列操作;
    • 排序和相关操作;
    • 通用数字运算.

16.6.2 算法的通用特征

  • STL函数使用迭代器和迭代器区间.区间参数必须是输入迭代器或更高级别的迭代器,而指示结果存储位置的跌大旗必须是输出迭代器或更高级别的迭代器.
  • 对算法进行分类的方式之一是按结果放置的位置进行分类.(就地算法in-place algorithm,复制算法copying algorithm).有些算法有两个版本:就地版本和复制版本.STL的约定是,复制版本的名称将以_copy结尾.复制版本将接受一个额外的输出迭代器参数,该参数制定结果的放置位置.
  • 注意:replace_copy()的返回类型为OutputIterator.对于复制算法,统一的约定是:返回一个迭代器,该迭代器指向复制的最后一个值后面的一个位置.
  • 另一个常见的变体是:有些函数有这样的版本,即根据将函数应用于容器元素得到的结果来执行操作.这些版本的名称通常以_if结尾.
  • 请记住,遂让文档可指出迭代器或函数符需求,但编译器不会对此进行检查.如果您使用了错误的迭代器,则编译器视图实例化模板时,将显示大量的错误信息.

16.6.3 STL和string类

  • string类虽然不是STL的组成部分,但设计它时考虑到了STL.
  • 程序清单16.17 strngst1.cpp
    • 注意,算法next_permutation()自动提供唯一的排列组合.

16.6.4 函数和容器方法

  • 有时可以选择使用STL方法或STL函数.通常方法是更好的选择.首先,它更适合于特定的容器;其次,作为成员函数,它可以使用模板类的内存管理工具,从而在需要时调整容器的长度.
  • 程序清单16.18 listrmv.cpp
  • 尽管方法通常更适合,但非方法函数更通用.正如您看到的,可以将它们用于数组,string对象,STL容器,还可以用它们来处理混合的容器类型,例如,将矢量容器中的数据存储到链表或集合中.

16.6.5 使用STL

  • STL是一个库,其组成部分被设计成协同工作.STL组件是工具,但也是创建其他工具的基本组件.
  • map类有一个有趣的特征:可以用数组表示法(将键用作索引)来访问存储的值.
  • 程序清单16.19 usealgo.cpp
//usealgo.cpp -- using several STL elements
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
char toLower(char ch) { return tolower(ch); }
string & ToLower(string & st);
void display(const string & s);
int main()
{
    vector<string> words;
    cout << "Enter words (enter quit to quit):\n";
    string input;
    while (cin >> input && input != "quit")
        words.push_back(input);
    cout << "You entered the following words:\n";
    for_each(words.begin(), words.end(), display);
    cout << endl;
    // place words in set, converting to lowercase
    set<string> wordset;
    transform(words.begin(), words.end(),
        insert_iterator<set<string> > (wordset, wordset.begin()),
        ToLower);
    cout << "\nAlphabetic list of words:\n";
    for_each(wordset.begin(), wordset.end(), display);
    cout << endl;
    // place word and frequency in map
    map<string, int> wordmap;
    set<string>::iterator si;
    for (si = wordset.begin(); si != wordset.end(); si++)
        wordmap[*si] = count(words.begin(), words.end(), *si);
    // display map contents
    cout << "\nWord frequency:\n";
    for (si = wordset.begin(); si != wordset.end(); si++)
        cout << *si << ": " << wordmap[*si] << endl;
    // cin.get();
    // cin.get();
    return 0;
}
string & ToLower(string & st)
{
    transform(st.begin(), st.end(), st.begin(), toLower);
    return st; 
}
void display(const string & s)
{
    cout << s << " ";
}

16.7 其他库
16.7.1 vector,valarray和array

  • C++为何提供三个数组模板,这些类是由不同的小组开发的,用于不同的目的.vertor模板来是一个容器类和算法系统的一部分,它支持面向容器的操作.而valarray类模板是面向数值计算的,不是STL的一部分.最后,array是为替代内置数组而设计的,它通过提供更好,更安全的街扩,让数组更紧凑,效率更高.
  • valaarya的接口更简单是否意味着性能更高呢?在大多数情况下,答案是否定的.简单表示法通常是使用类似于您处理常规数组时使用的循环实现的.然而,有些硬件设计允许在执行矢量操作时,同时将一个数组中的值加载到一组寄存器中,然后并行的进行处理.从原则上说,valarray操作也可以实现成利用这样的设计.
  • C++11提供了接受valarray对象作为参数的模板函数begin()和end().
  • 程序清单16.20 valvect.cpp
  • 程序清单16.21 vslice.cpp

16.7.2 模板initializer_list(C++11)
16.7.3 使用initializer_list

  • 要在代码中使用initializer_list对象,必须包含头文件initializer_list.
  • 程序清单16.22 ilist.cpp
    • 可按值传递initializer_list对象,也可按引用传递.这种对象本身很小,采用的传递方式不会带来重大的性能影响.STL按值传递它们.

16.8 总结

  • string类提供了自动内存管理功能以及众多处理字符串的方法和函数.
  • 诸如auto_ptr以及C++11新增的shared_ptr和unique_ptr等职能指针模板使得管理由new分配的内存更容易.如果使用这些职能指针(而不是常规指针)来保存new返回的地址,则不必在以后使用删除运算符.职能指针对象过期时,其析构函数将自动调用delete运算符.
  • STL是一个容器类模板,迭代器类模板,函数对象模板和算法函数模板的集合,他们的设计是一致的,都是基于泛型编程原则的.算法使用模板,从而独立于所存储的对象的类型;通过使用迭代器接口,从而独立于容器的类型.迭代器是广义指针.
  • STL使用术语”概念”来描述一组要求.概念真正的实现方式被称为概念的”模型”.
  • 容器和算法都是由其提供或需要的迭代器类型表征的.应当检查容器是否具备支持算法要求的迭代器概念.
  • STL还提供了函数对象(函数符),函数对象是冲在了()运算符(即定义了operator()()方法)的类.
  • 模板类complex和valarray支持复数和数组的数值运算.

16.9 复习题

本章源代码下载地址

你可能感兴趣的:(C/C++,读书笔记,C++,primer,plus,第六版,读书笔记重点)