stl应用常见问题

9/25/2006 12:26:25 PM

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 序列化到文件,是字符长度在前面的。提前知道字符串长度有利于优化
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));
注意一个隐私问题。如下一个map n 还没有元素,但使用n[0]就会插入一个元素,0 对应一个空字符串。这样会导致一些隐藏问题
    map<int,string> n;
    cout<<n[0]<<endl;

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判断是否为头节点,但如果去面试的话。不介意你"画蛇添足"。
sgi stl 一般都没有对这些边界条件做判断,没人会使用list earse()去删end节点。同样如果没有确定front()是否有元素, 也不要在deque上执行pop_front操作。
注意链表 erase 一个迭代器会导致迭代器丢失.必须重新获得
iter = list.erase(iter); 还要注意这个迭代器位置已经加了1.不能在for 循环中再 +1


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>());
 
13 resize 和 reserve 之区别
resize 扩展空间并且初始化元素,reserve 只扩展空间.对于 vector  调用 resize() 函数后。对 empty () 检测为 false. 而 reserve 后检查empty() 仍为 true. 由于visual assist 的帮助今天,一不小心就调用了resize() .结果白浪费好长时间调试。当然对于string,resize 和 reserve也是相同功能

posted on 2010-10-08 13:17  阿蒙1024  阅读(467)  评论(0编辑  收藏  举报

导航