程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++ STL 基礎及應用(6) 容器

C++ STL 基礎及應用(6) 容器

編輯:關於C++
讀者可能有這樣的經歷,自己編寫了動態數組類、鏈表類、集合類和映射類等程序,然後小心地維護著。其實 STL 提供了專家級的幾乎我們所需要的各種容器,功能更好,效率更高,復用性更強,所以開發應用系統應該首選 STL 容器類,摒棄自己的容器類,盡管它可能花費了你很多的開發時間。

本章將介紹 STL 中的通用容器

包括 vector、deque、list、queue和stack、priority_queue、bitset、set和multiset、map和multimap等等。

概述

容器分類

(1)序列性容器

按照線性排列來存儲某類型值的集合,每個元素都有自己特定的位置,順序容器主要有 vector、deque、list。
vector:動態數組。它在堆中分配內存,元素連續存放,有保留內存,分配的內存大小只增加不減小,當它滿時再添加元素會重新分配一塊更大的內存,此時需要移動內存,如果你的元素是結構體或者是類,那麼移動的同時還會進行構造和析構操作。對最後元素操作最快(在後面添加刪除最快),此時一般不需要移動內存。
deque:與 vector 類似,支持隨機訪問和快速插入刪除,它在容器某一位置上的操作花費的是線性時間。與 vector 不同的是,deque 支持從開始端插入、刪除元素。由於它主要對前端、後端進行操作,因此也叫做雙端隊列。
list:又叫鏈表,只能順序訪問(從前往後或者從後往前),與前面兩種容器有一個明顯的區別就是它不支持隨機訪問。訪問鏈表某一個元素需要從表頭或表尾開始循環,直到找到該元素。

(2)關聯式容器

與順序性容器相比,關聯式容器更注重快速和高效地檢索數據的能力。這些容器是根據鍵值(key)來檢索數據的,鍵可以是值也可以是容器中的某一成員。這一類中的成員在初始化後都是按一定順序排好序的。關聯式容器主要有 set、multiset、map、multimap。
set:集合,快速查找,不允許重復值。
multiset:集合,快速查找,允許重復值。
map:一對一映射,基於關鍵字快速查找,不允許重復值。
multimap:一對多映射,基於關鍵字快速查找,允許重復值。

(3)容器適配器

對已有的容器進行某些特性的再封裝,不是一個真正的新容器,主要有 stack、queue。
stack:棧類,特點是後進先出。
queue:隊列類,特點是先進先出。

容器共性

容器一般來說都有下列函數:
默認構造函數: 容器默認初始化。
拷貝構造函數: 容器初始化為同類容器副本。
析構函數: 容器關閉,資源釋放。
empty(): 判斷容器是否為空。
max_size(): 返回容器中最大元素個數。
size(): 返回容器中當前元素個數。
operator = () : 將一個容器賦給另一個容器。
operator < () : 容器比較。
operator <= ():
operator > ():
operator >= ():
operator == ():
operator != ():
swap(): 交換兩個容器的元素
begin(): 返回第一個元素迭代器指針。
end(): 返回最後一個元素的後一位的迭代器指針。
rbegin(): 返回最後一個元素迭代器指針。
rend(): 返回第一個元素的前一位的迭代器指針。
erase(): 從容器中清除一個或幾個元素。
clear(): 清除容器中所有元素。

vector 容器

使用 vector 容器時需要頭文件
(1)構造函數
vector(); //創建一個空的 vector
vrctor(int nSize); //創建一個 vector,元素個數為 nSize
vrctor(int nSize const T& t); //創建一個 vector,元素個數為 nSize,且值均為 T
vector(const vector&); //拷貝構造函數
(2)添加函數
void push_back(const T& x); //在尾部添加元素 x
iterator insert(iterator it,const T& x); //在某一元素前添加元素 x
void insert(iterator it,int n,const T& x); //在某一元素前添加 n 個相同元素 x
void insert(iterator it,const_iterator first,const_iterator last); //在某一元素前插入另一個相同類型的[first,last)間的元素
(3)刪除函數
iterator erase(iterator it); //刪除某一元素
iterator erase(iterator first,iterator last); //刪除[first,last)之間的元素
void pop_back(); //刪除最後一個元素
void clear(); //刪除所有元素
(4)遍歷函數
reference at(int pos); //返回 pos 位置元素的引用
reference front(); //返回首元素的引用
reference back(); //返回尾元素的引用
iterator begin(); //返回首元素指針
iterator end(); //返回尾元素的後一個位置的指針
reverse_iterator rbegin(); //反向迭代器,返回尾元素迭代指針
reverse_iterator rend(); //反向迭代器,返回首元素的前一個位置的迭代指針
(5)判斷函數
bool empty() const; //判斷 vector 是否為空,為空返回 true
(6)大小函數
int size() const; //返回向量中元素個數
int capacity() const; //返回 vector 當前所能容納元素數量的最大值
int max_size() const; //返回最大可允許的 vector 元素數量值
(7)其他函數
void swap(vector&); //交換兩個同類型 vector 的元素
void assign(int n,const T& x); //設置容器大小為 n,每個元素為 x
void assign(const_iterator first,const_iterator last); //將容器中[first,last)間的元素設置成當前元素

