C++ 第10章 檢討程序與C++標準模板庫

10.1 泛型程序設計及STL的結構

10.1.1 泛型程序設計的基本概念

  • 編寫不依賴於具體數據類型的程序
  • 將算法從特定的數據結構中抽象出來,成爲通用的
  • C++的模板爲泛型程序設計奠基了關鍵的基礎

術語:概念ios

  • 用來界定具有必定功能的數據類型。例如:
    • 將「能夠比大小的全部數據類型(有比較運算符)」這一律念記爲Comparable
    • 將「具備公有的複製構造函數並能夠用‘=’賦值的數據類型」這一律念記爲Assignable
    • 將「能夠比大小、具備公有的複製構造函數並能夠用‘=’賦值的全部數據類型」這個概念記做Sortable
  • 對於兩個不一樣的概念A和B,若是概念A所需求的全部功能也是概念B所需求的功能,那麼就說概念B是概念A的子概念。例如:
    • Sortable既是Comparable的子概念,也是Assignable的子概念

術語:模型算法

  • 模型(model):符合一個概念的數據類型稱爲該概念的模型,例如:
    • int型是Comparable概念的模型。
    • 靜態數組類型不是Assignable概念的模型(沒法用「=」給整個靜態數組賦值)

用概念作模板參數名數組

  • 不少STL的實現代碼就是使用概念來命名模板參數的。
  • 爲概念賦予一個名稱,並使用該名稱做爲模板參數名。
  • 例如安全

    • 表示insertionSort這樣一個函數模板的原型:數據結構

template <class Sortable>
void insertionSort(Sortable a[], int n);

 

10.1.2 STL簡介

標準模板庫(Standard Template Library,簡稱STL)提供了一些很是經常使用的數據結構和算法less

STL簡介dom

  • 標準模板庫(Standard Template Library,簡稱STL)定義了一套概念體系,爲泛型程序設計提供了邏輯基礎數據結構和算法

  • STL中的各個類模板、函數模板的參數都是用這個體系中的概念來規定的。ide

  • 使用STL的模板時,類型參數既能夠是C++標準庫中已有的類型,也能夠是自定義的類型——只要這些類型是所要求概念的模型。函數

STL的基本組件

  • 容器(container)

  • 迭代器(iterator)

  • 函數對象(function object)

  • 算法(algorithms)

STL的基本組件間的關係

  • Iterators(迭代器)是算法和容器的橋樑。

    • 將迭代器做爲算法的參數、經過迭代器來訪問容器而不是把容器直接做爲算法的參數。

  • 函數對象做爲算法的參數而不是將函數所執行的運算做爲算法的一部分。

  • 使用STL中提供的或自定義的迭代器和函數對象,配合STL的算法,能夠組合出各類各樣的功能。

10-1.png

STL的基本組件——容器(container)

  • 容納、包含一組元素的對象。

  • 基本容器類模板

    • unorderedset (無序集合)、unorderedmultiset(無序多重集合)

    • unorderedmap(無序映射)、unordermultimap(無序多重映射)

    • set(集合)、multiset(多重集合)、map(映射)、multimap(多重映射)

    • array(數組)、vector(向量)、deque(雙端隊列)、forward_list(單鏈表)、list(列表)

    • 順序容器

    • (有序)關聯容器

    • 無序關聯容器

  • 容器適配器

    • stack(棧)、queue(隊列)、priority_queue(優先隊列)

  • 使用容器,須要包含對應的頭文件

STL的基本組件——迭代器(iterator)

  • 迭代器是泛化的指針,提供了順序訪問容器中每一個元素的方法

  • 提供了順序訪問容器中每一個元素的方法;

  • 可使用「++」運算符來得到指向下一個元素的迭代器;

  • 可使用「*」運算符訪問一個迭代器所指向的元素,若是元素類型是類或結構體,還可使用「->」運算符直接訪問該元素的一個成員;

  • 有些迭代器還支持經過「--」運算符得到指向上一個元素的迭代器;

  • 迭代器是泛化的指針:指針也具備一樣的特性,所以指針自己就是一種迭代器;

  • 使用獨立於STL容器的迭代器,須要包含頭文件

STL的基本組件——函數對象(function object)

  • 一個行爲相似函數的對象,對它能夠像調用函數同樣調用。

  • 函數對象是泛化的函數:任何普通的函數和任何重載了「()」 運算符的類的對象均可以做爲函數對象使用

  • 使用STL的函數對象,須要包含頭文件

STL的基本組件——算法(algorithms)

  • STL包括70多個算法

    • 例如:排序算法,消除算法,計數算法,比較算法,變換算法,置換算法和容器管理等

  • 能夠普遍用於不一樣的對象和內置的數據類型。

  • 使用STL的算法,須要包含頭文件。

  • 例10-1從標準輸入讀入幾個整數,存入向量容器,輸出它們的相反數

例10-1:STL程序實例

transform算法的一種實現:

template <class InputIterator, class OutputIterator, class UnaryFunction>
OutputIterator transform(InputIterator first, InputIterator last, OutputIterator result, UnaryFunction op) {
    for (;first != last; ++first, ++result)
        *result = op(*first);
    return result;
}
  • transform算法順序遍歷firstlast兩個迭代器所指向的元素;

  • 將每一個元素的值做爲函數對象op的參數;

  • 將op的返回值經過迭代器result順序輸出;

  • 遍歷完成後result迭代器指向的是輸出的最後一個元素的下一個位置,transform會將該迭代器返回

