1. 程式人生 > 實用技巧 >函式引數

函式引數

資料結構

研究資料的特定排序方式,以利於搜尋、排序或者其他特殊目的,這樣的一門學科我們稱為資料結構。

任何資料結構都不是平白無故的被創造出來,而是為了實現某種特定的演算法來才被創造出來的。

演算法,問題之解法也

以有限的步驟,解決邏輯或者數學上的問題,這樣一門學科我們稱之為演算法。

演算法是為了解決問題而設計出來的方法。要解決的問題就是我們要實現的特定目的。

就像在作業系統中一樣,檔案時管理資料的基本單位,一個檔案在儲存時被分為兩部分,檔案屬性和資料。檔案屬性真正決定了這個檔案是否存在,而這個檔案存在的意義在於管理和存放檔案中的資料。就像一個裝著米粒的碗,碗是我們的檔案,但是碗中的米才是我們的資料。碗是容器,是為了我們能夠存放和管理資料。

而在這裡也相同,資料結構是容器,容器中存放的資料是我們關心的物件。容器中的資料都是按照特定的排列方式來存放的,之所以將容器中的資料按照不同的特定順序來放置為了實現特定的目的,可能是排序,可能是搜尋等。所以任何資料結構都不是平白無故的被創造出來的,資料結構的產生都是為了實現一定的演算法。

容器主要分為兩大類,序列式容器和關聯式容器。

STL

STL從廣義上可以分為三個組成部分:容器、演算法和迭代器。

所以stl的一個重要特性就是實現了資料和操作之間的分離。資料按照特定的排列方式存放在資料結構中,也就是容器之中(不同的容器內的資料具有不同的排序方式,就是為了實現不同的演算法)。而操作就是指操作資料的函式,也就是我們所說的演算法。我們前面說過,資料結構,也就是不同的容器被設計出來都是為了實現不同的演算法,所以演算法是要用來操作資料的。容器與演算法之間通過迭代器來實現無縫銜接,充當兩者之間的粘合劑,以便演算法和容器互動運作。

所以,stl的中心思想就是將容器和演算法分開,彼此獨立設計,最後再用一貼膠著劑將兩者沾和再一起。

STL具有高效能、高移植性、跨平臺和高可重用性的特點。在這裡我們重點解釋一下高可重用性。

高可重用性:STL中幾乎多有的程式碼都是通過模板了和模板函式的方法來實現的,這相比於傳統的由類和函式組成的庫來說提供了更好的程式碼重用機會。

這個點在編寫程式碼上的影響:

因為stl中幾乎所有的程式碼都是使用模板類和模板函式來實現的,所以在編寫程式的時候第一步一定要傳入的引數就是資料型別。粘合不同的容器和演算法的迭代器是專用的,所以在宣告迭代器的時候要加上作用域運算子,表明迭代器的型別。如下

vector<int
> vec; vector<int>::iterator it1;

上述兩行程式碼無論是在宣告迭代器還是在宣告vector容器都傳入了型別引數int型。

注意:

這裡宣告的迭代器其實就是int* it1,也就是說vector容器的迭代器就是普通指標,同義不同名而已。其迭代器和指標的用法完全相同。

vector容器

vector容器又稱動態陣列,其資料安排和操作方式與陣列非常相似。兩者之間唯一的差別在於空間運用的靈活性。

陣列array是靜態空間,一旦配置了之後空間大小就不再發生變化。當陣列空間不足時就會產生溢位,如果想要換大點的或者小一點的空間,一切操作都要我們自己手動操作進行,具體過程為1.首先申請一塊新的記憶體空間,2.將原來記憶體空間中的資料拷貝到新的記憶體空間中,3.釋放掉舊的記憶體空間。

而vector容器是動態陣列,當陣列空間不足時,vector容器會自動配置更大的記憶體空間。在vector容器動態增加記憶體空間時並不是在原有的記憶體空間尾部新增新的記憶體空間(無法保證原有的記憶體空間尾部還有空閒的記憶體空間),而還是“配置新的記憶體空間-拷貝資料-釋放原空間”過程,但是這些過程不再需要我們來手動操作,由其內部演算法自動實現。

vector容器的資料結構非常簡單,仍然是線性連續空間。但是分別以兩個迭代器_Myfirst和_Mylast分別指向配置得來的連續空間中已經使用的範圍,即指向vector中第一個元素和最後一個元素,並以_Myend指向整塊連續記憶體空間的尾端,就是指向最後一個元素的下一個位置。

vector容器的示意圖如下

注意:

1.觀察vector容器的邏輯示意圖可知,這是一個單向開口的容器,類似於棧的邏輯。但是與棧的邏輯不同的是vector容器是可以在vector容器的中部和底部插入資料的,但是在底部操作效率奇差,無法被接受;而棧只能在棧頂插入和刪除元素。

2.圖中所示的v.rend(), v.begin(), v.rbegin(), v.end()都是迭代器,指的是vector中的位置;而front(), back(), pop_back(), push_back()是方法,也就是操作。

deque容器

deque是一種雙向開口的連續線性空間。雙向開口是一種邏輯結構,就是指可以在頭尾兩端分別做元素的插入和刪除操作。而vector容器是一種單向開口的容器,但是也可以在頭尾兩端插入元素,但是在頭部做插入和刪除操作的效率奇差。

deque的邏輯圖如下

1.如圖所示,deque容器是一個雙向開口的容器,可以通過push_front(), pop_front()方法在頭部操作元素,通過push_back(), pop_back()方法在尾部操作元素。

2.deque容器允許常數項時間對頭部進行元素的插入和刪除操作,但是vector容器在頭部插入和刪除元素的效率奇差。

3.vector容器在記憶體中佔據一塊連續的記憶體空間,是真正意義上的連續線性空間,有容量的概念,在記憶體空間不足時則配置一段新的記憶體空間-複製元素-釋放舊的空間來實現擴充;而deque容器則是分段連續空間組合而成,沒有容量概念,可以隨時增加一段新的空間並銜接起來,具體看實現原理部分。

4.deque容器也提供了迭代器,但是他的迭代器並不是普通的指標。與可以作為普通指標使用的vector容器迭代器相比,其複雜度不是在一個兩量級。因此,我們應該儘可能使用vector,而不是deque。

deque容器的實現原理

deque容器在邏輯上看是連續線性空間,但是在物理空間上並不是連續的,而是由一段段的定量的連續空間構成。一旦有必要在deque容器的前端或者尾端新增新的空間,便配置一段連續定量的空間,串接在deque的頭部或者尾端。原理示意圖如下

1.Deque採取一塊所謂的map(注意,不是STL的map容器)作為主控,這裡所謂的map是一小塊連續的記憶體空間,其中每一個元素(此處成為一個結點)都是一個指標,指向另一段連續性記憶體空間,稱作緩衝區。緩衝區才是deque的儲存空間的主體。

2.deque容器的最大工作就是維護這些分段連續的記憶體空間的整體性的假象,並提供隨機存取的介面,避開了重新配置空間、複製、釋放的輪迴,但代價就是複雜的迭代器架構。

1 deque<int> deq;
2 push_front(ele);
3 pop_front(ele);
4 push_back(ele);
5 pop_back(ele);

stack容器

stack容器,又稱棧,是一種先進後出的資料結構,它只有一個出口,邏輯結構如圖所示。

如圖所示,棧的所有操作都在棧頂進行,除了棧頂位置,其他任何位置不允許進行操作。也就是棧容器允許新增元素,移除元素,取得棧頂元素,但是除了最頂端外,沒有其他任何方法可以取得stack的其他元素。換言之,stack不允許由遍歷行為,只能操作棧頂元素。

有元素推入棧的操作叫做入棧,使用方法push()實現;將元素推出棧的操作稱為出棧,通過方法pop()實現。獲得位於棧頂位置的元素通過方法top()來實現。

注意:

1.棧容器只能操作棧頂的元素和進行出棧、入棧操作,而不能夠獲得其他位置的元素,所以不支援遍歷行為,也。

2.stack不提供遍歷功能,也不提供迭代器。

