C++學習筆記十一 -泛型算法


一、概述:

      1.因為它們實現共同的操作,所以稱之為“算法”;而“泛型”指的是它們可以操作在多種容器類型上——不但可作用於 vectorlist 這些標准庫類型,還可用在內置數組類型、甚至其他類型的序列上,這些我們將在本章的后續內容中了解。自定義的容器類型只要與標准庫兼容,同樣可以使用這些泛型算法。解算法的最基本方法是了解該算法是否讀元素、寫元素或者對元素進行重新排序。

 

      2.大多數算法是通過遍歷由兩個迭代器標記的一段元素來實現其功能。典型情況下,算法在遍歷一段元素范圍時,操縱其中的每一個元素。算法通過迭代器訪問元素,這些迭代器標記了要遍歷的元素范圍。

 

      3.泛型算法本身從不執行容器操作,只是單獨依賴迭代器和迭代器操作實現。算法基於迭代器及其操作實現,而並非基於容器操作。這個事實也許比較意外,但本質上暗示了:使用“普通”的迭代器時,算法從不修改基礎容器的大小。正如我們所看到的,算法也許會改變存儲在容器中的元素的值,也許會在容器內移動元素,但是,算法從不直接添加或刪除元素.算法不直接修改容器的大小。

     

      4.除了少數例外情況,所有算法都在一段范圍內的元素上操作,我們將這段范圍稱為“輸出范圍(input range)”。帶有輸入范圍參數的算法總是使用頭兩個形參標記該范圍。這兩個形參是分別指向要處理的第一個元素和最后一個元素的下一位置的迭代器。

 

二、初窺算法:

     1.accumulate算法:容器內的元素類型必須與第三個實參的類型匹配,或者可轉換為第三個實參的類型。

     // sum the elements in vec starting the summation with the value 42
     int sum = accumulate(vec.begin(), vec.end(), 42);

 

     2.泛型算法都是在標記容器(或其他序列)內的元素范圍的迭代器上操作的。標記范圍的兩個實參類型必須精確匹配,而迭代器本身必須標記一個范圍:它們必須指向同一個容器中的元素(或者超出容器末端的下一位置),並且如果兩者不相等,則第一個迭代器通過不斷地自增,必須可以到達第二個迭代器。

 

     3.find_first_of:這個算法帶有兩對迭代器參數來標記兩段元素范圍,在第一段范圍內查找與第二段范圍中任意元素匹配的元素,然后返回一個迭代器,指向第一個匹配的元素。如果找不到元素,則返回第一個范圍的 end 迭代器。

 

     4.寫入到輸入序列的算法本質上是安全的——只會寫入與指定輸入范圍數量相同的元素。

       fill(vec.begin(), vec.end(), 0); // reset each element to 0

      fill_n 函數帶有的參數包括:一個迭代器、一個計數器以及一個值。在沒有元素的空容器上調用 fill_n 函數是錯誤的.對指定數目的元素做寫入運算,或者寫到目標迭代器的算法,都不檢查目標的大小是否足以存儲要寫入的元素。

 

三、back_inserter插入迭代器:

      1.確保算法有足夠的元素存儲輸出數據的一種方法是使用插入迭代器插入迭代器是可以給基礎容器添加元素的迭代器。通常,用迭代器給容器元素賦值時,被賦值的是迭代器所指向的元素。而使用插入迭代器賦值時,則會在容器中添加一個新元素,其值等於賦值運算的右操作數的值。

    

     2.copy算法:

         A.假設 ilst 是一個存放 int 型數據的 list 對象,可如下將它 copy 給一個 vector 對象:

     vector
   
   
   
           
            
    
    
     ivec; // empty vector
     // copy elements from ilst into ivec
     copy (ilst.begin(), ilst.end(), back_inserter(ivec));
   
   
   
           
 
     B.調用該函數后,ilst 沒有改變,ivec 存儲 ilst 一份副本,而 ilst 內所有的 0 在 ivec 中都變成了 42。
     replace_copy (ilst.begin(), ilst.end(), back_inserter(ivec), 0, 42);
 
   3.排序算法:
    sort(b,e)           //b為起始迭代器, e為指定范圍最后一個元素的下一個迭代器
    sort(b,e,謂詞函數)   //b為起始迭代器, e為指定范圍最后一個元素的下一個迭代器,謂詞函數用來自定義比較兩個元素的大小