10.2 迭代器

  • 迭代器是算法和容器的橋樑

    • 迭代器用做訪問容器中的元素

    • 算法不直接操做容器中的數據,而是經過迭代器間接操做

  • 算法和容器獨立

    • 增長新的算法,無需影響容器的實現

    • 增長新的容器,原有的算法也能適用

10.2.1 輸入流迭代器和輸出流迭代器

  • 輸入流迭代器

    istream_iterator<T>
    • 以輸入流(如cin)爲參數構造

    • 可用*(p++)得到下一個輸入的元素

  • 輸出流迭代器

    ostream_iterator<T>
    • 構造時須要提供輸出流(如cout)

    • 可用(*p++) = x將x輸出到輸出流

  • 兩者都屬於適配器

    • 適配器是用來爲已有對象提供新的接口的對象

    • 輸入流適配器和輸出流適配器爲流對象提供了迭代器的接口

例10-2從標準輸入讀入幾個實數,分別將它們的平方輸出

//10_2.cpp
#include <iterator>
#include <iostream>
#include <algorithm>
using namespace std;

//求平方的函數
double square(double x) {
    return x * x;
}
int main() {
    //從標準輸入讀入若干個實數,分別將它們的平方輸出
    transform(istream_iterator<double>(cin), istream_iterator<double>(),
        ostream_iterator<double>(cout, "\t"), square);
    cout << endl;
    return 0;
}

10.2.2 迭代器的分類

10-2.png
 

迭代器支持的操做

  • 迭代器是泛化的指針,提供了相似指針的操做(諸如++、*、->運算符)

  • 輸入迭代器

    • 能夠用來從序列中讀取數據,如輸入流迭代器

  • 輸出迭代器

    • 容許向序列中寫入數據,如輸出流迭代器

  • 前向迭代器

    • 既是輸入迭代器又是輸出迭代器,而且能夠對序列進行單向的遍歷

  • 雙向迭代器

    • 與前向迭代器類似,可是在兩個方向上均可以對數據遍歷

  • 隨機訪問迭代器

    • 也是雙向迭代器,但可以在序列中的任意兩個位置之間進行跳轉,如指針、使用vector的begin()、end()函數獲得的迭代器

10.2.3 迭代器的區別

  • 兩個迭代器表示一個區間:[p1, p2)

  • STL算法常以迭代器的區間做爲輸入,傳遞輸入數據

  • 合法的區間

    • p1通過n次(n > 0)自增(++)操做後知足p1 == p2

  • 區間包含p1,但不包含p2

例10-3 綜合運用幾種迭代器的示例

程序涉及到輸入迭代器、輸出迭代器、隨機訪問迭代器這三個迭代器概念,而且之前兩個概念爲基礎編寫了一個通用算法。

//10_3.cpp
#include <algorithm>
#include <iterator>
#include <vector>
#include <iostream>
using namespace std;

//未來自輸入迭代器的n個T類型的數值排序,將結果經過輸出迭代器result輸出
template <class T, class InputIterator, class OutputIterator>
void mySort(InputIterator first, InputIterator last, OutputIterator result) {
    //經過輸入迭代器將輸入數據存入向量容器s中
    vector<T> s;
    for (;first != last; ++first)
        s.push_back(*first);
    //對s進行排序,sort函數的參數必須是隨機訪問迭代器
    sort(s.begin(), s.end());  
    copy(s.begin(), s.end(), result);   //將s序列經過輸出迭代器輸出
}

int main() {
    //將s數組的內容排序後輸出
    double a[5] = { 1.2, 2.4, 0.8, 3.3, 3.2 };
    mySort<double>(a, a + 5, ostream_iterator<double>(cout, " "));
    cout << endl;
    //從標準輸入讀入若干個整數,將排序後的結果輸出
    mySort<int>(istream_iterator<int>(cin), istream_iterator<int>(), ostream_iterator<int>(cout, " "));
    cout << endl;
    return 0;
}

運行結果:
0.8 1.2 2.4 3.2 3.3
2 -4 5 8 -1 3 6 -5
-5 -4 -1 2 3 5 6 8
 

10.2.4 迭代器的輔助函數

  • advance(p, n)

    • 對p執行n次自增操做

  • distance(first, last)

    • 計算兩個迭代器first和last的距離,即對first執行多少次「++」操做後可以使得first == last

10.3 容器

  • 容器類是容納、包含一組元素或元素集合的對象。

  • 基於容器中元素的組織方式:順序容器、關聯容器

  • 按照與容器所關聯的迭代器類型劃分:可逆容器隨機訪問容器

10.3.1 容器的基本功能

  • 容器

    • unorderedset (無序集合)、unorderedmultiset(無序多重集合)

    • unorderedmap(無序映射)、unordermultimap(無序多重映射)

    • set(集合)、multiset(多重集合)、map(映射)、multimap(多重映射)

    • array(數組)、vector(向量)、deque(雙端隊列)、forward_list(單鏈表)、list(列表)

    • 順序容器

    • (有序)關聯容器

    • 無序關聯容器

