C++ STL 學習筆記


#.string 建議

  使用string 的方便性就不用再說了,這里要重點強調的是string的安全性。    

  string並不是萬能的,如果你在一個大工程中需要頻繁處理字符串,而且有可能是多線程,那么你一定要慎重(當然,在多線程下你使用任何STL容器都要慎重)。

  string的實現和效率並不一定是你想象的那樣,如果你對大量的字符串操作,而且特別關心其效率,那么你有兩個選擇,首先,你可以看看你使用的STL版本中string實現的源碼;另一選擇是你自己寫一個只提供你需要的功能的類。

  string的c_str()函數是用來得到C語言風格的字符串,其返回的指針不能修改其空間。而且在下一次使用時重新調用獲得新的指針。

  string的data()函數:如果string的size()不為0,返回的指針指向string對應的字符串的第一個字符,大小為size()個;如果string的size()為0,返回一個非null的指針,該指針不能被解引用。 對於c_str() data()函數,返回的指針指向的內容都是const的,內容都是由string本身擁有,不可修改其內容。其原因是許多string實現的時候采用了引用機制,也就是說,有可能幾個string使用同一個字符存儲空間。而且你不能使用sizeof(string)來查看其大小。

  盡量去使用操作符,這樣可以讓程序更加易懂(特別是那些腳本程序員也可以看懂)。

  find()函數都返回一個size_type類型,這個返回值一般都是所找到字符串的位置,如果沒有找到,則返回string::npos。有一點需要特別注意,所有和string::npos的比較一定要用string::size_type來使用,不要直接使用int或者unsigned int等類型。

#.sort 建議

  默認的都是從小到大排序

  1.若需對vector, string, deque, 或 array 容器進行全排序,你可選擇sort或stable_sort;

  2.若只需對vector, string, deque, 或 array 容器中取得top n的元素,部分排序partial_sort是首選.   如:前5個:partial_sort(vect.begin(), vect.begin()+5, vect.end());

  3.若對於vector, string, deque 或 array 容器,你需要找到第n個位置的元素或者你需要得到top n且不關系top n中的內部順序,nth_element是最理想的。   如:找排在第5的位置 nth_element(vect.begin(), vect.begin()+4, vect.end()); //注意是begin()+4

  partial_sort()和nth_element()都把5個最小的放在前面

  4.若你需要從標准序列容器或者array中把滿足某個條件或者不滿足某個條件的元素分開,你最好使用partition或stable_partition。   如:student exam("pass", 60); stable_partition(vect.begin(), vect.end(), bind2nd(less<student>(), exam));

  5.若使用的list容器,你可以直接使用partition和stable_partition算法,你可以使用list::sort代替sort和stable_sort排序。若你需要得到partial_sort或nth_element的排序效果,你必須間接使用。

  6.sort, stable_sort, partial_sort, 和nth_element算法都需要以隨機迭代器(random access iterators)為參數,因此這些算法能只能用於vector, string, deque, 和array等容器,對於標准的關聯容器map、set、multimap、multiset等,這些算法就有必要用了,這些容器本身的比較函數使得容器內所有元素一直都是有序排列的。

  7.對於容器list,看似可以用這些排序算法,其實也是不可用的(其iterator的類型並不是隨機迭代器),不過在需要的時候可以使用list自帶的排序函數sort(有趣的是list::sort函數和一個“穩定”排序函數的效果一樣)。 對一個list容器使用partial_sort或nth_element,只能間接使用: 方法(1):把list中的元素拷貝到帶有隨機迭代器的容器中,然后再使用這些算法; 方法(2):生成一個包含list::iterator的容器,直接對容器內的list::iterator進行排序,然后通過list::iterator得到所指的元素; 方法(3):借助一個包含iterator的有序容器,然后反復把list中的元素連接到你想要鏈接的位置。

  list成員函數的行為和它們兄弟的行為經常不同。如想從容器刪除對象,調用remove,remove_if和unique算法后,必須接着調用erase才能真正刪除對象,但list的remove,remove_if和unique真的刪除掉了對象。sort算法不能用於list,但list可以調用自己的sort成員函數。

  8.partition和stable_partition與sort、stable_sort、partial_sort和nth_element不同,它們只需要雙向迭代器。因此可以在任何標准序列迭代器上使用partition和stable_partition

  9.時間和空間復雜度: stable_sort > sort > partial_sort > nth_element > stable_partition > partition

