C++入門系列博客五 C++ STL

C++ 標準模板庫(STL)


作者:AceTan,轉載請標明出處!


0x00 何為STL##

STL(Standard Template Library)標準模板庫。它是一個具有工業強度,高效的C++程序庫。它包含了諸多在計算機科學領域里所常用的基本數據結構和算法。這些數據結構可以與標準算法一起很好的工作,這為我們的軟件開發提供了良好的支持。如果你還不理解它的重要性,那我換個說法。這就好比你去打架,你不會使用STL,那就手里的武器就相當于彈弓。敵人熟練使用STL,人家手里拿的是AK47,開著坦克。

從名字就可以知道,它的設計基石是模板技術。這樣的設計帶來了更好的重用機會。STL共有以下六大組件。

  • 迭代器(iterator)

  • 容器(container)

  • 算法(algorithm)

  • 仿函數(function object)

  • 適配器(Adaptor)

  • 空間配制器(allocator)

仿函數和空間配制器不是很常用,我們主要討論一下迭代器,容器,算法和適配器。其中,我們以容器的用法為重點。


0x01 迭代器

迭代器在STL中起著粘合劑的作用,它將算法和容器聯系起來,主要用來存取容器中的元素。幾乎所有的算法都是通過迭代器存取元素進行工作的。每一個容器也都定義了其本身所專有的迭代器,用以存取容器中的元素。想象一下,你面前有一缸水(缸就好比容器),你喝水需要要到瓢(咱是文明人,不帶用雙手直接捧著喝的)。這個瓢就相當于迭代器,你可以用它來打水喝,也可以用瓢來把水缸裝滿。

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> v;        // 定義一個vector容器
    
    v.push_back(1);        // 向容器中添加3個元素
    v.push_back(2);
    v.push_back(3);

    // 遍歷向量的元素
    vector<int>::iterator b = v.begin();        // 指向容器的第一個元素
    vector<int>::iterator e = v.end();            // 指向容器尾元素的下一個位置

    // C++11新標準的寫法, auto關鍵字為類型推斷,由編譯器自動完成
    // auto b = v.begin();
    // auto e = v.end();

    for (vector<int>::iterator iter = b; iter != e; ++iter)
    {
        cout << *iter << endl;
    }

    return 0;
}

迭代器的使用,上面給了一段簡單的代碼,我們來精析一下。

迭代器最常用到的就是beginend成員。其中begin成員負責返回指向第一個元素。end成員則負責返回指向容器的“ 尾元素的下一個位置(one past the end) ”。要特別注意end成員不是指向尾元素,而是指向尾元素的下一個位置! end成員返回的迭代器也叫尾后迭代器(off-the-end iterator),簡稱尾迭代器。

如果容器為空呢?那么begin和end返回的是同一個迭代器,都是尾后迭代器。

這里要注意一下for循環的循環條件。

  • 初始化語句:vector<int>::iterator iter = b; 如果你的環境支撐C++11標準,那么強烈建議你寫成auto iter = b; 即使用類型自動推斷關鍵字auto。使用auto使程序更為簡潔,也不會出錯,由編譯器自動推斷。

  • 條件語句 iter != e; 一般的for循環里我們會用itet < e 這樣的形式,當然,在vector里改成這樣也是可以的。但是,并非所有的容器都重載了 < 運算符,所有的容器都重載了== 和 != 運算符。所以我們應該習慣使用 == 和 != 運算符。

  • 表達式語句 ++iter。 建議使用前置++而非后置++。 在迭代器中,前置++的效率高于后置++。實際上,除非邏輯需要,一般都使用前置++ 進行向前迭代。關于前置++和后置++的本質區別,看官可自行查看其它資料。

標準容器迭代器的運算符:

  • *iter: 返回迭代器iter所指元素的引用

  • iter->mem: 解引用iter并獲取該元素的名為mem的成員,等價于(*item).mem

  • ++iter: 另iter指向容器的下一個元素

  • --iter: 另iter指向元素的前一個元素

  • iter1 == iter2:判斷兩個迭代器是否相等

  • iter1 != iter2: 判斷兩個迭代器是否不相等

迭代器類型

  • iterator :可讀可寫。

  • const_iterator : 可讀不可寫。使用迭代器帶c的版本來返回,尤其是使用auto關鍵字的時候。

迭代器的范圍:

迭代器范圍(iterator range) 由一對迭代器表示,最常見的就是begin和end。begin和end所表示的范圍恰好是容器的全部元素。這是一個左閉合區間(left-inclusive interval),其標準的數學表達式為:

[begin,end)

其他迭代器:

除了為每個容器定義迭代器外,標準庫在頭文件 iterator中還定義了額外幾種迭代器,這些迭代器包括以下幾種。

  • 插入迭代器(insert iterator): 這些迭代器被綁定到一個容器上,可用來向容器中插入元素。

  • 流迭代器(stream iterator): 這些迭代器被綁定到輸入或輸出流上,可用來遍歷相關的IO流。

  • 反向迭代器(reverse iterator) 這些迭代器和正常的迭代器移動方向相反。例如++操作是指向前一個元素。除了forward_list之外的標準庫庫容器都有反向迭代器。即迭代器的r版本。

  • 移動迭代器(move iterator) 這些專用的迭代器不是拷貝其中的元素,而是移動它們。

迭代器類別:

算法所要求的迭代器可以分為5個迭代器類別(iterator category)

  • 輸入迭代器 : 只讀,不寫。單遍掃描,只能遞增。

  • 輸出迭代器 : 只寫,不讀。單遍掃描,只能遞增。

  • 前向迭代器 : 可讀可寫。多遍掃描,只能遞增。

  • 雙向迭代器 : 可讀可寫。多遍掃描,可遞增遞減。

  • 隨機訪問迭代器 : 可讀可寫。多遍掃描,支持全部迭代器運算。

下面的例子演示一下迭代器的運算,c版本的迭代器,r版本的迭代器。

#include <iostream>
#include <vector>

using namespace std;

int main()
{
    vector<int> v;        // 定義一個vector容器
    
    v.push_back(1);        // 向容器中添加5個元素
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);

    // 使用c版本的迭代器
    auto b = v.cbegin();    // 帶c版本的迭代器表示const_iterator類型的迭代器
    auto e = v.cend();        // 指向容器尾元素的下一個位置

    for (auto iter = b; iter != e; ++iter)
    {
        // *iter *= 2;            // 報錯,試圖給常量賦值!
    }

    // 反向輸出容器中的元素,使用r版本的迭代器
    auto rb = v.rbegin();        // 實際指向尾元素
    auto re = v.rend();            // 指向第一個元素的前一個位置

    for (auto iter = rb; iter != re; ++iter)
    {
        cout << *iter << endl;
    }

    // 進行迭代器的運算,輸出容器的中間元素
    auto mid = v.begin() + v.size() / 2;
    cout << "該容器的中間元素為:" << *mid << endl;

    return 0;
}

0x02 容器

容器的定義是:特定類型對象的集合

在沒有使用容器之前,我們可能會用數組解決一些問題。使用數組解決問題,那么我們必須要知道或者估算出大約要存儲多少個對象,這樣我們會創建能夠容納這些對象的內存空間大小。當我們要處理一些完全不知道要存儲多少對象的問題時,數組顯的力不從心。我們可以使用容器來解決這個問題。容器具有很高的可擴展性,我們不需要預先告訴它要存儲多少對象,只要創建一個容器,并合理的調用它所提供的方法,所有的處理細節由容器自身完成。

新標準庫的容器的性能幾乎肯定與最精心優化過的同類數據結構一樣好(通常會更好)。現代C++程序應該使用標準容器庫,而不是更原始的數據結構,如內置數組。


通用容器的分類

通用容器分為3類:順序容器、關聯容器、容器適配器


