阜城網(wǎng)站建設(shè)公司啟信聚客通網(wǎng)絡(luò)營銷策劃
文章目錄
- 使用STL庫的55條建議
- 1.慎重選擇容器的類型
- 2.不要試圖編寫?yīng)毩⒂谌萜鞯拇a
- 3.確定容器中的對象拷貝正確且高效
- 4.調(diào)用empty判斷是否為空,而不是size
- 5.區(qū)間成員函數(shù)優(yōu)于與之對應(yīng)單元素成員函數(shù)
- 6.如果容器中包含了通過new操作創(chuàng)建的指針,切記在容器對象析構(gòu)前,要將指針delete。
- 7.切勿創(chuàng)建包含auto_ptr的容器對象
- 8.慎重選擇刪除元素的方法
- 9.了解分配子allocator的約定和限制
- 10.切勿對STL容器的線程不安全性有不切實際的依賴
- 11.vector和string優(yōu)于動態(tài)分配的數(shù)組
- 12.使用reserve避免不必要的重新分配
- 13.注意string實現(xiàn)的多樣性
- 14.了解如何將string和vector 傳給舊的API
- 15.使用‘swap技巧’去除多余的內(nèi)存
- 16.避免使用vector<bool>
- 17,理解相等和等價的區(qū)別
- 18.為包含指針的關(guān)聯(lián)函數(shù)指定比較類型
- 19.總讓比較函數(shù)在相等的情況下返回false
- 20.切勿直接修改set 或者 multiset的鍵
- 21.考慮用排序vector代替關(guān)聯(lián)容器
- 22.當(dāng)效率至關(guān)重要,謹(jǐn)慎選擇opertor[] 和insert
- 23.使用distance 和advance將容器的const_iterator轉(zhuǎn)換為iterator
- 24.正確理解由reverse_iterator的base()成員函數(shù)所產(chǎn)生的iterator的用法
- 25.對于逐字符的輸入請考慮使用istreambuf_iterator
- 26.確保目標(biāo)區(qū)間足夠大
- 27.了解各種和排序有關(guān)的選擇
- 28.如果確實需要刪除元素,則需要在remove這一類算法之后調(diào)用erase
- 29.使用accumulate進行區(qū)間統(tǒng)計
- 30.遵循按值傳遞的原則來設(shè)計函數(shù)子類
- 31.確保判別式是純函數(shù)
- 32.若一個類是函數(shù)子,應(yīng)該讓它可配接
- 33.理解ptr_fun,mem_fun和mem_fun_ref的由來
- 34.算法調(diào)用優(yōu)于手寫的循環(huán)
- 35.容器的成員函數(shù)優(yōu)于同名算法
- 35.正確區(qū)分count,find,binary_search,lower_bound,upper_bound和equal_range
- 36.避免直寫型的代碼
- 37.總是包含(#include)正確的頭文件
- 36.避免直寫型的代碼
- 37.總是包含(#include)正確的頭文件
使用STL庫的55條建議
1.慎重選擇容器的類型
根據(jù)你的業(yè)務(wù)需求和算法去選擇不同的容器,將數(shù)據(jù)結(jié)構(gòu)和算法結(jié)合起來。
c++容器類型:
- 序列容器:其中對象有序排列,根據(jù)整數(shù)值進行索引,有vector,list,deque,string
- 關(guān)聯(lián)容器:其中對象的順序不重要,根據(jù)鍵值進行索引,set,map,multiset,multimap
- 適配器:調(diào)正原有容器的行為,使對外產(chǎn)生新的接口,類型和返回元素。
- 生成器:構(gòu)造元素序列
標(biāo)準(zhǔn)容器:指的是c++標(biāo)準(zhǔn)化后的容器,遵從c++的標(biāo)準(zhǔn),不同的編譯器都支持,代碼移植能力強。vector,list,deque,string,set,map,multiset,multimap
非標(biāo)準(zhǔn)容器:指的是違背標(biāo)準(zhǔn)化后的容器,在不同版本的編譯器上可能不支持,hash_set,hast_map等等
使用參考:
例如需要按順序插入元素就需要序列容器,如果元素的順序不重要就使用關(guān)聯(lián)容器,如果就頻繁刪除容器內(nèi)元素就避免使用內(nèi)存連續(xù)的容器
2.不要試圖編寫?yīng)毩⒂谌萜鞯拇a
因為不同的容器所支持的函數(shù)和迭代器類型使不同的,可能會報錯,所以不要編寫?yīng)毩⒂谌萜鞯拇a。
3.確定容器中的對象拷貝正確且高效
當(dāng)對vector,list,deque進行元素的插入或者刪除操作時,現(xiàn)有的元素的位置通常會被移動(拷貝)。使用排序,反轉(zhuǎn)也會被移動(拷貝)??截愂荢TL的工作方式。
當(dāng)給容器中存入對象時存入的是對象的拷貝,當(dāng)從容器中取出對象時,取出的容器中對象的拷貝。所以要確保拷貝的正確和高效。
當(dāng)你創(chuàng)建的時基類對象卻存著派生類對象時,拷貝就會剝離將派生的內(nèi)容剝離。如果想要從基類容器使用派生類的虛方法,就要指針。
而且拷貝指針。
由于是拷貝的,因此在最后析構(gòu)的時候容易出問題,因此最好采用智能指針。
4.調(diào)用empty判斷是否為空,而不是size
首先empty一般是內(nèi)聯(lián)函數(shù),而且empty的時間復(fù)雜度是常數(shù)。
size不是內(nèi)聯(lián)函數(shù),而且size的時間復(fù)雜度是線性的。
5.區(qū)間成員函數(shù)優(yōu)于與之對應(yīng)單元素成員函數(shù)
給定v1和v2兩個矢量,使v1的內(nèi)容等于v2后半段內(nèi)容相同。怎么實現(xiàn)。
//1.使用區(qū)間函數(shù)
v1.assign(v2.begin()+v2.size()/2,v2.end());
//2.使用copy函數(shù),內(nèi)部還是循環(huán)
v1.clear();
copy(v2.begin()+v2.size()/2,v2.end(),back_inserter(v1));
//3.使用insert,這樣v1是反著的,因此每次都插到v1前面
insert(v1.begin(),v2.begin()+v2.size()/2,v2.end());
//使用單元素方法
//1.使用賦值
v1.clear()
for(vector<int>::const_iterator ci=v2.begin()+v2.size()/2;ci!=v2.end();++ci){v2.push_back(ci);
}
從代碼上
- 使用區(qū)間函數(shù),可以少寫一些代碼
- 使用區(qū)間函數(shù),意圖更明確。
//將數(shù)組data內(nèi)的元素添加到向量v中
//使用單元素成員函數(shù)
vector<int>::iterator insertloc(v.begin());
for(int i=0;i<numValues;i++){insertloc=v.insert(insertloc,data[i]);++insertloc;
}
從效率上,調(diào)用單元素成員函數(shù)
- 不必要的函數(shù)調(diào)用,將v.insert()函數(shù)調(diào)用numValues遍
- 將v中已有元素頻繁的移動到插入元素的后面,每個元素移動numValues遍,如果插入元素為對象的化進行拷貝復(fù)制對象內(nèi)的參數(shù),需要的賦值次數(shù)更多。
- 如果容器為順序存儲的容器則可能會造成內(nèi)存的多次擴充,則會把舊內(nèi)存的元素拷貝進入新內(nèi)存,并將舊內(nèi)存的東西銷毀然后插入新內(nèi)存的東西。
對于vector 和string這三條都滿足,對于deque第三條不滿足。
對于list只有第一條滿足,但是單個插入list會導(dǎo)致list需要不斷變化pre 和next的指向,沒有必要。
6.如果容器中包含了通過new操作創(chuàng)建的指針,切記在容器對象析構(gòu)前,要將指針delete。
void dosomething(){
vector<widget *>vmp;
for(int i=0;i<N;++i)vwp.push_back(new wideget);
}
//當(dāng)vmp作用域結(jié)束后,它的全部元素會被析構(gòu),但是new出來的內(nèi)存沒有刪除,導(dǎo)致內(nèi)存泄漏//第一版本
void dosomething(){
vector<widget *>vmp;
for(int i=0;i<N;++i)vwp.push_back(new wideget);
}
...
for(vector<widget*>::iterator i=vmp.begin();i!=vmp.end();++i)delete i;
//當(dāng)時當(dāng)new 過程中出現(xiàn)異常,還是會導(dǎo)致內(nèi)存泄露//第二版本
struct DeleteObject{template<typename T>void operator()(const T *ptr){delete ptr;}
};
void dosomething(){typedef boost::shared_ptr<widget>spw;vector<spw>vmp;
for(int i=0;i<N;++i)vwp.push_back(spw(new wideget);
}
...
for_each(vmp.begin(),vmp.end(),DeleteObject());//將指針給智能指針,不怕發(fā)生異常內(nèi)存泄漏,然后采用for_each,區(qū)間比單元素需要要高
7.切勿創(chuàng)建包含auto_ptr的容器對象
首先auto_ptr的對象是被c++標(biāo)準(zhǔn)所禁止的,說明可以移植性不強。
上面提到,STL中采用的是拷貝的方法,auto_ptr對象進行拷貝時會將原來的對象所有權(quán)設(shè)置交給新對象,然后將原來對象所有權(quán)設(shè)為為NULL
例如在排序時,會將基準(zhǔn)元素拷貝給一個臨時對象,使容器中一個元素為null,臨時對象會在作用域結(jié)束后被銷毀。導(dǎo)致容器可能會失去一個或者多個對象。
8.慎重選擇刪除元素的方法
container<int>c;
- 對于容器c,如果想刪除值為1963的元素。對于不同容器應(yīng)該采用不同的方法。
對于連續(xù)容器例如 vector,deque,string
c.earse(remove(c.begin(),c.end(),1963),c.end());
對于list
c.remove(1963);
對于關(guān)聯(lián)容器采用
c.erase(1963);
- 當(dāng)要刪除判別式為 true的值
bool badValue(int);
對于連續(xù)容器例如 vector,deque,string
c.earse(remove_if(c.begin(),c.end(),badValue),c.end());
對于list
c.remove_if(badValue);
對于關(guān)聯(lián)容器,兩種方法
//方法一
AddocContainer<int>c;
...
AddocContainer<int>goodValues;
remove_copy_if(c.begin(),c.end(),inserter(goodValues,goodValues.end(),badValue);
//將不用刪除的元素放入goodValues容器中
//將兩容器元素交換
c.swap(goodVlues;)
//方法二
for(AddocContainer<int>::iterator i=c.begin();i!=c.end();){if(badValues(*i))c.earse(i++); //確保i被刪除后,有一個迭代器指向下一個元素。關(guān)聯(lián)容器不反悔元素else++i;
}
//對于 vector,deque,string,list,想要在為true時,寫日志for(AddocContainer<int>::iterator i=c.begin();i!=c.end();){if(badValues(*i)){logFile<<"earse "<<*i<<"/n";i =c.earse(i); }//確保i被刪除后,有一個迭代器指向下一個元素。序列元素返回之下下一個元素的迭代器else++i;
}
9.了解分配子allocator的約定和限制
- 你的分配子是一個模板,模板參數(shù)T代表你為它分配內(nèi)存對象的類型
- 提供類型定義pointer和reference,但始終讓pointer為T* ,讓reference 為T&
- 千萬不要讓你的分配子擁有隨對象而不同的狀態(tài)。通常分配子不應(yīng)該有非靜態(tài)的數(shù)據(jù)結(jié)構(gòu)
- 傳給分配子的allocate成員函數(shù)的是那些要求內(nèi)存的對象的個數(shù),new傳遞的是void 需要的字節(jié)個數(shù)。同時記住,allocate返回的T*指針,既然沒有T對象被構(gòu)造。*
- 一定也要提供嵌套的rebind模板,因為標(biāo)準(zhǔn)容器依賴該模板。
10.切勿對STL容器的線程不安全性有不切實際的依賴
STL不提供線程安全,需要自己寫互斥鎖
//定義lock類
template<tyname Container>
class Lock{
public:
Lock(const Container& container):c(container){getMutexFor(c);//創(chuàng)造互斥體
}
~Lock(){releaseMutexFor(c);//析構(gòu)互斥體
}
private:
const Container& c;
}//
vector<int>v;
...
{Lock<vector<int>>lock(v);vector<int>::iteator first5(find(v.begin(),v.end(),5));if(first5 !=v.begin())*first5=0;
}//代碼塊結(jié)束,互斥體被釋放
11.vector和string優(yōu)于動態(tài)分配的數(shù)組
vector和string可以實現(xiàn)自己釋放內(nèi)存,并且可以使用STL算法
但是在多線程情況下使用string,可能會造成性能下跌,不如使用vector,因為string采用的是引用計數(shù)。
12.使用reserve避免不必要的重新分配
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-C8FNfW2Q-1692172443245)(C:\Users\18440\AppData\Roaming\Typora\typora-user-images\image-20220911205652519.png)]
因為當(dāng)容器容量不滿足需求時,會進行容量擴充,重新申請一個是當(dāng)前容量數(shù)倍的內(nèi)存,把當(dāng)前內(nèi)存中元素移入然后將原來的內(nèi)存進行析構(gòu)。這個過程很浪費時間,因此兩種方法去避免。
- 當(dāng)知道需要預(yù)留的空間大小使,用reserve函數(shù)提前去開辟。
- 當(dāng)不知道時,開辟足夠大的,到最后在去除多余容量。
13.注意string實現(xiàn)的多樣性
STL對string的實現(xiàn)有不同的方式,每個方式都有不同的特點,詳情看第十五條
它們的區(qū)別主要是:
string的值可能會被引用,也可能不會被引用
string對象的大小范圍是char*的1倍到7倍
創(chuàng)建一個新字符串可能需要0次,1,2次動態(tài)分配內(nèi)存
string對象可能被共享,也可能不共享其大小和內(nèi)存信息
string可能支持,也可能不支持對單個對象的分配子
不同實現(xiàn)對內(nèi)存的最小分配有不同策略。
14.了解如何將string和vector 傳給舊的API
對于vector
//對于這個舊的API
void dosomething(const int* pInts,size_t numInts);
//vector 應(yīng)該傳遞
vector<int>v;
dosomething(&v[0], numInts);
//也有人說傳遞 dosomething(v.begin(), numInts);
//begin()返回是一個迭代器,事實上對于vector迭代器就是指針,但是事實上迭代器不是一個指針
對于string
//對于舊的API
void dosomething(const char *pstring)
//strng 應(yīng)該傳遞
string str;
dosomething(str.c_str())
//因為string不是連續(xù)存儲的,而且string不一定是以空字符結(jié)尾的。所以string內(nèi)部有個c_str,指向字符串值的指針
15.使用‘swap技巧’去除多余的內(nèi)存
vector<int>n(100,0);
//刪除一部分元素,只留下十個元素,但是容器的容量還是100
n.erase(b.begin()+10,n.end());
//為了去除多余的容量,使用swap
vector<int>(n).swap(n);
//通過vector<int>(n)創(chuàng)建一個隱式對象,這個對象的容量大小就是n實際包含元素的大小,然后與n進行交換,再將隱式對象析構(gòu)掉。消除對于容量。
//swap調(diào)用拷貝構(gòu)造函數(shù)一次,賦值函數(shù)兩次,將兩個對象指針進行調(diào)換。不僅容器的內(nèi)容就交換了,指針,引用都被交換了(string除外),但是相對來說還是在原來的容器中。//使用swap使內(nèi)存最小
vector<int>().swap(n);
string str="dada";
string ().swap(str);
16.避免使用vector
因為vector不滿足c++標(biāo)準(zhǔn):c如果是包含對象T的容器,則 T* p=&c[0]是可以通過編譯的。但是對于vector是不行的。因為,vector在內(nèi)部是一位一位存的,而不是按自己存儲,因為沒辦法創(chuàng)建指向單個位的指針
可以用deque替代和bitset替代(大小固定,不能插入元素)。
17,理解相等和等價的區(qū)別
等價不一定是相等的,相等肯定是等價的。
對于類的等價,取決于operator = =函數(shù)中的函數(shù)體。在關(guān)聯(lián)容器中,例如map,set這種順序存儲的容器,需要定義一個比較類去確定它們的順序。
struct CIstringCompare{
public:
binary_function<string,string,bool>
bool operator()(const string&lhs,const string&rhs )const{
return ciStringCompare(lhs,rhs);//忽略string 大小寫的比較
}
};
set<string,CIstringCompare)ciss;
18.為包含指針的關(guān)聯(lián)函數(shù)指定比較類型
set<string *>ssp;
ssp.insert(new string("Anteater"));
ssp.insert(new string("Wombat"));
ssp.insert(new string("Lemur"));
ssp.insert(new string("Penguin"));
for(set<string *>::iterator it;it!=ssp.end();it++){
cout<<*it<<endl; //打印的是地址 ,而且不一定是按照字符串順序打印的,因為set是根據(jù)地址大小進行排序的
}//因此需要寫一個比較類去進行比較
struct DereferenceLess{template<typename,PtrType>bool operator()(PtrType lhs,PtrType rhs){return *lhs<*rhs;}
}
set<string*,DereferenceLess>ssp;//這樣就按照字符串順序進行打印
19.總讓比較函數(shù)在相等的情況下返回false
//例如給set<int,less_equal<int>> s;
s.insert(10);
s.insert(10);
//這會導(dǎo)致容器中出現(xiàn)兩個10,違背了set的定義,因為在比較兩個10是否等價時采用
!(10<=10)&&!(10<=10) //false 不等價 所以導(dǎo)致存了兩個10
20.切勿直接修改set 或者 multiset的鍵
所謂的鍵就是進行比較等價的內(nèi)容,因為set是按順序存儲的所以不能直接修改。而map是修改不了,因為map存儲類型是pair<const k,v>。
//但是有的sTL不支持修改set 它返回迭代器類型為 const T&,想要修改就要類型轉(zhuǎn)化,但是存在風(fēng)險
//const_cast 指向原來的引用
const_cast<employee>(*it).setTitle("dasd");
//使用static_cast則不行,它會生成一個臨時隱匿對象,修改的是這個對象
static_cast<employee>(*it).setTitle("dasd");
//相當(dāng)于
(employee(*it)).setTitle("dasd");
安全的方法就是拷貝一份元素將其修改,然后刪除原有的,再將其添加。
21.考慮用排序vector代替關(guān)聯(lián)容器
當(dāng)你的數(shù)據(jù)類型滿足三個階段時,可以考慮用排序vector代替關(guān)聯(lián)容器
- 插入刪除階段,幾乎不查詢
- 查找階段,幾乎不查找
- 重組階段,將元素全部刪除再添加。
因為關(guān)聯(lián)容器底層為平衡二叉樹,一個節(jié)點?;顁oot,left,right三個地址,相比vector太空間。
22.當(dāng)效率至關(guān)重要,謹(jǐn)慎選擇opertor[] 和insert
map<int,widget>m
//在插入時使用operator[],相當(dāng)于先要構(gòu)造臨時對象然后析構(gòu)然后賦值
m[1]=1.50;
//相當(dāng)于
typedef map<int,widget>IntWidgetMap;
pair<IntWidgetMap::iterator,bool>result=m.insert(IntWidgetMap::value_type(1,widget()));
result.first->second=1.5;
//不如使用insert
m.insert(IntWidgetMap::value_type(1,50);//當(dāng)更新數(shù)據(jù)時
m[1]=51;
//用insert需要構(gòu)造,然后析構(gòu)然后賦值
m.insert(IntWidgetMap::value_type(k,v).first->second=v;
當(dāng)插入時用insert效率高,當(dāng)更新時用operator[]效率高。
23.使用distance 和advance將容器的const_iterator轉(zhuǎn)換為iterator
當(dāng)有的容器類的成員函數(shù)僅接受iterator作為參數(shù),const_iterator不能作為他們都參數(shù)。必須進行強制類型轉(zhuǎn)換。
typedef deque<int> IntDeque;
typedef IntDeque:: iterator Iter;
typedef IntDeque:: const_iterator ConstIter;
ConstIter ci;
Iter i(ci);
Iter i(const_cast<Iter>(ci));//const_iterator不能強制轉(zhuǎn)為iterator
//iterator 與const_iterator是兩個不同的類,它們之間的差距可能比string 與 double都遠
//但是對于vector 與string是可以通過的,因為它們的迭代器底層是char*//使用advance 與distance實現(xiàn)安全轉(zhuǎn)換 將ci 從const_iterator 轉(zhuǎn)為iterator
Iter i(d.begin());
advance(i,distance<ConstIter>(i,ci));//要加ConstIter 進行強制類型轉(zhuǎn)換
24.正確理解由reverse_iterator的base()成員函數(shù)所產(chǎn)生的iterator的用法
vector<int>v;
v.reserve(5);
for(int i=1;i<=5;++i){v.push_back(i);
}
vector<int>::reverse_iterator ri=find(v.rbegin(),v.rend(),3);
vector<int>::iterator i(ri.base());
執(zhí)行完上述代碼之后,該vector和相應(yīng)迭代器的狀態(tài)如下圖所示:
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GGivqON4-1692172443245)(C:\Users\18440\AppData\Roaming\Typora\typora-user-images\image-20220915221408689.png)]
如果在reverse_iterator ri指向的位置插入新元素,只需要在ri.base()位置處插入元素就行了。
但是reverse_iterator處刪除元素的話,則需要執(zhí)行v.((++ri).base());
25.對于逐字符的輸入請考慮使用istreambuf_iterator
//將一個文本文件拷貝到string中
ifstream inputFile("interestingData.txt");
//這段代碼沒有將空白字符加載進去,因為istream_iterator使用operator>>函數(shù)完成實際的讀操作,默認operator>>函數(shù)會跳過空白字符
//如果沒有消除skipws標(biāo)志,將默認忽略空白字符
inputFile.unsetf(ios::skipws);//消除sKipws標(biāo)志位
string fileData((istream_iterator<char>(inputFile)),istream_iterator<char>());//但是拷貝速度不快,因為每一個operator>>操作 需要執(zhí)行許多額外的操作
//推薦使用istreambuf_iterator,速度快40%,它是直接從緩沖區(qū)中讀取下一個字符
ifstream inputFile("intersetingData.txt");
string fileData((istreambuf_iterator<char>(inputFile)),istreambuf_iterator<char>());
#include<iostream>
#include<random>
#include<ctime>
#include<fstream>
#include<iterator>
using namespace std;int main(){ofstream outfile;outfile.open("file.txt",ios::app);srand(time(nullptr));for(int i=0;i<1000000;++i){outfile<<rand()%26+'a';}clock_t start,stop;ifstream inputFile("file.txt");inputFile.unsetf(ios::skipws);start=clock();string fileData1((istream_iterator<char>(inputFile)),istream_iterator<char>());stop=clock();cout<<"istream花費了"<<(stop-start)<<endl;start=clock();string fileData2((istreambuf_iterator<char>(inputFile)),istreambuf_iterator<char>());stop=clock();cout<<"istreambuf花費了"<<(stop-start)<<endl;return 0;
}
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-6UDM6W5Q-1692172443246)(C:\Users\18440\AppData\Roaming\Typora\typora-user-images\image-20220916223026949.png)]
26.確保目標(biāo)區(qū)間足夠大
//給一個容器后面插入元素
int transmogrify(int x);//該函數(shù)根據(jù)x生成一個新值
vector<int> values;
...//給values 里面插入新值
vector<int>results;
//給results.end()插入元素是錯誤的,因為*resultes.end()里面沒有元素
transform(values.begin(),values.end(),results.end(),transmogrify);
//應(yīng)該用back_inserter生成迭代器進行插入,返回的迭代器將被push_back調(diào)用
transform(values.begin(),values.end(),back_inserter(results),transmogrify);
//插在前面,被front_back調(diào)用 ,沒有front_back的容器不能使用,比如vector
transform(values.begin(),values.end(),front_inserter(results),transmogrify);
//如果擔(dān)心,需要重新分配空間,就需要使用 reserver
vector<int>results;
results.reverse(results.size()+values.size());
transform(values.begin(),values.end(),front_inserter(results),transmogrify);
27.了解各種和排序有關(guān)的選擇
- 如果需要對vector,string,deque或者數(shù)組中的元素執(zhí)行一次完全排序,那么可以使用sort或者stable_sort進行
- 如果有一個vector,string,deque或者數(shù)組,并且只需要對等價性最前面的n個元素進行排序,那么可以使用partial_sort
//將質(zhì)量最好的前n個放在widgets的最前面
partial_sort(widgets.begin(),widgets.begin()+n,widgets.end(),quealityCompare);
- 如果有一個vector,string,deque或者數(shù)組,并且只需要只需要找個第n個位置的元素,或者需要找到等價性最前面的n個元素但有不對這n個元素進行排序,那么可以使用nth_element
//找出質(zhì)量最好的前n個元素,不用對n個元素進行排序
nth_element(widgets.begin(),widgets.begin()+n-1,widgets.end(),quealityCompare);
//找到質(zhì)量中間的元素
vector<widget>::iterator it=nth_element(widgets.begin(),widgets.begin()+widgets.size()/2,widgets.end(),quealityCompare);
- 如果需要將一個標(biāo)準(zhǔn)序列容器的元素按照是否滿足某個特定的條件區(qū)分開,那么partition和stable_partition很合適
//找到第一個質(zhì)量比二級還差元素的位置
vector<widget>::iterator goodEnd=partition(widgets.begin(),widgets.end(),hasAcceptableQulity);
//返回第一個不滿足大于二級質(zhì)量條件的widget
-
如果你的數(shù)據(jù)在一個list中,仍然可以使用partition和stable_partition
-
排序算法的效率排序
partition,stable_partition,nth_element,partial_sort,sort,stable_sort.
28.如果確實需要刪除元素,則需要在remove這一類算法之后調(diào)用erase
//remove的參數(shù)是迭代器,并不知道容器的類型,無法調(diào)用容器的成員函數(shù) earse,所以用remove 容器中元素的數(shù)目并不會減少
template<<class ForwardIterator,class T>
ForwardIterator remove(ForwardIterator first,ForwardIterator last,const T&value);
//remove只是把不用刪除的元素移到區(qū)間的前部,它返回迭代器指向最后一個“不用刪除”的元素之后的元素。
remove(v.begin(),v.end(),99);
事實上并沒有將99的放在后面,后面還保留著舊值,用保留的值覆蓋掉要刪除的值
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OVd8Qqaz-1692172443246)(C:\Users\18440\AppData\Roaming\Typora\typora-user-images\image-20220917190200302.png)]
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FpdnjEKB-1692172443246)(C:\Users\18440\AppData\Roaming\Typora\typora-user-images\image-20220917190127261.png)]
//為了真的刪除空間中值,使用erase
v.erase(remove(v.begin(),v.end(),99),v.end());
//list的remove是和erase融合的
list<int>li;
li.remove(99);
remove對于包含指針的容器,再用在和erase進行連用時,記得先要釋放內(nèi)存,防止內(nèi)存泄漏。對于智能指針不用這樣做。
29.使用accumulate進行區(qū)間統(tǒng)計
//統(tǒng)計list中所有值的和
list<double> ld;
...//添加元素
double sum=accumulate(ld.begin(),id.end(),0.0);
//統(tǒng)計容器中所有字符串長度之和
string::size_type
StringLengthSum(string::size_type sumSoFar,const string& s){return sumFoFar+s.size();
}
set<string>ss;
string::size_type lengthSum=accumlate(ss.begin(),ss.end(),static_cast<string::size_type(0),stringLengthSum);//統(tǒng)計區(qū)間內(nèi)所有點的平均值
struct Point{Point(double initX,double initY):x(initX),y(initY){}double x,y;
}
class PointAverage:public binary_function<Point,Point,Point>{public:PointAverage():xSum(0),ySum(0),numPoints(0){}const Point operator()(const Point& avgSoFar,const Point &p){++numPoints;xSum+=p.x;ySUm+=p.y;return Point(xSum/numPoints,ySum/numPoints);}private:size_t numPoints;double xSum;double ySum;
}
Point avg=acculate(lp.begin(),lp.end(),Point(0,0),PointAverage());
30.遵循按值傳遞的原則來設(shè)計函數(shù)子類
對于多態(tài)的函數(shù)對象,不能使用虛函數(shù),因為參數(shù)類型是基類,而實參類型是派生類的,在傳遞的過程中會產(chǎn)生剝離問題:在對象拷貝的過程中,派生部分可能會被去掉,而僅保留基類部分。
函數(shù)對象在STL中作為參數(shù)或者返回時總是值傳遞,意味著第一:函數(shù)對象小巧,第二,函數(shù)對象是單態(tài)。
31.確保判別式是純函數(shù)
- 判別式:是一個返回值為bool的函數(shù)。在STL中,判別式有著廣泛的用途。標(biāo)準(zhǔn)關(guān)聯(lián)容器的比較函數(shù)就是判別式,對于find_if以及各種排序算法,判別式往往也被作為參數(shù)來傳遞。
- 純函數(shù):是指返回值僅僅依賴其參數(shù)的函數(shù)。
- 判別類:是一個函數(shù)的子類,它的operator()函數(shù)是一個判別式。
判別式是純函數(shù)的理由:STL傳遞是按值傳遞,所以如果依賴判別式依賴外界參數(shù)可能會導(dǎo)致出錯。
32.若一個類是函數(shù)子,應(yīng)該讓它可配接
list<widget*>widgetPtrs;//一個包含Widget對象指針的list容器
bool isInteresting(const Widget* pw);//判斷某個Widget指針?biāo)笇ο笫欠褡銐蛴腥?//找第一個有趣的widget
list<Widget *>::iterator i=find_if(widgetPtrs.begin().widgetPtrs.end(),isInteresting);
//如果想第一個不有趣的widget
list<Widget *>::iterator i=find_if(widgetPtrs.begin().widgetPtrs.end(),not1(isInteresting);//錯誤
list<Widget *>::iterator i=find_if(widgetPtrs.begin().widgetPtrs.end(),not1(ptr_fun(isInteresting));//正確//not1是函數(shù)配接器,ptr_fun的作用是完成類型定義
//只有提供必要類型定義的函數(shù)對象被稱為可被配接的函數(shù)對象
//提供這些類型定義最簡單的方式就是讓函數(shù)子從特定的基類繼承。
//函數(shù)子operator只有一個參數(shù)就從std::unary_function繼承
//函數(shù)子兩個參數(shù)就從std::binary_function繼承
//unary_function和binary_function是模板,不能直接繼承,需要提供類型實參
//operator一個參數(shù)繼承std::unary_function
template<typename T>
//提供operator第一個參數(shù)的類型和返回值類型
class MeetsThreshold:public std::unary_function<widget,bool>{
private:const T threshold;
public:bool operator()(const Widget&) const;
}//operator兩個參數(shù)的繼承
//當(dāng)函數(shù)子不需要任何私有成員
//提供operator第一個參數(shù)的類型,第二個參數(shù)的類型和返回值類型
//operator是帶引用和const而模板沒有,傳遞給unary_function,binary_function非指針類型需要去掉const 和 &
struct WidgetNameComapre:public std::binary_function<Widget,Widget,bool>{bool operator()(const Widgets& lhs,const widget&rhs)const;
}
//對于指針類型 則是可以
struct WidgetNameComapre:public std::binary_function<const Widget *,const Widget*,bool>{bool operator()(const Widgets* lhs,const widget* rhs)const;
}//類型定義后的子類可以直接只有函數(shù)配接器
list<widget>::reverse_iterator i=find_if(widgets.begin(),widgets.end(),not1(MeetsThreshold<int>()))s
33.理解ptr_fun,mem_fun和mem_fun_ref的由來
f(x);//f是一個非成員函數(shù)
x.f();//f是成員函數(shù),并且x是一個對象或者對象的引用
p->f();//f是成員函數(shù),并且p是一個指向?qū)ο髕的指針
void test(widget &w);
vector<widget>vw;
for_each(vw.begin(),vw.end(),test);//可以通過編譯
class widget{public:...void test();...
}
for_each(vw.begin(),vm.end(),&widget::test);//不能通過編譯
list<widget *>lpw;
for_each(lpw.begin(),plw.end(),&widget::test);//不能通過編譯
//原因 for_each算法是基于非成員函數(shù)實現(xiàn)的
//for_each算法的實現(xiàn)
template<typename InputIterator ,typename Function>
Function for_each(InputIterator begin,InputIterator end,Function f){while(begin!=end)f(*begin++);
}
//mem_fun,mem_fun_ref用來調(diào)整使能夠被成員函數(shù)使用
//mem_fun的定義
template<typename R,typename C>
mem_fun<R,C>;//C是類,R是指向成員函數(shù)的返回類型
mem_fun(R(C::*pmf)());
//mem_fun帶一個指向某個成員函數(shù)的指針參數(shù)pmf,并且返回一個mem_fun_t類型的對象。mem_fun_t是一個函數(shù)子類,它擁有該成員函數(shù)的指針,并提供operator函數(shù),在operator函數(shù)中調(diào)用通過參數(shù)傳遞進來的對象上的成員函數(shù)
for_each(lpw.begin(),lpw.end(),mem_fun(&widget::test));//可以通過編譯
34.算法調(diào)用優(yōu)于手寫的循環(huán)
- 效率:算法通常比程序員自己寫的循環(huán)效率高
- 正確性:自己寫的循環(huán)比使用算法更容易出錯
- 可維護性:使用算法的代碼通常比手寫循環(huán)的代碼更加簡潔明了。
如果要做的工作有算法實現(xiàn)就用算法。但是如果使用循環(huán)很簡單,而使用算法實現(xiàn)的話卻要求使用綁定器和配接器或者要求一個單獨的函數(shù)子類,就使用循環(huán)。
35.容器的成員函數(shù)優(yōu)于同名算法
- 成員函數(shù)往往速度很快
- 成員函數(shù)通常和容器結(jié)合的更加緊密
set<int>s;
... //插入一百萬個值
set<int>::iterator i=s.find(727);//以對數(shù)時間運行
if(i!+s.end())...
set<int>::iterator i=find(s.begin(),s.end(),727);//使用find算法 以線性時間運行
35.正確區(qū)分count,find,binary_search,lower_bound,upper_bound和equal_range
-
對于區(qū)間沒有排序的,應(yīng)當(dāng)使用count和find,它們是線性時間,使用相等性查找
vector<int>s;
count是找區(qū)間中存在某個特定值的個數(shù)
int num=count(s.begin,s.end(),5);//查找5在區(qū)間存在的個數(shù)
find 是找區(qū)間中第一個特定值的位置
int index=count(s.begin,s.end(),5);//查找第一個5的位置
-
對于區(qū)間排序的,可以使用binary_search,equal_range,lower_bound,對數(shù)時間,使用等價性查找
vector<widget>vm; ... wdiget w;
binary_search,只回答是否存在的問題
bool exist=binary_search(vm.begin(),vm.end(),w);//查看5是否存在
lower_bound,返回一個迭代器,如果存在返回第一份的拷貝,如果不存在返回適合于插入該值的位置
vector<widget>::iterator it=lower_bound(vm.begin(),vm.end(),w);
equal_range,返回一對迭代器,第一個迭代器等于lower_bound返回的迭代器,第二個迭代器等于upper_bound(指向區(qū)間內(nèi)最后一個等價元素的下一個位置)返回的迭代器
typedef vector<widget>::iterator VMIter; typedef pare<VMIter,VMIter> VMIterPair; VMIterPair p=equal_range(vm.begin(),vm.end(),w); //兩個迭代器之間的距離,即使原始區(qū)間查找等價對象的數(shù)目 if(p.first!=p.second){ //說明存在 } { //說明不存在,兩個迭代器都指向w的插入位置 }
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TvGCesVE-1692172443247)(C:\Users\18440\AppData\Roaming\Typora\typora-user-images\image-20220918171324230.png)]
36.避免直寫型的代碼
假如有一個vector,現(xiàn)在想刪除其中所有其值小于x的元素,但是,在最后一個其值不小于y的元素之前的所有元素應(yīng)該保留下來。
vector<int> v;
int x,y;
...
v.erase(remove_if(v.rbegin(),v.rend(),bind2nd(greater_equal<int>(),y)).base(),v.end(),bind2nd<less<int>>(),x)),v.end());
//可讀性太差,不利于后來者維護
//進行分解
typedef vector<int>::iterator VecIntIter;
//初始化rangeBegin,使它指向v中大于等于y的最后一個元素之后的元素
//如果不存在這樣的元素,則rangeBegin被初始化為v.bgein()
//如果這樣的元素正好是V最后一個元素,則rangeBegin被初始化為v.end()
VecIntIter rangeBegin=find_if(v.rbegin(),v.rend(),bind2nd(greater_equal<int>(),y)).base();
//從rangeBegin到v.end()的區(qū)間中刪除所有小于x的值
v.erase(remove_if(rangeBegin,v.end(),bin2nd(less<int>(),x)),v.end());
//bind2nd 將二元函數(shù)轉(zhuǎn)為一元函數(shù),因為函數(shù)內(nèi)一個參數(shù)已經(jīng)被確定
37.總是包含(#include)正確的頭文件
因為不同的平臺頭文件之間包含關(guān)系不通,因此任何時間如果你使用某個頭文件中的一個STL組件,一定要提供對應(yīng)的#include 指令
[外鏈圖片轉(zhuǎn)存中…(img-TvGCesVE-1692172443247)]
36.避免直寫型的代碼
假如有一個vector,現(xiàn)在想刪除其中所有其值小于x的元素,但是,在最后一個其值不小于y的元素之前的所有元素應(yīng)該保留下來。
vector<int> v;
int x,y;
...
v.erase(remove_if(v.rbegin(),v.rend(),bind2nd(greater_equal<int>(),y)).base(),v.end(),bind2nd<less<int>>(),x)),v.end());
//可讀性太差,不利于后來者維護
//進行分解
typedef vector<int>::iterator VecIntIter;
//初始化rangeBegin,使它指向v中大于等于y的最后一個元素之后的元素
//如果不存在這樣的元素,則rangeBegin被初始化為v.bgein()
//如果這樣的元素正好是V最后一個元素,則rangeBegin被初始化為v.end()
VecIntIter rangeBegin=find_if(v.rbegin(),v.rend(),bind2nd(greater_equal<int>(),y)).base();
//從rangeBegin到v.end()的區(qū)間中刪除所有小于x的值
v.erase(remove_if(rangeBegin,v.end(),bin2nd(less<int>(),x)),v.end());
//bind2nd 將二元函數(shù)轉(zhuǎn)為一元函數(shù),因為函數(shù)內(nèi)一個參數(shù)已經(jīng)被確定
37.總是包含(#include)正確的頭文件
因為不同的平臺頭文件之間包含關(guān)系不通,因此任何時間如果你使用某個頭文件中的一個STL組件,一定要提供對應(yīng)的#include 指令
在一個聲明為const的成員函數(shù)內(nèi)部,該類所有的非靜態(tài)數(shù)據(jù)成員都自動被轉(zhuǎn)換為相應(yīng)的const類型。