10-3.png

容器的通用功能

  • 容器的通用功能

    • 用默認構造函數構造空容器

    • 支持關係運算符:==、!=、<、<=、>、>=

    • begin()、end():得到容器首、尾迭代器

    • clear():將容器清空

    • empty():判斷容器是否爲空

    • size():獲得容器元素個數

    • s1.swap(s2):將s1和s2兩容器內容交換

  • 相關數據類型(S表示容器類型)

    • S::iterator:指向容器元素的迭代器類型

    • S::const_iterator:常迭代器類型

對可逆容器的訪問

  • STL爲每一個可逆容器都提供了逆向迭代器,逆向迭代器能夠經過下面的成員函數獲得:

    • rbegin() :指向容器尾的逆向迭代器

    • rend():指向容器首的逆向迭代器

  • 逆向迭代器的類型名的表示方式以下:

    • S::reverse_iterator:逆向迭代器類型

    • S::constreverseiterator:逆向常迭代器類型

隨機訪問容器

  • 隨機訪問容器支持對容器的元素進行隨機訪問

    • s[n]:得到容器s的第n個元素

 

10.3.2 順序容器

一、順序容器的基本功能

順序容器

  • 向量(vector)
  • 雙端隊列(deque)
  • 列表(list)
  • 單向鏈表(forward_list) (以上四種在邏輯上可看做是一個長度可擴展的數組)
  • 數組(array)
  • 元素線性排列,能夠隨時在指定位置插入元素和刪除元素。
  • 必須符合Assignable這一律念(即具備公有的拷貝構造函數並能夠用「=」賦值)。
  • array對象的大小固定,forward_list有特殊的添加和刪除操做。

順序容器的接口(不包含單向鏈表(forward_list)和數組(array))

  • 構造函數
  • 賦值函數
    • assign
  • 插入函數
    • insert, pushfront(只對list和deque), pushback,emplace,emplace_front
    • 刪除函數
    • erase,clear,popfront(只對list和deque) ,popback,emplace_back
  • 首尾元素的直接訪問
    • front,back
  • 改變大小
    • resize

例10-4 順序容器的基本操做

//10_4.cpp
#include <iostream>
#include <list>
#include <deque>

//輸出指定的順序容器的元素
template <class T>
void printContainer(const char* msg, const T& s) {
    cout << msg << ": ";
    copy(s.begin(), s.end(), ostream_iterator<int>(cout, " "));
    cout << endl;
}

int main() {
    //從標準輸入讀入10個整數,將它們分別從s的頭部加入
    deque<int> s;
    for (int i = 0; i < 10; i++) {
        int x;
        cin >> x;
        s.push_front(x);
    }
    printContainer("deque at first", s);
    //用s容器的內容的逆序構造列表容器l
    list<int> l(s.rbegin(), s.rend());
    printContainer("list at first", l);

    //將列表容器l的每相鄰兩個元素順序顛倒
    list<int>::iterator iter = l.begin();
    while (iter != l.end()) {
        int v = *iter;  
        iter = l.erase(iter);
        l.insert(++iter, v);
    }
    printContainer("list at last", l);
    //用列表容器l的內容給s賦值,將s輸出
    s.assign(l.begin(), l.end());
    printContainer("deque at last", s);
    return 0;
}
運行結果以下: 
0 9 8 6 4 3 2 1 5 4
deque at first: 4 5 1 2 3 4 6 8 9 0
list at first: 0 9 8 6 4 3 2 1 5 4
list at last: 9 0 6 8 3 4 1 2 4 5
deque at last: 9 0 6 8 3 4 1 2 4 5

二、順序容器特性

  • 順序容器:向量、雙端隊列、列表、單向鏈表、數組
  • 向量(Vector)
    • 特色
      • 一個能夠擴展的動態數組
      • 隨機訪問、在尾部插入或刪除元素快
      • 在中間或頭部插入或刪除元素慢
    • 向量的容量
      • 容量(capacity):實際分配空間的大小
      • s.capacity() :返回當前容量
      • s.reserve(n):若容量小於n,則對s進行擴展,使其容量至少爲n
  • 雙端隊列(deque)
    • 特色
      • 在兩端插入或刪除元素快
      • 在中間插入或刪除元素慢
      • 隨機訪問較快,但比向量容器慢

例10-5 奇偶排序

先按照從大到小順序輸出奇數,再按照從小到大順序輸出偶數。

// 頭部分省略
int main() {
    istream_iterator<int> i1(cin), i2;  //創建一對輸入流迭代器
    vector<int> s1(i1, i2); //經過輸入流迭代器從標準輸入流中輸入數據
    sort(s1.begin(), s1.end()); //將輸入的整數排序
    deque<int> s2;
    //如下循環遍歷s1
    for (vector<int>::iterator iter = s1.begin(); iter != s1.end(); ++iter) 
    {
         if (*iter % 2 == 0)    //偶數放到s2尾部
             s2.push_back(*iter);
         else       //奇數放到s2首部
             s2.push_front(*iter);
    }
    //將s2的結果輸出
    copy(s2.begin(), s2.end(), ostream_iterator<int>(cout, " "));
    cout << endl;
    return 0;
}

