stl應用小問題

1. 編譯器的解析
list<int> data(istream_iterator<int>(cin),istream_iterator<int>());
這不是聲明一個list變量 data,而是被認爲是一格函數聲明. 可以使用如下方法(effective stl 有講)
istream_iterator<int> dataBeg(cin);
list<int> data(dataBeg,istream_iterator<int>());
當然還有 
stack<int,list<int>> sk;  >> 被解析爲操作符.當然這個容易避免,中間加個空格就解決了stack<int,list<int> > sk;

2 . front() 與 begin()
一般經常使用容器的begin() 函數,因爲常用 iterator 取得返回值,而front() 返回的是容器內第一個變量的引用.
vector<int> iv;
vector<int>::iterator it = v.begin(); 
vector<int>::reference ref = v.front();
對於一些api函數如fun(int *)需要的參數,做爲輸入參數  &*begin()寫起來總是怪異。&front()相對好一點,當然&v[0] 更自然。特別是如需從第3個元素開始。&v[3] 更是最佳選擇。
vc6 裏面vector的begin()返回的是原類型(如vector<int> 是 int )。所以可以寫 fun(begin())不會出錯, stl 源碼分析裏面講的 sgi stl 版本也是,但dev c++, vc2003 裏面都明確改成了對內部類iterator的返回 。所以任何時候不要用fun(begin()),儘管有時候他能工作

3. 名稱過長的警告
對於vc6使用stl(強烈建議換掉,巨多不標準,標準代碼不過的地方,如list 的sort 不能指定排序比較函數 bool comp(參數1,參數2) 這樣的函數) 纔有這種現象,產生過多得c4786警告,影響編譯速度,可以使用如下命令關閉此編譯選項
#pragma warning(disable:4786)

4. 字符串
字符串長度變量要用 string::size_type 聲明而不是int。基本每種容器都有這個typedef. 如需返回長度基本都是size_type類型。
判斷是否到字符串結尾與 string::npos比較,跟'/0'比較時char* 字符的行爲。

vc2003 的string並沒有使用引用計數(dev裏面使用了引用計數,好像vc6也用了), 這樣 string str1 = str2; 的操作與char* 的 strcopy 效率沒什麼區別。
但對於 char* s = "123"; string str = s;這樣的語句任何版本string 都不會使用引用計數,引用計數只有在同類之間賦值才存在
如 string s1 = "hello"; string s2 = s1;
如果使用引用計數 s1[1]='q'時系統要重新給s1分配內存。早分配還是晚分配區別並不明顯? 假設str2沒有使用,編譯將其優化掉,那都是隻分配一次內存。但沒有考慮引用計數 s1[1]='q' 操作,就可以省掉對引用計數的判斷。相反倒提升了速度。如果需要和s1相同指向的字符串,用引用就好了,string& s2=s1. 當然這種情況只是對字符串來說的

 
btw:
mfc 的CString 是採用引用計數的。c 字符串不像pascal 把字符串長度放在開頭。但CString 序列化到文件,是字符長度在前面的。提前知道字符串長度有利於優化

還有 char* p = "hello world"; p 指向的是常量字符串, p[1] = '1', strcpy(p,"12345") 都是錯誤的,經常有新人問,這裏提一下

5. map 插入元素,如果開始插入map中沒有的元素使用insert 函數比用 map[]=這樣賦值好一些,如果是hash_map這樣先查找元素,找不到才執行插入. 如:
    typedef map<string,string> m_type;
    typedef m_type::value_type valType;
    map< string, string > m;
    string h = "hello";
    string w = "world";
    m.insert( valType(h,w));

6. do while(0)
相信有人看到下面這樣的宏,其實這樣就強制你必須在應用宏時加上結束符;  這樣看起來纔像是一個函數
#define xxx()  do {xxx } while(0)

7. 初始化數組
int a[10];
memset(a,0,10*sizeof(int));
相信肯定有人這麼做過(int a[]={0,0,0,0...} 這麼幹的肯定也有),其實不必這麼麻煩 int a[10] = {0};這樣就ok 了。如果不是初始化的時候,而且不是賦0 ,還是老老實實用 fill(a,a+10,1); (其實對於char數組他調用的還是memset,不存在效率問題)
當然對於vector 容器定義時便可指定初始值 vector<int> v(10,1);

