泛型演算法結構

2021-09-08 22:04:49 字數 3454 閱讀 8301

任何演算法的最基本的特性是它要求其迭代器提供哪些操作。某些演算法,如find,只要求通過迭代器訪問元素、遞增迭代器以及比較兩個迭代器是否相等這些能力。其他一些演算法,如sort,還要求讀、寫和隨機訪問元素的能力。演算法所要求的迭代器操作可以分為5個迭代器類別,如表所示:

迭代器類別

輸入迭代器        唯讀,不寫;單遍掃瞄,只能遞增

輸出迭代器        只寫,不讀;單遍掃瞄,只能遞增

前向迭代器        可讀寫;多遍掃瞄,只能遞增

雙向迭代器        可讀寫;多遍掃瞄,可遞增遞減

隨機訪問迭代器      可讀寫;多遍掃瞄,支援全部迭代器運算

類似容器,迭代器也定義了一組公共操作,一些操作所有迭代器都支援,另外一些只有特定類別的迭代器才支援。例如,ostream_iterator只支援遞增、解引用和賦值。vector、string和deque的迭代器除了這些操作外,還支援遞減、關係和算術運算。

迭代器是按它們所提供的操作來分類的,而這種分類形成了一種層次。除了輸出迭代器之外,乙個高層類別的迭代器支援底層類別迭代器的所有操作。

輸入迭代器:可以讀取序列中的元素。乙個輸入迭代器必須支援

輸入迭代器只用於順序訪問。對於乙個輸入迭代器,*it++保證是有效的,但遞增它可能導致所有其他指向流的迭代器失效。其結果就是,不能保證輸入迭代器的狀態可以儲存下來並用來訪問元素。因此,輸入迭代器只能用於單遍掃瞄演算法。演算法find和accumulate要求輸入迭代器;而istream_iterator是一種輸入迭代器。

輸出迭代器:可以看做輸入迭代器功能上的補集——只寫而不讀元素。輸出迭代器必須支援

我們只能向乙個輸出迭代器賦值一次。類似輸入迭代器,輸出迭代器只能用於單遍掃瞄演算法。用作目的位置的迭代器通常都是輸出迭代器。例如,copy函式的第三個引數就是輸出迭代器。ostream_iterator型別也是輸出迭代器。

前向迭代器:可以讀元素。這類迭代器只能在序列中沿乙個方向移動。前向迭代器支援所有輸入和輸出迭代器的操作,而且可以多次讀寫同乙個元素。因此,我們可以儲存前向迭代器的狀態,使用前向迭代器的演算法可以對序列進行多遍掃瞄。演算法replace要求前向迭代器,forward_list上的迭代器就是前向迭代器。

雙向迭代器:可以正向/反向讀寫序列中的元素。除了支援所有前向迭代器的操作之外,雙向迭代器還支援前置和後置遞減運算子(--)。演算法reverse要求雙向迭代器,除了forward_list之外,其他標準庫都提供符合雙向迭代器要求的迭代器。

隨機訪問迭代器:提供在常量時間內訪問序列中的任意元素的能力。此類迭代器支援雙向迭代器的所有功能,此外還支援如下的操作:

演算法sort要求隨機訪問迭代器,array、deque、string和vector的迭代器都是隨機訪問迭代器,用於訪問內建陣列元素的指標也是。

在任何其他演算法分類之上,還有一組引數規範。大多數演算法具有如下4種形式之一:

alg(beg,end,other args);

alg(beg,end,dest,other args);

alg(beg,end,beg2,other args);

alg(beg,end,beg2,end2,other args);

其中alg是演算法的名字,beg和end表示演算法所操作的輸入範圍。幾乎所有演算法都接受乙個輸入範圍,是否有其他引數依賴於要執行的操作。這裡列出了常見的一種——dest、beg2和end2,都是迭代器引數。顧名思義,如果用到了這些迭代器引數,它們分別承擔指定目的位置和第二個範圍的角色。除了這些迭代器引數,一些演算法還接受額外的、非迭代器的特定引數。

接受單個目標迭代器的演算法

dest引數是乙個表示演算法可以寫入的目的位置的迭代器。演算法假定:按其需要寫入資料,不管寫入多少個元素都是安全的。

如果dest是乙個直接指向容器的迭代器,那麼演算法將輸出資料寫到容器中已存在的元素內。更常見的情況是,dest被繫結到乙個插入迭代器或是乙個ostream_iterator。插入迭代器會將新元素新增到容器中,因而保證空間是足夠的。ostream_iterator會將資料寫入乙個輸出流,同樣不管要寫多少個元素都沒有問題。