順序容器

順序容器是一種元素之間有順序的線性表,是一種線性結構的可序群集。這和我們數據結構課程上所講的線性表是一樣的。順序容器中的每個元素位置是固定的,除非你使用了插入或者刪除操作改變了這個位置。順序容器不會根據元素的特點排序而是直接保存了元素操作時的邏輯順序。比如我們一次性對一個順序容器追加三個元素,這三個元素在容器中的相對位置和追加時的邏輯次序是一致的。

順序容器都提供了快速順序訪問元素的能力。但是,他們在以下方面都有不同的性能折中:

  • 向容器中添加或者向容器中刪除元素的代價。(不是末端)

  • 非順序訪問容器中元素的代價。

順序容器的類型:

  • vector : 可變大小數組,支持快速隨機訪問。在尾部之外的位置插入或者刪除元素可能很慢。

  • deque : 雙端隊列。支持快速隨機訪問。在頭尾位置插入、刪除速度很快。

  • list : 雙向鏈表。只支持雙向順序訪問。在list中任何位置進行插入、刪除操作速度都很快。

  • forward_list : 單向鏈表。只支持單向順序訪問。在鏈表的任何位置進行插入、刪除操作都很快。(C++11標準新加)

  • array : 固定大小數組。支持快速隨機訪問。不能添加或者刪除元素。(C++11標準新加)

  • string : 與vector相似的容器,但專門用于保存字符。隨機訪問快,在尾部插入刪除快。

如何選擇呢?是不是又犯了選擇困難癥? 我們一般對癥下藥,了解這些容器的特性,根據自己的編程需求選擇適合的容器。vector、deque和list這三者我們可以優先考慮vector。vector容器適用于大量讀寫,而插入、刪除比較少的操作。list容器適用于少量讀寫,大量插入,刪除的情況。deque折中了vector和deque, 如果你需要隨機存取又關心數據的插入和刪除,那么可以選擇deque。forward_list適用于符合它這種邏輯結構的情況,array一般用來代替原生的數組。string用于和字符串操作有關的一些情況,也是實際開發中應用最多的。

關于各容器的操作,實在是太多了,下面的示例程序列舉一些比較常見的操作和用法。

#include <iostream>
#include <vector>
#include <string>
#include <deque>
#include <list>
#include <forward_list>
#include <array>

using namespace std;