列表(list)

  • 特色
    • 在任意位置插入和刪除元素都很快
    • 不支持隨機訪問
  • 接合(splice)操做

    • s1.splice(p, s2, q1, q2):將s2中[q1, q2)移動到s1中p所指向元素以前

      // 頭部分省略
      int main() {
          string names1[] = { "Alice", "Helen", "Lucy", "Susan" };
          string names2[] = { "Bob", "David", "Levin", "Mike" };
          //用names1數組的內容構造列表s1
          list<string> s1(names1, names1 + 4); 
          //用names2數組的內容構造列表s2
          list<string> s2(names2, names2 + 4); 
      
          //將s1的第一個元素放到s2的最後
          s2.splice(s2.end(), s1, s1.begin());
          list<string>::iterator iter1 = s1.begin(); //iter1指向s1首
          advance(iter1, 2); //iter1前進2個元素,它將指向s1第3個元素
          list<string>::iterator iter2 = s2.begin();  //iter2指向s2首
          ++iter2; //iter2前進1個元素,它將指向s2第2個元素
          list<string>::iterator iter3 = iter2; //用iter2初始化iter3
          advance(iter3, 2); //iter3前進2個元素,它將指向s2第4個元素
          //將[iter2, iter3)範圍內的結點接到s1中iter1指向的結點前
          s1.splice(iter1, s2, iter2, iter3); 
      
          //分別將s1和s2輸出
          copy(s1.begin(), s1.end(), ostream_iterator<string>(cout, " "));
          cout << endl;
          copy(s2.begin(), s2.end(), ostream_iterator<string>(cout, " "));
          cout << endl;
          return 0;
      }

單向鏈表(forward_list)

  • 單向鏈表每一個結點只有指向下個結點的指針,沒有簡單的方法來獲取一個結點的前驅;
  • 未定義insert、emplace和erase操做,而定義了insertafter、emplaceafter和erase_after操做,其參數與list的insert、emplace和erase相同,但並非插入或刪除迭代器p1所指的元素,而是對p1所指元素以後的結點進行操做;
  • 不支持size操做。

數組(array)

  • array是對內置數組的封裝,提供了更安全,更方便的使用數組的方式
  • array的對象的大小是固定的,定義時除了須要指定元素類型,還須要指定容器大小。
  • 不能動態地改變容器大小

順序容器的比較

  • STL所提供的順序容器各有所長也各有所短,咱們在編寫程序時應當根據咱們對容器所須要執行的操做來決定選擇哪種容器。
    • 若是須要執行大量的隨機訪問操做,並且當擴展容器時只須要向容器尾部加入新的元素,就應當選擇向量容器vector;
    • 若是須要少許的隨機訪問操做,須要在容器兩端插入或刪除元素,則應當選擇雙端隊列容器deque;
    • 若是不須要對容器進行隨機訪問,可是須要在中間位置插入或者刪除元素,就應當選擇列表容器list或forward_list;
    • 若是須要數組,array相對於內置數組類型而言,是一種更安全、更容易使用的數組類型。

三、順序容器的插入迭代器與適配器

順序容器的插入迭代器

  • 用於向容器頭部、尾部或中間指定位置插入元素的迭代器
  • 包括前插迭代器(frontinserter)、後插迭代器(backinsrter)和任意位置插入迭代器(inserter)
  • 例:

    list<int> s;
    back_inserter iter(s);
    *(iter++) = 5; //經過iter把5插入s末尾

順序容器的適配器

  • 以順序容器爲基礎構建一些經常使用數據結構,是對順序容器的封裝
    • 棧(stack):最早壓入的元素最後被彈出
    • 隊列(queue):最早壓入的元素最早被彈出
    • 優先級隊列(priority_queue):最「大」的元素最早被彈出

棧和隊列模板

  • 棧模板

    template <class T, class Sequence = deque<T> > class stack;
  • 隊列模板

    template <class T, class FrontInsertionSequence = deque<T> > class queue;
  • 棧能夠用任何一種順序容器做爲基礎容器,而隊列只容許用前插順序容器(雙端隊列或列表)

棧和隊列共同支持的操做

  • s1 op s2 op能夠是==、!=、<、<=、>、>=之一,它會對兩個容器適配器之間的元素按字典序進行比較
  • s.size() 返回s的元素個數
  • s.empty() 返回s是否爲空
  • s.push(t) 將元素t壓入到s中
  • s.pop() 將一個元素從s中彈出,對於棧來講,每次彈出的是最後被壓入的元素,而對於隊列,每次被彈出的是最早被壓入的元素
  • 不支持迭代器,由於它們不容許對任意元素進行訪問

棧和隊列不一樣的操做

  • 棧的操做
    • s.top() 返回棧頂元素的引用
  • 隊列操做
    • s.front() 得到隊頭元素的引用
    • s.back() 得到隊尾元素的引用

例10-7 利用棧反向輸出單詞

//10_7.cpp, 省略頭部分
int main() {
    stack<char> s;
    string str;
    cin >> str; //從鍵盤輸入一個字符串
    //將字符串的每一個元素順序壓入棧中
    for (string::iterator iter = str.begin(); iter != str.end(); ++iter)
        s.push(*iter);
    //將棧中的元素順序彈出並輸出
    while (!s.empty()) {
        cout << s.top();
        s.pop();
    }
    cout << endl;
    return 0;
}
運行結果以下:
congratulations
snoitalutargnoc