#.刪除容器中的特定值

  1.如果容器是vector string deque,使用erase和remove的慣用法:          

  c.erase(remove(c.begin(),c.end(),value),c.end())   remove並不能“真的”刪除元素,因為它做不到,如果你真的要刪除元素,要在remove上接上erase

  2.如果容器是list,使用list::remove (ls.remove(value))

  3.如果容器是關聯容器,用erase刪除容器中滿足一個特定條件的值:c.erase(c.begin())  

  1.如果容器是vector string deque,使用erase和remove_if的慣用法

  2.如果容器是list,使用list::remove_if

  3.如果容器是關聯容器,用remove_copy_if和swap,或寫一個循環遍歷容器元素,把迭代器傳給erase時后置遞增它。

循環遍歷刪除容器中的值:

//順序容器循環遍歷容器元素刪除值:
for(vector<int>::iterator i=c.begin(); i!=c.end(); )
    if( condition(*i) ) i=c.erase(i);
    else ++i;

//關聯容器循環遍歷容器元素刪除值:關聯容器的erase(i)會返回刪除元素的個數,這里只關心刪除的值
for(map<int,int>::iterator i=c.begin(); i!=c.end(); )
    if( condition(*i) ) c.erase(i++);
    else ++i;

#.迭代器失效

vector:

1.當插入(push_back)一個元素后,end操作返回的迭代器肯定失效。

2.當插入(push_back)一個元素后,capacity返回值與沒有插入元素之前相比有改變,則需要重新加載整個容器,此時begin和end操作返回的迭代器都會失效。

3.當進行刪除操作(erase,pop_back)后,指向刪除點的迭代器失效;指向刪除點后面的元素的迭代器也將全部失效。

deque迭代器的失效情況:

1.在deque容器首部(push_front)或者尾部(push_back)插入元素不會使得任何迭代器失效。

2.在其首部(pop_front)或尾部(pop_back)刪除元素則只會使指向被刪除元素的迭代器失效。

3.在deque容器的任何其他位置的插入(insert)和刪除(erase)操作將使指向該容器元素的所有迭代器失效。

list/set/map

1.刪除時,指向該刪除節點的迭代器失效

迭代器使用注意事項:

1.resize 操作可能會使迭代器失效。在 vector 或 deque 容器上做 resize 操作有可能會使其所有的迭代器都失效。

2.不要存儲 end 操作返回的迭代器。添加或刪除 deque 或 vector 容器內的元素都會導致存儲的end迭代器失效。

3.使用越界的下標,或調用空容器的 front 或 back 函數,都會導致程序出現嚴重的錯誤。

4.賦值和 assign 操作使左操作數容器的所有迭代器失效。swap 操作則不會使迭代器失效。完成 swap 操作后,盡管被交換的元素已經存放在另一容器中,但迭代器仍然指向相同的元素。

5.由於 assign 操作首先刪除容器中原來存儲的所有元素,因此,傳遞給 assign 函數的迭代器不能指向調用該函數的容器內的元素。

6.只適用於vector和deque容器的迭代器操作:iter + n、iter - n、iter1 += iter2、>, >=, <, <=

7.list 容器的迭代器既不支持算術運算(加法或減法),也不支持關系運算(<=, <, >=, >),它只提供前置和后置的自增、自減運算以及相等(不等)運算。

#.使用“交換技巧”來休整過剩容量

  class Contestant{....}
  vector<Contestant> v;
  ....  //是v變大然后刪除部分
  vector<Contestant>(v).swap(v); //在v上“收縮到合適”(v.size()=v.capacity())
  vector<Contestant>( ).swap(v); //清除v而且最小化它的容量

  string s;
  .....     //是s變大然后刪除部分
  string(s).swap(s); //在s上“收縮到合適”
  string( ).swap(s); //清除s而且最小化它的容量

#.為指針的關聯容器指定比較類型(不是比較函數)

set<string*> ssp; //建立一個指針的關聯容器,容器會以指針的值排序,所以輸出時候string不會按字典順序

struct StringLess: public binary_function<const string*, const string*, bool>
{
    bool operator()(const string* ps1,const string* ps2) const
    {
        return *ps1 < *ps2;
    }
};

typedef set<string*,StringLess> StringPtrSet;
StringPtrSet ssp;

或者:

struct DereferenceLess
{
    template<typename PtrType>
    bool operator()(PtrType p1, PtrType p2) const
    {
        return *p1 < *p2;
    }
};

set<string*,DereferenceLess> ssp;

如果有一個智能指針或迭代器的關聯容器,也得為它指定“比較類型”

#.equal_range的用法

 typedef vector<string>::iterator vit; 
 typedef pair<vit,vit> vitpair;
 sort(svec.begin(),svec.end());
 vitpair vp = equal_range(svec.begin(),svec.end(),"aaa");
 if(vp.first != vp.second)
    cout<<"\nFind the string\n";
 else cout<<" Not find the string\n";
 if(vp.first != vp.second)
    cout<<"There are "<<distance(vp.first,vp.second)<<" aaa \n";

#.使iterator(i)指向const iterator(ci)

   typedef vector<int>::iterator Iter;
   typedef vector<int>::const_iterator ConstIter;
   從iterator到const iterator沒有隱式類型轉換,也不能用Iter i(const_cast<Iter>(ci));因為iterator(i)和const iterator(ci)是兩種完全不同的類型
   可以用以下技巧:
   advance(i,distance<ConstIter>(i,ci)); //使i指向ci指向的元素
   建議:盡量用iterator代替const_iterator和reverse_iterator

#.只能操作有序數據的算法

  搜索算法:  binary_search,lower_bound,upper_bound,equal_range,   set_union,set_intersection,set_difference,set_symmetric_difference   merge, inplace_merge includes

  一般用於有序區間:unique,unique_copy  

#.保證用於算法的比較函數和用於排序的一致

  sort(v.begin(),v.end(),greater<int>())
  //bool it = binary_search(v.begin(),v.end(),5); //error 默認的是升序,會導致未定義的行為
  bool it = binary_search(v.begin(),v.end(),5,greater<int>());

#.用accumulate和for_each來統計區間

  #include<numeric>   //包含accumulate
  double sum = accumulate(v.begin(),v.end(),0.0);

#.ptr_fun(),mem_fun()和mem_fun_ref()的用法

  ptr_fun()可以把指向一個函數的指針轉化為一個函數對象。函數必須是一元或是二元函數(不能為無參的函數)
  transform(begin,end,dest,not1(ptr_fun(fun)));

  mem_fun 與 mem_fun_ref 是為了使 STL 算法可以將成員函數(member functions)當作參數而加入的,如下:
  list<Widget *> lpw;
  for_each(lpw.begin(), lpw.end(),mem_fun(&Widget::test)); // pw->test();

  vector<Widget> vw;
  for_each(vw.begin(), vw.end(),mem_fun_ref(&Widget::test)); // w.test();
  mem_fun_ref的作用和用法跟mem_fun一樣,唯一的不同就是:當容器中存放的是對象實體的時候用mem_fun_ref,當容器中存放的是對象的指針的時候用mem_fun。

#.避免對 set 及 multiset 的key進行原地修改

  這里的“鍵部分(key part)”指的是 set/multiset 存儲的對象 T 中對 set/multiset 的排序算法有影響的部分,或者說是參與排序的部分。比如下例中 User::ID:

class User {
public:
    unsigned int ID;
    string name;
    unsinged int age;
    const string& gettitle() const;
    void settitle(string& title);  
};

