1. 程式人生 > >STL之容器

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() 改變容器的容量
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() 返回指向最後一個元素後面的(常)(反)迭代器
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() 訪問某個下標的元素
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的優化版本
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