STL學習第一章 了解STL
知識內容:
1.STL介紹
2.C++基礎知識復習
3.C++中的模板簡單介紹
4.STL組成部分
一、STL介紹
1.什麽是STL?
學過C++的應該都聽說過STL,那麽什麽是STL呢?STL是Standard Template Library的簡稱,翻譯為標準模板庫,是惠普實驗室開發的一系列軟件的統稱。它是由Alexander Stepanov、Meng Lee和David R Musser在惠普實驗室工作時所開發出來的。從根本上說,STL是一些“容器”的集合,這些“容器”有list,vector,set,map等,STL也是算法和其他一些組件的集合。這裏的“容器”和算法的集合指的是世界上很多聰明人很多年的傑作。STL的目的是標準化組件,這樣就不用重新開發,可以使用現成的組件。STL現在是C++的一部分,因此不用安裝額外的庫文件,只用在使用STL時包含相應頭文件就行了。
2.STL組成部分
在C++標準中,STL被組織為下面的13個頭文件:<algorithm>、<deque>、<functional>、<iterator>、<array>、<vector>、<list>、<forward_list>、<map>、<unordered_map>、<memory>、<numeric>、<queue>、<set>、<unordered_set>、<stack>和<utility>
STL可分為容器(containers)、叠代器(iterators)、空間配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函數(functors)六個部分,我將在下面詳細解釋。
二、C++基礎知識復習
要學習STL,毫無疑問C++基礎必須掌握,在這就稍微復習一下C++的基礎知識
1.C++基本輸入輸出:
1 #include <iostream> 2 using namespace std; 3 4 int main() 5 { 6 int n; 7 cin << n; //C++基本輸入 8 cout << "You enter " << n << endl; //C++基本輸出 9 }
2.C++引用:
C++定義引用的表示方法與定義指針相似,只是用&代替了*。引用是c++對C語言的重要擴充。引用就是某一變量(目標)的一個別名,對引用的操作與對變量直接操作完全一樣。引用的聲明方法:類型標識符 &引用名=目標變量名;
1 //交換兩個數的值 2 #include <iostream> 3 using namespace std; 4 5 void swap_number(int &i, int &j) 6 { 7 int temp = i; 8 i = j; 9 j = temp; 10 } 11 12 int main() 13 { 14 int a, b; 15 cin >> a >> b; 16 swap_number(a, b); 17 cout << a << " " << b << endl; 18 return 0; 19 }
3.C++類的基礎知識:
類: 具有相同行為和屬性的對象的集合
構造函數: 類的對象創建時就會被自動調用
析構函數: 類的對象銷毀時就會被自動調用
成員變量、成員函數 -> 通過對象來訪問
static成員變量: 一個類所有對象共享的數據,不屬於某個對象
static成員函數: 一個類所有對象共享的函數,不能訪問非static成員變量,不屬於某個對象,應用於單例模式中
類的訪問控制: private、protected、public
繼承: 一個類共享另一個類或多個類的數據或方法,私有成員不會被繼承
3種繼承: public繼承、protected繼承、private繼承,用的最多的是public繼承
4.綁定:
靜態綁定: 編譯階段確定函數調用 例如: 函數重載
動態綁定: 運行時確定函數調用 例如: 虛函數
5.多態:
多態一詞最初來源於希臘語,意思是具有多種形式或形態的情形,在C++中是指同樣的消息被不同類型的對象接收時導致不同的行為,這裏講的消息就是指對象的成員函數的調用,而不同的行為是指不同的實現。也就是調用了不同的函數。
6.操作符重載:
C++中可以實現操作符重載,通俗地講就是在某些時候改變運算符的功能
使用運算符重載實現矩陣加減乘:
1 //矩陣的加減乘 2 #include <iostream> 3 using namespace std; 4 #include <cstdio> 5 int const N = 2; 6 int const M = 2; 7 8 class Matrix{ 9 private: 10 double ptr[N][M]; 11 public: 12 Matrix(double i=0, double j=0, double k=0, double l=0); 13 void Show(); 14 Matrix operator+(Matrix a); 15 Matrix operator-(Matrix a); 16 Matrix operator*(Matrix a); 17 }; 18 19 Matrix::Matrix(double i, double j, double k, double l) 20 { 21 ptr[0][0] = i; 22 ptr[0][1] = j; 23 ptr[1][0] = k; 24 ptr[1][1] = l; 25 } 26 27 Matrix Matrix::operator+(Matrix a) 28 { 29 Matrix temp; 30 for(int i=0;i<N;i++) 31 for(int j=0;j<M;j++) 32 { 33 temp.ptr[i][j] = this->ptr[i][j] + a.ptr[i][j]; 34 } 35 return temp; 36 } 37 38 Matrix Matrix::operator-(Matrix a) 39 { 40 Matrix temp; 41 for(int i=0;i<N;i++) 42 for(int j=0;j<M;j++) 43 { 44 temp.ptr[i][j] = this->ptr[i][j] - a.ptr[i][j]; 45 } 46 return temp; 47 } 48 49 Matrix Matrix::operator*(Matrix a) 50 { 51 Matrix temp; 52 for(int i=0;i<N;i++) 53 for(int j=0;j<M;j++) 54 for(int k=0;k<M;k++) 55 { 56 temp.ptr[i][j] += this->ptr[i][k]*a.ptr[k][j]; 57 } 58 return temp; 59 } 60 61 void Matrix::Show() 62 { 63 for(int i=0;i<N;i++) 64 { 65 cout << "( "; 66 for(int j=0;j<M;j++) 67 { 68 cout << ptr[i][j] << " "; 69 } 70 cout << ")" << endl; 71 } 72 cout << endl; 73 } 74 75 int main() 76 { 77 Matrix first(1,2,3,4), second(2,6,8,10), total, sub, mul, div; 78 cout << "這是兩個矩陣: " << endl; 79 first.Show(); 80 second.Show(); 81 cout << "兩個矩陣相加得: " << endl; 82 total = first + second; 83 total.Show(); 84 cout << "兩個矩陣相減得: " << endl; 85 sub = first - second; 86 sub.Show(); 87 cout << "兩個矩陣相乘得: " << endl; 88 mul = first*second; 89 mul.Show(); 90 91 92 return 0; 93 }
7.C++的異常處理:
C++中處理異常的機制由檢查、拋出和捕獲三個部分組成,分別由三種語句來完成: try(檢查)、throw(拋出)、catch(捕獲),
把需要檢查的語句放在try中,throw用在出現異常時發出一個信息,而catch用來捕獲異常,並在捕獲異常後對其進行處理。
8.C++的命名空間:
(1)定義與解釋:
假設這樣一種情況,當一個班上有兩個名叫 Zara 的學生時,為了明確區分它們,我們在使用名字之外,不得不使用一些額外的信息,比如他們的家庭住址,或者他們父母的名字等等。
同樣的情況也出現在 C++ 應用程序中。例如,您可能會寫一個名為 xyz() 的函數,在另一個可用的庫中也存在一個相同的函數 xyz()。這樣,編譯器就無法判斷您所使用的是哪一個 xyz() 函數。
因此,引入了命名空間這個概念,專門用於解決上面的問題,它可作為附加信息來區分不同庫中相同名稱的函數、類、變量等。使用了命名空間即定義了上下文。本質上,命名空間就是定義了一個範圍。
(2)C++標準庫命名空間的使用:
C++標準庫內的所有表示符都被定義在一個名為std的命名空間中,使用標識符時可以直接在前面加上std::即可,示例:
1 #include <iostream> 2 3 int main() 4 { 5 std::cout << "C++命名空間第一種使用方法" << std::endl;
return 0; 6 }
但是這樣使用命名空間std也有弊端,當程序中使用的標識符較多時,寫起來比較麻煩,可以使用 using namespace 指令,這樣在使用命名空間時就可以不用在前面加上命名空間的名稱。這個指令會告訴編譯器,後續的代碼將使用指定的命名空間中的名稱,示例:
1 #include <iostream> 2 using namespace std; 3 4 int main() 5 { 6 cout << "C++命名空間第二種使用方法" << endl; 7 cout << "直接使用using namespace指令" << endl; 8 9 return 0; 10 }
三、C++中的模板簡單介紹
1.C++為什麽需要模板功能
C++需要模板這個功能的原因很簡單,就是計算機實在是太傻了。在大多數人的眼中,計算機即神秘又能幹,而在程序員的眼中,計算機實在是又蠢又笨。
只不過運算的比人類速度快,記憶力好,但是如果我們不給它指令,計算機就什麽幹不了,就算是給指令, 寫一個程序,計算機也不是很靈活。比如說在C++
中同樣一個加法,要對不同的數據類型給出不同代碼,要寫不同的函數,於是有人提出了一種新思想-泛型編程,那麽什麽是泛型編程呢?
2.泛型編程
泛型編程最初提出的動機很簡單,就是要發明一種語言機制,能實現:
(1)一個通用的標準容器庫。何為通用的標準容器庫?比如用一個list類能存放所有的數據類型的對象:整形、浮點型、字符串類型、自定義對象類型、、、
(2)編寫完全一般化並可以重復使用的算法,其效率與針對某特定數據結構設計的算法不相上下。泛型是指在多種數據類型上皆可操作的含義,這樣的算法與
數據結構相分離,其中的算法是泛型的,不與任何特定數據結構或對象類型聯系在一起,從而提供了工作效率。
3.C++的模板
我們為了讓程序更加智能化,C++很需要泛型這種新的編程方式,於是引入了模板這個功能,也就是說在C++中引入了關鍵字template
使用模板是為了實現泛型,可以減輕編程的工作量,增強函數的重用性。
比如說將兩個變量相加的函數add,如果不是用模板來實現,我們需要針對不同的類型寫多個功能相同的函數,例如int、float等,而使用了模板後只需使用以下一個函數即可:
1 template <class T> 2 T add(T m, T n) 3 { 4 return m+n; 5 }
調用時可以說明類型,例如: add<int>(3,5); 但是也可以不用使用類型,函數會根據參數的類型自動確定類型: add(3,5);
當然,還可以自定義數據類型,甚至自己定義的類
四、STL組成部分
STL可分為容器(containers)、算法(algorithms)、叠代器(iterators)、仿函數(functors)、配接器(adapters)、空間配置器(allocator)六個部分
1、容器
作為STL的最主要組成部分--容器,分為向量(vector),雙端隊列(deque),表(list),隊列(queue),堆棧(stack),集合(set),多重集合(multiset),映射(map),多重映射(multimap)。
容器 |
特性 |
所在頭文件 |
向量vector |
可以用常數時間訪問和修改任意元素,在序列尾部進行插入和刪除時,具有常數時間復雜度,對任意項的插入和刪除就有的時間復雜度與到末尾的距離成正比,尤其對向量頭的添加和刪除的代價是驚人的高的 |
<vector> |
雙端隊列deque |
基本上與向量相同,唯一的不同是,其在序列頭部插入和刪除操作也具有常量時間復雜度 |
<deque> |
表list |
對任意元素的訪問與對兩端的距離成正比,但對某個位置上插入和刪除一個項的花費為常數時間。 |
<list> |
隊列queue |
插入只可以在尾部進行,刪除、檢索和修改只允許從頭部進行。按照先進先出的原則。 |
<queue> |
堆棧stack |
堆棧是項的有限序列,並滿足序列中被刪除、檢索和修改的項只能是最近插入序列的項。即按照後進先出的原則 |
<stack> |
集合set |
由節點組成的紅黑樹,每個節點都包含著一個元素,節點之間以某種作用於元素對的謂詞排列,沒有兩個不同的元素能夠擁有相同的次序,具有快速查找的功能。但是它是以犧牲插入刪除操作的效率為代價的 |
<set> |
多重集合multiset |
和集合基本相同,但可以支持重復元素具有快速查找能力 |
<set> |
映射map |
由{鍵,值}對組成的集合,以某種作用於鍵對上的謂詞排列。具有快速查找能力 |
<map> |
多重集合multimap |
比起映射,一個鍵可以對應多了值。具有快速查找能力 |
<map> |
2、算法
算法部分主要由頭文件<algorithm>,<numeric>和<functional>組成。<algorithm>是所有STL頭文件中最大的一個,它是由一大堆模版函數組成的,可以認為每個函數在很大程度上都是獨立的,其中常用到的功能範 圍涉及到比較、交換、查找、遍歷操作、復制、修改、移除、反轉、排序、合並等等。<numeric>體積很小,只包括幾個在序列上面進行簡單數學運算的模板函數,包括加法和乘法在序列上的一些操作。<functional>中則定義了一些模板類,用以聲明函數對象。
STL的算法也是非常優秀的,它們大部分都是類屬的,基本上都用到了C++的模板來實現,這樣很多相似的函數就不用自己寫了,只要用函數模板就可以了。
我們使用算法的時候,要針對不同的容器,比如:對集合的查找,最好不要用通用函數find(),它對集合使用的時候,性能非常的差,最好用集合自帶的find()函數,它針對了集合進行了優化,性能非常的高。
3、叠代器
它的具體實現在<itertator>中,我們完全可以不管叠代器類是怎麽實現的,大多數的時候,把它理解為指針是沒有問題的(指針是叠代器的一個特例,它也屬於叠代器),但是,決不能完全這麽做。
叠代器功能 |
||
輸入叠代器 Input iterator |
、Reads forward |
istream |
輸出叠代器 Output iterator |
向前寫 Writes forward |
ostream,inserter |
前向叠代器 Forward iterator |
向前讀寫 Read and Writes forward |
|
雙向叠代器 Bidirectional iterator |
向前向後讀寫 Read and Writes forward and backward |
list,set,multiset,map,mul timap |
隨機叠代器 Random access iterator |
隨機讀寫 Read and Write with random access |
vector,deque,array,string |
4、仿函數
(1)仿函數的概念:
仿函數(functor),就是使一個類的使用看上去象一個函數。其實現就是類中實現一個operator(),這個類就有了類似函數的行為,就是一個仿函數類了。 在我們寫代碼時有時會發現有些功能的實現的代碼,會不斷的在不同的成員函數中用到,但是又不好將這些代碼獨立出來成為一個類的一個成員函數。但是又很想復用這些代碼。寫一個公共的函數,可以,這是一個解決方法,不過函數用到的一些變量,就可能成為公共的全局變量,再說為了復用這麽一片代碼,就要單立出一個函數,也不是很好維護。這時就可以用仿函數了,寫一個簡單類,除了那些維護一個類的成員函數外,就只是實現一個operator(),在類實例化時,就將要用的,非參數的元素傳入類中。這樣就免去了對一些公共變量的全局化的維護了。又可以使那些代碼獨立出來,以便下次復用。而且這些仿函數,還可以用關聯,聚合,依賴的類之間的關系,與用到他們的類組合在一起,這樣有利於資源的管理(這點可能是它相對於函數最顯著的優點了)。如果在配合上模板技術和policy編程思想,那就更是威力無窮了,大家可以慢慢的體會。仿函數,又或叫做函數對象,是STL六大組件之一;仿函數雖然小,但卻極大的拓展了算法的功能,幾乎所有的算法都有仿函數版本。例如,查找算法find_if就是對find算法的擴展,標準的查找是兩個元素相等就找到了,但是什麽是相等在不同情況下卻需要不同的定義,如地址相等,地址和郵編都相等,雖然這些相等的定義在變,但算法本身卻不需要改變,這都多虧了仿函數。仿函數(functor)又稱之為函數對象(function object),其實就是重載了()操作符的struct,沒有什麽特別的地方
(2)在C++裏,我們通過在一個類中重載括號運算符的方法使用一個函數對象而不是一個普通函數。
1 class compare_class 2 { 3 public: 4 bool operator()(int A, int B)const{return A < B;} 5 }; 6 7 // Declaration of C++ sorting function. 8 template<class ComparisonFunctor> 9 void sort_ints(int* begin_items, int num_items, ComparisonFunctor c); 10 11 int main() 12 { 13 int items[]={4, 3, 1, 2}; 14 compare_class functor; 15 sort_ints(items, sizeof(items)/sizeof(items[0]), functor); 16 17 return 0; 18 }
(3)為什麽要使用仿函數:
仿函數比一般的函數靈活;仿函數有類型識別,可以作為模板參數;執行速度上仿函數比函數和指針要更快的。
5、適配器(配接器)
適配器是用來修改其他組件接口的STL組件,是帶有一個參數的類模板(這個參數是操作的值的數據類型)。STL定義了3種形式的適配器:容器適配器,叠代器適配器,函數適配器。
(1)容器適配器:
包括棧(stack)、隊列(queue)、優先(priority_queue)。使用容器適配器,stack就可以被實現為基本容器類型(vector,dequeue,list)的適配。可以把stack看作是某種特殊的vctor,deque或者list容器,只是其操作仍然受到stack本身屬性的限制。queue和priority_queue與之類似。容器適配器的接口更為簡單,只是受限比一般容器要多。
(2)叠代器適配器:
修改為某些基本容器定義的叠代器的接口的一種STL組件。反向叠代器和插入叠代器都屬於叠代器適配器,叠代器適配器擴展了叠代器的功能。
(3)函數適配器:
通過轉換或者修改其他函數對象使其功能得到擴展。這一類適配器有否定器(相當於"非"操作)、綁定器、函數指針適配器。函數對象適配器的作用就是使函數轉化為函數對象,或是將多參數的函數對象轉化為少參數的函數對象。
6、空間配置器
空間配置器,也叫內存配置器,空間配置器代表一種特定的內存模型,並提供一種抽象概念,便於將內存的申請轉換成對內存的直接調用。配置器主要用於將算法和容器的實現隔離於物理存儲細節之外。
STL學習第一章 了解STL