class UserIDLess : public binary_function<User, User, bool> {
public:
    operator() (const User &lhs, const User &rhs) const {
       return lhs.ID < rhs.ID;
    }
};
typedef set<User, UserIDLess> IDUserSet;

修改 set/multiset 中的對象時,注意不要改變對象的key(對set/multiset的排序算法有影響的部分),否則容器會被破壞。
如果必須改變鍵部分,采取如下策略:
1. 找到要修改的對象。i = set.find();
2. 復制對象。        User b(*i);
3. 修改復制的對象。  b.changeSome();
4. 刪除原對象。      set.erase(i++); //自增這個迭代器,保持它有效
5. 插入復制的對象。  set.insert(i,b);

#.copy_if的正確實現(STL里沒有copy_if)

#include <iostream>
#include <vector>
#include <functional>
#include <iterator>
#include <algorithm>
using namespace std;

template<typename InputIterator,typename OutputIterator,typename Predicate>
OutputIterator copy_if(InputIterator begin,InputIterator end,OutputIterator destBegin,Predicate p)
{
    while(begin!=end)
    {
        if(p(*begin)) *destBegin++=*begin;
        ++begin;
    }
    return destBegin;
}

int main()
{
    int a[]={0,3,2,1,5,4,6,8,7,9};
    vector<int> v(a,a+sizeof(a)/sizeof(int));
    cout<<"\nOutput the number greater than 4:\n";
    copy_if(v.begin(),v.end(),ostream_iterator<int>(cout," "),bind2nd(greater<int>(),4));
    cout<<"\nOutput the number greater than 4:\n";
    //STl里沒有copy_of,但有 remove_copy_of
    remove_copy_if(v.begin(),v.end(),ostream_iterator<int>(cout," "),bind2nd(less_equal<int>(),4)); 

    return 0;
}

#.使仿函數類可適配

ptr_fun可以使四個標准函數適配器(not1、not2、bind1st和bind2nd)存在某些typedef,提供這些必要的typedef的函數對象稱為可適配的。

operator()帶一個實參的仿函數類,要繼承的結構是std::unary_function。operator()帶有兩個實參的仿函數類,要繼承的結構是std::binary_function。

unary_function和binary_function是模板,所以你不能直接繼承它們。取而代之的是,你必須從它們產生的類繼承,而那就需要你指定一些類型實參,對於unary_function,你必須指定的是由你的仿函數類的operator()所帶的參數的類型和它的返回類型。對於binary_function,你要指定三個類型:你的operator的第一個和第二個參數的類型,和你的operator地返回類型。

兩個的例子:

template<typename T>
class MeetsThreshold: public unary_function<Widget, bool>
{
private:
    const T threshold;

public:
    MeetsThreshold(const T& threshold);
    bool operator()(const Widget&) const;
    ...
};

struct WidgetNameCompare:public binary_function<Widget, Widget, bool>
{
    bool operator()(const Widget& lhs, const Widget& rhs) const;
};

一般來說,傳給unary_function或binary_function的非指針類型都去掉了const和引用。

當operator()的參數是指針時這個規則變了。這里有一個和WidgetNameCompare相似的結構,但這個使用Widget*指針:

struct PtrWidgetNameCompare:public binary_function<const Widget*, const Widget*, bool> 
{
    bool operator()(const Widget* lhs, const Widget* rhs) const;
};

#.其他

當用某個模板類型下的成員前面需加上typename,但是不能用class更不能用struct
typename vector<T>::iterator it = v.begin();

用empty()來代替檢查size()是否為0

給map添加一個元素時,insert比operator[]效率高

更新已經在map的元素時,operator[]效率高

盡量用算法代替手寫循環

盡量用成員函數代替同名算法

C++中重載[]要兩個版本來滿足需要:

char & String::operator[](int n)
{
    return data[n];
}

const char & String::operator[](int n) const
{
    return data[n];
}

 

 


免責聲明!

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



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