应用标准库时一些常见的小问题
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() 开始都没得到链表节点。