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; } |
01 三大組件
容器 vector
算法 for_each 頭文件:algorithm
迭代器 iterator 頭文件: iterator 每個容器有專屬的迭代器
vector<int> vector
vector<int>::iterator it=....
v.begin() 指向第一個數據
v.end() 指向最後一個數據的下一個地址
#include <iostream>
using namespace std;
//容器 vector
#include <vector>
//使用系統算法的頭文件
#include <algorithm>
//迭代器 遍歷功能 用指針理解
//普通指針也算一種迭代器
#include <string>
void test01() {
int array[5] = { 1,3,5,7,9 };
int* p = array;//指針指向數組首地址 &array[0]
for (int i = 0; i < 5; i++) {
//cout << array[i] << endl;
cout << *(p++) << endl;
}
}
void myPrint(int v) {
cout << v << endl;
}
void test02() {
//聲明容器
vector<int> v;//聲明一個容器 這個容器中存放int類型數據 對象名稱v
v.push_back(10);//尾插法
v.push_back(20);
v.push_back(30);
v.push_back(40);
//遍歷容器中的數據
//利用迭代器
//第一種遍歷方式
//聲明迭代器
vector<int>::iterator itBegin=v.begin();//這種容器下的迭代器,itBegin指向的是v容器的起始位置
vector<int>::iterator itEnd = v.end(); //注意:itEnd指向v容器中最後一個位置的下一個地址
/*
while (itBegin != itEnd) {
cout << *itBegin<<endl;
itBegin++;
}
*///這種方法遍歷很笨重,通常不用這種方法去遍歷
//第二種遍歷方式
/*
for (vector<int>::iterator it = v.begin(); it != v.end();it++) {
cout << *it << endl;
}
*/
//第三種方式:利用算法
for_each(v.begin(), v.end(), myPrint);//第三個函數是回調函數
//不認識for_each要引用一個頭文件 algorithm
}
//操作自定義的數據類型
class Person {
public:
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
void test03()
{
vector<Person> v;
Person p1("大頭兒子", 10);
Person p2("小頭爸爸", 32);
Person p3("隔壁王叔叔", 30);
Person p4("圍裙媽媽", 28);
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
//遍歷
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
{
cout << "姓名:"<<(*it).m_Name<<"\t年齡:" <<it->m_Age<< endl;
}
}
//存放自定義數據類型的指針
void test04() {
vector<Person *> v;
Person p1("大頭兒子", 10);
Person p2("小頭爸爸", 32);
Person p3("隔壁王叔叔", 30);
Person p4("圍裙媽媽", 28);
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
v.push_back(&p4);
for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {
cout << "姓名:" << (*it)->m_Name << "\t年齡:" << (*it)->m_Age << endl;
//指針訪問成員用-> *it就是尖括號裏的內容
}
}
//容器嵌套容器
void test05() {
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++) {
//弄明白*it是什麼 它又是一個vector
for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
cout << *vit << "\t";
}
cout << endl;
}
}
int main()
{
//test01();//最常規的遍歷
//test02();
//test03();
//test04();
test05();
}
(本筆記整理自網絡資源,侵刪)