優先級隊列

  • 優先級隊列也像棧和隊列同樣支持元素的壓入和彈出,但元素彈出的順序與元素的大小有關,每次彈出的老是容器中最「大」的一個元素。

    template <class T, class Sequence = vector<T> > class priority_queue;
  • 優先級隊列的基礎容器必須是支持隨機訪問的順序容器。

  • 支持棧和隊列的size、empty、push、pop幾個成員函數,用法與棧和隊列相同。
  • 優先級隊列並不支持比較操做。
  • 與棧相似,優先級隊列提供一個top函數,能夠得到下一個即將被彈出元素(即最「大」的元素)的引用。

例10-8 細胞分裂模擬

一種細胞在誕生(即上次分裂)後會在500到2000秒內分裂爲兩個細胞,每一個細胞又按照一樣的規律繼續分裂。

// 10.8.cpp, 頭部分省略
const int SPLIT_TIME_MIN = 500;    //細胞分裂最短期
const int SPLIT_TIME_MAX = 2000;  //細胞分裂最長時間

class Cell;
priority_queue<Cell> cellQueue;

class Cell {    //細胞類
private:
    static int count;   //細胞總數
    int id;     //當前細胞編號
    int time;   //細胞分裂時間
public:
    Cell(int birth) : id(count++) { //birth爲細胞誕生時間
        //初始化,肯定細胞分裂時間
        time = birth + (rand() % (SPLIT_TIME_MAX - SPLIT_TIME_MIN))+ SPLIT_TIME_MIN;
    }
    int getId() const { return id; }        //獲得細胞編號
    int getSplitTime() const { return time; }   //獲得細胞分裂時間
    bool operator < (const Cell& s) const      //定義「<」
    { return time > s.time; }
    void split() {  //細胞分裂
        Cell child1(time), child2(time);    //創建兩個子細胞
        cout << time << "s: Cell #" << id << " splits to #"
        << child1.getId() << " and #" << child2.getId() << endl;
        cellQueue.push(child1); //將第一個子細胞壓入優先級隊列
        cellQueue.push(child2); //將第二個子細胞壓入優先級隊列
    }
};
int Cell::count = 0;
int main() {
    srand(static_cast<unsigned>(time(0)));
    int t;  //模擬時間長度
    cout << "Simulation time: ";
    cin >> t;
    cellQueue.push(Cell(0));    //將第一個細胞壓入優先級隊列
    while (cellQueue.top().getSplitTime() <= t) {
        cellQueue.top().split();    //模擬下一個細胞的分裂
        cellQueue.pop();    //將剛剛分裂的細胞彈出
    }
    return 0;
}
/*
運行結果以下:
Simulation time: 5000
971s: Cell #0 splits to #1 and #2
1719s: Cell #1 splits to #3 and #4
1956s: Cell #2 splits to #5 and #6
2845s: Cell #6 splits to #7 and #8
3551s: Cell #3 splits to #9 and #10
3640s: Cell #4 splits to #11 and #12
3919s: Cell #5 splits to #13 and #14
4162s: Cell #10 splits to #15 and #16
4197s: Cell #8 splits to #17 and #18
4317s: Cell #7 splits to #19 and #20
4686s: Cell #13 splits to #21 and #22
4809s: Cell #12 splits to #23 and #24
4818s: Cell #17 splits to #25 and #26
*/

 

10.3.3 關聯容器

一、關聯容器分類和的基本功能

關聯容器的特色和接口

  • 關聯容器的特色
    • 每一個關聯容器都有一個鍵(key)
    • 能夠根據鍵高效地查找元素
  • 接口
    • 插入:insert
    • 刪除:erase
    • 查找:find
    • 定界:lowerbound、upperbound、equal_range
    • 計數:count

關聯容器概念圖

四種關聯容器

  • 單重關聯容器(set和map)
    • 鍵值是惟一的,一個鍵值只能對應一個元素
  • 多重關聯容器(multiset和multimap)
    • 鍵值是不惟一的,一個鍵值能夠對應多個元素
  • 簡單關聯容器(set和multiset)
    • 容器只有一個類型參數,如set、multiset,表示鍵類型
    • 容器的元素就是鍵自己
  • 二元關聯容器(map和multimap)
    • 容器有兩個類型參數,如map、multimap,分別表示鍵和附加數據的類型
    • 容器的元素類型是pair,即由鍵類型和元素類型複合而成的二元組

無序關聯容器

  • C++11新標準中定義了4個無序關聯容器
    • unorderedset、unorderedmap、unorderedmultiset、unorderedmultimap
  • 不是使用比較運算符來組織元素的,而是經過一個哈希函數和鍵類型的==運算符。
  • 提供了與有序容器相同的操做
  • 能夠直接定義關鍵字是內置類型的無序容器。
  • 不能直接定義關鍵字類型爲自定義類的無序容器,若是須要,必須提供咱們本身的hash模板。

二、集合(set)

集合用來存儲一組無重複的元素。因爲集合的元素自己是有序的,能夠高效地查找指定元素,也能夠方便地獲得指定大小範圍的元素在容器中所處的區間。

例10-9

輸入一串實數,將重複的去掉,取最大和最小者的中值,分別輸出小於等於此中值和大於等於此中值的實數

