【C++】《C++标准程序库》小结第六章(容器)

来源:互联网 发布:东北师大网络教育 编辑:程序博客网 时间:2024/05/01 07:18

总论

1、容器中的元素,必须具备以下条件:

a)             可以复制,有复制构造、operator=函数(这两个标准上都要求必须写)。因为容器添加的元素是复制的副本,这时候改变任何一个,都不会影响另外一个,故auto_ptr是不可用的。有时候为了节约成本,或者就是希望改变容器元素来相应改变原来的数据,可以放入原始数据的指针,不过一个巨大的问题就是很容易发生资源泄露,所以Effective C++强烈推荐用智能指针代替,如在VS2008sp1版本中使用vector<std::tr1::shared_ptr<Widget>>,注意为了兼顾旧标准,后面两个> 号之间打上空格;C++11标准则不需要添加tr1前缀,两个>号不需要刻意打空格。

b)            可以通过assign之类的赋值函数,在初始化的时候就完成构造。

c)             能够正常析构,不能抛出异常,否则发生资源泄露。

 

2、请注意如果容器如果放的是指针或者引用,不要出现循环引用。由于指针可以拷贝,最好不要复制指针,不然数据被谁改了你都不知道。

 

3、vector内部由于是连续的,故可以这样用: memset(&vec[0],0, sizeof(type) * vec.size())。编译不会报错,也是有用的,不过安全性堪忧……你觉得没问题就这样做吧,真正的大牛可不认同。

 

4、STL的设计原则是尽量不抛出异常。正常使用情况下,STL不会发生资源泄露。

 

5、经试验,64位环境下面,vector::max_size()远远超过了40亿,可以说支持2的64次方个数据。这说明返回的内部通用字符是_int64而不是unsignedint。

 

6、swap有很好的优化性能,且原则上不允许抛出异常,防止资源泄露。在STLport这个第三方STL库中,对vector容器进行swap只需要交换内部指针。copy-and-swap技术就是看中了这点,先拷贝一个临时变量,然后swap到目标变量,节约了从临时变量复制数据到目标变量,然后再销毁临时变量这两步操作。

 

7、vector:对于vector赋值方式中,assign的速度是最快的,其次是resize以后用copy算法赋值,而最先能够想到的赋值操作符operator=,速度却并不快,只能够排名第三,目前还不知道这是为什么,采用插入迭代器再用copy的方式是速度最慢的一种。

list:对于list赋值,赋值操作符operator=的速度是最快的,其次是assign,然后是采用resize的copy,最后一位同样是采用插入迭代子方式的copy。

 

8、外部的remove等函数,并不能能删除容器元素。只有容器本身的erase函数才能删除自身的元素!!!这是STL的约定,外部函数不能过多干涉容器,只有容器自身的函数能对自身修改负责,为了安全与设计考虑。

vector

1、内存绝对连续,你可以直接当成数组。

2、绝大多数情况下,如果没有特殊考虑,果断使用vector。

3、vector::push_back()函数添加元素很高效,使用了“分期摊还”技术,大多数情况即使使用了reserve或者resize空间,性能也没很大提升,直接用push_back()就行了。当有大量数据要push_back时,先进行vector::reserve()会在一定程度上提高效率。

4、clear()可以清空所有元素。但是即使clear(),所占用的内存空间依然还在,可以使用第5条的方法销毁残留内存。如果要求空间能够动态缩小,可以考虑使用deque。

5、在《effective STL》和其实很多C++文章中都有指明,用clear()无法保证内存回收。但是swap技法可以。具体方法如下所示:

vector<int>nums;

nums.push_back(1);nums.push_back(1);nums.push_back(2);nums.push_back(2);

vector<int>().swap(nums);//或者nums.swap(vector<int>())

swap技法就是通过交换函数swap(),使得vector离开其自身的作用域,从而强制释放vector所占的内存空间。

 

6、上面是回收空间,这个是缩减预留空间,当然效率肯定是低了。