int main()
{
    /*--------------------- vector容器的一些操作  ------------------*/
    vector<int> vect1;            // 定義一個vector容器
    vect1.push_back(1);            // push_back: 向容器的末尾添加元素
    vect1.push_back(2);
    vect1.push_back(3);
    vect1.pop_back();            // pop_back: 去除末尾的元素

    vect1.insert(vect1.begin() + 1, 8);    // 在某個位置插入一個元素,效率低,不適合大批操作
    vect1.at(0);                        // at:取某個位置的元素
    vect1.capacity();                    // capacity: 不分配新的內存空間的前提下它最多能保存多少元素。這個和下面的size 是有區別的!!
    vect1.size();                        // size: 已經保存的元素的數目
    vect1.empty();                        // empty:判斷容器是否為空
    vect1.front();                        // front:取第一個元素
    vect1.back();                        // back:取最后一個元素
    vect1.erase(vect1.begin() + 1);        // erase:刪除指定位置的元素
    vector<int> vect2;
    vect2.assign(vect1.begin(), vect1.end()); // 賦值操作
    /*------------------------------------------------------------*/

    // 其他容器操作都和vector差不多,以下列舉一些其他容器特有的操作


    /*--------------------- string容器一些操作  --------------------*/
    string str1 = "Hello Ace";            // string的幾種構造方法
    string str2("Hello World");        
    string str3(str1, 6);                // 從str1下標6開始構造, str3 -> Ace
    
    string str4 = str2.substr(0, 5);    // 求子串: str4 -> Hello
    string str5 = str2.substr(6);        // 求子串: str5 -> World
    string str6 = str2.substr(6, 11);    // 求子串: str6 -> World
    // string str7 = str2.substr(12);    // 拋異常: out_of_range

    string str8 = str2.replace(6, 5, "Game");    // 替換:str8 -> Hello Game 從位置6開始,刪除5個字符,并替換成"Game"
    
    string str9 = str2.append(", Hello Beauty");// 追加字符串: str9 -> Hello World, Hello Beauty

    auto pos1 = str1.find("Ace");                // 查找字符串    : pos1 -> 6 ,返回第一次出現字符串的位置,如果沒找著,則返回npos

    int res = str1.compare("Hello, Ace");        // 比較字符串: res -> -1, 根據str1是等于、大于還是小于參數指定的字符串, 返回0、整數或者負數

    string str10 = "Pi = 3.14159";
    double pi = stod(str10.substr(str10.find_first_of("+-.0123456789")));    // 數值轉換: pi -> 3.14159
    /*------------------------------------------------------------*/


    /*--------------------- deque容器一些操作  --------------------*/
    deque<int> d1;
    d1.push_back(1);                            // 尾后壓入元素
    d1.push_back(2);
    d1.push_back(3);
    d1.push_front(4);                            // 隊頭壓入元素
    d1.push_front(5);
    d1.push_front(6);
    d1.pop_back();                                // 尾后彈出一個元素
    d1.pop_front();                                // 隊頭彈出一個元素

    d1.front();                                    // 取隊頭元素
    d1.back();                                    // 取隊尾元素
    /*------------------------------------------------------------*/


    /*--------------------- list容器一些操作  --------------------*/
    list<int> l;
    l.push_back(1);                                // 尾后壓入元素
    l.push_back(2);
    l.push_back(3);
    l.push_front(4);                            // 隊頭壓入元素
    l.push_front(5);
    l.push_front(6);
    l.pop_back();                                // 尾后彈出一個元素
    l.pop_front();                                // 隊頭彈出一個元素
    l.front();                                    // 取隊頭元素
    l.back();                                    // 取隊尾元素

    l.insert(l.begin(), 88);                    // 某個位置插入元素(性能好)
    l.remove(2);                                // 刪除某個元素(和所給值相同的都刪除)
    l.reverse();                                // 倒置所有元素
    l.erase(--l.end());                            // 刪除某個位置的元素(性能好)
    /*------------------------------------------------------------*/


    /*--------------------- forward_list容器一些操作  --------------*/
    forward_list<int> fl = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    fl.push_front(0);                // 壓入元素,該容器沒有push_back方法
    auto prev = fl.before_begin();    // 表示fl的"首前元素"
    auto curr = fl.begin();            // 表示fl的第一個元素

    // 循環遍歷
    while (curr != fl.end())        // 表示仍有元素要處理
    {
        if (*curr % 2)                // 若元素為奇數,則刪除
        {
            curr = fl.erase_after(prev);    // 刪除它并移動curr
        }
        else
        {
            prev = curr;            // 移動迭代器curr,指向下一個元素,prev指向curr之前的元素
            ++curr;
        }
    }

    // 操作后: fl = {0, 2, 4, 6, 8}
    /*------------------------------------------------------------*/


    /*--------------------- array容器一些操作  --------------------*/
    array<int, 5> myArray1 = { 1, 2, 3, 4, 5 };    // 定義一個一維數組
    array<array<int, 2>, 3> myArray2 = {1, 2, 3, 4, 5, 6};    // 定義一個二維數組
    array<int, 5> myArray3 = {6, 7, 8, 9, 10};
    array<int, 5> myArray4;                // 此數組并未初始化

    // array.resize();        // array 不能有改變容器大小的操作,它的效率比vector高
    myArray1.swap(myArray3);// 交換兩個數組的的元素
    myArray4 = myArray1;    // 支持直接這樣賦值,原生的數組不可以這樣。它把值全部復制過去,而不是引用
    myArray1.assign(0);        // 把myArray1的元素全部置為0

    // 遍歷數組元素
    for (int i = 0; i < myArray1.size(); ++i)
    {
        cout << myArray1[i] << endl;
    }
    /*------------------------------------------------------------*/

    return 0;
}

關聯容器

關聯容器(associative-container)和順序容器有著根本的不同:關聯容器中元素定義是按關鍵字來保存和訪問的。與之相對,順序容器中的元素是按他們在容器中的位置來順序保存和訪問的。雖然關聯容器的很多行為和順序容器相同,但其不同之處反映了關鍵字的作用。

關聯容器支持高效的關鍵字查詢和訪問。標準庫一共定義了8個關聯容器,最主要的類型是mapset。8個容器中,每個容器:

  • 是一個map或者是一個set。map保存關鍵字-值對;set只保存關鍵字。

  • 要求關鍵字唯一或者不要求。

  • 保持關鍵字有序或者不保證有序。

關聯容器類型:

按關鍵字有序保存元素

  • map : 關聯數組;保存關鍵字-值對

  • set : 關鍵字即值,即只保存關鍵字的容器

  • multimap : 關鍵字可重復的map

  • multiset :關鍵字可重復的set

無序集合

  • unordered_map : 用哈希函數組織的map

  • unordered_set : 用哈希函數組織的set

  • unordered_multimap : 哈希組織的map;關鍵字可以重復出現

  • unordered_multiset : 哈希組織的set;關鍵字可以重復出現

從上面的容器名稱可以看出:允許重復關鍵字的容器名字都包含multi;而使用哈希技術的容器名字都以unordered開頭。

pair類型

使用關聯容器,繞不開pair類型。它定義在標準庫頭文件utility中。一個pair保存兩個數據成員。類似容器,pair是一個用來生成特定類型的模板。當創建pair時,我們必須提供兩個類型名,pair的成員將具有對應的類型。與其他標準庫類型不同,pair的數據成員是public的。兩個成員分別命名為first和second。

pair<string, string> author{"Stanley", "C++ Prime"};    // 構造一個pair

make_pair(v1, v2);                                        // 返回一個用v1和v2初始化的pair。pair的類型從v1和v2的類型推斷出來

map的使用

下面的程序是統計每個單詞在輸入中出現的次數:

#include <iostream>
#include <map>
#include <string>

using namespace std;

int main()
{
    // 統計每個單詞在輸入中出現的次數
    map<string, size_t> word_count;        // string到map的空map
    string word;
    while (cin >> word)
    {
        ++word_count[word];                // 提取word的計數器并將其加1
    }

    for (const auto &w : word_count)    // 遍歷map的每個元素
    {
        cout << w.first << "出現的次數為: " << w.second << endl;
    }

    return 0;
}

set的使用

對上面那個統計單詞的程序做一個擴展,忽略常見單詞。比如 the and or then等。 我們使用set保存想要忽略的單詞,只對不在集合中的單詞進行統計。

#include <iostream>
#include <map>
#include <set>
#include <string>

using namespace std;

int main()
{
    // 統計每個單詞在輸入中出現的次數
    map<string, size_t> word_count;        // string到map的空map
    set<string> exclude = {"The", "But", "And", "Or", "An", "A", 
                            "the", "but", "and", "or", "an", "a"};
    string word;
    while (cin >> word)
    {
        // 只統計不在exclude中的單詞。find調用返回一個迭代器,如果在集合中,返回的迭代器指向其該關鍵中。否則返回尾后迭代器
        if (exclude.find(word) == exclude.end())
        {
            ++word_count[word];                // 提取word的計數器并將其加1
        }
    }

    for (const auto &w : word_count)    // 遍歷map的每個元素
    {
        cout << w.first << "出現的次數為: " << w.second << endl;
    }

    return 0;
} 

0x03 容器適配器