//10_9.cpp
#include <set>
#include <iterator>
#include <utility>
#include <iostream>
using namespace std;

int main() {
    set<double> s;
    while (true) {
        double v;
        cin >> v;
        if (v == 0) break;  //輸入0表示結束
        //嘗試將v插入
       pair<set<double>::iterator,bool> r=s.insert(v); 
        if (!r.second)  //若是v已存在,輸出提示信息
           cout << v << " is duplicated" << endl;
    }
  //獲得第一個元素的迭代器
    set<double>::iterator iter1=s.begin();
    //獲得末尾的迭代器
    set<double>::iterator iter2=s.end();    
  //獲得最小和最大元素的中值    
    double medium=(*iter1 + *(--iter2)) / 2;    
    //輸出小於或等於中值的元素
    cout<< "<= medium: "
    copy(s.begin(), s.upper_bound(medium), ostream_iterator<double>(cout, " "));
    cout << endl;
    //輸出大於或等於中值的元素
    cout << ">= medium: ";
    copy(s.lower_bound(medium), s.end(), ostream_iterator<double>(cout, " "));
    cout << endl;
    return 0;
}
運行結果以下:
  1 2.5 5 3.5 5 7 9 2.5 0
  5 is duplicated
  2.5 is duplicated
  <= medium: 1 2.5 3.5 5
  >= medium: 5 7 9

三、映射

  • 映射與集合同屬於單重關聯容器,它們的主要區別在於,集合的元素類型是鍵自己,而映射的元素類型是由鍵和附加數據所構成的二元組。
  • 在集合中按照鍵查找一個元素時,通常只是用來肯定這個元素是否存在,而在映射中按照鍵查找一個元素時,除了能肯定它的存在性外,還能夠獲得相應的附加數據。

例10-10

有五門課程,每門都有相應學分,從中選擇三門,輸出學分總和

//10_10.cpp
#include <iostream>
#include <map>
#include <string>
#include <utility>
using namespace std;
int main() {
    map<string, int> courses;
    //將課程信息插入courses映射中
    courses.insert(make_pair("CSAPP", 3));
    courses.insert(make_pair("C++", 2));
    courses.insert(make_pair("CSARCH", 4));
    courses.insert(make_pair("COMPILER", 4));
    courses.insert(make_pair("OS", 5));
    int n = 3;      //剩下的可選次數
    int sum = 0;    //學分總和
    while (n > 0) {
        string name;
        cin >> name;    //輸入課程名稱
        map<string, int>::iterator iter = courses.find(name);//查找課程
        if (iter == courses.end()) {    //判斷是否找到
            cout << name << " is not available" << endl;
        } else {
            sum += iter->second;    //累加學分
            courses.erase(iter);    //將剛選過的課程從映射中刪除
            n--;
        }
    }
    cout << "Total credit: " << sum << endl;    //輸出總學分
    return 0;
}
運行結果以下:
C++
COMPILER
C++
C++ is not available
CSAPP
Total credit: 9

例10-11

統計一句話中每一個字母出現的次數

// 10_11.cpp
#include <iostream>
#include <map>
#include <cctype>
using namespace std;
int main() {
    map<char, int> s;   //用來存儲字母出現次數的映射
    char c;     //存儲輸入字符
    do {
      cin >> c; //輸入下一個字符
      if (isalpha(c)){ //判斷是不是字母
          c = tolower(c); //將字母轉換爲小寫
          s[c]++;      //將該字母的出現頻率加1
      }
    } while (c != '.'); //碰到「.」則結束輸入
    //輸出每一個字母出現次數
    for (map<char, int>::iterator iter = s.begin(); iter != s.end(); ++iter)
        cout << iter->first << " " << iter->second << "  ";
    cout << endl;
    return 0;
}

四、多重集合(multiset)與多重映射(multimap)

  • 多重集合是容許有重複元素的集合,多重映射是容許一個鍵對應多個附加數據的映射。

  • 多重集合與集合、多重映射與映射的用法差很少,只在幾個成員函數上有細微差別,其差別主要表如今去除了鍵必須惟一的限制。

例10-12 上課時間查詢

//10_12.cpp
#include <iostream>
#include <map>
#include <utility>
#include <string>
using namespace std;
int main() {
    multimap<string, string> courses;
    typedef multimap<string, string>::iterator CourseIter;

    //將課程上課時間插入courses映射中
    courses.insert(make_pair("C++", "2-6"));
    courses.insert(make_pair("COMPILER", "3-1"));
    courses.insert(make_pair("COMPILER", "5-2"));
    courses.insert(make_pair("OS", "1-2"));
    courses.insert(make_pair("OS", "4-1"));
    courses.insert(make_pair("OS", "5-5"));
    //輸入一個課程名,直到找到該課程爲止,記下每週上課次數
    string name;
    int count;
    do {
        cin >> name;
        count = courses.count(name);
        if (count == 0)
          cout << "Cannot find this course!" << endl;
    } while (count == 0);
    //輸出每週上課次數和上課時間
    cout << count << " lesson(s) per week: ";
    pair<CourseIter, CourseIter> range = courses.equal_range(name);
    for (CourseIter iter = range.first; iter != range.second; ++iter)
        cout << iter->second << " ";
    cout << endl;

    return 0;
}
運行結果以下:
JAVA
Cannot find this course!
OS
3 lesson(s) per week: 1-2 4-1 5-5

