#.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]; }