vector 使用代碼如下:

 

#include 
#include 
using namespace std;
//使用迭代器正序輸出 vector 元素
void print(vector v,char s[])
{
	vector::iterator it;
	cout< v1;    //默認構造函數,創建一個空的 vector
	vector v2(2);    //創建一個大小為2的 vector
	vector v3(3,0);    //穿件一個 vector,並填充3個0
	vector v4(v3);    //拷貝構造函數,創建v3的副本v4

	v1.push_back(1);    //在尾部添加1,此時v1:1
	v1.insert(v1.end(),2);    //在尾部添加2,此時v1:12
	v1.insert(v1.begin(),2,0);    //在首部添加2個0,此時v1:0012
	v1.insert(v1.begin(),v1.begin(),v1.end());    //在首部添加v1所有內容,此時v1:00120012
	print(v1,"v1");
	cout<
做幾點說明:
1.編寫 list 的 print() 函數時,想用 cout<<*it 必須先重載全局函數 ostream& operator<< ,具體見第2章。
2.使用容器的 sort() 函數時,若元素不是基本類型,必須先重載 operator < 函數以供 sort() 函數比較。
3.使用容器的 unique() 函數時,若元素不是基本類型,必須先重載 operator == 函數以供 unique() 函數比較。

queue和stack

隊列和棧是常用而且重要的數據結構。隊列先進先出,棧後進先出,STL 中將基本容器再次封裝,實現這兩個數據結構。
隊列獨有函數:
queue>; //構造函數,創建元素類型為T的空隊列,默認容器是 deque
T& front(); //當隊列非空情況下,返回隊頭元素引用
T& back(); //當隊列非空情況下,返回隊尾元素引用

棧獨有函數:
stack>; //構造函數,創建元素類型為T的空棧,默認容器是 deque
T& top(); //當棧非空情況下,返回棧頂元素的引用

隊列和棧共有函數:
bool empty(); //隊列(棧)為空返回 true,否則返回 false
int size(); //返回隊列(棧)中元素數值
void push(const T& t); //把 t 元素壓入隊尾(棧頂)
void pop(); //當隊列(棧)非空時,返回隊頭(棧頂)元素

使用 queue 需要頭文件,使用 stack 需要頭文件,比如隊列使用如下:
queue> q;

容器適配器

為了清除容器適配器的概念,先看一段 STL 中 stack 的源代碼:
template >
class stack
{	
protected:
	_Container c;	// the underlying container
public:
	......

	stack(): c()
	{	// 使用容器類的默認構造函數
	}

	explicit stack(const _Container& _Cont): c(_Cont)
	{	//使用容器類的拷貝構造函數
	}

	void push(value_type&& _Val)
		{	
		c.push_back(_STD move(_Val));
		}

	size_type size() const
		{	
		return (c.size());
		}
	......
};
可以發現構造 stack 時傳入的容器對象即為 stack 類的成員函數 _Container c,棧的各元素都存儲於 c 中。查看 push() 函數得知, stack 的 push() 實際上是調用了 c.push__back(),也就是說,stack 實際上只是調用了傳入的容器對象的原有函數而已,它的各種操作函數只是起到一個適配器的作用,幾乎沒有自己獨有的功能。廣而言之,stack 類是對基礎容器類的再封裝,不是重新定義,queue 類也很相似。注意!由於 queue 類有 T& front() 函數,因此要求封裝的容器必須有 pop_front()函數,因此 vector 不能被封裝為 queue。

priority_queue

優先隊列即 priority_queue 類,帶優先權的隊列,根據優先權決定出隊順序,默認的序列容器是 vector。

構造函數
//創建元素類型為 T 的空優先隊列,Pred 是二員比較函數,默認是 less
priority_queue(const Pred& pr=Pred(),const allocator_type& al=allocator_type());
//以迭代器[first,last)指向元素,創建元素類型為 T 的優先隊列,Pred 是二員比較函數,默認是 less,可以傳 greater
priority_queue(const value_type* first,const value_type* last,const Pred& pr=Pred(),const allocator_type& al=allocator_type());
操作函數同 queue 類似。

priority_queue 使用代碼如下:
#include <iostream>
#include <queue>
#include <string>
#include <vector>
using namespace std;
class student
{
public:
	int number;    //學號
	string name;    //姓名
	int math;    //數學成績
	int chinese;    //語文成績
public:
	student(int number,const string &name,int math,int chinese)
	{
		this->number=number;
		this->name=name;
		this->math=math;
		this->chinese=chinese;
	}
	friend ostream& operator << (ostream& out,const student& s)
	{
		cout<<"學號:"<<s.number<<"\t姓名:"<<s.name<<"\t數學:\t"<<s.math<<"\t語文:"<<s.chinese<<endl;
		return out;
	}
	bool operator < (const student& s) const
	{
		//先比較數學成績,相同再比較語文成績,相同再比較學號
		//return this->number < s.number;
		if(this->math < s.math) return true;
		if(this->math == s.math && this->chinese < s.chinese) return true;
		if(this->math == s.math && this->chinese == s.chinese && this->number < s.number) return true;
		return false;
	}
};
int main()
{
	priority_queue<student,vector<student>,less<student> > p;
	p.push(student(3,"李世民",60,70));
	p.push(student(1,"秦始皇",70,90));
	p.push(student(2,"康熙",70,50));
	while(!p.empty())
	{
		cout<<p.top();
		p.pop();
	}
	return 0;
}

輸出:

學號:1 姓名:秦始皇 數學: 70 語文:90

學號:2 姓名:康熙 數學: 70 語文:50

學號:3 姓名:李世民 數學: 60 語文:70

上述程序中有一個 student 類,主函數以 vector 為內置容器建立了一個優先隊列,並使用 less 作為比較函數,為了使用該函數,必須重載 student 類的 operator< 操作符。程序中先比較數學成績,相同再比較語文成績,相同再比較學號,權值小的先輸出。從輸出中可以看到,"李世民"的數學最低,因此權值最小,所以最後才輸出。如果想先輸出權值大的該怎麼辦呢?很簡單,只需要再建立優先隊列時使用 gteater 參數,即 priority_queue,greater > p,並重載 student 類的 operator> 操作符即可。

bitset 容器

C 是一種"接近硬件"的語言,但 C 語言並沒有固定的二進制表示法。bitset 可以看做是二進制位的容器,並提供了位的相關操作數。

bitset常用函數如下所示:

(1)構造函數

bitset(); //默認構造函數,Bits 為位最大長度

bitset(const bitset&); //拷貝構造函數

bitset(unsigned long val); //由無符號長整型數構建位容器

bitset(const string& str,size_t pos,size_t n=-1); //由字符串創建位容器,pos 為 str 起始位置,n 為根據 str 構建字符個數

bitset& operator= (const bitset&); //賦值操作

(2)邏輯運算操作

bitset& operator &= (const bitset&); //返回兩個位容器 & 運算後的引用,並修改第一個位容器值

bitset& operator |= (const bitset&);

bitset& operator ^= (const bitset&);

bitset& operator << = (size_t n); //返回位容器左移 n 位後的引用,並修改第一個位容器值

bitset& operator >> = (size_t n);

bitset operator << (size_t n) const; //返回位容器左移 n 位後的備份

bitset operator >> (size_t n) const;

bitset operator & (const bitset&,const bitset&); //返回兩個位容器 & 運算後的備份

bitset operator | (const bitset&,const bitset&);

bitset operator ^ (const bitset&,const bitset&);

(3)其他操作函數

string to_string(); //將位容器內容轉換成字符串

size_t size() const; //返回位容器大小

size_t count() const; //返回設置成 1 的位個數

bool any() const; //是否有位設置 1

bool none() const; //是否沒有為設置 1

bool test(size_t n)const; //測試某位是否為 1

bool operator[](size_t n)const; //隨機訪問位元素

unsigned long to_ulong() const; //若沒有溢出異常,返回無符號長整型數

bitset& set(); //位容器所有位置 1

bitset& flip(); //位容器所有位翻轉

bitset& reset(); //位容器所有位置 0

bitset& set(size_t n,int val=1); //設置某位為 1 或 0,默認為 1

bitset& reset(size_t n); //復位某位為 0

bitset& flip(size_t n); //翻轉某位

bitset 容器的使用比較簡單,這裡直接給出一個它的簡易應用。已知有 n 個整形數組,長度都是 10,元素都在[1,20]之間,且均遞增排列,無重復數據。試利用 bitset 壓縮這些數組,並存入文件中。

分析:一個數組大小=4*10=40字節,而每一位數字其實都能用一個20位大小的 bitset 容器存儲,數字是幾位容器的第幾位就是幾。如一個數n=5,則存儲為 00000000000000010000。20位相當於2.5字節,與原先的40字節相比,壓縮成了原來的1/16。但實際上文件操作的最小單位是字節,無法讀寫2.5字節,因此位容器需要選擇24位大小,這樣讀寫操作正好是3字節了。

使用 bitset 壓縮數組代碼如下:

#include <bitset>
#include <iostream>
#include <fstream>
using namespace std;
template <size_t N>
class Mynum
{
public:
	bitset<N> b;
public:
	void set(int array[],int nSize)
	{
		b.reset();
		for(int i=0;i<nSize;i++)
		{
			b.set(array[i]-1,1);
		}
	}
};
int main()
{
	int a[4][10]={
	{1,2,3,4,5,6,7,8,9,10},
	{11,12,13,14,15,16,17,18,19,20},
	{2,4,6,8,10,12,14,16,18,20},
	{1,3,5,7,9,11,13,15,17,19}};
	
	fstream out("test.txt");
	Mynum<24> m;
	for(int i=0;i<4;i++)
	{
		m.set(a[i],(sizeof(a[i])/sizeof(int)));
		out.write((char*)&(m.b),3);    //將bitset寫入文件
	}
	out.close();

	ifstream in("test.txt");
	bitset<24> b;
	if(!in) return 0;
	else
	{	
		for(int k=0;k<4;k++)
		{
			in.read((char*)(&b),3);    //從文件中讀出並存儲到bitset中
			for(int i=0;i<24;i++)
			{
				if(b.test(i))
				{
					cout<<i+1<<"\t";
				}
			}			
			cout<<endl;
		}
	}
	in.close();
	return 0;
}

輸出:

1 2 3 4 5 6 7 8 9 10

11 12 13 14 15 16 17 18 19 20

2 4 6 8 10 12 14 16 18 20

1 3 5 7 9 11 13 15 17 19

實際中應該把 main 函數中的寫入讀出功能封裝至 Mynum 類中,這裡為了簡便就這麼寫了。可以在程序目錄下找到"test.txt",可以看到其大小為12字節,而正常存儲40個int應該為160字節。

set和multiset

multiset 與 set 的區別就是 multiset 允許重復元素存在,兩者都是有序集合。

大部分函數與之前介紹的容器的函數類似,這裡就介紹下獨有的函數。

首先需要介紹 pair 數據結構,定義如下:
 

template <class T,class U>
struct pair : public _Pair_base<T, U>
{
	typedef T first_type;
	typedef U second_type;
	T first;
	U second;
	pair();
	pair(const T&x,const U& y);
	template <class V,class W>
		pair(const pair<V,W>& pr);
}

在父結構 _Pair_base 中定義了 T first;U second; 簡單地說 pair 是有著兩個動態類型成員變量的數據結構。

(1)插入函數:

pairinsert(const value_type& x); //插入成功時,pair.first 為插入元素的位置迭代器,pair.second 為 true,插入成功時,pair.first 為與插入元素重復元素的位置迭代器,pair.second 為 false

(2)操作函數

const_iterator lower_bound(const Key& key); //返回容器元素大於等於 key 的迭代指針,否則返回 end()

const_iterator upper_bound(const Key& key); //返回容器元素大於 key 的迭代指針,否則返回 end()

pairequal_range(const Key& key) const; //返回容器中包含值等於 key 元素的最小范圍[first,last)

可以發現,equal_range() 返回的 pair 的first 即是 lower_bound(),其 second 即是 upper_bound()。

map和multimap

在之前的容器中,僅保存著一樣東西,但是在 map和multimap 中將會得到兩樣東西,關鍵字 Key 和以關鍵字查詢得到的結果值 Value,即一對值,map 但映射中,Key 和 Value 是一對一的關系,而在 multimap 中,Key 和 Value 可以是一對多的關系。map和multimap 的函數使用基本與 set 類似,值得講的是,map和multimap 可以用 [] 運算符來給映射添加鍵-值對,並返回值得引用。

map和multimap使用代碼如下:

#include <map>
#include <string>
#include <iostream>
using namespace std;
int main()
{
	map<string,string> m;
	m["1-1"]="元旦節";    //使用賦值形式添加映射,鍵在[]內,值在賦值號右側
	m["5-1"]="勞動節";
	pair<string,string> p("8-1","建軍節");    //使用鍵值對通過 insert() 添加
	m.insert(p);
	cout<<m["8-1"];
	return 0;
}
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved