首頁 關於 支持
上一篇:章節1-2 下一篇:章節1-4 章節1-3練習題
快速移動至特定內容
什麼是STL
實用工具:pair
內建函式

章節1-3.STL(一)

什麼是STL

STL是標準模板函式庫(Standard Template Library)的縮寫。
模板是C++中的一個重要功能,有時候某些函式執行完全一樣的功能、過程,但若要分出許多函式處理不同型別的參數或回傳值顯然很浪費資源,因此模板允許在編寫這類程式時進行泛型,也就是事後呼叫時再決定變數的確切型別,如此一來就可以編寫一個沒有確切型別的函式,等執行的當下再將該型別帶入函式裡。
而STL正是將許多實用的函式、功能內建在C++中的程式庫。

簡而言之,以模板定義的函式是靈活的機器,不會限制輸入的材料必須是什麼類型,但會對不同材料進行同樣的處理。而STL就是C++內建包含許多方便的模板函式的神器。

其實STL主要提供的是一卡車的「演算法函式」跟一卡車的「容器(用來對眾多資料進行特定方式儲存的工具)」,不過在此之前先介紹一項雖然不歸類在上述兩種,但超級方便並且會經常使用的工具,讓我們從STL的pair開始。

實用工具:pair

直接開門見山的解釋pair的用途,他可以將兩個一樣或不一樣的型別包在一起。
使用方式為「pair<型別1,型別2>」,如「pair<int,string>」,左邊這坨就是將int與string包裝在一起,也算是一個型別,不僅可以整個處理,也可以將包裝起來的內容物拿出來。
不多說直接看程式碼。

#include<bits/stdc++.h>
using namespace std;
int main(){
    pair<int,string> p;
    //賦值給pair或者說製作pair的方式有三種,以下由複雜但不容易錯的介紹到簡單但容易不小心寫錯的
    p=pair<int,string>(5,"Samuel");//第一是在該pair型別後加上(值1,值2),型別固定
    p=make_pair(5,"Samuel");//第二是C++內建的make_pair()函式,會依據填入的引述決定pair型別,如左所示會回傳pair<int,string>型別
    //假設寫成make_pair(5LL,'S')就會回傳pair<long long int,char>型別
    p={5,"Samuel"};//最偷懶的寫法,唯一需要注意的是需要在確定p之型別時使用,換句話說不能搭配auto進行宣告(auto是另一種偷懶方式,以下介紹)
}
重要技巧:auto自動型別

而前面介紹了如何把兩個變數綁起來變成pair,這裡要開始介紹如何把pair拆開了,直接在以下示範。

#include<bits/stdc++.h>
using namespace std;
int main(){
    pair<int,string> p=pair<int,string>(5,"Samuel");//宣告p
    cout<<p.first<<','<<p.second<<'\n';//輸出「5,Samuel」
    //在pair變數後加上.first、.second可以取得pair下的兩個變數
    p.first=10;//取出後的變數可以直接使用,也可以操作單個變數來改變p,此時p變為「10,Samuel」

    //以上就是最常用的拆解方法了,但下面補充一個有點抽象的拆解方法,若記不起來可跳過
    p=pair<int,string>(5,"Samuel");
    auto [x,y]=p;//此時p的內容會被拆解並賦值給兩個新變數x,y,但x,y與p沒有連結,修改x,y值不影響p
    cout<<x<<','<<y<<'\n';//輸出「5,Samuel」
    auto &[a,b]=p;//還記得上個章節的&取別名嗎,在此方法加上&可以讓a,b為p.first,p.second的別名,修改a,b值影響p
    cout<<a<<','<<b<<'\n';//輸出「5,Samuel」
}

此外pair還有一個強大的功能,就是可以直接判斷相等、比較大小,若兩個pair的first和second都一樣,那麼是相等很好理解,但若不相等的話,會先依據兩個pair的first來決定大小,若相同就依second決定大小,其實就是字典序的概念。

以上就是pair的用法。若能熟悉pair的使用將很有幫助,如函式回傳值只會有一個,但利用pair就可一次回傳多個數值,另外在後續STL的課程中還會有許多pair的應用。

補充:綁定更多數值的方法

內建函式

前面提到了STL有許多方便的函式,以下挑出最常見的介紹,都跟陣列有關,需要搭配陣列的指標,若不熟悉可以回章節1-2的指標pro複習。

事先預告一下,下面的函式會需要對「一個範圍的陣列」進行操作,而丟給函式要操作範圍的方式就是提供「起始位址」與「結束位址」,這時函式就會幫我們從「起始位址」處理到「結束位址的前一個」,沒錯,實際處理的範圍不會包含「結束位址」
或許你會覺得很奇怪,為什麼要設計成這樣?這樣不是很難思考嗎?但其實一切都在安排裡。
假設今天剛好有一個長度為n的數列,我們將其存在陣列的a[0]~a[n-1],對應位址a~a+n-1,但因為函式不會執行到我們給他的「結束位址」,因此要將a~a+n丟給函式,有沒有發現這個範圍看起來簡潔多了。
但相對的如果存在a[1]~a[n]就要丟給函式位址a+1~a+n+1,因此筆者才會在章節0-2時就建議將資料存在從0開始(也是伏筆!)。