10.4 函數對象

10.4.1 函數對象基本概念及分類

  • 一個行爲相似函數的對象

  • 能夠沒有參數,也能夠帶有若干參數

  • 其功能是獲取一個值,或者改變操做的狀態。

    • 普通函數就是函數對象

    • 重載了「()」運算符的類的實例是函數對象

函數對象概念圖

10-6.png

例10-1三、例10-14:

  • 使用兩種方式定義表示乘法的函數對象

    • 經過定義普通函數(例10-13)

    • 經過重載類的「()」運算符(例10-14)

  • 用到如下算法:

    template<class InputIterator, class Type, class BinaryFunction>
    Type accumulate(InputIterator first, InputIterator last, Type val, BinaryFunction binaryOp);
    • 對[first, last)區間內的數據進行累「加」,binaryOp爲用二元函數對象表示的「加」運算符,val爲累「加」的初值

例10-13

#include <iostream>   
#include <numeric> //包含數值算法頭文件
using namespace std;

//定義一個普通函數
int mult(int x, int y) { return x * y; };   

int main() {
    int a[] = { 1, 2, 3, 4, 5 };
    const int N = sizeof(a) / sizeof(int);
    cout << "The result by multipling all elements in a is "
        << accumulate(a, a + N, 1, mult)
        << endl;
    return 0;
}

例10-14

//10_14.cpp
#include <iostream>
#include <numeric> //包含數值算法頭文件
using namespace std;
class MultClass{  //定義MultClass類
public:
  //重載操做符operator()
    int operator() (int x, int y) const { return x * y; }   
};
int main() {
    int a[] = { 1, 2, 3, 4, 5 };
    const int N = sizeof(a) / sizeof(int);
    cout << "The result by multipling all elements in a is "
        << accumulate(a, a + N, 1, MultClass()) //將類multclass傳遞給通用算法
        << endl;
    return 0;
}

STL提供的函數對象

  • 用於算術運算的函數對象:

    • 一元函數對象(一個參數) :negate

    • 二元函數對象(兩個參數) :plus、minus、multiplies、divides、modulus

  • 用於關係運算、邏輯運算的函數對象(要求返回值爲bool)

    • 一元謂詞(一個參數):logical_not

    • 二元謂詞(兩個參數):equalto、notequalto、greater、less、greaterequal、lessequal、logicaland、logical_or

例10-15 利用STL標準函數對象

//10_15.cpp
#include <iostream>   
#include <numeric>   //包含數值算法頭文件
#include <functional>  //包含標準函數對象頭文件
using namespace std;    
int main() {
    int a[] = { 1, 2, 3, 4, 5 };
    const int N = sizeof(a) / sizeof(int);
    cout << "The result by multipling all elements in A is 「
            << accumulate(a, a + N, 1, multiplies<int>())
         << endl; //將標準函數對象傳遞給通用算法
    return 0;
}

例10-16利用STL中的二元謂詞函數對象

// 10_16.cpp
#include <functional>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main() {
    int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 };
    const int N = sizeof(intArr) / sizeof(int);
    vector<int> a(intArr, intArr + N);
    cout << "before sorting:" << endl;
    copy(a.begin(),a.end(),ostream_iterator<int>(cout,"\t"));
    cout << endl;

    sort(a.begin(), a.end(), greater<int>());

    cout << "after sorting:" << endl;
    copy(a.begin(),a.end(),ostream_iterator<int>(cout,"\t"));
    cout << endl;
    return 0;
}

10.4.2 函數適配器

  • 綁定適配器:bind1st、bind2nd
    • 將n元函數對象的指定參數綁定爲一個常數,獲得n-1元函數對象
  • 組合適配器:not一、not2
    • 將指定謂詞的結果取反
  • 函數指針適配器:ptr_fun
    • 將通常函數指針轉換爲函數對象,使之可以做爲其它函數適配器的輸入。
    • 在進行參數綁定或其餘轉換的時候,一般須要函數對象的類型信息,例如bind1st和bind2nd要求函數對象必須繼承於binary_function類型。但若是傳入的是函數指針形式的函數對象,則沒法得到函數對象的類型信息。
  • 成員函數適配器:ptrfun、ptrfun_ref
    • 對成員函數指針使用,把n元成員函數適配爲n + 1元函數對象,該函數對象的第一個參數爲調用該成員函數時的目的對象
    • 也就是須要將「object->method()」轉爲「method(object)」形式。將「object->method(arg1)」轉爲二元函數「method(object, arg1)」。

綁定適配器

  • binder2nd的實例構造一般比較冗長,bind2nd函數用於輔助構造binder2nd,產生它的一個實例。
  • binder1st和bind1st,將一個具體值綁定到二元函數的第一個參數。

例10-17:函數適配器實例——找到數組中第一個大於40的元素

//10_17.cpp
#include <functional>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

int main() {
    int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 };
    const int N = sizeof(intArr) / sizeof(int);
    vector<int> a(intArr, intArr + N);
    vector<int>::iterator p = find_if(a.begin(), a.end(), bind2nd(greater<int>(), 40));
    if (p == a.end())
        cout << "no element greater than 40" << endl;
    else
        cout << "first element greater than 40 is: " << *p << endl;
    return 0;
}

