STL之容器
STL容器
STL容器
在現實生活中,容器是用來裝其他東西的器具。在C++中,容器就是能容納其他物件的物件。STL中提供了豐富的容器,並且它們都是泛型容器,可以容納不同的資料型別。
STL中定義的容器可分為三類:
類別 | 容器 |
---|---|
順序容器 | vector(向量)、list(列表)、deque(佇列) |
關聯容器 | map(集合)、set(對映)、multimap(多重集合)、multiset(多重對映) |
容器介面卡 | stack(棧)、queue(佇列)、priority_queue(優先佇列) |
順序容器
順序容器中各元素之間有順序關係,順序容器中每個元素均有固定的位置,位置的先後關係由新增進容器的先後順序決定。STL中有三個順序容器,它們分別是vector、list和deque
vector(向量)
vectors是一種線性順序結構,與陣列很類似,但不用預先指定其大小。當資料所需的儲存空間大於當前分配的空間時,vector會自動申請一塊更大的記憶體,然後將原來的資料拷貝到新的記憶體中並銷燬原記憶體中的資料,最後將原來的記憶體空間釋放掉。因此,當資料量變化較大時,vector的效能就不是很好。
vector的特點如下:
1.不用預先指定大小,可動態分配記憶體
2.由於是線性結構,可對元素進行隨機訪問
3.插入和刪除的效率非常低
4.當動態新增的資料超過預設分配的大小時,進行動態記憶體分配,資料拷貝等操作非常消耗效能。
建立
vector的操作定義於<vector>標頭檔案中。vector提供了很多的建構函式過載,因此可以很靈活的定義並初始化一個vector。此外,vector還是一個模板容器,因此適用於不同的資料型別。下面是一個建立vector的簡單例子。
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
int main() {
vector<int> vec;
vector<int> vec1 = vec;
vector<int> vec2(vec);
vector<int> vec3(vec.begin(), vec.end());
vector<int> vec4(10);
vector<int> vec5(10, 1);
vector<char> vec6(10, 'X');
vector<string> vec7(10, "Hello World");
cout << vec.size() << endl;
cout << vec1.size() << endl;
cout << vec5.size() << endl;
return 0;
}
0
0
10
增刪插入
上面說到,由於vector在記憶體中的儲存特性,因此vector在插入與刪除效能很差。因此儘量需要頻繁的在資料中間進行插入和刪除操作時,不宜使用vector容器。vector提供了一個在末尾增刪元素的方法,當資料長度在當前分配資料大小以內時,效果還是不錯的。
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
void print_vec(const vector<int> &vec);
int main() {
vector<int> vec;
for (int i = 0; i < 10; ++i)
vec.push_back(i);//新增10個元素{0, 1, ..., 9}
print_vec(vec);
vec.pop_back();//刪除最後一個元素
print_vec(vec);
vec.pop_back();//刪除最後一個元素
print_vec(vec);
vec.insert(vec.begin(), 2, 0);//效率很低
print_vec(vec);
vec.erase(vec.begin(), vec.end());
//vec.clear();
print_vec(vec);
cout << "sizeof vec: " << vec.size() << endl;
return 0;
}
vec: 0 1 2 3 4 5 6 7 8 9
vec: 0 1 2 3 4 5 6 7 8
vec: 0 1 2 3 4 5 6 7
vec: 0 0 0 1 2 3 4 5 6 7
vec:
sizeof vec: 0
遍歷
訪問vector中元素的方法一般有三種:
1.由於vector的隨機訪問特性,因此它可以和陣列一樣使用下標訪問。
2.使用迭代器訪問,迭代器也是STL一個非常重要的內容,這裡先給出遍歷方法,後面會詳細討論。
3.使用range_based for(後面簡稱rangefor),這是C++11新增的,類似於python中的 for in等,不需要指出開始和結束的條件。
void print_vec(const vector<int> &vec) {
int length = vec.size();
for (int i=0; i<length; i++)
cout << vec[i] << " ";
cout << endl;
}
void print_vec(const vector<int> &vec) {
for (vector<int>::const_iterator it = vec.begin(); it != vec.end(); it++)
cout << *it << " ";
cout << endl;
}
void print_vec(const vector<int> &vec) {
for(auto& i:vec)
cout << i << " ";
cout << endl;
}
相關函式
函式 | 作用 |
---|---|
size() | 返回容器中元素個數 |
max_size() | 返回容器的最大容量 |
resize() | 改變容器的容量 |
at() | 訪問某個下標的元素 |
capacity() | 返回當前分配的空間大小(元素個數) |
empty() | 判斷容器是否為空 |
reserve() | 改變capacity大小 |
shrink_to_fit() | 減少capacoty,使之與size大小相同 |
front() | 返回首元素 |
back() | 返回尾元素 |
data() | 返回指向容器中陣列的指標 |
assign | 給容器賦值(完全替換掉以前的內容) |
push_back() | 在容器末尾增加一個元素 |
pop_back() | 在容器末尾移出一個元素 |
insert() | 在某個位置插入元素 |
erase() | 刪除某個位置的元素 |
swap() | 交化兩個容器的內容 |
clear() | 清除容器內容 |
emplace() | insert的優化版本 |
emplace_back() | push_back的優化版本 |
emplace_back() | 在最後插入一個元素 |
get_allocator() | 給容器分配空間 |
(c)(r)begin() | 返回指向第一個元素的(常)(反)迭代器 |
(c)(r)end() | 返回指向最後一個元素後面的(常)(反)迭代器 |
list(列表)
list是一種線性連結串列結構,學過資料結構的都知道,連結串列由若干個結點組成,每個節點包含資料域和指標域。而list就相當於一個雙向連結串列,它的每個結點包含一個指向前驅的指標和一個指向後驅的指標。list在記憶體中不是連續的,因此無法像vector那樣隨機存取。但由於list的鏈式結構,其插入和刪除的效率很高。
vector的特點如下:
1.不用預先指定大小,可動態分配記憶體
2.不能隨機訪問,查詢效率低
3.插入和刪除的效率高
4.由於存在指標域,因此比vector佔用更多空間
建立
list的操作定義於<list>標頭檔案中。list的定義和初始化與vector非常相似,
#include <iostream>
#include <iterator>
#include <list>
using namespace std;
int main() {
list<int> list0;
list<int> list1 = list0;
list<int> list2(list0);
list<int> list3(list0.begin(), list0.end());
list<int> list4(10);
list<int> list5(10, 1);
list<char> list6(10, 'X');
list<string> list7(10, "Hello World");
cout << list0.size() << endl;
cout << list2.size() << endl;
cout << list6.size() << endl;
return 0;
}
0
0
10
增刪插入
list的增刪插入操作和vector的也十分類似,不過list中一些操作的效率要高很多。
#include <iostream>
#include <iterator>
#include <list>
using namespace std;
int main() {
list<int> list1;
for (int i = 0; i < 10; ++i)
list1.push_back(i);//新增10個元素{0, 1, ..., 9}
print_list(list1);
list1.pop_back();//刪除最後一個元素
print_list(list1);
list1.pop_back();//刪除最後一個元素
print_list(list1);
list1.insert(list1.begin(), 2, 0);
print_list(list1);
list1.erase(list1.begin(), list1.end());
//vec.clear();
print_list(list1);
cout << "sizeof vec: " << list1.size() << endl;
return 0;
}
遍歷
list容器無法用下標進行遍歷,因此只能用迭代器和rangefor對list進行遍歷:
void print_list(const list<int> &contain) {
for (list<int>::const_iterator it = contain.begin(); it != contain.end(); it++)
cout << *it << " ";
cout << endl;
}
void print_list(const list<int> &contain) {
for(auto& it:contain)
cout << *it << "";
}
相關函式
list容器有關的函式大多數都和vector容器的類似,不過其中也有一些不同,少了關於capacity相關的函式,另外還多了幾個特殊的函式。
函式 | 作用 |
---|---|
size() | 返回容器中元素個數 |
max_size() | 返回容器的最大容量 |
resize() | 改變容器的容量 |
empty() | 判斷容器是否為空 |
front() | 返回首元素 |
back() | 返回尾元素 |
data() | 返回指向容器中陣列的指標 |
assign | 給容器賦值(完全替換掉以前的內容) |
push_back() | 在容器末尾增加一個元素 |
pop_back() | 在容器末尾移出一個元素 |
insert() | 在某個位置插入元素 |
erase() | 刪除某個位置的元素 |
swap() | 交化兩個容器的內容 |
clear() | 清除容器內容 |
emplace() | insert的優化版本 |
emplace_back() | push_back的優化版本 |
emplace_back() | 在最後插入一個元素 |
get_allocator() | 給容器分配空間 |
(c)(r)begin() | 返回指向第一個元素的(常)(反)迭代器 |
(c)(r)end() | 返回指向最後一個元素後面的(常)(反)迭代器 |
push_front() | 容器最前面插入一個元素 |
pop_front() | 容器最前面移除一個元素 |
merge() | 合併兩個容器 |
splice() | 合併兩個容器 |
reverse() | 將容器中的元素進行反轉 |
sort() | 排序 |
unique() | 刪除重複元素 |
remove() | 移除某些特定元素 |
remove_if() | 移除某些滿足條件的元素 |
deque(雙端佇列)
從上面的討論可以看出,vector的查詢效率高,插入和刪除的效率低;而list的插入和刪除效率低,但查詢效率卻很低。而deque則是一種結合了vector和list各自有點的容器,它有著較高的查詢效率和插入刪除效率。當然,deque在查詢上肯定不如vector, 插入刪除上不如list,但它兼顧了兩者的優點,在很多情況下是很好的選擇。
因此,deque的特點如下:
1.支援隨機訪問,但效能不如vector
2.支援在內部進行插入和刪除,但效能不如list
3.它也支援雙端push和pop
建立
deque的定義和初始化與二者類似:
#include <iostream>
#include <iterator>
#include <deque>
using namespace std;
int main() {
deque<int> deque0;
deque<int> deque1 = deque0;
deque<int> deque2(deque0);
deque<int> deque3(deque0.begin(), deque0.end());
deque<int> deque4(10);
deque<int> deque5(10, 1);
deque<char> deque6(10, 'X');
deque<string> deque7(10, "Hello World");
cout << deque0.size() << endl;
cout << deque2.size() << endl;
cout << deque6.size() << endl;
return 0;
}
增刪插入
list的插入與刪除與list差不太多,不過在效率上稍低,它同樣支援在容器頭進行push和pop。
#include <iostream>
#include <iterator>
#include <deque>
using namespace std;
int main() {
deque<int> deque1;
for (int i = 0; i < 10; ++i)
deque1.push_front(i);//從deque頭新增10個元素{0, 1, ..., 9}
print_deque(deque1);
deque1.pop_back();//刪除最後一個元素
print_deque(deque1);
deque1.pop_front();//刪除第一個元素
print_deque(deque1);
deque1.insert(deque1.begin(), 2, 0);
print_deque(deque1);
deque1.erase(deque1.begin(), deque1.end());
//vec.clear();
print_deque(deque1);
cout << "sizeof vec: " << deque1.size() << endl;
return 0;
}
遍歷
由於deque支援下標訪問,因此deque也支援下標遍歷、迭代器遍歷以及rangefor三種方式,此處省略,可參考vector的遍歷。
相關函式
deque大多數函式和vector一致,不過刪除了兩個與capacity有關的函式,增加了幾個操作首元素的函式,詳細變動如下:
函式 | 作用 |
---|---|
size() | 返回容器中元素個數 |
max_size() | 返回容器的最大容量 |
resize() | 改變容器的容量 |
at() | 訪問某個下標的元素 |
empty() | 判斷容器是否為空 |
shrink_to_fit() | 減少capacoty,使之與size大小相同 |
front() | 返回首元素 |
back() | 返回尾元素 |
data() | 返回指向容器中陣列的指標 |
assign | 給容器賦值(完全替換掉以前的內容) |
push_back() | 在容器末尾增加一個元素 |
pop_back() | 在容器末尾移出一個元素 |
insert() | 在某個位置插入元素 |
erase() | 刪除某個位置的元素 |
swap() | 交化兩個容器的內容 |
clear() | 清除容器內容 |
emplace() | insert的優化版本 |
emplace_back() | push_back的優化版本 |
get_allocator() | 給容器分配空間 |
(c)(r)begin() | 返回指向第一個元素的(常)(反)迭代器 |
(c)(r)end() | 返回指向最後一個元素後面的(常)(反)迭代器 |
push_front() | 容器最前面插入一個元素 |
emplace_front() | push_front的優化版本 |
pop_front() | 容器最前面移除一個元素 |
關聯容器
關聯容器內部是一種非線性的樹形結構,具體來說是採用的一種高效的平衡檢索二叉樹-紅黑樹。關聯容器分為兩類,集合(set)和對映(map)。
set(集合)
set是一種非線性結構,因此它不具有隨機訪問的特性。set中的元素是唯一的,所以一個set中不允許有重複的元素,並且set會根據元素的值進行自動排序,因此set中的元素是有序的。multiset和set非常相似,不過取消元素值唯一這個約束。
set的特點如下:
1.非線性結構,不能隨機訪問
2.內部元素是唯一的
3.內部元素自動排序
4.具有優秀的檢索以及插入刪除特性
建立
#include <iostream>
#include <iterator>
#include <set>
using namespace std;
void print_set(const set<int> &s) {
for (set<int>::const_iterator it = s.begin(); it != s.end(); it++)
cout << *it << " " ;
cout << endl;
}
int main() {
set<int> set1;
set<int> set2 = { 3,2,2,4,5,6,1,9,2,2,3,4 };
set<int> set3 = set2;
set<int> set4(set2);
cout << "set1: "; print_set(set1);
cout << "set2: "; print_set(set2);
cout << "set3: "; print_set(set3);
cout << "set4: "; print_set(set4);
return 0;
}
set1:
set2: 1 2 3 4 5 6 9
set3: 1 2 3 4 5 6 9
set4: 1 2 3 4 5 6 9
從結果可以看出,set中元素是唯一的且預設按升序排序。這裡讀者不妨試試multiset的效果,結果應該如下:
set1:
set2: 1 2 2 2 2 3 3 4 4 5 6 9
set3: 1 2 2 2 2 3 3 4 4 5 6 9
set4: 1 2 2 2 2 3 3 4 4 5 6 9
增刪插入
由於set的樹形結構,沒有樹尾的說法,因此未提供push、pop等方法進行插入刪除。下面是利用insert\emplace和erase進行插入刪除的例子:
int main() {
set<int> set1 = { 3,2,2,4,5,6,1,9,2,2,3,4 };
cout << "origin: "; print_set(set1);
set1.insert(10);
cout << "inset(10): "; print_set(set1);
set1.emplace(8);
cout << "emplace(8): "; print_set(set1);
set1.erase(2);
cout << "erase(2): "; print_set(set1);
return 0;
}
origin: 1 2 3 4 5 6 9
inset(10): 1 2 3 4 5 6 9 10
emplace(8): 1 2 3 4 5 6 8 9 10
erase(2): 1 3 4 5 6 8 9 10
注意這裡插入和刪除可能不成功,因為set中的元素唯一,如果該元素已經存在,就會插入失敗。因此insert和emplace其實是有返回值的,它的型別為:pair<set::iterator, bool>,代表返回一個pair,分別是指向該元素的迭代器和插入成功與否的bool值,用.first和.second來獲取二者,如下例:
int main() {
set<int> set1 = { 3,2,2,4,5,6,1,9,2,2,3,4 };
cout << *set1.insert(4).first << endl;
cout << set1.insert(5).second << endl;
cout << set1.insert(8).second << endl;
return 0;
}
4
0
1
執行結果中,4代表返回的迭代器指向的是4,0代表插入5失敗,因為5已經存在於set中了,1代表插入8成功。
遍歷
set只能通過迭代器和rangefor進行遍歷,迭代器方法在上面的例子中其實已經給出,下面給出rangefor方法的示例。
for