這個謂詞函數必須接受兩個實參,實參的類型必須與元素類型相同,並返回一個可用作條件檢測的值。
    除了 sort 之外,標准庫還定義了 stable_sort 算法,stable_sort 保留相等元素的原始相對位置。
 
    4.unique算法:使用該算法前,要先對元素進行排序.該算法,將容器中重復的元素,放在容器的尾部,並返回一個迭代器,表示無重復的值范圍的結束。

 

     5.find_if 函數帶有一對迭代器形參,指定其操作的范圍。該函數還帶有第三個形參,表明用於檢查范圍內每個元素的謂詞函數。find_if 返回一個迭代器,指向第一個謂詞函數返回非零值的元素。如果這樣的元素不存在,則返回第二個迭代器實參。

      用法形式如下:find_if(b,e,謂詞函數)

 

     6.count_if 函數帶有一對迭代器形參,指定其操作的范圍。該函數還帶有第三個形參,表明用於檢查范圍內每個元素的謂詞函數。count_if 返回一個sizetype類型的數值,代表所有謂詞函數返回非零值的元素的個數

 

四、再論迭代器:

     1.插入迭代器:插入器是一種迭代器適配器,帶有一個容器參數,並生成一個迭代器,用於在指定容器中插入元素。通過插入迭代器賦值時,迭代器將會插入一個新的元素。C++ 語言提供了三種插入器,其差別在於插入元素的位置不同。

        A.back_inserter,創建使用 push_back 實現插入的迭代器。

 

        B.front_inserter,使用 push_front 實現插入。只有當容器提供 push_front 操作時,才能使用 front_inserter。在 vector 或其他沒有 push_front 運算的容器上使用 front_inserter,將產生錯誤。front_inserter 的使用將導致元素以相反的次序出現在目標對象中,這點非常重要。

 

        C.inserter,使用 insert 實現插入操作。除了所關聯的容器外,inserter 還帶有第二實參:指向插入起始位置的迭代器。

       inserter 適配器提供更普通的插入形式。這種適配器帶有兩個實參:所關聯的容器和指示起始插入位置的迭代器。

     // position an iterator into ilst
     list
   
   
   
           
            
    
    
    ::iterator it =
     find (ilst.begin(), ilst.end(), 42);
     // insert replaced copies of ivec at that point in ilst
     replace_copy (ivec.begin(), ivec.end(),inserter (ilst, it), 100, 0);
   
   
   
           


五、iostream 迭代器:

流迭代器都是類模板:任何已定義輸入操作符(>> 操作符)的類型都可以定義 istream_iterator。類似地,任何已定義輸出操作符(<< 操作符)的類型也可定義 ostream_iterator。在創建流迭代器時,必須指定迭代器所讀寫的對象類型.

 

   1.istream_iterator:用於讀取輸入流

istream_iterator<T> in(strm);創建從輸入流 strm 中讀取 T 類型對象的 istream_iterator 對象

istream_iterator<T> in;        istream_iterator 對象的超出末端迭代器

it1 == it2 it1 != it2;  比較兩上 istream_iterator 對象是否相等(不等)。迭代器讀取的必須是相同的類型。如果兩個迭代器都是 end 值,則它們相等。對於兩個都不指向流結束位置的迭代器,如果它們使用同一個輸入流構造,則它們也相等

*it;        返回從流中讀取的值

it->mem;    (*it).mem 的同義詡。返回從流中讀取的對象的 mem 成員

++it it++;  通過使用元素類型提供的 >> 操作從輸入流中讀取下一個元素值,使迭代器向前移動。通常,前綴版本使用迭代器在流中向前移動,並返回對加 1 后的迭代器的引用。而后綴版本使迭代器在流中向前移動后,返回原值

  

    2.ostream_iterator :則用於寫輸出流

ostream_iterator<T> in(strm);  創建將 T 類型的對象寫到輸出流 strmostream_iterator 對象

ostream_iterator<T> in(strm, delim);  創建將 T 類型的對象寫到輸出流 strmostream_iterator 對象,在寫入過程中使用 delim 作為元素的分隔符。delim 是以空字符結束的字符數組

用法示例:

     istream_iterator
   
   
   
           
            
    
    
     cin_it(cin);    // reads ints from cin
     istream_iterator
    
    
    
            
              end_of_stream; // end iterator value // initialize vec from the standard input: vector 
             
               vec(cin_it, end_of_stream); sort(vec.begin(), vec.end()); // writes ints to cout using " " as the delimiter ostream_iterator 
              
                output(cout, " "); // write only the unique elements in vec to the standard output unique_copy(vec.begin(), vec.end(), output); 
               
              
            
   
   
   
           

 

    3.流迭代器的限制:

       A.不可能從 ostream_iterator 對象讀入,也不可能寫到 istream_iterator 對象中。

       B.一旦給 ostream_iterator 對象賦了一個值,寫入就提交了。賦值后,沒有辦法再改變這個值。此外,ostream_iterator 對象中每個不同的值都只能正好輸出一次。

       C.ostream_iterator 沒有 -> 操作符

 