注:
find_if算法在STL中的原型聲明爲:
template<class InputIterator, class UnaryPredicate>
InputIterator find_if(InputIterator first, InputIterator last, UnaryPredicate pred);
它的功能是查找數組[first, last)區間中第一個pred(x)爲真的元素。

組合適配器

  • 對於通常的邏輯運算,有時可能還須要對結果求一次邏輯反。
  • unarynegate和binarynegate實現了這一適配功能。STL還提供了not1和not2輔助生成相應的函數對象實例,分別用於一元謂詞和二元謂詞的邏輯取反。

例10-18 ptr_fun、not1和not2產生函數適配器實例

// 10_18.cpp
#include <functional>
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

bool g(int x, int y) {
    return x > y;
}

int main() {
    int intArr[] = { 30, 90, 10, 40, 70, 50, 20, 80 };
    const int N = sizeof(intArr) / sizeof(int);
    vector<int> a(intArr, intArr + N);
    vector<int>::iterator p;
    p = find_if(a.begin(), a.end(), bind2nd(ptr_fun(g), 40));
    if (p == a.end())
        cout << "no element greater than 40" << endl;
    else
        cout << "first element greater than 40 is: " << *p << endl;
    p = find_if(a.begin(), a.end(), not1(bind2nd(greater<int>(), 15)));
    if (p == a.end())
        cout << "no element is not greater than 15" << endl;
    else
        cout << "first element that is not greater than 15 is: " << *p << endl;

    p = find_if(a.begin(), a.end(), bind2nd(not2(greater<int>()), 15));
    if (p == a.end())
        cout << "no element is not greater than 15" << endl;
    else
        cout << "first element that is not greater than 15 is: " << *p << endl;
    return 0;
}

例10-19 成員函數適配器實例

//10_19.cpp
#include <functional>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Car {
    int id;
    Car(int id) { this->id = id; }
    void display() const { cout << "car " << id << endl; }
};

int main() {
    vector<Car *> pcars;
    vector<Car> cars;
    for (int i = 0; i < 5; i++)
        pcars.push_back(new Car(i));
    for (int i = 5; i < 10; i++)
        cars.push_back(Car(i));
    cout << "elements in pcars: " << endl;
    for_each(pcars.begin(), pcars.end(), std::mem_fun(&Car::display));
    cout << endl;

    cout << "elements in cars: " << endl;
    for_each(cars.begin(), cars.end(), std::mem_fun_ref(&Car::display));
    cout << endl;

    for (size_t i = 0; i < pcars.size(); ++i)
        delete pcars[i];

    return 0;
}

 

10.5 算法

STL算法特色

  • STL算法自己是一種函數模版

    • 經過迭代器得到輸入數據

    • 經過函數對象對數據進行處理

    • 經過迭代器將結果輸出

  • STL算法是通用的,獨立於具體的數據類型、容器類型

10.5.1 STL算法基礎

  • 不可變序列算法

  • 可變序列算法

  • 排序和搜索算法

  • 數值算法

10.5.2 不可變序列算法

  • 不直接修改所操做的容器內容的算法

  • 用於查找指定元素、比較兩個序列是否相等、對元素進行計數等

  • 例:

    template<class InputIterator, class UnaryPredicate>
    InputIterator find_if(InputIterator first, InputIterator last, UnaryPredicate pred);

    查找[first, last)區間內pred(x)爲真的首個元素

10.5.3 可變序列算法

  • 能夠修改它們所操做的容器對象

  • 包括對序列進行復制、刪除、替換、倒序、旋轉、交換、分割、去重、填充、洗牌的算法及生成一個序列的算法

  • 例:

    template<class ForwardIterator, class T>
    InputIterator find_if(ForwardIterator first, ForwardIterator last, const T& x);

    將[first, last)區間內的元素所有改寫爲x。

10.5.4 排序和搜索算法

  • 對序列進行排序

  • 對兩有序序列進行合併

  • 對有序序列進行搜索

  • 有序序列的集合操做

  • 堆算法

  • 例:

    template <class RandomAccessIterator , class UnaryPredicate>
    void sort(RandomAccessIterator first, RandomAccessIterator last, UnaryPredicate comp);

    以函數對象comp爲「<」,對 [first, last)區間內的數據進行排序

10.5.5 數值算法

  • 求序列中元素的「和」、部分「和」、相鄰元素的「差」或兩序列的內積

  • 求「和」的「+」、求「差」的「-」以及求內積的「+」和「·」均可由函數對象指定

  • 例:

    template<class InputIterator, class OutputIterator, class BinaryFunction>
    
    OutputIterator partial_sum(InputIterator first, InputIterator last, OutputIterator result, BinaryFunction op);

    對[first, last)內的元素求部分「和」(所謂部分「和」,是一個長度與輸入序列相同的序列,其第n項爲輸入序列前n個元素的「和」),以函數對象op爲「+」運算符,結果經過result輸出,返回的迭代器指向輸出序列最後一個元素的下一個元素

算法應用舉例

  • 例10-20——例10-23演示了幾類算法的應用。

  • 詳見教材第10章

相關文章
相關標籤/搜索