除了順序容器外,標準庫還定義了三個順序容器適配器:stackqueuepriority_queue適配器(adaptor)是標準庫的一個通用概念。容器、迭代器和函數都有適配器。

本質上,一個適配器是一種機制,能使某種事物的行為看起來像另一種事物一樣。

所有容器適配器都支持的操作和類型

  • size_type : 一種類型,足以保存當前類型的最大對象的大小

  • value_type : 元素類型

  • container_type : 實現適配器的底層容器類型

  • A a : 創建一個名為a的空適配器

  • A a(c) : 創建一個名為a的適配器,帶有容器c的一個拷貝

  • 關系運算符 : 每個適配器都支持所有關系運算符: ==、!=、<、<=、>、和>=。這些運算符返回底層容器的比較結果。

  • a.empty() : 若a包含任何元素,返回fasle,反正返回true

  • a.size() : 返回a中的元素數目

  • swap(a, b) : 或寫作a.swap(b)、b.swap(a)。交換a和b的內容。a和b必須有相同的類型,包括底層容器類型也必須相同

棧適配器(stack)的額外操作

  • s.pop() : 刪除棧頂元素,但不返回該元素值。

  • s.push(item) : 創建一個新元素壓入棧頂

  • s.emplace(args) : 同push,其值由args構造

  • s.top() : 返回棧頂元素,但不將元素彈出棧

queue和priority_queue的額外操作

  • q.pop() : 返回queue的首元素或priority_queue的最高優先級的元素,但不刪除此元素。

  • q.front() : 返回首元素或者尾元素,但不刪除此元素

  • q.back() : 只使用于queue

  • q.top() : 返回最高優先級元素,但不刪除此元素

  • q.push(item) : 在queue末尾或者priority_queue中恰當的位置創建一個元素,其值為item

  • q.emplace(args) : 同push,其值由args構造

棧默認基于deque實現。queue默認基于deque實現。priority_queue默認基于vector實現。

stack和queue的使用方法比較簡單,priority_queue在存儲自己定義的數據結構時,必須重載 operator < 或者自己寫仿函數。下面給個簡單的例子:

#include <iostream>
#include <queue>

using namespace std;

struct Node
{
    int x;
    int y;
};

struct MyCmp
{
    // 自定義的比較函數
    bool operator ()(Node a, Node b)
    {
        if (a.x == b.x)
        {
            return a.y > b.y;
        }
        
        return a.x > b.x;
    }
};

int main()
{
    // priority_queue<Type, Container, Functional>
    // Type 為數據類型,Container 為保存數據的容器,Functional 為元素比較方式
    priority_queue<Node, vector<Node>, MyCmp>  myQueue;

    // 添加一些元素
    for (int i = 1; i <= 10; ++i)
    {
        Node node;
        node.x = i;
        node.y = i * i;
        myQueue.push(node);
    }

    // 遍歷元素
    while (!myQueue.empty())
    {
        cout << myQueue.top().x << "," << myQueue.top().y << endl;
        myQueue.pop();            // 出隊
    }

    return 0;
}

0x04 泛型算法

雖然容器提供了眾多操作,但有些常見的操作,比如查找特定的元素,替換或者刪除某個特定值,重新排序等,這些由一組泛型算法(generic algorithm)來實現。

大多數的算法都定義在頭文件algorithm中,有些關于數值的泛型算法定義在numeric這個頭文件中。

標準庫提供了上百個算法,幸運地是,它們的算法結構基本上是一致的。這樣我們就不用死記硬背了。

算法的形參模式

大多數的算法具有如下4種形式之一:

  • alg(beg, end, other args);

  • alg(beg, end, dest, other args);

  • alg(beg, end, beg2, other args);

  • alg(beg, end, beg2, end2, other args);

其中alg是算法的名字,beg和end表示算法所操作的輸入范圍。dest表示指定目的位置,beg2和end2表示接受第二個范圍。

標準算法庫對迭代器而不是容器進行操作。因此,算法不能直接添加或者刪除元素(可以調用容器本身的操作來完成)。