六、反向迭代器:反向迭代器是一種反向遍歷容器的迭代器。也就是,從最后一個元素到第一個元素遍歷容器。反向迭代器將自增(和自減)的含義反過來了:對於反向迭代器,++ 運算將訪問前一個元素,而 -- 運算則訪問下一個元素。

     1.容器定義了 rbeginrend 成員,分別返回指向容器尾元素和首元素前一位置的反向迭代器。與普通迭代器一樣,反向迭代器也有常量(const)和非常量(nonconst)類型。

 

      2.對於反向迭代器,++ 運算將訪問前一個元素,而 -- 運算則訪問下一個元素。雖然顛倒自增和自減這兩個操作符的意義似乎容易使人迷惑,但是它讓程序員可以透明地向前或向后處理容器。例如,為了以降序排列 vector,只需向 sort 傳遞一對反向迭代器:

     // sorts vec in "normal" order
     sort(vec.begin(), vec.end());
     // sorts in reverse: puts smallest element at the end of vec
     sort(vec.rbegin(), vec.rend());
   
   3.反向迭代器需要使用自減操作符:標准容器上的迭代器既支持自增運算,也支持自減運算。但是,流迭代器卻不然,由於不能反向遍歷流,因此流迭代器不能創建反向迭代器。
 
   4.普通迭代器與反向迭代器之間的關系是為了適應左閉合范圍這個性質的,所以,對於vector line, [line.rbegin(), rcomma)[rcomma.base(), line.end()) 標記的是 line 中的相同元素。
    飛信截圖20120512144811 
 
 
七、const迭代器:使用const迭代器則無法用來修改容器中的元素。
    1.同時要注意const迭代器在使用過程中的細微差別:用來指定范圍的兩個迭代器,如果一個該容器的const迭代器,另一個是普通的迭代器,則無法編譯通過,因為兩個迭代器的類型不同。
 
 2.如果該容器是 const 對象,則返回的迭代器是 const_iterator 類型;否則,就是普通的 iterator 類型。
 
八、五種迭代器:根據算法要求它的迭代器提供什么類型的操作,對算法分類。算法要求的迭代器操作分為五個類別:

Input iterator(輸入迭代器):              讀,不能寫;只支持自增運算;相等和不等操作符(==!=);解引用操作符;

Output iterator(輸出迭代器)              寫,不能讀;只支持自增運算;解引用操作符;

Forward iterator(前向迭代器)            讀和寫;只支持自增運算;相等和不等操作符(==!=);解引用操作符;

Bidirectional iterator(雙向迭代器)      讀和寫;支持自增和自減運算;相等和不等操作符(==!=);解引用操作符;

Random access iterator(隨機訪問迭代器)  讀和寫;關系操作符 <<=>>= ;支持自增和自減運算;相等和不等操作符(==!=);解引用操作符;下標操作符 iter[n] ;兩個迭代器之間的減法操作符(--),得到兩個迭代器間的距離 ;迭代器與整型數值 n 之間的加法和減法操作符 ++=--=,結果是迭代器在容器中向前(或退回)n 個元素。

 

        1.在處理算法時,最好將關聯容器上的迭代器視為支持自減運算的輸入迭代器,而不是完整的雙向迭代器。

        2.向算法傳遞無效的迭代器類別所引起的錯誤,無法保證會在編譯時被捕獲到

 

九、泛型算法的結構:

     1.根據對元素的操作將算法分為下面幾種:

        A.只讀算法,不改變元素的值順序。

        B.給指定元素賦新值的算法。

        C.將一個元素的值移給另一個元素的算法。

C++ 還提供了另外兩種算法模式:一種模式由算法所帶的形參定義;另一種模式則通過兩種函數命名和重載的規范定義。

 