stack<int> stk;
stk.push(ele);
stk.top();
stk.pop();

queue容器

queue容器,又稱佇列,是一種先進先出的資料結構。佇列和dequeue相同,都有兩個開口,但是與dequeue不同的是queue容器只允許從一端增加元素,而從另一端刪除元素。queue的邏輯圖如下

1.佇列是一種先進先出的資料結構,資料元素只能從佇列容器的尾部進入佇列,而只能從對頭出隊。那麼也就是說,只能訪問獲得隊尾的元素和隊頭的元素,而在隊尾向佇列中新增元素,稱為入隊,從隊頭中移除元素,稱為出隊。

2.我們通過方法back()訪問隊尾的元素, front()方法訪問隊頭的資料元素, push()從隊尾向佇列新增元素, pop()方法從對頭移除元素。

3.對於一個佇列而言,除了隊頭元素和隊尾元素之外,我們無法操作其他任何元素,所以佇列容器也不提供遍歷功能,不能夠訪問佇列中間的資料元素。換言之,佇列中部的元素對我們來說是黑匣子,所以佇列容器也不提供迭代器。

1 queue<int> que;
2 pop();
3 push();
4 back();
5 front();

List容器

List容器就是我們所說的連結串列,連結串列容器在邏輯上是一種順序和連續的資料結構,但是連結串列在物理空間上是非連續和非順序的。物理空間上的非連續表現為邏輯空間上的連續是通過連結串列中的指標實現的,連結串列結點中的指標依次銜接起來表現為邏輯上的順序表。

連結串列由一系列結點組成,結點可以在執行時動態生成。每個結點由兩部分構成:一是存放資料元素的資料域,一是儲存下一個結點實現銜接作用的指標域。

而在c++的STL庫中list容器是雙向連結串列,在連結串列每個結點的指標域都存放了兩個指標,分別是prev和next。除了第一個結點和最後一個結點,其他結點的prev指標都指向該節點的前一個結點,next指標都指向該節點的下一個結點。而第一個結點的prev指標置為NULL,next指標指向第二個節點,最後一個結點的next指標置為NULL,prev指標指向倒數第二個結點。其邏輯圖如圖所示

1.List容器每次插入和刪除一個元素都是配置或者釋放一個元素的空間,因此,list容器具有優秀的記憶體空間利用率。

2.list容器採用動態儲存分配記憶體空間,不會造成記憶體的溢位和浪費。

3.list容器執行插入和刪除操作只需修改前後結點中指標的指向即可,不需要移動大量的資料元素。所以,對於任何位置的元素的插入和移除操作,list永遠都是常數項時間。

list容器的迭代器

list容器的迭代器必須能夠指向list容器內的資料元素,也就是list的結點上,並且能夠支援遞增、遞減、取值、成員存取等操作。但是list容器在記憶體中並不是連續的,所以這就決定了list容器的迭代器不能夠像ector容器那樣以普通指標作為迭代器。

由於list是一個雙向連結串列,迭代器必須能夠具備前移、後移的能力,所以list容器提供的是Bidirectional Iterators.

list容器和vector容器的對比

1.vector容器是動態陣列,在記憶體空間不足時,會按照一定的演算法來擴充套件記憶體空間,擴充套件的記憶體空間是按照原來記憶體空間的一定倍率擴充套件的;而List容器則是按照逐漸增加結點數,使用多說就增加多少個結點。

2.vector容器具有容量和儲存儲存量的概念,而list容器沒有。

3.vector容器在插入資料元素時,當儲存數量達到vector容器的容量而繼續向迭代器中新增元素時,就會導致vector容器重新配置一塊更大的記憶體空間,那麼這個重新配置的操作很可能就會改變vector容器中記憶體空間的改變而使原來的迭代器失效。但是list容器在增加元素時,只是建立了一個新的結點改變前後結點中指標的指向將這個結點新增到list中去,並不會導致原有結點的記憶體地址的改變,所以不會引起原有迭代器的失效。甚至在list容器刪除元素時,也只是會導致指向刪除結點的迭代器失效,而不會對其他迭代器造成任何影響。