由於以上概念類似於數學中「前閉後開」的區間,也就是包含範圍開頭但不包含範圍結尾,記做[a,b),因此以下將位址first到位址last但不含last記做[first,last)

為了統一版面,以下將直接寫出函式的名稱、引數、回傳、說明、時間複雜度(忘記的建議複習章節1-1)、範例程式。

find(first, last, val)
first:填入位址;last:填入位址;val:填入與陣列中變數相同的型別
回傳:位址
說明:在[first,last)尋找val出現的第一個位址,回傳該位址,若找不到則回傳last
時間複雜度:O(n),n為尋找範圍大小。因為尋找方式為從頭到尾判斷一遍是否等於val,所以複雜度O(n),就算找到會提早回傳,但考慮最壞情況是找不到或者出現在隨機位置時期望值需要跑一半,複雜度仍為O(n)。

find範例程式

count(first, last, val)
first:填入位址;last:填入位址;val:填入與陣列相同的型別
回傳:int
說明:在[first,last)計算val出現的次數
時間複雜度:O(n),n為計算範圍大小。因為要從頭到尾判斷是否等於val,故複雜度O(n)。

count範例程式

reverse(first, last)
first:填入位址;last:填入位址
回傳:void,無回傳
說明:反轉[first,last)的值
時間複雜度:O(n),n為反轉範圍大小。其實反轉就是從頭跟從尾跑,兩兩配對交換直到跑到正中間,故複雜度O(n)。

reverse範例程式

sort(first, last, cmp)
first:填入位址;last:填入位址;cmp:可留空,或填入函式或greater<與陣列相同的型別>()
回傳:void,無回傳
說明:由小到大排序[first,last)的值,若有cmp函式則依據cmp函式排序,詳細解釋請查看下方補充
時間複雜度:O(n log n),n為排序範圍大小。關於排序演算法的原理與複雜度,請參考章節2-2。

sort範例程式
補充:cmp函式解釋
sort函式的進階應用

lower_bound(first, last, val, cmp)
first:填入位址;last:填入位址;val:填入與陣列相同的型別;cmp:可留空,或填入函式或greater<與陣列相同的型別>()
回傳:位址
說明:必須將[first,last)先進行sort,cmp函式與sort時使用的相同,而函式會回傳在[first,last)中第一個大於等於的位址,或者在提供cmp的情況下會回傳在[first,last)中第一個等於val或排序會在val之後的位址,若無符合條件的元素會回傳last
時間複雜度:O(log n),n為[first,last)範圍大小。使用二分搜尋實現,關於二分搜尋的原理與複雜度,請參考章節2-1。

lower_bound範例程式

upper_bound(first, last, val, cmp)
first:填入位址;last:填入位址;val:填入與陣列相同的型別;cmp:可留空,或填入函式或greater<與陣列相同的型別>()
回傳:位址
說明:必須將[first,last)先進行sort,cmp函式與sort時使用的相同,而函式會回傳在[first,last)中第一個嚴格大於(即不含等於)的位址,或者在提供cmp的情況下會回傳在[first,last)中第一個不等於val且排序會在val之後的位址,若無符合條件的元素會回傳last
時間複雜度:O(log n),n為[first,last)範圍大小。使用二分搜尋實現,關於二分搜尋的原理與複雜度,請參考章節2-1。

upper_bound範例程式

這時或許有人在思考,那如果要找到小於等於或嚴格小於特定值之最大值的函式呢?
將sort與lower_bound、upper_bound都加上cmp函式greater<型別>是個方法,但不推薦,有更好的。
考慮到我們可以用lower_bound在預設排序下找到大於等於特定值的最小值,那麼其前一個數(位址是lower_bound-1)其實就是嚴格小於特定值的最大值,但須注意若lower_bound之值已經是最小值就代表沒有嚴格小於特定值的最大值。(該結果之證明可以直接帶數字嘗試或者以數學下去思考,此處不贅述)
同理,upper_bound在預設排序下找到嚴格大於特定值的最小值,其前一個數(位址是upper_bound-1)就是小於等於特定值的最大值。

accumulate(first, last, val)
first:填入位址;last:填入位址;val:填入與陣列相同或可以相加的型別
回傳:與val相同的型別
說明:將[first,last)的所有值加到以val為初始值的變數上,或者說回傳val加上[first,last)的總和
時間複雜度:O(n),n為[first,last)範圍大小。其實也是從頭到尾一個一個加,故複雜度O(n)。
註:若相加總合會超過int範圍,記得把val值型別改為long long int。

accumulate範例程式