8. 警惕函數參數
很多函數都是有偏特化版本的,對於特殊參數保證效率或有不同處理方法,這時要警惕輸入參數。想使用特化版本,參數要保持一直。雖然好的編譯器在release版本可能幫你選中特化版本。但最好不要太依賴於編譯器。例如fill_n 版本
template<class _OutIt,class _Diff, class _Ty> inline
    void fill_n(_OutIt _First, _Diff _Count, const _Ty& _Val){ ..... }
inline void fill_n(char *_First, size_t _Count, int _Val)
    {  
        ::memset(_First, _Val, _Count);
    }
inline void fill_n(signed char *_First, size_t _Count, int _Val)
    {  
    ::memset(_First, _Val, _Count);
    }
inline void fill_n(unsigned char *_First, size_t _Count, int _Val)
    { 
    ::memset(_First, _Val, _Count);
    }
針對字符串選用memset進行賦值(能提升相當效率)。下面代碼:
char str[300];
fill_n(str,150,97);
看似調用了fill_n(char*,size_t,int) (第二個函數), 其實調用的還是模板函數(第一個)。因爲150 被編譯器解釋爲int 類型,與fill_n(char*,size_t,int) 是不相符合的。還有 fill_n(str,(size_t)150,'a') 也沒有調用優化版本,因爲'a' 是char 而不是int 類型。
所以fill_n(str,150*sizeof(char),97) 這種寫法纔算完美。
主要注意有 size_t 參數的函數, 用size_t 聲明或者多*個sizeof(type)

9. list 的 earse
對於vc2003 來說,判斷了如果是end節點 則不刪除。但sgi stl 沒有判斷。所以注意你在list 裏面執行erase操作時是否會刪掉這個節點。刪掉的話整個鏈表的基石也就垮了。可以在dev 編譯器(同gcc)裏面執行下面程序,這個循環從begin() 開始都沒得到鏈表節點。
 int a[5] ={2,3,5,4,1};
  list<int> lt(a,a+5);
  lt.erase(lt.end());  //明顯了點。
 
  list<int>::iterator it;
  for(it=lt.begin();it!=lt.end();it++)
  {
     cout<<*it<<" ";
  }
或許不能刪除end節點是使用者應該注意的。但看兩個庫的做法,即使牛人們也難以統一意見。個人意見是不用erase判斷是否爲頭節點,但如果去面試的話。不介意你"畫蛇添足"。
對於某些面試經常抓着這點不放,你的鏈表刪除怎麼沒判斷是否爲頭節點。你可以飛出一本stl源碼剖析,砸在他頭上,指着他的鼻子說: 你看人家C++標準庫都可以這麼做。

sgi stl 一般都沒有對這些邊界條件做判斷,沒人會使用list earse()去刪end節點。同樣如果沒有確定front()是否有元素, 也不要在deque上執行pop_front操作。

10 deque 的空間
vc 中deque跟vector相似,自增長,但deque有多個大小相同的緩衝區,使用空間只會增長不會降低。如果想刪除彈出節點空間應該使用list 。 而sgi stl 實現的deque (默認對於類型長度大於int)如果一個緩衝區沒有數據就刪除。對於pj stl 保證了速度,sgi stl 保證了空間。比較也沒有太大意義。

11 set 的比較函數對象
給set 指定比較函數對象時,重載operator() 要聲明成常量函數。如
class less_key{
public:
    bool operator() (const foo &f1, const foo &f2) const
    {
        return (f1.key < f2.key);
    }
};
因爲set 取得比較函數對象類型後,把該類的常量類型傳遞給了底層的rb_tree,所以不聲明operator()爲
常量函數,編譯便會失敗。而且這種關係比較函數對象並沒有改類內成員,所以任何時候最好都聲明爲 const .

12 vector <vector<int> >
一般對於原始類型聲明可變數組可以不指定長度如:
vector<int> iv;
iv.push_back(1);
但對於類型也是vector<vector<int> >
    vector<vector<int> > ivv;
    ivv[0].push_back(1);
    cout<<ivv[0][0]<<endl;
當執行時。呵呵出錯了。ivv[0] 還沒有初始化就調用當然會出錯。
    vector<vector<int> > ivv;
    ivv.push_back(vector<int>());
    ivv[0].push_back(1);
    cout<<ivv[0][0]<<endl;
如果先指定長度vector<vector<int> > ivv(10); 便可以初始化10個,畢竟使用2維數組,而且兩個維度都可變的情況不多。這樣
在這10個之內就不需要調用ivv.push_back(vector<int>());



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