接受第二個輸入序列的演算法

接受單獨的beg2或是接受beg2和end2的演算法用這些迭代器表示第二個輸入範圍。這些演算法通常使用第二個範圍中的元素與第乙個輸入範圍結合來進行一些運算。

除了引數規範,演算法還遵循一套命名和過載規範。這些規範處理諸如:如何提供乙個操作代替預設的《或=運算子以及演算法是將輸出資料寫入輸入序列還是乙個分離的目的位置等為題。

一些演算法使用過載形式傳遞乙個謂詞

接受謂詞引數來代替《或==運算子的演算法,以及哪些不接受額外引數的演算法,通常都是過載的函式。函式的乙個版本用元素型別的運算子來比較元素;另乙個版本接受乙個額外謂詞引數,來代替《或==:

unique(beg,end);  //使用==運算子比較元素

unique(beg,end,comp);   //使用comp比較運元素

兩個呼叫都重新整理給定序列,將相鄰的重複元素刪除。第乙個呼叫使用元素型別的==運算子來檢查重複元素;第二個則呼叫comp來確定兩個元素是否相等。由於兩個版本的函式在引數個數上不相等,因此具體應該呼叫那個不會產生歧義。

_if版本的演算法

接受乙個元素值的演算法通常有另乙個不同名的版本,該版本接受乙個謂詞代替元素值。接受謂詞引數的演算法都有附加_if字首:

find(beg,end,val); //查詢輸入範圍中val第一次出現的位置

find_if(beg,end,pred);  //查詢第乙個令pred為真的元素

這兩個演算法都在輸入範圍中查詢特定的元素第一次出現的位置。演算法find查詢乙個指定值;演算法find_if查詢使得pred返回非零值的元素。

這兩個演算法提供了命名上的差異的版本,而非過載版本,因為兩個版本的演算法都接受相同數目的引數。因此可能產生過載歧義,雖然很罕見,但為了避免任何可能的歧義,標準庫提供不同名字的版本而不是過載。

區分拷貝元素的版本和不拷貝的版本

預設情況下,重排元素的演算法將重排後的元素寫回固定的輸入序列中。這些演算法還提供另乙個版本,將元素寫到乙個指定的輸出目的位置。如我們所見,寫到額外目的空間的演算法都在名字後面附加乙個_copy:

reverse(beg,end); //反轉輸入範圍中元素的順序

reverse_copy(beg,end,dest); //將元素按逆序拷貝到dest

一些演算法同時提供_copy和_if版本。這些版本接受乙個目的位置迭代器和乙個謂詞:

//從v1中刪除奇數元素

remove_if(v1.begin(),v1.end(),

(int i) );

//將偶數元素從v1拷貝到v2;v1不變

remove_copy_if(v1.begin(),v1.end(),back_inserter(v2),

(int i) );

兩個演算法都呼叫了lambda來確定元素是否為奇數。在第乙個呼叫中,我們從輸入序列中將奇數元素刪除。在第二個呼叫中,我們將非奇數(即偶數)元素從輸入範圍拷貝到v2中。

10 5 泛型演算法結構

目錄10.5.2 演算法的形參模式 10.5.3 演算法的命名規範 按操作分類,形成了一種層次,高類別的迭代器支援低類別的所有操作 對於向乙個演算法傳遞錯誤類別的迭代器的問題,很多編譯器不會給出任何警告或提示。輸入迭代器必須支援 輸入迭代器只用於順序訪問。對於乙個輸入迭代器,it 保證是有效的,但遞...

泛型結構使用大全(泛型類 泛型介面)

1 泛型類可能有多個引數,此時應將多個引數一起放到尖括號內,比如 2 泛型類的構造器如下 public genericclass 錯誤示例 public genericclass 3 例項化後,操作原來泛型位置的結構必須與指定的泛型型別一致。4 泛型不同的引用不能互相賦值。儘管在編譯時arrayli...

C 泛型演算法

標準庫並未給每個容器都定義成員函式來實現這些操作,而是定義了一組泛型演算法,稱他們為演算法是因為他們實現了一些經典演算法的公共介面,如排序和搜尋 稱他們為排序的是因為它們可以用於不同型別的元素和多種容器型別。大多數演算法都定義在標頭檔案algorithm中。標準庫還在標頭檔案numeric中定義了一...