{

std::vector<T> tmp(v);

v.swap(tmp);

}//大括号用来析构tmp

 

//或者

vector<T>(v).swap(v)

 

7、很多改变了容器(不是元素)的操作会使得迭代器失效,有些不会。不过不管怎样,都当做迭代器失效是最安全的做法。

8、at函数有异常检测,[]就没有,不过速度快。

9、vector<bool>是一个失败的代理类设计模式,一般不要用,效率低。bitset可以用,不过是定长的。详见配接器一章。


Deque

1、deque是顺序容器,但是是由多个连续内存块组成,之间通过智能指针连接起来的。从接口表现上Deque好像是连续存储的,但是实际不是的,所以vector可以对内部数据指针直接使用memset,但是deque就不行了。

2、deque性能直逼vector,但是总体性能还是不如vector。除非需要对头尾部都使用pop操作,还是使用vector比较好。

3、队列用deque是绝配。先进先出。

4、deque的内存分配很大,但是析构很慢。另外由于是不连续区块,所以删除一定元素后可以直接释放一块内存,内存占用自动缩减,而vector则不能。vector要做到缩减内存,见上一节。所以内存敏感的地方用deque比较好。

5、尽管是不连续区块,对中间元素进行添加、删除仍然很慢。

List

1、list是双向链表,当然你可以自己做个配接器,在list基础上做个环状链表。

2、不支持随机存取,迭代器只能一步一步走,寻址很慢。大量元素存取最好还是不用list。

3、list中间元素的添加、删除很快。list一般用在这个地方。

4、list可以做成广义表,排序也比较快。

5、对list操作,调用其自身的成员函数是最快的,比如list.sort()比sort(list.begin(),list.end())快的多。同样,要真正删除元素也要调用自身的函数。

6、由于不支持随机读取,只能用迭代器进行数据操作。

 

Set和Multiset

1、两个都在<set>里定义。定义set和mutiset,要加入排序准则函数,因此set的元素必须要能够排序!

2、你无法改变元素的值,只能删除它,然后加入新元素。因此set的迭代器都是const的,无法改变值。

3、set和multiset没有提供元素直接存取的函数。

4、他们的典型操作函数有count,find,lower_bound,upper_bound,equal_range(这个返回一个pair数据)。

5、set和multiset的insert函数返回一个pair,first成员返回新元素位置,second成员返回成功与否。

6、调用自身的函数更快,比如find。

 

Map和MultiMap

1、都在<map>里面定义。同样需要传入排序函数,只针对key值排序。

2、请关注pair数据部分,这是map的基础。

3、他们最特别的地方在于寻址:map[key]->value。key可以是任意数据结构。

4、元素的value可以改变,但是key是不能改变,只能删除。

5、你有三种方法建立map元素,例子:

a)             coll.insert(std::map<std::string,float>::value_type(“otto”, 22.3f));

b)            coll.insert(std::pair<std::string,float>:(“otto”, 22.3f));

c)             coll.insert(make_pair(“otto”,22.3f));

 

6、这段代码coll[“hello”] = 1;可以两个解释:一,修改“hello”的value元素值,二,添加一个(“hello”,1)的元素。

7、上面的特性导致一个问题,比如代码cout<< coll[“hello”];如果没有“hello”的元素,他会自动创建一个,而不是告诉你没有这个值!!

8、同样,用自身的函数最快。

9、multimap的一个key可以有多个value,不能单纯的删除key,一般来说只能用迭代器遍历然后删除。

 

其他容器

1、string,这个后面专门有一章来讲。

2、Array是一个可能没有实现的STL组件,不过你可以自己实现,很简单。

3、哈希表很有用,vs2008 sp1的tr1标准和C++11标准里自带哈希表结构:hash_map,hash_set。

 

说明

这些总结没有说明各大容器的详细情况、内部函数,而是专注于一些特殊情况,以及一些书上没有的东西。具体操作请自己查询。


1 0
原创粉丝点击