find和sort是兩個比較常見的泛型算法,我們以這兩個為例子,來演示一下泛型算法的使用。

find的簡單使用

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
    int val = 5;
    int arr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    vector<int> vec = { 11, 22, 33, 44, 55, 66, 77, 88, 99 };
    
    // 查找元素的范圍是第2個元素到第8個元素,支持內置數組
    // 如果找到想要的元素,則返回結果指向它
    auto result = find(arr + 1, arr + 7, val);
    cout << *result << endl;    // 輸出結果為 5,如果沒找到返回7,想一下為什么

    int val2 = 100;
    // 沒有找到這個值,返回vec.cend()
    auto res = find(vec.begin(), vec.end(), val2);
    if (res == vec.cend())
    {
        cout << "沒找到元素!" << endl;
    }
    else
    {
        cout << *res << endl;
    }

    return 0;
}

簡述一下find的執行步驟

  1. 訪問序列中的元素
  2. 比較此元素與我們要查找的值
  3. 如果此元素與我們要查找的值匹配,find返回標示此元素的值。
  4. 否則,find前進到下一個元素,重復執行步驟2和3。
  5. 如果到達序列尾,find停止。
  6. 如果find到達序列末尾,它應該返回一個指出元素未找到的值。此值和步驟3中返回的值必須具有相同的類型。

sort的簡單使用

參數形式為:sort(beg, end, cmp)

對于基本數據類型,第三個參數是可以省略的,有默認的實現。但對于自定義的數據類型,我們要提供第三個參數。第三個參數叫做謂詞(predicate)。標準庫有一元謂詞(unary predicate)和二元謂詞(binary predicate)之分,分別表示只接受1個參數和只接受2個參數。

下面實現一個小程序,有語文和數學兩門課的成績,按總分從大到小排序。如果總分相同,數學成績高的排在前面。

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using namespace std;

struct CoureSocre
{
    string name;    // 姓名
    int math;        // 數學成績
    int chinese;    // 語文成績
    int total;        // 總成績

    CoureSocre(string _name, int _math, int _chinese)
    {
        name = _name;
        math = _math;
        chinese = _chinese;
        total = math + chinese;
    }
};

bool myCmp(CoureSocre c1, CoureSocre c2)
{
    // 如果總成績相同
    if (c1.total == c2.total)
    {
        return c1.math >= c2.math;
    }

    return c1.total > c2.total;
}

int main()
{
    // 初始化5個學生的程序
    CoureSocre c1("Ace", 90, 95);
    CoureSocre c2("Shawna", 99, 100);
    CoureSocre c3("Kelly", 100, 99);
    CoureSocre c4("Jordan", 88, 90);
    CoureSocre c5("Kobe", 90, 88);

    // 加入容器
    vector<CoureSocre> vecScoreList = { c1, c2, c3, c4, c5 };

    // 調用sort算法進行排序
    sort(vecScoreList.begin(), vecScoreList.end(), myCmp);

    cout << "學生的成績排名為:" << endl;
    for each (CoureSocre c in vecScoreList)        // 使用for each 算法進行遍歷
    {
        cout << "姓名:" << c.name << "\t總成績:" << c.total << "\t數學:" << c.math << "\t語文:" << c.chinese << endl;
    }

    return 0;
}

另外一個和sort相關的是stable_sort算法。這種穩定排序算法維持相等元素的原有順序。

傳遞的謂詞只能接受1個或者2個參數,如果我們想傳入更多的參數怎么辦呢,這就超出了算法對謂詞的限制。這時候,我們就需要上lambda表達式了。具體細節以后會介紹。

0x05 結束語

C++ STL很好很強大,熟練使用它將使你如虎添翼。補充一個新手常踩的坑,在使用for循環時,不要在里面使用改變迭代器的操作,比如insert和erase,這些操作會使迭代器失效,從而引發意想不到的bug。

程序猿
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內容