2.算法的形參模式:大多數算法采用下面四種形式之一:

     alg (beg, end, other parms);
     alg (beg, end, dest, other parms);//dest 形參是一個迭代器,用於指定存儲輸出數據的目標對象。算法假定無論需要寫入多少個元素都是安全的。
     alg (beg, end, beg2, other parms);
     alg (beg, end, beg2, end2, other parms);//算法同時使用 beg2end2 時,這些迭代器用於標記完整的第二個范圍:帶有 beg2 而不帶 end2 的算法將 beg2 視為第二個輸入范圍的首元素,但沒有指定該范圍的最后一個元素。這些算法假定以 beg2 開始的范圍至少與 begend 指定的范圍一樣大。
 
    3.區別帶有一個值或一個謂詞函數參數的算法版本:
   A.這些算法通常要用到標准關系操作符:==<。其中的大部分算法會提供第二個版本的函數,允許程序員提供比較或測試函數取代操作符的使用.
   B.重新對容器元素排序的算法要使用 < 操作符。這些算法的第二個重載版本帶有一個額外的形參,表示用於元素排序的不同運算:
      sort (beg, end);         // use < operator to sort the elements
     sort (beg, end, comp);   // use function named comp to sort the elements
   C.檢查指定值的算法默認使用 == 操作符。系統為這類算法提供另外命名的(而非重載的)版本,帶有謂詞函數形參。帶有謂詞函數形參的算法,其名字帶有后綴 _if
     find(beg, end, val);       // find first instance of val in the input range
     find_if(beg, end, pred);   // find first instance for which pred is true
 
    4.區別是否實現復制的算法版本:無論算法是否檢查它的元素值,都可能重新排列輸入范圍內的元素。在默認情況下,這些算法將重新排列的元素寫回其輸入范圍。標准庫也為這些算法提供另外命名的版本,將元素寫到指定的輸出目標。此版本的算法在名字中添加了 _copy 后綴:
     reverse(beg, end);
     reverse_copy(beg, end, dest);
 
十、容器特有的算法:list 容器上的迭代器是雙向的,而不是隨機訪問類型。由於 list 容器不支持隨機訪問,因此,在此容器上不能使用需要隨機訪問迭代器的算法。這些算法包括 sort 及其相關的算法。還有一些其他的泛型算法,如 mergeremovereverseunique,雖然可以用在 list 上,但卻付出了性能上的代價。如果這些算法利用 list 容器實現的特點,則可以更高效地執行。
 
    1.list 容器特有的算法與其泛型算法版本之間有兩個至關重要的差別。其中一個差別是 removeuniquelist 版本修改了其關聯的基礎容器:真正刪除了指定的元素。例如,list::uniquelist 中第二個和后續重復的元素刪除出該容器。與對應的泛型算法不同,list 容器特有的操作能添加和刪除元素。
 
    2.另一個差別是 list 容器提供的 mergesplice 運算會破壞它們的實參。使用 merge 的泛型算法版本時,合並的序列將寫入目標迭代器指向的對象,而它的兩個輸入序列保持不變。但是,使用 list 容器的 merge 成員函數時,則會破壞它的實參 list 對象——當實參對象的元素合並到調用 merge 函數的 list 對象時,實參對象的元素被移出並刪除。
 
 

3.list 容器特有的操作:

 lst.merge(lst2) lst.merge(lst2, comp)

lst2 的元素合並到 lst 中。這兩個 list 容器對象都必須排序。lst2 中的元素將被刪除。合並后,lst2 為空。返回 void 類型。第一個版本使用 < 操作符,而第二個版本則使用 comp 指定的比較運算

 lst.remove(val) lst.remove_if(unaryPred)

調用 lst.erase 刪除所有等於指定值或使指定的謂詞函數返回非零值的元素。返回 void 類型

lst.reverse()

反向排列 lst 中的元素

lst.sort

lst 中的元素排序

lst.splice(iter, lst2)

lst.splice(iter, lst2, iter2)

lst.splice(iter, beg, end)

lst2 的元素移到 lst 中迭代器 iter 指向的元素前面。在 lst2 中刪除移出的元素。第一個版本將 lst2 的所有元素移到 lst 中;合並后,lst2 為空。lstlst2 不能是同一個 list 對象。第二個版本只移動 iter2 所指向的元素,這個元素必須是 lst2 中的元素。在這種情況中,lstlst2 可以是同一個 list 對象。也就是說,可在一個 list 對象中使用 splice 運算移動一個元素。第三個版本移動迭代器 begend 標記的范圍內的元素。begend 照例必須指定一個有效的范圍。這兩個迭代器可標記任意 list 對象內的范圍,包括 lst。當它們指定 lst 的一段范圍時,如果 iter 也指向這個范圍的一個元素,則該運算未定義。

 lst.unique() lst.unique(binaryPred)

調用 erase 刪除同一個值的團結副本。第一個版本使用 == 操作符判斷元素是否相等;第二個版本則使用指定的謂詞函數實現判斷


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM