C++ STL學習筆記

#.string 建議

  使用string 的方便性就不用再說了,這裏要重點強調的是string的安全性。
 
  string並不是萬能的,如果你在一個大工程中需要頻繁處理字符串,而且有可能是多線程,那麼你一定要慎重(當然,在多線程下你使用任何STL容器都要慎重)。

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

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

  string的data()函數返回的字符串指針不會以'\0'結束,千萬不可忽視。
對於c_str() data()函數,返回的數組都是由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;

//關聯容器循環遍歷容器元素刪除值:
for(map<int,int>::iterator i=c.begin(); i!=c.end(); )
    if( condition(*i) ) c.erase(i++);
    else ++i;
關聯容器的erase(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.刪除時,指向該刪除節點的迭代器失效

resize 操作可能會使迭代器失效。在 vector 或 deque 容器上做 resize 操作有可能會使其所有的迭代器都失效。
不要存儲 end 操作返回的迭代器。添加或刪除 deque 或 vector 容器內的元素都會導致存儲的end迭代器失效。
使用越界的下標,或調用空容器的 front 或 back 函數,都會導致程序出現嚴重的錯誤。
賦值和 assign 操作使左操作數容器的所有迭代器失效。swap 操作則不會使迭代器失效。完成 swap 操作後,儘管被交換的元素已經存放在另一容器中,但迭代器仍然指向相同的元素。
由於 assign 操作首先刪除容器中原來存儲的所有元素,因此,傳遞給 assign 函數的迭代器不能指向調用該函數的容器內的元素。

只適用於vector和deque容器的迭代器操作:iter + n、iter - n、iter1 += iter2、>, >=, <, <=
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;
};


#.其他

template <class T1, class T2, class T3 = A>裏面的class表示類型,不能用struct替換,但是可以用typename替換

當用某個模板類型下的成員前面需加上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 ch[n];
}
const char& String::operator[](int n) const
{
    return ch[n];
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章