STL完整版整理
參考:大佬部落格
1. STL概論
長久以來,軟體界一直希望建立一種可重複利用的東西,以及一種得以製造出”可重複運用的東西”的方法,讓程式設計師的心血不止於隨時間的遷移,人事異動而煙消雲散,從函式(functions),類別(classes),函式庫(function libraries),類別庫(class libraries)、各種元件,從模組化設計,到面向物件(object oriented ),為的就是複用性的提升。
複用性必須建立在某種標準之上。但是在許多環境下,就連軟體開發最基本的資料結構(data structures) 和演算法(algorithm)都未能有一套標準。大量程式設計師被迫從事大量重複的工作,竟然是為了完成前人已經完成而自己手上並未擁有的程式程式碼,這不僅是人力資源的浪費,也是挫折與痛苦的來源。
為了建立資料結構和演算法的一套標準,並且降低他們之間的耦合關係,以提升各自的獨立性、彈性、互動操作性(相互合作性,interoperability),誕生了STL。
1.1 STL基本概念
STL(Standard Template Library,標準模板庫),是惠普實驗室開發的一系列軟體的統
稱。現在主要出現在 c++中,但是在引入 c++之前該技術已經存在很長時間了。
STL 從廣義上分為: 容器(container) 演算法(algorithm) 迭代器(iterator),容器和演算法之間通過迭代器進行無縫連線。STL 幾乎所有的程式碼都採用了模板類或者模板函式,這相比傳統的由函式和類組成的庫來說提供了更好的程式碼重用機會。STL(Standard Template Library)標準模板庫,在我們 c++標準程式庫中隸屬於 STL 的佔到了 80%以上。
1.2 STL六大元件簡介
STL提供了六大元件,彼此之間可以組合套用,這六大元件分別是:容器、演算法、迭代器、仿函式、介面卡(配接器)、空間配置器。
容器:各種資料結構,如vector、list、deque、set、map等,用來存放資料,從實現角度來看,STL容器是一種class template。
演算法:各種常用的演算法,如sort、find、copy、for_each。從實現的角度來看,STL演算法是一種function tempalte.
迭代器:扮演了容器與演算法之間的膠合劑,共有五種型別,從實現角度來看,迭代器是一種將operator* , operator-> , operator++,operator--等指標相關操作予以過載的class template. 所有STL容器都附帶有自己專屬的迭代器,只有容器的設計者才知道如何遍歷自己的元素。原生指標(native pointer)也是一種迭代器。
仿函式:行為類似函式,可作為演算法的某種策略。從實現角度來看,仿函式是一種過載了operator()的class 或者class template
介面卡:一種用來修飾容器或者仿函式或迭代器介面的東西。
空間配置器:負責空間的配置與管理。從實現角度看,配置器是一個實現了動態空間配置、空間管理、空間釋放的class tempalte.
STL六大元件的互動關係,容器通過空間配置器取得資料儲存空間,演算法通過迭代器儲存容器中的內容,仿函式可以協助演算法完成不同的策略的變化,介面卡可以修飾仿函式。
1.3 STL優點
STL 是 C++的一部分,因此不用額外安裝什麼,它被內建在你的編譯器之內。
STL 的一個重要特性是將資料和操作分離。資料由容器類別加以管理,操作則由可定製的演算法定義。迭代器在兩者之間充當“粘合劑”,以使演算法可以和容器互動運作
程式設計師可以不用思考 STL 具體的實現過程,只要能夠熟練使用 STL 就 OK 了。這樣他們就可以把精力放在程式開發的別的方面。
STL 具有高可重用性,高效能,高移植性,跨平臺的優點。
高可重用性:STL 中幾乎所有的程式碼都採用了模板類和模版函式的方式實現,這相比於傳統的由函式和類組成的庫來說提供了更好的程式碼重用機會。關於模板的知
識,已經給大家介紹了。
高效能:如 map 可以高效地從十萬條記錄裡面查找出指定的記錄,因為 map 是採用紅黑樹的變體實現的。
高移植性:如在專案 A 上用 STL 編寫的模組,可以直接移植到專案 B 上。
STL之父Alex Stepanov 亞歷山大·斯特潘諾夫(STL建立者)
2. STL三大元件
2.1 容器
容器,置物之所也。
研究資料的特定排列方式,以利於搜尋或排序或其他特殊目的,這一門學科我們稱為資料結構。大學資訊類相關專業裡面,與程式設計最有直接關係的學科,首推資料結構與演算法。幾乎可以說,任何特定的資料結構都是為了實現某種特定的演算法。STL容器就是將運用最廣泛的一些資料結構實現出來。
常用的資料結構:陣列(array),連結串列(list),tree(樹),棧(stack),佇列(queue),集合(set),對映表(map),根據資料在容器中的排列特性,這些資料分為序列式容器和關聯式容器兩種。
序列式容器強調值的排序,序列式容器中的每個元素均有固定的位置,除非用刪除或插入的操作改變這個位置。Vector容器、Deque容器、List容器等。
關聯式容器是非線性的樹結構,更準確的說是二叉樹結構。各元素之間沒有嚴格的物理上的順序關係,也就是說元素在容器中並沒有儲存元素置入容器時的邏輯順序。關聯式容器另一個顯著特點是:在值中選擇一個值作為關鍵字key,這個關鍵字對值起到索引的作用,方便查詢。Set/multiset容器 Map/multimap容器
2.2 演算法
演算法,問題之解法也。
以有限的步驟,解決邏輯或數學上的問題,這一門學科我們叫做演算法(Algorithms).
廣義而言,我們所編寫的每個程式都是一個演算法,其中的每個函式也都是一個演算法,畢竟它們都是用來解決或大或小的邏輯問題或數學問題。STL收錄的演算法經過了數學上的效能分析與證明,是極具複用價值的,包括常用的排序,查詢等等。特定的演算法往往搭配特定的資料結構,演算法與資料結構相輔相成。
演算法分為:質變演算法和非質變演算法。
質變演算法:是指運算過程中會更改區間內的元素的內容。例如拷貝,替換,刪除等等
非質變演算法:是指運算過程中不會更改區間內的元素內容,例如查詢、計數、遍歷、尋找極值等等
再好的程式設計技巧,也無法讓一個笨拙的演算法起死回生。
2.3 迭代器
迭代器(iterator)是一種抽象的設計概念,現實程式語言中並沒有直接對應於這個概念的實物。在<<Design Patterns>>一書中提供了23中設計模式的完整描述,其中iterator模式定義如下:提供一種方法,使之能夠依序尋訪某個容器所含的各個元素,而又無需暴露該容器的內部表示方式。
迭代器的設計思維-STL的關鍵所在,STL的中心思想在於將容器(container)和演算法(algorithms)分開,彼此獨立設計,最後再一貼膠著劑將他們撮合在一起。從技術角度來看,容器和演算法的泛型化並不困難,c++的class template和function template可分別達到目標,如果設計出兩這個之間的良好的膠著劑,才是大難題。
迭代器的種類:
輸入迭代器 提供對資料的只讀訪問 只讀,支援++、==、!=
輸出迭代器 提供對資料的只寫訪問 只寫,支援++
前向迭代器 提供讀寫操作,並能向前推進迭代器 讀寫,支援++、==、!=
雙向迭代器 提供讀寫操作,並能向前和向後操作 讀寫,支援++、--,
隨機訪問迭代器 提供讀寫操作,並能以跳躍的方式訪問容器的任意資料,是功能最強的迭代器 讀寫,支援++、--、[n]、-n、<、<=、>、>=
2.3 案例
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//STL 中的容器 演算法 迭代器
void test01(){
vector<int> v; //STL 中的標準容器之一 :動態陣列
v.push_back(1); //vector 容器提供的插入資料的方法
v.push_back(5);
v.push_back(3);
v.push_back(7);
//迭代器
vector<int>::iterator pStart = v.begin(); //vector 容器提供了 begin()方法 返回指向第一個元素的迭代器
vector<int>::iterator pEnd = v.end(); //vector 容器提供了 end()方法 返回指向最後一個元素下一個位置的迭代器
//通過迭代器遍歷
while (pStart != pEnd){
cout << *pStart << " ";
pStart++;
}
cout << endl;
//演算法 count 演算法 用於統計元素的個數
int n = count(pStart, pEnd, 5);
cout << "n:" << n << endl;
}
//STL 容器不單單可以儲存基礎資料型別,也可以儲存類物件
class Teacher
{
public:
Teacher(int age) :age(age){};
~Teacher(){};
public:
int age;
};
void test02(){
vector<Teacher> v; //儲存 Teacher 型別資料的容器
Teacher t1(10), t2(20), t3(30);
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
vector<Teacher>::iterator pStart = v.begin();
vector<Teacher>::iterator pEnd = v.end();
//通過迭代器遍歷
while (pStart != pEnd){
cout << pStart->age << " ";
pStart++;
}
cout << endl;
}
//儲存 Teacher 型別指標
void test03(){
vector<Teacher*> v; //儲存 Teacher 型別指標
Teacher* t1 = new Teacher(10);
Teacher* t2 = new Teacher(20);
Teacher* t3 = new Teacher(30);
v.push_back(t1);
v.push_back(t2);
v.push_back(t3);
//拿到容器迭代器
vector<Teacher*>::iterator pStart = v.begin();
vector<Teacher*>::iterator pEnd = v.end();
//通過迭代器遍歷
while (pStart != pEnd){
cout << (*pStart)->age << " ";
pStart++;
}
cout << endl;
}
//容器巢狀容器 難點(不理解,可以跳過)
void test04()
{
vector< vector<int> > v;
vector<int>v1;
vector<int>v2;
vector<int>v3;
for (int i = 0; i < 5;i++)
{
v1.push_back(i);
v2.push_back(i * 10);
v3.push_back(i * 100);
}
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
for (vector< vector<int> >::iterator it = v.begin(); it != v.end();it++)
{
for (vector<int>::iterator subIt = (*it).begin(); subIt != (*it).end(); subIt ++)
{
cout << *subIt << " ";
}
cout << endl;
}
}
int main(){
//test01();
//test02();
//test03();
test04();
system("pause");
return EXIT_SUCCESS;
}
3. 常用容器
3.1 string容器
3.1.1 string容器基本概念
C風格字串(以空字元結尾的字元陣列)太過複雜難於掌握,不適合大程式的開發,所以C++標準庫定義了一種string類,定義在標頭檔案<string>。
String和c風格字串對比:
Char*是一個指標,String是一個類
string封裝了char*,管理這個字串,是一個char*型的容器。
String封裝了很多實用的成員方法
查詢find,拷貝copy,刪除delete 替換replace,插入insert
不用考慮記憶體釋放和越界
string管理char*所分配的記憶體。每一次string的複製,取值都由string類負責維護,不用擔心複製越界和取值越界等。
3.1.2 string容器常用操作
3.1.2.1 string 建構函式
string();//建立一個空的字串 例如: string str;
string(const string& str);//使用一個string物件初始化另一個string物件
string(const char* s);//使用字串s初始化
string(int n, char c);//使用n個字元c初始化
3.1.2.2 string基本賦值操作
string& operator=(const char* s);//char*型別字串 賦值給當前的字串
string& operator=(const string &s);//把字串s賦給當前的字串
string& operator=(char c);//字元賦值給當前的字串
string& assign(const char *s);//把字串s賦給當前的字串
string& assign(const char *s, int n);//把字串s的前n個字元賦給當前的字串
string& assign(const string &s);//把字串s賦給當前字串
string& assign(int n, char c);//用n個字元c賦給當前字串
string& assign(const string &s, int start, int n);//將s從start開始n個字元賦值給字串
3.1.2.3 string存取字元操作
char& operator[](int n);//通過[]方式取字元
char& at(int n);//通過at方法獲取字元
3.1.2.4 string拼接操作
string& operator+=(const string& str);//過載+=操作符
string& operator+=(const char* str);//過載+=操作符
string& operator+=(const char c);//過載+=操作符
string& append(const char *s);//把字串s連線到當前字串結尾
string& append(const char *s, int n);//把字串s的前n個字元連線到當前字串結尾
string& append(const string &s);//同operator+=()
string& append(const string &s, int pos, int n);//把字串s中從pos開始的n個字元連線到當前字串結尾
string& append(int n, char c);//在當前字串結尾新增n個字元c
3.1.2.5 string查詢和替換
int find(const string& str, int pos = 0) const; //查詢str第一次出現位置,從pos開始查詢
int find(const char* s, int pos = 0) const; //查詢s第一次出現位置,從pos開始查詢
int find(const char* s, int pos, int n) const; //從pos位置查詢s的前n個字元第一次位置
int find(const char c, int pos = 0) const; //查詢字元c第一次出現位置
int rfind(const string& str, int pos = npos) const;//查詢str最後一次位置,從pos開始查詢
int rfind(const char* s, int pos = npos) const;//查詢s最後一次出現位置,從pos開始查詢
int rfind(const char* s, int pos, int n) const;//從pos查詢s的前n個字元最後一次位置
int rfind(const char c, int pos = 0) const; //查詢字元c最後一次出現位置
string& replace(int pos, int n, const string& str); //替換從pos開始n個字元為字串str
string& replace(int pos, int n, const char* s); //替換從pos開始的n個字元為字串s
3.1.2.6 string比較操作
/*
compare函式在>時返回 1,<時返回 -1,==時返回 0。
比較區分大小寫,比較時參考字典順序,排越前面的越小。
大寫的A比小寫的a小。
*/
int compare(const string &s) const;//與字串s比較
int compare(const char *s) const;//與字串s比較
3.1.2.7 string子串
string substr(int pos = 0, int n = npos) const;//返回由pos開始的n個字元組成的字串
3.1.2.8 string插入和刪除操作
string& insert(int pos, const char* s); //插入字串
string& insert(int pos, const string& str); //插入字串
string& insert(int pos, int n, char c);//在指定位置插入n個字元c
string& erase(int pos, int n = npos);//刪除從Pos開始的n個字元
3.1.2.9 string和c-style字串轉換
//string 轉 char*
string str = "itcast";
const char* cstr = str.c_str();
//char* 轉 string
char* s = "itcast";
string str(s);
提示:
在c++中存在一個從const char*到string的隱式型別轉換,卻不存在從一個string物件到C_string的自動型別轉換。對於string型別的字串,可以通過c_str()函式返回string物件對應的C_string.
通常,程式設計師在整個程式中應堅持使用string類物件,直到必須將內容轉化為char*時才將其轉換為C_string.
提示:
為了修改string字串的內容,下標操作符[]和at都會返回字元的引用。但當字串的記憶體被重新分配之後,可能發生錯誤.
string s = "abcdefg";
char& a = s[2];
char& b = s[3];
a = '1';
b = '2';
cout << s << endl;
cout << (int*)s.c_str() << endl;
s = "pppppppppppppppppppppppp";
//a = '1';
//b = '2';
cout << s << endl;
cout << (int*)s.c_str() << endl;
3.2 vector容器
3.2.1 vector容器基本概念
vector的資料安排以及操作方式,與array非常相似,兩者的唯一差別在於空間的運用的靈活性。Array是靜態空間,一旦配置了就不能改變,要換大一點或者小一點的空間,可以,一切瑣碎得由自己來,首先配置一塊新的空間,然後將舊空間的資料搬往新空間,再釋放原來的空間。Vector是動態空間,隨著元素的加入,它的內部機制會自動擴充空間以容納新元素。因此vector的運用對於記憶體的合理利用與運用的靈活性有很大的幫助,我們再也不必害怕空間不足而一開始就要求一個大塊頭的array了。
Vector的實現技術,關鍵在於其對大小的控制以及重新配置時的資料移動效率,一旦vector舊空間滿了,如果客戶每新增一個元素,vector內部只是擴充一個元素的空間,實為不智,因為所謂的擴充空間(不論多大),一如剛所說,是”配置新空間-資料移動-釋放舊空間”的大工程,時間成本很高,應該加入某種未雨綢繆的考慮,稍後我們便可以看到vector的空間配置策略。
3.2.2 vector迭代器
Vector維護一個線性空間,所以不論元素的型別如何,普通指標都可以作為vector的迭代器,因為vector迭代器所需要的操作行為,如operaroe*, operator->, operator++, operator--, operator+, operator-, operator+=, operator-=, 普通指標天生具備。Vector支援隨機存取,而普通指標正有著這樣的能力。所以vector提供的是隨機訪問迭代器(Random Access Iterators).
根據上述描述,如果我們寫如下的程式碼:
Vector<int>::iterator it1;
Vector<Teacher>::iterator it2;
it1的型別其實就是Int*,it2的型別其實就是Teacher*.
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> v;
for (int i = 0; i < 10;i ++){
v.push_back(i);
cout << v.capacity() << endl; // v.capacity()容器的容量
}
system("pause");
return EXIT_SUCCESS;
}
3.2.3 vector的資料結構
Vector所採用的資料結構非常簡單,線性連續空間,它以兩個迭代器_Myfirst和_Mylast分別指向配置得來的連續空間中目前已被使用的範圍,並以迭代器_Myend指向整塊連續記憶體空間的尾端。
為了降低空間配置時的速度成本,vector實際配置的大小可能比客戶端需求大一些,以備將來可能的擴充,這便是容量的概念。換句話說,一個vector的容量永遠大於或等於其大小,一旦容量等於大小,便是滿載,下次再有新增元素,整個vector容器就得另覓居所。
注意:
所謂動態增加大小,並不是在原空間之後續接新空間(因為無法保證原空間之後尚有可配置的空間),而是一塊更大的記憶體空間,然後將原資料拷貝新空間,並釋放原空間。因此,對vector的任何操作,一旦引起空間的重新配置,指向原vector的所有迭代器就都失效了。這是程式設計師容易犯的一個錯誤,務必小心。
3.2.4 vector常用API操作
3.2.4.1 vector建構函式
vector<T> v; //採用模板實現類實現,預設建構函式
vector(v.begin(), v.end());//將v[begin(), end())區間中的元素拷貝給本身。
vector(n, elem);//建構函式將n個elem拷貝給本身。
vector(const vector &vec);//拷貝建構函式。
//例子 使用第二個建構函式 我們可以...
int arr[] = {2,3,4,1,9};
vector<int> v1(arr, arr + sizeof(arr) / sizeof(int));
3.2.4.2 vector常用賦值操作
assign(beg, end);//將[beg, end)區間中的資料拷貝賦值給本身。
assign(n, elem);//將n個elem拷貝賦值給本身。
vector& operator=(const vector &vec);//過載等號操作符
swap(vec);// 將vec與本身的元素互換。
3.2.4.3 vector大小操作
size();//返回容器中元素的個數
empty();//判斷容器是否為空
resize(int num);//重新指定容器的長度為num,若容器變長,則以預設值填充新位置。如果容器變短,則末尾超出容器長度的元素被刪除。
resize(int num, elem);//重新指定容器的長度為num,若容器變長,則以elem值填充新位置。如果容器變短,則末尾超出容器長>度的元素被刪除。
capacity();//容器的容量
reserve(int len);//容器預留len個元素長度,預留位置不初始化,元素不可訪問。
3.2.4.4 vector資料存取操作
at(int idx); //返回索引idx所指的資料,如果idx越界,丟擲out_of_range異常。
operator[];//返回索引idx所指的資料,越界時,執行直接報錯
front();//返回容器中第一個資料元素
back();//返回容器中最後一個數據元素
3.2.4.5 vector插入和刪除操作
insert(const_iterator pos, int count,ele);//迭代器指向位置pos插入count個元素ele.
push_back(ele); //尾部插入元素ele
pop_back();//刪除最後一個元素
erase(const_iterator start, const_iterator end);//刪除迭代器從start到end之間的元素
erase(const_iterator pos);//刪除迭代器指向的元素
clear();//刪除容器中所有元素
3.2.5 vector小案例
3.2.5.1巧用swap,收縮記憶體空間
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> v;
for (int i = 0; i < 100000;i ++){
v.push_back(i);
}
cout << "capacity:" << v.capacity() << endl;
cout << "size:" << v.size() << endl;
//此時 通過resize改變容器大小
v.resize(10);
cout << "capacity:" << v.capacity() << endl;
cout << "size:" << v.size() << endl;
//容量沒有改變
vector<int>(v).swap(v);
cout << "capacity:" << v.capacity() << endl;
cout << "size:" << v.size() << endl;
system("pause");
return EXIT_SUCCESS;
}
3.2.5.2 reserve預留空間
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> v;
//預先開闢空間
v.reserve(100000);
int* pStart = NULL;
int count = 0;
for (int i = 0; i < 100000;i ++){
v.push_back(i);
if (pStart != &v[0]){
pStart = &v[0];
count++;
}
}
cout << "count:" << count << endl;
system("pause");
return EXIT_SUCCESS;
}
3.3 deque容器
3.3.1 deque容器基本概念
Vector容器是單向開口的連續記憶體空間,deque則是一種雙向開口的連續線性空間。所謂的雙向開口,意思是可以在頭尾兩端分別做元素的插入和刪除操作,當然,vector容器也可以在頭尾兩端插入元素,但是在其頭部操作效率奇差,無法被接受。
Deque容器和vector容器最大的差異,一在於deque允許使用常數項時間對頭端進行元素的插入和刪除操作。二在於deque沒有容量的概念,因為它是動態的以分段連續空間組合而成,隨時可以增加一段新的空間並連結起來,換句話說,像vector那樣,”舊空間不足而重新配置一塊更大空間,然後複製元素,再釋放舊空間”這樣的事情在deque身上是不會發生的。也因此,deque沒有必須要提供所謂的空間保留(reserve)功能.
雖然deque容器也提供了Random Access Iterator,但是它的迭代器並不是普通的指標,其複雜度和vector不是一個量級,這當然影響各個運算的層面。因此,除非有必要,我們應該儘可能的使用vector,而不是deque。對deque進行的排序操作,為了最高效率,可將deque先完整的複製到一個vector中,對vector容器進行排序,再複製回deque.
3.3.2 deque容器實現原理
Deque容器是連續的空間,至少邏輯上看來如此,連續現行空間總是令我們聯想到array和vector,array無法成長,vector雖可成長,卻只能向尾端成長,而且其成長其實是一個假象,事實上(1) 申請更大空間 (2)原資料複製新空間 (3)釋放原空間 三步驟,如果不是vector每次配置新的空間時都留有餘裕,其成長假象所帶來的代價是非常昂貴的。
Deque是由一段一段的定量的連續空間構成。一旦有必要在deque前端或者尾端增加新的空間,便配置一段連續定量的空間,串接在deque的頭端或者尾端。Deque最大的工作就是維護這些分段連續的記憶體空間的整體性的假象,並提供隨機存取的介面,避開了重新配置空間,複製,釋放的輪迴,代價就是複雜的迭代器架構。
既然deque是分段連續記憶體空間,那麼就必須有中央控制,維持整體連續的假象,資料結構的設計及迭代器的前進後退操作頗為繁瑣。Deque程式碼的實現遠比vector或list都多得多。
Deque採取一塊所謂的map(注意,不是STL的map容器)作為主控,這裡所謂的map是一小塊連續的記憶體空間,其中每一個元素(此處成為一個結點)都是一個指標,指向另一段連續性記憶體空間,稱作緩衝區。緩衝區才是deque的儲存空間的主體。
3.3.3 deque常用API
3.3.3.1 deque建構函式
deque<T> deqT;//預設構造形式
deque(beg, end);//建構函式將[beg, end)區間中的元素拷貝給本身。
deque(n, elem);//建構函式將n個elem拷貝給本身。
deque(const deque &deq);//拷貝建構函式。
3.3.3.2 deque賦值操作
assign(beg, end);//將[beg, end)區間中的資料拷貝賦值給本身。
assign(n, elem);//將n個elem拷貝賦值給本身。
deque& operator=(const deque &deq); //過載等號操作符
swap(deq);// 將deq與本身的元素互換
3.3.3.3 deque大小操作
deque.size();//返回容器中元素的個數
deque.empty();//判斷容器是否為空
deque.resize(num);//重新指定容器的長度為num,若容器變長,則以預設值填充新位置。如果容器變短,則末尾超出容器長度的元素被刪除。
deque.resize(num, elem); //重新指定容器的長度為num,若容器變長,則以elem值填充新位置,如果容器變短,則末尾超出容器長度的元素被刪除。
3.3.3.4 deque雙端插入和刪除操作
push_back(elem);//在容器尾部新增一個數據
push_front(elem);//在容器頭部插入一個數據
pop_back();//刪除容器最後一個數據
pop_front();//刪除容器第一個資料
3.3.3.5 deque資料存取
at(idx);//返回索引idx所指的資料,如果idx越界,丟擲out_of_range。
operator[];//返回索引idx所指的資料,如果idx越界,不丟擲異常,直接出錯。
front();//返回第一個資料。
back();//返回最後一個數據
3.3.3.6 deque插入操作
insert(pos,elem);//在pos位置插入一個elem元素的拷貝,返回新資料的位置。
insert(pos,n,elem);//在pos位置插入n個elem資料,無返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)區間的資料,無返回值。
3.3.3.7 deque刪除操作
clear();//移除容器的所有資料
erase(beg,end);//刪除[beg,end)區間的資料,返回下一個資料的位置。
erase(pos);//刪除pos位置的資料,返回下一個資料的位置。
3.3.3.8 案例(作業)
/*
有5名選手:選手ABCDE,10個評委分別對每一名選手打分,去除最高分,去除評委中最低分,取平均分。
//1. 建立五名選手,放到vector中
//2. 遍歷vector容器,取出來每一個選手,執行for迴圈,可以把10個評分打分存到deque容器中
//3. sort演算法對deque容器中分數排序,pop_back pop_front去除最高和最低分
//4. deque容器遍歷一遍,累加分數,累加分數/d.size()
//5. person.score = 平均分
*/
3.4 stack容器
3.4.1 stack容器基本概念
stack是一種先進後出(First In Last Out,FILO)的資料結構,它只有一個出口,形式如圖所示。stack容器允許新增元素,移除元素,取得棧頂元素,但是除了最頂端外,沒有任何其他方法可以存取stack的其他元素。換言之,stack不允許有遍歷行為。
有元素推入棧的操作稱為:push,將元素推出stack的操作稱為pop.
3.4.2 stack沒有迭代器
Stack所有元素的進出都必須符合”先進後出”的條件,只有stack頂端的元素,才有機會被外界取用。Stack不提供遍歷功能,也不提供迭代器。
3.4.3 stack常用API
3.4.3.1 stack建構函式
stack<T> stkT;//stack採用模板類實現, stack物件的預設構造形式:
stack(const stack &stk);//拷貝建構函式
3.4.3.2 stack賦值操作
stack& operator=(const stack &stk);//過載等號操作符
3.4.3.3 stack資料存取操作
push(elem);//向棧頂新增元素
pop();//從棧頂移除第一個元素
top();//返回棧頂元素
3.4.3.4 stack大小操作
empty();//判斷堆疊是否為空
size();//返回堆疊的大小
3.5 queue容器
3.5.1 queue容器基本概念
Queue是一種先進先出(First In First Out,FIFO)的資料結構,它有兩個出口,queue容器允許從一端新增元素,從另一端移除元素。
3.5.2 queue沒有迭代器
Queue所有元素的進出都必須符合”先進先出”的條件,只有queue的頂端元素,才有機會被外界取用。Queue不提供遍歷功能,也不提供迭代器。
3.5.3 queue常用API
3.5.3.1 queue建構函式
queue<T> queT;//queue採用模板類實現,queue物件的預設構造形式:
queue(const queue &que);//拷貝建構函式
3.5.3.2 queue存取、插入和刪除操作
push(elem);//往隊尾新增元素
pop();//從隊頭移除第一個元素
back();//返回最後一個元素
front();//返回第一個元素
3.5.3.3 queue賦值操作
queue& operator=(const queue &que);//過載等號操作符
3.5.3.4 queue大小操作
empty();//判斷佇列是否為空
size();//返回佇列的大小
3.6 list容器
3.6.1 list容器基本概念
連結串列是一種物理儲存單元上非連續、非順序的儲存結構,資料元素的邏輯順序是通過連結串列中的指標連結次序實現的。連結串列由一系列結點(連結串列中每一個元素稱為結點)組成,結點可以在執行時動態生成。每個結點包括兩個部分:一個是儲存資料元素的資料域,另一個是儲存下一個結點地址的指標域。
相較於vector的連續線性空間,list就顯得負責許多,它的好處是每次插入或者刪除一個元素,就是配置或者釋放一個元素的空間。因此,list對於空間的運用有絕對的精準,一點也不浪費。而且,對於任何位置的元素插入或元素的移除,list永遠是常數時間。
List和vector是兩個最常被使用的容器。
List容器是一個雙向連結串列。
採用動態儲存分配,不會造成記憶體浪費和溢位
連結串列執行插入和刪除操作十分方便,修改指標即可,不需要移動大量元素
連結串列靈活,但是空間和時間額外耗費較大
3.6.2 list容器的迭代器
List容器不能像vector一樣以普通指標作為迭代器,因為其節點不能保證在同一塊連續的記憶體空間上。List迭代器必須有能力指向list的節點,並有能力進行正確的遞增、遞減、取值、成員存取操作。所謂”list正確的遞增,遞減、取值、成員取用”是指,遞增時指向下一個節點,遞減時指向上一個節點,取值時取的是節點的資料值,成員取用時取的是節點的成員。
由於list是一個雙向連結串列,迭代器必須能夠具備前移、後移的能力,所以list容器提供的是Bidirectional Iterators.
List有一個重要的性質,插入操作和刪除操作都不會造成原有list迭代器的失效。這在vector是不成立的,因為vector的插入操作可能造成記憶體重新配置,導致原有的迭代器全部失效,甚至List元素的刪除,也只有被刪除的那個元素的迭代器失效,其他迭代器不受任何影響。
3.6.3 list容器的資料結構
list容器不僅是一個雙向連結串列,而且還是一個迴圈的雙向連結串列。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<list>
using namespace std;
int main(){
list<int> myList;
for (int i = 0; i < 10; i ++){
myList.push_back(i);
}
list<int>::_Nodeptr node = myList._Myhead->_Next;
for (int i = 0; i < myList._Mysize * 2;i++){
cout << "Node:" << node->_Myval << endl;
node = node->_Next;
if (node == myList._Myhead){
node = node->_Next;
}
}
system("pause");
return EXIT_SUCCESS;
}
3.6.4 list常用API
3.6.4.1 list建構函式
list<T> lstT;//list採用採用模板類實現,物件的預設構造形式:
list(beg,end);//建構函式將[beg, end)區間中的元素拷貝給本身。
list(n,elem);//建構函式將n個elem拷貝給本身。
list(const list &lst);//拷貝建構函式。
3.6.4.2 list資料元素插入和刪除操作
push_back(elem);//在容器尾部加入一個元素
pop_back();//刪除容器中最後一個元素
push_front(elem);//在容器開頭插入一個元素
pop_front();//從容器開頭移除第一個元素
insert(pos,elem);//在pos位置插elem元素的拷貝,返回新資料的位置。
insert(pos,n,elem);//在pos位置插入n個elem資料,無返回值。
insert(pos,beg,end);//在pos位置插入[beg,end)區間的資料,無返回值。
clear();//移除容器的所有資料
erase(beg,end);//刪除[beg,end)區間的資料,返回下一個資料的位置。
erase(pos);//刪除pos位置的資料,返回下一個資料的位置。
remove(elem);//刪除容器中所有與elem值匹配的元素。
3.6.4.3 list大小操作
size();//返回容器中元素的個數
empty();//判斷容器是否為空
resize(num);//重新指定容器的長度為num,
若容器變長,則以預設值填充新位置。
如果容器變短,則末尾超出容器長度的元素被刪除。
resize(num, elem);//重新指定容器的長度為num,
若容器變長,則以elem值填充新位置。
如果容器變短,則末尾超出容器長度的元素被刪除。
3.6.4.4 list賦值操作
assign(beg, end);//將[beg, end)區間中的資料拷貝賦值給本身。
assign(n, elem);//將n個elem拷貝賦值給本身。
list& operator=(const list &lst);//過載等號操作符
swap(lst);//將lst與本身的元素互換。
3.6.4.5 list資料的存取
front();//返回第一個元素。
back();//返回最後一個元素。
3.6.4.6 list反轉排序
reverse();//反轉連結串列,比如lst包含1,3,5元素,執行此方法後,lst就包含5,3,1元素。
sort(); //list排序
3.7 set/multiset容器
3.7.1 set/multiset容器基本概念
3.7.1.1 set容器基本概念
Set的特性是。所有元素都會根據元素的鍵值自動被排序。Set的元素不像map那樣可以同時擁有實值和鍵值,set的元素即是鍵值又是實值。Set不允許兩個元素有相同的鍵值。
我們可以通過set的迭代器改變set元素的值嗎?不行,因為set元素值就是其鍵值,關係到set元素的排序規則。如果任意改變set元素值,會嚴重破壞set組織。換句話說,set的iterator是一種const_iterator.
set擁有和list某些相同的性質,當對容器中的元素進行插入操作或者刪除操作的時候,操作之前所有的迭代器,在操作完成之後依然有效,被刪除的那個元素的迭代器必然是一個例外。
3.7.1.2 multiset容器基本概念
multiset特性及用法和set完全相同,唯一的差別在於它允許鍵值重複。set和multiset的底層實現是紅黑樹,紅黑樹為平衡二叉樹的一種。
樹的簡單知識:
二叉樹就是任何節點最多隻允許有兩個位元組點。分別是左子結點和右子節點。
二叉樹示意圖
二叉搜尋樹,是指二叉樹中的節點按照一定的規則進行排序,使得對二叉樹中元素訪問更加高效。二叉搜尋樹的放置規則是:任何節點的元素值一定大於其左子樹中的每一個節點的元素值,並且小於其右子樹的值。因此從根節點一直向左走,一直到無路可走,即得到最小值,一直向右走,直至無路可走,可得到最大值。那麼在兒茶搜尋樹中找到最大元素和最小元素是非常簡單的事情。下圖為二叉搜尋樹:
上面我們介紹了二叉搜尋樹,那麼當一個二叉搜尋樹的左子樹和右子樹不平衡的時候,那麼搜尋依據上圖表示,搜尋9所花費的時間要比搜尋17所花費的時間要多,由於我們的輸入或者經過我們插入或者刪除操作,二叉樹失去平衡,造成搜尋效率降低。
所以我們有了一個平衡二叉樹的概念,所謂的平衡不是指的完全平衡。
RB-tree(紅黑樹)為二叉樹的一種。
3.7.2 set常用API
3.7.2.1 set建構函式
set<T> st;//set預設建構函式:
mulitset<T> mst; //multiset預設建構函式:
set(const set &st);//拷貝建構函式
3.7.2.2 set賦值操作
set& operator=(const set &st);//過載等號操作符
swap(st);//交換兩個集合容器
3.7.2.3 set大小操作
size();//返回容器中元素的數目
empty();//判斷容器是否為空
3.7.2.4 set插入和刪除操作
insert(elem);//在容器中插入元素。
clear();//清除所有元素
erase(pos);//刪除pos迭代器所指的元素,返回下一個元素的迭代器。
erase(beg, end);//刪除區間[beg,end)的所有元素 ,返回下一個元素的迭代器。
erase(elem);//刪除容器中值為elem的元素。
3.7.2.5 set查詢操作
find(key);//查詢鍵key是否存在,若存在,返回該鍵的元素的迭代器;若不存在,返回set.end();
count(key);//查詢鍵key的元素個數
lower_bound(keyElem);//返回第一個key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一個key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key與keyElem相等的上下限的兩個迭代器。
set的返回值 指定set排序規則:
//插入操作返回值
void test01(){
set<int> s;
pair<set<int>::iterator,bool> ret = s.insert(10);
if (ret.second){
cout << "插入成功:" << *ret.first << endl;
}
else{
cout << "插入失敗:" << *ret.first << endl;
}
ret = s.insert(10);
if(ret.second){
cout << "插入成功:" << *ret.first << endl;
}
else{
cout << "插入失敗:" << *ret.first << endl;
}
}
struct MyCompare02{
bool operator()(int v1,int v2){
return v1 > v2;
}
};
//set從大到小
void test02(){
srand((unsigned int)time(NULL));
//我們發現set容器的第二個模板引數可以設定排序規則,預設規則是less<_Kty>
set<int, MyCompare02> s;
for (int i = 0; i < 10;i++){
s.insert(rand() % 100);
}
for (set<int, MyCompare02>::iterator it = s.begin(); it != s.end(); it ++){
cout << *it << " ";
}
cout << endl;
}
//set容器中存放物件
class Person{
public:
Person(string name,int age){
this->mName = name;
this->mAge = age;
}
public:
string mName;
int mAge;
};
struct MyCompare03{
bool operator()(const Person& p1,const Person& p2){
return p1.mAge > p2.mAge;
}
};
void test03(){
set<Person, MyCompare03> s;
Person p1("aaa", 20);
Person p2("bbb", 30);
Person p3("ccc", 40);
Person p4("ddd", 50);
s.insert(p1);
s.insert(p2);
s.insert(p3);
s.insert(p4);
for (set<Person, MyCompare03>::iterator it = s.begin(); it != s.end(); it++){
cout << "Name:" << it->mName << " Age:" << it->mAge << endl;
}
}
3.7.3 對組(pair)
對組(pair)將一對值組合成一個值,這一對值可以具有不同的資料型別,兩個值可以分別用pair的兩個公有屬性first和second訪問。
類模板:template <class T1, class T2> struct pair.
如何建立對組?
//第一種方法建立一個對組
pair<string, int> pair1(string("name"), 20);
cout << pair1.first << endl; //訪問pair第一個值
cout << pair1.second << endl;//訪問pair第二個值
//第二種
pair<string, int> pair2 = make_pair("name", 30);
cout << pair2.first << endl;
cout << pair2.second << endl;
//pair=賦值
pair<string, int> pair3 = pair2;
cout << pair3.first << endl;
cout << pair3.second << endl;
3.8 map/multimap容器
3.8.1 map/multimap基本概念
Map的特性是,所有元素都會根據元素的鍵值自動排序。Map所有的元素都是pair,同時擁有實值和鍵值,pair的第一元素被視為鍵值,第二元素被視為實值,map不允許兩個元素有相同的鍵值。
我們可以通過map的迭代器改變map的鍵值嗎?答案是不行,因為map的鍵值關係到map元素的排列規則,任意改變map鍵值將會嚴重破壞map組織。如果想要修改元素的實值,那麼是可以的。
Map和list擁有相同的某些性質,當對它的容器元素進行新增操作或者刪除操作時,操作之前的所有迭代器,在操作完成之後依然有效,當然被刪除的那個元素的迭代器必然是個例外。
Multimap和map的操作類似,唯一區別multimap鍵值可重複。
Map和multimap都是以紅黑樹為底層實現機制。
3.8.2 map/multimap常用API
3.8.2.1 map建構函式
map<T1, T2> mapTT;//map預設建構函式:
map(const map &mp);//拷貝建構函式
3.8.2.2 map賦值操作
map& operator=(const map &mp);//過載等號操作符
swap(mp);//交換兩個集合容器
3.8.2.3 map大小操作
size();//返回容器中元素的數目
empty();//判斷容器是否為空
3.8.2.4 map插入資料元素操作
map.insert(...); //往容器插入元素,返回pair<iterator,bool>
map<int, string> mapStu;
// 第一種 通過pair的方式插入物件
mapStu.insert(pair<int, string>(3, "小張"));
// 第二種 通過pair的方式插入物件
mapStu.inset(make_pair(-1, "校長"));
// 第三種 通過value_type的方式插入物件
mapStu.insert(map<int, string>::value_type(1, "小李"));
// 第四種 通過陣列的方式插入值
mapStu[3] = "小劉";
mapStu[5] = "小王";
3.8.2.5 map刪除操作
clear();//刪除所有元素
erase(pos);//刪除pos迭代器所指的元素,返回下一個元素的迭代器。
erase(beg,end);//刪除區間[beg,end)的所有元素 ,返回下一個元素的迭代器。
erase(keyElem);//刪除容器中key為keyElem的對組。
3.8.2.6 map查詢操作
find(key);//查詢鍵key是否存在,若存在,返回該鍵的元素的迭代器;/若不存在,返回map.end();
count(keyElem);//返回容器中key為keyElem的對組個數。對map來說,要麼是0,要麼是1。對multimap來說,值可能大於1。
lower_bound(keyElem);//返回第一個key>=keyElem元素的迭代器。
upper_bound(keyElem);//返回第一個key>keyElem元素的迭代器。
equal_range(keyElem);//返回容器中key與keyElem相等的上下限的兩個迭代器。
3.8.3 multimap案例
//公司今天招聘了5個員工,5名員工進入公司之後,需要指派員工在那個部門工作
//人員資訊有: 姓名 年齡 電話 工資等組成
//通過Multimap進行資訊的插入 儲存 顯示
//分部門顯示員工資訊 顯示全部員工資訊
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<map>
#include<string>
#include<vector>
using namespace std;
//multimap 案例
//公司今天招聘了 5 個員工,5 名員工進入公司之後,需要指派員工在那個部門工作
//人員資訊有: 姓名 年齡 電話 工資等組成
//通過 Multimap 進行資訊的插入 儲存 顯示
//分部門顯示員工資訊 顯示全部員工資訊
#define SALE_DEPATMENT 1 //銷售部門
#define DEVELOP_DEPATMENT 2 //研發部門
#define FINACIAL_DEPATMENT 3 //財務部門
#define ALL_DEPATMENT 4 //所有部門
//員工類
class person{
public:
string name; //員工姓名
int age; //員工年齡
double salary; //員工工資
string tele; //員工電話
};
//建立5個員工
void CreatePerson(vector<person>& vlist){
string seed = "ABCDE";
for (int i = 0; i < 5; i++){
person p;
p.name = "員工";
p.name += seed[i];
p.age = rand() % 30 + 20;
p.salary = rand() % 20000 + 10000;
p.tele = "010-8888888";
vlist.push_back(p);
}
}
//5名員工分配到不同的部門
void PersonByGroup(vector<person>& vlist, multimap<int, person>& plist){
int operate = -1; //使用者的操作
for (vector<person>::iterator it = vlist.begin(); it != vlist.end(); it++){
cout << "當前員工資訊:" << endl;
cout << "姓名:" << it->name << " 年齡:" << it->age << " 工資:" << it->salary << " 電話:" << it->tele << endl;
cout << "請對該員工進行部門分配(1 銷售部門, 2 研發部門, 3 財務部門):" << endl;
scanf("%d", &operate);
while (true){
if (operate == SALE_DEPATMENT){ //將該員工加入到銷售部門
plist.insert(make_pair(SALE_DEPATMENT, *it));
break;
}
else if (operate == DEVELOP_DEPATMENT){
plist.insert(make_pair(DEVELOP_DEPATMENT, *it));
break;
}
else if (operate == FINACIAL_DEPATMENT){
plist.insert(make_pair(FINACIAL_DEPATMENT, *it));
break;
}
else{
cout << "您的輸入有誤,請重新輸入(1 銷售部門, 2 研發部門, 3 財務部門):" << endl;
scanf("%d", &operate);
}
}
}
cout << "員工部門分配完畢!" << endl;
cout << "***********************************************************" << endl;
}
//列印員工資訊
void printList(multimap<int, person>& plist, int myoperate){
if (myoperate == ALL_DEPATMENT){
for (multimap<int, person>::iterator it = plist.begin(); it != plist.end(); it++){
cout << "姓名:" << it->second.name << " 年齡:" << it->second.age << " 工資:" << it->second.salary << " 電話:" << it->second.tele << endl;
}
return;
}
multimap<int, person>::iterator it = plist.find(myoperate);
int depatCount = plist.count(myoperate);
int num = 0;
if (it != plist.end()){
while (it != plist.end() && num < depatCount){
cout << "姓名:" << it->second.name << " 年齡:" << it->second.age << " 工資:" << it->second.salary << " 電話:" << it->second.tele << endl;
it++;
num++;
}
}
}
//根據使用者操作顯示不同部門的人員列表
void ShowPersonList(multimap<int, person>& plist, int myoperate){
switch (myoperate)
{
case SALE_DEPATMENT:
printList(plist, SALE_DEPATMENT);
break;
case DEVELOP_DEPATMENT:
printList(plist, DEVELOP_DEPATMENT);
break;
case FINACIAL_DEPATMENT:
printList(plist, FINACIAL_DEPATMENT);
break;
case ALL_DEPATMENT:
printList(plist, ALL_DEPATMENT);
break;
}
}
//使用者操作選單
void PersonMenue(multimap<int, person>& plist){
int flag = -1;
int isexit = 0;
while (true){
cout << "請輸入您的操作((1 銷售部門, 2 研發部門, 3 財務部門, 4 所有部門, 0退出):" << endl;
scanf("%d", &flag);
switch (flag)
{
case SALE_DEPATMENT:
ShowPersonList(plist, SALE_DEPATMENT);
break;
case DEVELOP_DEPATMENT:
ShowPersonList(plist, DEVELOP_DEPATMENT);
break;
case FINACIAL_DEPATMENT:
ShowPersonList(plist, FINACIAL_DEPATMENT);
break;
case ALL_DEPATMENT:
ShowPersonList(plist, ALL_DEPATMENT);
break;
case 0:
isexit = 1;
break;
default:
cout << "您的輸入有誤,請重新輸入!" << endl;
break;
}
if (isexit == 1){
break;
}
}
}
int main(){
vector<person> vlist; //建立的5個員工 未分組
multimap<int, person> plist; //儲存分組後員工資訊
//建立5個員工
CreatePerson(vlist);
//5名員工分配到不同的部門
PersonByGroup(vlist, plist);
//根據使用者輸入顯示不同部門員工資訊列表 或者 顯示全部員工的資訊列表
PersonMenue(plist);
system("pause");
return EXIT_SUCCESS;
}
3.9 STL容器使用時機
vector deque list set multiset map multimap
典型記憶體結構 單端陣列 雙端陣列 雙向連結串列 二叉樹 二叉樹 二叉樹 二叉樹
可隨機存取 是 是 否 否 否 對key而言:不是 否
元素搜尋速度 慢 慢 非常慢 快 快 對key而言:快 對key而言:快
元素安插移除 尾端 頭尾兩端 任何位置 - - - -
vector的使用場景:比如軟體歷史操作記錄的儲存,我們經常要檢視歷史記錄,比如上一次的記錄,上上次的記錄,但卻不會去刪除記錄,因為記錄是事實的描述。
deque的使用場景:比如排隊購票系統,對排隊者的儲存可以採用deque,支援頭端的快速移除,尾端的快速新增。如果採用vector,則頭端移除時,會移動大量的資料,速度慢。
vector與deque的比較:
一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的開始位置 卻是不固定的。
二:如果有大量釋放操作的話,vector花的時間更少,這跟二者的內部實現有關。
三:deque支援頭部的快速插入與快速移除,這是deque的優點。
list的使用場景:比如公交車乘客的儲存,隨時可能有乘客下車,支援頻繁的不確實位置元素的移除插入。
set的使用場景:比如對手機遊戲的個人得分記錄的儲存,儲存要求從高分到低分的順序排列。
map的使用場景:比如按ID號儲存十萬個使用者,想要快速要通過ID查詢對應的使用者。二叉樹的查詢效率,這時就體現出來了。如果是vector容器,最壞的情況下可能要遍歷完整個容器才能找到該使用者。
4. 常用演算法
4.1 函式物件
過載函式呼叫操作符的類,其物件常稱為函式物件(function object),即它們是行為類似函式的物件,也叫仿函式(functor),其實就是過載“()”操作符,使得類物件可以像函式那樣呼叫。
注意:
1.函式物件(仿函式)是一個類,不是一個函式。
2.函式物件(仿函式)過載了”() ”操作符使得它可以像函式一樣呼叫。
分類:假定某個類有一個過載的operator(),而且過載的operator()要求獲取一個引數,我們就將這個類稱為“一元仿函式”(unary functor);相反,如果過載的operator()要求獲取兩個引數,就將這個類稱為“二元仿函式”(binary functor)。
函式物件的作用主要是什麼?STL提供的演算法往往都有兩個版本,其中一個版本表現出最常用的某種運算,另一版本則允許使用者通過template引數的形式來指定所要採取的策略。
//函式物件是過載了函式呼叫符號的類
class MyPrint
{
public:
MyPrint()
{
m_Num = 0;
}
int m_Num;
public:
void operator() (int num)
{
cout << num << endl;
m_Num++;
}
};
//函式物件
//過載了()操作符的類例項化的物件,可以像普通函式那樣呼叫,可以有引數 ,可以有返回值
void test01()
{
MyPrint myPrint;
myPrint(20);
}
// 函式物件超出了普通函式的概念,可以儲存函式的呼叫狀態
void test02()
{
MyPrint myPrint;
myPrint(20);
myPrint(20);
myPrint(20);
cout << myPrint.m_Num << endl;
}
void doBusiness(MyPrint print,int num)
{
print(num);
}
//函式物件作為引數
void test03()
{
//引數1:匿名函式物件
doBusiness(MyPrint(),30);
}
總結:
1、函式物件通常不定義建構函式和解構函式,所以在構造和析構時不會發生任何問題,避免了函式呼叫的執行時問題。
2、函式物件超出普通函式的概念,函式物件可以有自己的狀態
3、函式物件可內聯編譯,效能好。用函式指標幾乎不可能
4、模版函式物件使函式物件具有通用性,這也是它的優勢之一
4.2 謂詞
謂詞是指普通函式或過載的operator()返回值是bool型別的函式物件(仿函式)。如果operator接受一個引數,那麼叫做一元謂詞,如果接受兩個引數,那麼叫做二元謂詞,謂詞可作為一個判斷式。
class GreaterThenFive
{
public:
bool operator()(int num)
{
return num > 5;
}
};
//一元謂詞
void test01()
{
vector<int> v;
for (int i = 0; i < 10;i ++)
{
v.push_back(i);
}
vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterThenFive());
if (it == v.end())
{
cout << "沒有找到" << endl;
}
else
{
cout << "找到了: " << *it << endl;
}
}
//二元謂詞
class MyCompare
{
public:
bool operator()(int num1, int num2)
{
return num1 > num2;
}
};
void test02()
{
vector<int> v;
v.push_back(10);
v.push_back(40);
v.push_back(20);
v.push_back(90);
v.push_back(60);
//預設從小到大
sort(v.begin(), v.end());
for (vector<int>::iterator it = v.begin(); it != v.end();it++)
{
cout << *it << " ";
}
cout << endl;
cout << "----------------------------" << endl;
//使用函式物件改變演算法策略,排序從大到小
sort(v.begin(), v.end(),MyCompare());
for (vector<int>::iterator it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
}
4.3 內建函式物件
STL內建了一些函式物件。分為:算數類函式物件,關係運算類函式物件,邏輯運算類仿函式。這些仿函式所產生的物件,用法和一般函式完全相同,當然我們還可以產生無名的臨時物件來履行函式功能。使用內建函式物件,需要引入標頭檔案 #include<functional>。
6個算數類函式物件,除了negate是一元運算,其他都是二元運算。
template<class T> T plus<T>//加法仿函式
template<class T> T minus<T>//減法仿函式
template<class T> T multiplies<T>//乘法仿函式
template<class T> T divides<T>//除法仿函式
template<class T> T modulus<T>//取模仿函式
template<class T> T negate<T>//取反仿函式
6個關係運算類函式物件,每一種都是二元運算。
template<class T> bool equal_to<T>//等於
template<class T> bool not_equal_to<T>//不等於
template<class T> bool greater<T>//大於
template<class T> bool greater_equal<T>//大於等於
template<class T> bool less<T>//小於
template<class T> bool less_equal<T>//小於等於
邏輯運算類運算函式,not為一元運算,其餘為二元運算。
template<class T> bool logical_and<T>//邏輯與
template<class T> bool logical_or<T>//邏輯或
template<class T> bool logical_not<T>//邏輯非
內建函式物件舉例:
//取反仿函式
void test01()
{
negate<int> n;
cout << n(50) << endl;
}
//加法仿函式
void test02()
{
plus<int> p;
cout << p(10, 20) << endl;
}
//大於仿函式
void test03()
{
vector<int> v;
srand((unsigned int)time(NULL));
for (int i = 0; i < 10; i++){
v.push_back(rand() % 100);
}
for (vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout << *it << " ";
}
cout << endl;
sort(v.begin(), v.end(), greater<int>());
for (vector<int>::iterator it = v.begin(); it != v.end(); it++){
cout << *it << " ";
}
cout << endl;
}
3.1.4 函式物件介面卡
//函式介面卡bind1st bind2nd
//現在我有這個需求 在遍歷容器的時候,我希望將容器中的值全部加上100之後顯示出來,怎麼做?
//我們直接給函式物件繫結引數 編譯階段就會報錯
//for_each(v.begin(), v.end(), bind2nd(myprint(),100));
//如果我們想使用繫結介面卡,需要我們自己的函式物件繼承binary_function 或者 unary_function
//根據我們函式物件是一元函式物件 還是二元函式物件
class MyPrint :public binary_function<int,int,void>
{
public:
void operator()(int v1,int v2) const
{
cout << "v1 = : " << v1 << " v2 = :" <<v2 << " v1+v2 = :" << (v1 + v2) << endl;
}
};
//1、函式介面卡
void test01()
{
vector<int>v;
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
cout << "請輸入起始值:" << endl;
int x;
cin >> x;
for_each(v.begin(), v.end(), bind1st(MyPrint(), x));
//for_each(v.begin(), v.end(), bind2nd( MyPrint(),x ));
}
//總結: bind1st和bind2nd區別?
//bind1st : 將引數繫結為函式物件的第一個引數
//bind2nd : 將引數繫結為函式物件的第二個引數
//bind1st bind2nd將二元函式物件轉為一元函式物件
class GreaterThenFive:public unary_function<int,bool>
{
public:
bool operator ()(int v) const
{
return v > 5;
}
};
//2、取反介面卡
void test02()
{
vector <int> v;
for (int i = 0; i < 10;i++)
{
v.push_back(i);
}
// vector<int>::iterator it = find_if(v.begin(), v.end(), GreaterThenFive()); //返回第一個大於5的迭代器
// vector<int>::iterator it = find_if(v.begin(), v.end(), not1(GreaterThenFive())); //返回第一個小於5迭代器
//自定義輸入
vector<int>::iterator it = find_if(v.begin(), v.end(), not1 ( bind2nd(greater<int>(),5)));
if (it == v.end())
{
cout << "沒找到" << endl;
}
else
{
cout << "找到" << *it << endl;
}
//排序 二元函式物件
sort(v.begin(), v.end(), not2(less<int>()));
for_each(v.begin(), v.end(), [](int val){cout << val << " "; });
}
//not1 對一元函式物件取反
//not2 對二元函式物件取反
void MyPrint03(int v,int v2)
{
cout << v + v2<< " ";
}
//3、函式指標介面卡 ptr_fun
void test03()
{
vector <int> v;
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
// ptr_fun( )把一個普通的函式指標適配成函式物件
for_each(v.begin(), v.end(), bind2nd( ptr_fun( MyPrint03 ), 100));
}
//4、成員函式介面卡
class Person
{
public:
Person(string name, int age)
{
m_Name = name;
m_Age = age;
}
//列印函式
void ShowPerson(){
cout << "成員函式:" << "Name:" << m_Name << " Age:" << m_Age << endl;
}
void Plus100()
{
m_Age += 100;
}
public:
string m_Name;
int m_Age;
};
void MyPrint04(Person &p)
{
cout << "姓名:" << p.m_Name << " 年齡:" << p.m_Age << endl;
};
void test04()
{
vector <Person>v;
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
Person p4("ddd", 40);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
//for_each(v.begin(), v.end(), MyPrint04);
//利用 mem_fun_ref 將Person內部成員函式適配
for_each(v.begin(), v.end(), mem_fun_ref(&Person::ShowPerson));
//