《C++标准程序库》第六章摘录与笔记

来源:互联网 发布:星际淘宝网第488章 编辑:程序博客网 时间:2024/05/18 11:25

《C++标准程序库》第六章摘录与笔记

6.1 容器的共通能力和共通操作

6.1.1 容器的共通能力

1、所有容器提供的都是“value语意”而非“reference语意”。容器进行的元素的安插操作时,内部实施的是拷贝操作,置于容器内。一次STL容器的每一个元素都必须能够被拷贝。如果你打算存放的对象不具有public copy构造函数,或者你要的不是副本(如你要的是被多个容器共同容纳的元素),那么容器元素就只能是指针(指向对象)。
2、总体而言,所有元素形成一个次序(order)。
3、一般而言,各项操作并非绝对安全。调用者必须确保传给操作函数的参数符合需求。违反这些需求(如使用非法索引)会导致未定义的行为。

6.1.2 容器的共通操作

初始化
每个容器型别都提供了一个default构造函数,一个copy构造函数和一个析构函数。
1、以另一个容器的元素为初值,完成初始化操作:ContType c(beg, end);
2、以某个数组的元素作为初值,完成初始化操作:ContType c(arr, arr+sizeof(arr)/sizeof(arr[0]));
3、以标准输入装置完成初始化操作:ContType c((std::istream_iterator<Type>(std::cin)), (std::istream_iterator<Type>()));注意,不要遗漏涵括“初始化参数”的那对“多余的”括号,否者这个表达式会将c视为一个函数,产生歧义。
与大小相关的操作函数
所有容器都提供了三个和大小相关的操作函数:
1、size()
2、empty()
3、max_size(),返回容器所能容纳的最大元素数量。通常返回索引型别的最大值。
比较
包括常用的比较操作符==, !=, <, <=, >, >=。它们的定义依据以下原则:
1、比较操作的两个容器必须属于同一型别。
2、如果两个容器的所有元素次序相等,那么这两个容器相等。
3、采用字典序顺序比较原则来判断某个容器是否小于另一个容器。
赋值和swap()
当你对容器赋值元素时,源容器的所有元素被拷贝到目标容器内,后者原来的所有元素全被移除。所以,容器的赋值操作代价比较昂贵。
如果两个容器型别相同,而且拷贝后源容器不再被使用,那么我们可以使用一个简单的优化方法:swap()。swap()的性能比上述优异的多,因为它只交换容器的内部数据。事实上,他只交换某些内部指针,所以时间复杂度是“常数”,不像实际赋值操作的复杂度为“线性”。

6.2 Vectors

6.2.3 将Vectors当做一般Arrays使用

对于vector v中任意一个合法索引i,以下表达式肯定为true:&v[i] = &v[0] + i; 所以,任何地点只要你需要一个动态数组,你就可以使用vector。只要你需要一个元素为T的数组,就可以采用vector<T>,然后传递第一个元素的地址给它。注意千万不要把迭代器当做第一个元素的地址来传递。vector迭代器是由实作版本定义的,也许并不是个一般指针。
std::vector<char> v;v.resize(41); // make room for 41 characters(including '\0')strcpy(&v[0], "hello, world");printf("%s\n", &v[0]);

6.2.5 Vectors运用实例

int main(){vector<string> sentence;sentence.reserve(5);cout << "max_size(): " << sentence.max_size() << endl;cout << "size(): " << sentence.size() << endl;cout << "capacity(): " << sentence.capacity() << endl;sentence.push_back("Hello, ");sentence.push_back("how");sentence.push_back("are");sentence.push_back("you");sentence.push_back("?");copy(sentence.begin(), sentence.end(), ostream_iterator<string>(cout, " "));cout << endl;cout << "max_size(): " << sentence.max_size() << endl;cout << "size(): " << sentence.size() << endl;cout << "capacity(): " << sentence.capacity() << endl;swap(sentence[1], sentence[3]);sentence.insert(find(sentence.begin(), sentence.end(), "?"), "always");sentence.back() = "!";copy(sentence.begin(), sentence.end(), ostream_iterator<string>(cout, " "));cout << endl;cout << "max_size(): " << sentence.max_size() << endl;cout << "size(): " << sentence.size() << endl;cout << "capacity(): " << sentence.capacity() << endl;return 0;}


6.2.6 Class vector<bool>

C++标准程序库专门针对元素型别为bool的vector设计了一个特殊版本,目的是获取一个优化的vector。其耗用空间远远小于一般的vector实作出来的bool vector。一般vector的实作版本会为每个元素至少分配一个byte空间,而vector特殊版本内部只用一个bit来存储一个元素。所以通常小8倍之多。不过由于C++的最小可寻址值通常以byte为单位,所以上述的vector特殊版本需针对references和iterators作特殊考虑。

6.3 Deques

与vector相似,也采用动态数组来管理元素,提供随机存取,有着和vector几乎相同的接口。不同的是deque的动态数组首尾都开放,因此能在头尾两端进行快速安插和删除。为了获取这种能力,deque通常实作为一组独立区块,第一区块朝某方向扩展,最后一个区块朝另一个方向扩展。和vector一样只要元素型别具有assignable和copyable就都可以胜任。

6.3.1 Deques的能力

与vectors相比,deques功能上的不同在于:
1、两端都能快速安插元素和移除元素(vector只在尾端快速)。这些操作可以在分期摊还的常数时间内完成。
2、存取元素时,deque的内部结构会多一个间接过程,所以元素的存取和迭代器的动作会稍稍慢一些。
3、迭代器需要在不同区块间跳转,所以必须是特殊的智能型指针,非一般指针。
4、在对内存区块有所限制的系统中,deque可以包含更多的元素,因为它使用不知一块内存。因此deque的max_size()可能更大。
5、deque不支持对容量和内存重分配时机的控制。不过deque的内存重分配优于vectors,因为其内部结构显示,deques不必再内存重分配时复制所有元素。
6、deque的内存区块不再被使用时,会被释放。deque的内存大小是可缩减的。不过这由实作版本定义。
与vector相似特性:
1、中段部分安插、移除元素的速度相对较慢,因为所有元素都需移动以腾出或填补空间。
2、迭代器属于随机存取迭代器。
总之,以下情形,最好使用deque:
1、你需要在两端安插和移除元素。
2、无需引用容器内的元素。
3、要求容器释放不再使用的元素(不过,标准规格没有保证这点)。

6.3.2 Deques的操作函数

与vectors不同点:
1、deques不提供容量操作(capacity()和reserve())。
2、deques直接提供函数,用以完成头部元素的安插和移除。

6.4 Lists

Lists使用一个双向链表来管理元素。任何型别只要具备assignable和copyable两性质,就可以作为list的元素。

6.4.1 Lists的能力

Lists的内部结构和vector和deque截然不同,与二者明显区别:
1、Lists不支持随机存取。在list中随机遍历任意元素都是一个很缓慢的行为。
2、任何位置上执行元素的安插和移除都非常快,始终是常数时间内完成,因为无需移动任何其他元素。实际上内部只是进行了一些指针操作而已。
3、安插和删除动作并不会造成指向其他元素的各个pointers、references、iterators失效。
4、Lists对于异常有着这样的处理方式:要么操作成功,要么什么都不发生。
5、由于不支持随机存取,lists既不提供下标操作符,也不提供at()。
6、Lists并未提供容量、空间重新分配等操作函数,因为全无必要。每个元素都有自己的内存,在被删除之前一直有效。
7、Lists提供了不少特殊的成员函数,专门用于移动元素。较之同名的STL通用算法,这些函数执行起来更快,因为它们无需拷贝或移动,只需调整若干指针即可。

6.4.2 Lists的操作函数

lists不支持随机存取,只有front()和back()能够直接存取元素。
只有运用迭代器,才能够存取list中的元素。list不能随机存取,迭代器只是双向迭代器。所以凡是用到随机存取迭代器的算法(所有用来操作元素顺序的算法——特别是排序算法——都归此类)你都不能调用。不过你可以拿list的特殊成员函数sort()取而代之。
为了移除元素,lists特别配备了remove()算法的特别版本。这些成员函数比remove()算法的速度快,因为它们只进行内部指针操作,无需顾忌元素。所以,面对list,你应该调用成员函数remove(),而不是像面对vectors和deques那样调用STL算法。
Lists提供很多成员函数来利用链表性质提高执行效率。如splice(),还有reverse(),sort(),merge()等。
void printLists(const list<int>& l1, const list<int>& l2){cout << "list1: ";copy(l1.begin(), l1.end(), ostream_iterator<int>(cout, " "));cout << endl << "list2: ";copy(l2.begin(), l2.end(), ostream_iterator<int>(cout, " "));cout << endl << endl;}int main(){// create two empty listslist<int> list1, list2;// fill both list with elementsfor (int i = 0; i < 6; ++i){list1.push_back(i);list2.push_front(i);}printLists(list1, list2);// insert all elements of list before the first element with value 3 of list2// -- find() returns an iterator to the first element with value 3list2.splice(find(list2.begin(), list2.end(), 3), list1);printLists(list1, list2);// list1 is empty now // if (list1.empty())// {// cout << "list1 is empty." << endl;// }// move first element to the endlist2.splice(list2.end(), list2, list2.begin());printLists(list1, list2);// sort second list, assign to list1 and remove duplicateslist2.sort();list1 = list2;list2.unique();printLists(list1, list2);// merge both sorted lists into the first listlist1.merge(list2);// sorted to sorted!printLists(list1, list2);// list2 is empty now// if (list2.empty())// {// cout << "list2 is empty." << endl;// }list1.reverse();list2 = list1;list2.reverse();printLists(list1, list2);list1.merge(list2); // if list1 and/or list2 is not sorted, merge.printLists(list1, list2);return 0;}


6.5 Sets和Multisets

set和multiset会根据特定的排序准则,自动将元素排序。两者不同在于multiset允许重复而set不允许。
只要是assignable、copyable、comparable(根据某个排序准则)的型别T,都可以成为set或multiset的元素型别。没有传入特别排序准则,就采用缺省准则less(这是一个仿函数,以operator<对元素进行比较,一般完成排序)。对于“排序准则”,必须是“反对称的”,必须是“可传递的”,必须是“非自反的”(x<x永远为假),所以排序准则可以用于相等性检验。

6.5.1 Sets和Multisets的能力

和所有标准关联式容器类似,sets和multisets同样以平衡二叉树完成。(事实上,sets和multisets通常以红黑树实作而成。红黑树在改变元素数量和元素搜寻方面都很出色,它保证节点安插时最多只会作两个重新连接动作,而且到达某个元素的最长路径深度,最多只是最短路径深度的两倍)不过这都是STL实现时考虑的内容。
自动排序造成sets和multisets的一个重要限制:你不能直接改变元素值,因为这样会打乱原本正确的顺序。因此,要改变元素值,必须先删除旧元素,再插入新元素。这里提供的接口正反映了这种行为:
1、sets和multisets不提供用来直接存取元素的任何操作函数。
2、通过迭代器进行元素间接存取,有一个限制:从迭代器的角度来看,元素值是常数。
有两种方式可以定义排序准则:
1、 以template参数定义之。这种情况下,排序准则是型别的一部分。因此型别系统确保“只有排序准则相同的容器才能被合并”。这是排序准则的通常指定法。为了产生他,容器构造函数会调用“排序准则型别”的default构造函数。
std::set<int, std::greater<int> > coll;
2、 以构造函数参数定义之。这种情况下,同一个型别可以运用不同的排序准则,而排序准则的初始值或状态也可以不同。如果执行期才获得排序准则,而且需要用到不同的排序准则(但数据型别必须相同),此一方式可派上用场。此时需要一个“用来表现排序准则”的特殊类型,使你能够在执行期间传递某个准则。
执行期间指定排序准则:
// type for sorting criteriontemplate <class T>class RuntimeCmp{public:enum cmp_mode {normal, reverse};private:cmp_mode mode;public:// constructor for sorting criterion// -- default criterion uses value normalRuntimeCmp(cmp_mode m = normal) : mode(m) {}// comparision of elementsbool operator()(const T& t1, const T& t2) const{return mode == normal ? t1 < t2 : t2 < t1;}// comparision of sorting criteriabool operator==(const RuntimeCmp& rc){return mode == rc.mode;}};// type of a set thar uses this sorting criteriontypedef set<int, RuntimeCmp<int> > IntSet;// forward declarationvoid fill(IntSet& set);int main(){// create, fill, and print set with normal element order// -- uses default sorting criterionIntSet coll1;fill(coll1);PrintElements(coll1, "coll1: ");// create sorting criterion with reverse element orderRuntimeCmp<int> reverse_order(RuntimeCmp<int>::reverse);// create, fill, and print set with reverse element orderIntSet coll2(reverse_order);fill(coll2);PrintElements(coll2, "coll2: ");// assign elements and sorting criterioncoll1 = coll2;coll1.insert(3);PrintElements(coll1, "coll2: ");// just to make sure...if (coll1.value_comp() == coll2.value_comp()){cout << "coll1 and coll2 have same sotring criterion" << endl;}else{cout << "coll1 and coll2 have different sorting criterion" << endl;}return 0;}void fill(IntSet& set){// fill insert elements in random orderset.insert(4);set.insert(7);set.insert(5);set.insert(1);set.insert(6);set.insert(2);set.insert(5);}
coll1和coll2拥有相同型别。assignment操作符不仅赋值了元素,也赋值了排序准则。

6.5.2 Sets和Multisets的操作函数

特殊的搜寻函数
sets和multisets在元素快速搜寻方面有优化设计,所以提供了特殊的搜寻函数。这些函数时同名的STL算法的特殊版本。面对sets和multisets,你应该优先采用这些优化算法,如此可获得对数复杂度,而非STL算法的线性复杂度。
迭代器相关函数
sets和multisets不提供元素直接存取,所以只能采用的迭代器。对迭代器操作而言,所有元素都被视为常数,这可确保你不会人为改变元素值,从而打乱既定顺序。然而这也使得你无法对sets和multisets元素调用任何变动性算法。如你不能对它们调用remove()算法,因为remove()算法实际上是以一个参数值覆盖被移除的元素。如果要移除sets和multisets的元素,你只能使用它们所提供的成员函数,如erase()、clear()成员函数。
元素的安插和移除
要删除“与某值相等”的元素,只需调用erase()。和lists不同的是,erase()并非取名为remove()。是的,它的行为不同,他返回被删除元素的个数,用在set身上,返回值非0即1。如果multisets内含重复元素,你不能使用erase()来删除这些重复元素中的第一个。可以先find再erase(成员函数)。
注意还有一个返回值不一致的情况。
1. 序列式容器提供的erase()成员函数:
iterator erase(iterator pos);iterator erase(iterator beg, iterator end);

2. 关联式容器提供下面的erase()成员函数:
void erase(iterator pos);void erase(iterator beg, iterator end);
这种差别完全为了性能。在关联式容器中“搜寻某元素并返回后继元素”可能颇为耗时,因为这种容器的底部是以二叉树完成,所以如果你想编写对所有容器都适合的程序代码,你必须忽略返回值。

6.6 Maps和Multimaps

Map和Multimap的元素型别Key和T,必须满足一些两个条件:
1、key/value必须具备assignable和copyable性质。
2、对排序准则而言,key必须是comparable。

6.6.1 Maps和Multimaps的能力

和所有标准的关联式容器一样,maps/multimaps通常以平衡二叉树完成。不过标准规范并未明定这一点。典型情况下,set,multisets,map,multimaps使用相同的内部数据结构。因此你可以吧set和multisets分别视为特殊的map和multimaps,只不过sets元素的value和key是指同一个对象。因此map和multimaps拥有set和multisets的所有能力和所有操作函数。当然某些细微差异还是有的:首先他们的元素时key/value pair,其次,map可作为关联式数组来运用。
map和multimaps根据元素的key自动对元素进行排序。所以根据key搜寻某个元素较根据value搜寻元素时效率要高。“自动排序”使得map和multimaps身上有了一条重要的限制:你不可以直接改变元素的key,因为这会破坏正确次序。要修改元素的key,你必须先移除拥有该key的元素,然后插入拥有新的key/value的元素。从迭代器的观点来看,元素的key是常数。至于元素的value倒是可以直接修改的,当然前提是value并非常数型态。
maps提供了一种非常方便的方法让你改变元素的key。只需:
// insert new element with value of old elementcoll["new_key"] = coll["old_key"];// remove old elementcoll.erase("old_key");

6.6.2 Maps和Multimaps的操作函数

与set和multisets类似,map和multimaps也有两种方式可以定义排序准则:
1、以template参数定义。2、以构造函数参数定义。
比较动作只能用于型别相同的容器。容器的key、value、排序准则都必须有相同的型别,否则编译期会产生型别方面的错误。
搜寻动作也是提供的特殊函数,以便利用内部树状结构获取较好的性能。搜寻动作的操作的是元素的key而不是value,如果搜寻value则必须改用通用算法如find_if(),或自己写一个显示循环。
元素的安插和移除
安插一个key/value pair的时候,一定要记住,在map和multimaps内部,key被视为常数。你要不提供正确型别,要不就得提供隐式或显示型别转换。有三个不同的方法可以将value传入map:
1、运用value_type
为了避免隐式型别转换,你可以利用value_type明白传递正确型别。value_type是容器本身提供的型别定义。(typedef pair<const Key, Type> value_type;)(value_type is declared to be pair <const key_type, mapped_type> and not simply pair <key_type, mapped_type> because the keys of an associative container may not be changed using a nonconstant iterator or reference.)
map<string, float> coll;coll.insert(map<string, float>::value_type("otto", 22.3));
2、运用pair<>
map<string, float> coll;// use implicit conversioncoll.insert(pair<string, float>("otto", 22.3));// use no implicit conversioncoll.insert(pair<const string, float>("otto", 22.3));
3、运用make_pair()
最方便的方法是运用make_pair()函数。这个函数根据传入的两个参数构造出一个pair对象:
map<string, float> coll;coll.insert(make_pair("otto", 22.3));
如果要移除“拥有某个value”的元素,调用erase()即可办到。如果multimap内含重复元素,你不能使用erase()来删除这些重复元素中的第一个。你可以这么做
typedef multimap<string, float> StringFloatMMap;StringFloatMMap coll;...// remove first element with passed keyStringFloatMMap::iterator pos;pos = coll.find(key);// do not use STL algorithm find(), as it is slowif (pos != coll.end()){coll.erase(pos);}
然而你不能使用成员函数find()来移除“拥有某个value(而非某个key)”的元素。
移除“迭代器所指元素”的正确作法:(对pos所指元素实施erase(),会使pos不再成为一个有效的coll迭代器,之后的++pos或者其他未对pos重新设值就改变pos会导致未定义的行为)
typedef map<string, float> StringFloatMap;StringFloatMap coll;StringFloatMap::iterator pos;...// remove all elements having a certain valuefor (pos = coll.begin; pos != coll.end();){if (pos->second == value){// coll.erase(pos); // Runtime error! if ++poscoll.erase(pos++);}else{++pos;}}
注意,pos++会将pos移向下一个元素,但返回其原始值的一个副本。因此,当erase()被调用,pos已经不再指向那个即将被移除的元素了。

6.6.3 将Maps视为关联式数组

通常,关联式容器并不提供元素的直接存取,你必须依靠迭代器。不过maps是个例外。Non-const maps提供下标操作符,支持元素的直接存取。不过下标操作符的索引值并非元素整数位置,而是元素的key。也就是说,索引可以是任意型别,而非局限为整数型别。这种接口正是我们所说的关联式数组。
和一般数组之间的区别还不仅仅在于索引型别。其他的区别包括:你不可能用上一个错误索引。如果你使用某个key作为索引,而容器之中尚未存在对应元素,那么就会自动安插该元素。新元素的value由default构造函数构造。如果元素的value型别没有提供构造函数,你就没这个福分了。再次提醒,所有基本数据型别都提供有default构造函数,以零位初值。
关联式数组的行为方式可说是毁誉参半:
优点:你可以透过更方便的接口对着map安插新元素。coll["otto"] = 7.7; 先安插(如无该key元素)返回引用再赋值
缺点:你可能不小心设置新元素。只要出现类似coll["otto"],如果无该key元素就会安插新元素。
注意这种方式的安插比一般的maps安插方式慢。(先default构造函数将value初始化,而这个初值马上被真正的value给覆盖了)
重命名一个key,可以先将原来元素的value赋值给该新key(利用索引方式),然后移除原来的元素。

6.6.5 Maps和Multimaps运用实例

将Map当做关联式数组
int main(){    /* create map / associative array     * - keys are strings     * - values are floats     */    typedef map<string,float> StringFloatMap;    StringFloatMap stocks;      // create empty container    // insert some elements    stocks["BASF"] = 369.50;    stocks["VW"] = 413.50;    stocks["Daimler"] = 819.00;    stocks["BMW"] = 834.00;    stocks["Siemens"] = 842.20;    // print all elements    StringFloatMap::iterator pos;    for (pos = stocks.begin(); pos != stocks.end(); ++pos) {        cout << "stock: " << pos->first << "\t"             << "price: " << pos->second << endl;    }    cout << endl;    // boom (all prices doubled)    for (pos = stocks.begin(); pos != stocks.end(); ++pos) {        pos->second *= 2;    }    // print all elements    for (pos = stocks.begin(); pos != stocks.end(); ++pos) {        cout << "stock: " << pos->first << "\t"             << "price: " << pos->second << endl;    }    cout << endl;    /* rename key from "VW" to "Volkswagen"     * - only provided by exchanging element     */    stocks["Volkswagen"] = stocks["VW"];    stocks.erase("VW");    // print all elements    for (pos = stocks.begin(); pos != stocks.end(); ++pos) {        cout << "stock: " << pos->first << "\t"             << "price: " << pos->second << endl;    }}
将Multimap当做字典
int main(){    // define multimap type as string/string dictionary    typedef multimap<string,string> StrStrMMap;    // create empty dictionary    StrStrMMap dict;    // insert some elements in random order    dict.insert(make_pair("day","Tag"));    dict.insert(make_pair("strange","fremd"));    dict.insert(make_pair("car","Auto"));    dict.insert(make_pair("smart","elegant"));    dict.insert(make_pair("trait","Merkmal"));    dict.insert(make_pair("strange","seltsam"));    dict.insert(make_pair("smart","raffiniert"));    dict.insert(make_pair("smart","klug"));    dict.insert(make_pair("clever","raffiniert"));    // print all elements    StrStrMMap::iterator pos;    cout.setf (ios::left, ios::adjustfield);    cout << ' ' << setw(10) << "english "         << "german " << endl;    cout << setfill('-') << setw(20) << ""         << setfill(' ') << endl;    for (pos = dict.begin(); pos != dict.end(); ++pos) {        cout << ' ' << setw(10) << pos->first.c_str()             << pos->second << endl;    }    cout << endl;    // print all values for key "smart"    string word("smart");    cout << word << ": " << endl;    for (pos = dict.lower_bound(word);         pos != dict.upper_bound(word); ++pos) {            cout << "    " << pos->second << endl;    }    // print all keys for value "raffiniert"    word = ("raffiniert");    cout << word << ": " << endl;    for (pos = dict.begin(); pos != dict.end(); ++pos) {        if (pos->second == word) {            cout << "    " << pos->first << endl;        }    }}
搜寻具有某特定实值(values)的元素
/* function object to check the value of a map element */template <class K, class V>class value_equals {  private:    V value;  public:    // constructor (initialize value to compare with)    value_equals (const V& v)     : value(v) {    }    // comparison    bool operator() (pair<const K, V> elem) {        return elem.second == value;    }};int main(){    typedef map<float,float> FloatFloatMap;    FloatFloatMap coll;    FloatFloatMap::iterator pos;    // fill container    coll[1]=7;    coll[2]=4;    coll[3]=2;    coll[4]=3;    coll[5]=6;    coll[6]=1;    coll[7]=3;    // search an element with key 3.0    pos = coll.find(3.0);                     // logarithmic complexity    if (pos != coll.end()) {        cout << pos->first << ": "             << pos->second << endl;    }    // search an element with value 3.0    pos = find_if(coll.begin(),coll.end(),    // linear complexity                  value_equals<float,float>(3.0));    if (pos != coll.end()) {        cout << pos->first << ": "             << pos->second << endl;    }}
map中的find()成员函数搜索的是key,也可以用STL算法来搜索key但效率不高。搜索value则不能使用find()成员函数。
运用Maps,Strings并于执行期指定排序准则
/* function object to compare strings * - allows you to set the comparison criterion at runtime * - allows you to compare case insensitive */class RuntimeStringCmp {  public:    // constants for the comparison criterion    enum cmp_mode {normal, nocase};  private:    // actual comparison mode    const cmp_mode mode;    // auxiliary function to compare case insensitive    static bool nocase_compare (char c1, char c2)    {        return toupper(c1) < toupper(c2);    }  public:      // constructor: initializes the comparison criterion    RuntimeStringCmp (cmp_mode m=normal) : mode(m) {    }    // the comparison    bool operator() (const string& s1, const string& s2) const {        if (mode == normal) {            return s1<s2;        }        else {            return lexicographical_compare (s1.begin(), s1.end(),                                            s2.begin(), s2.end(),                                            nocase_compare);        }    }};/* container type: * - map with *       - string keys *       - string values *       - the special comparison object type */typedef map<string,string,RuntimeStringCmp> StringStringMap;// function that fills and prints such containersvoid fillAndPrint(StringStringMap& coll);int main(){    // create a container with the default comparison criterion    StringStringMap coll1;    fillAndPrint(coll1);    // create an object for case-insensitive comparisons    RuntimeStringCmp ignorecase(RuntimeStringCmp::nocase);    // create a container with the case-insensitive comparisons criterion    StringStringMap coll2(ignorecase);    fillAndPrint(coll2);}void fillAndPrint(StringStringMap& coll){    // fill insert elements in random order    coll["Deutschland"] = "Germany";    coll["deutsch"] = "German";    coll["Haken"] = "snag";    coll["arbeiten"] = "work";    coll["Hund"] = "dog";    coll["gehen"] = "go";    coll["Unternehmen"] = "enterprise";    coll["unternehmen"] = "undertake";    coll["gehen"] = "walk";    coll["Bestatter"] = "undertaker";    // print elements    StringStringMap::iterator pos;    cout.setf(ios::left, ios::adjustfield);    for (pos=coll.begin(); pos!=coll.end(); ++pos) {        cout << setw(15) << pos->first.c_str() << " "             << pos->second << endl;    }    cout << endl;}
static bool nocase_compare()成员函数必须为static或者定义为全局函数。因为它要用于lexicographical_compar(),需要的是一个BinaryPredicate,如果为一般的成员函数由于有this指针,这样就有三个参数则会出错。
coll1和coll2的排序准则不同:
1、coll1使用一个型别为RuntimeStringCmp的缺省仿函数。这个仿函数以元素的operator<来执行比较操作。
2、coll2使用一个型别为RuntimeStringCmp的仿函数,并以nocase为初值。nocase会令这个仿函数以“大小写无关”模式来完成字符串的比较和排序。
这里使用multimap更合适,它是一个用来表现字典的典型容器。

6.7 其他SL容器

6.7.1 Strings可被视为一种STL容器

6.7.2 Arrays可被视为一种STL容器

可以直接直接运用数组,也可以外包装数组
// 部分头文件没写#include <cstddef>template <class T, std::size_t thesize>class carray{private:T v[thesize];// fixed-size array of elements of type Tpublic:// type definitiontypedef T value_type;typedef T*iterator;typedef const T*const_iterator;typedef T&reference;typedef const T&const_reference;typedef std::size_tsize_type;typedef std::ptrdiff_tdifference_type;// iterator supportiterator begin() { return v; }const_iterator begin() const { return v; }iterator end() { return v + thesize; }const_iterator end() const { return v + thesize; }// direct element accessreference operator[](std::size_t i) { return v[i]; }const_reference operator[](std::size_t i) const { return v[i]; }// size is constantsize_type size() const { return thesize; }size_type max_size() const { return thesize; }// conversion to ordinary arrayT* as_array() { return v; }};int main(){carray<int, 10> a;for (unsigned int i = 0; i < a.size(); ++i){a[i] = i + 1;}PrintElements(a);// defined before!reverse(a.begin(), a.end());PrintElements(a);transform(a.begin(), a.end(), a.begin(), negate<int>());PrintElements(a);return 0;}

6.8 动手实现Reference语义

通常,STL容器提供的“value语义”而非“reference语义”,后者在内部构造了元素副本,任何操作返回的也是这些副本。第五章中讨论过这种做法的优劣。总之,要在STL容器中用到“reference语义”(不论是因为元素的复制代价太大,或是因为需要在不同群集中共享同一元素),就要采用智能指针,避免可能的错误。这里有一个解决方法:对指针所指的对象采用reference counting智能型指针(即引用计数指针),与auto_ptr不同,引用计数指针一旦被复制,原指针和新的副本指针都是有效的。只有当指向同一个对象的最后一个智能型指针被摧毁,其所指对象才会被删除。
#ifndef COUNTER_PTR_H#define COUNTER_PTR_Htemplate <class T>class CounterPtr{private:T* ptr;long* count;public:explicit CounterPtr(T* p = 0) : ptr(p), count(new long(1)) {}CounterPtr(const CounterPtr<T>& p) throw() : ptr(p.ptr), count(p.count) { ++*count; } ~CounterPtr() throw() { dispose(); } CounterPtr<T>& operator=(const CounterPtr<T>& p) throw() { if (this != &p) { dispose(); ptr = p.ptr; count = p.count; ++*count; } return *this; } T& operator*() const throw() { return *ptr; } T* operator->() const throw() { return ptr; }private:void dispose(){if (--*count == 0){delete count;delete ptr;}}};#endif// COUNTER_PTR_H
 使用上面定义的引用计数指针
#include "countptr.h"void printCountedPtr(CounterPtr<int> elem){cout << *elem << ' ';}int main(){static int values[] = {3, 5, 9, 1, 6, 4};typedef CounterPtr<int> IntPtr;deque<IntPtr> coll1;list<IntPtr> coll2;for (int i = 0; i < sizeof(values)/sizeof(values[0]); ++i){IntPtr ptr(new int(values[i]));coll1.push_back(ptr);coll2.push_front(ptr);}for_each(coll1.begin(), coll1.end(), printCountedPtr);cout << endl;for_each(coll2.begin(), coll2.end(), printCountedPtr);cout << endl;*coll1[2] *= *coll1[2];(**coll1.begin()) *= -1;(**coll2.begin()) = 0;for_each(coll1.begin(), coll1.end(), printCountedPtr);cout << endl;for_each(coll2.begin(), coll2.end(), printCountedPtr);cout << endl;return 0;}

6.9 各种容器的运用时机


以下规则作为表6.33的补充:
1、缺省情况下应使用vector。vector的内部结构最简单,并允许随机存取,所以数据的存取十分方便灵活,数据的处理也足够快(可有效利用高速缓冲)。
2、如果经常要在序列的头部和尾部安插和移除元素,应该采用deque。如果你希望元素被移除时,容器能够自动缩减内存,那么你也应该采用deque。此外,由于vectors通常采用一个内存区块来存放元素,而deque采用多个区块,所以后者可包含更多元素。
3、如果需要经常在容器的中段执行元素的安插、移除和移动,可考虑使用list。List提供特殊的成员函数,可以在常数时间内将元素从A容器转移到B容器。但由于list不支持随机存取,所以如果只知道list的头部却要造访list的中段元素,性能会大打折扣。
和所有“以节点为基础”的容器相似,只要元素还是容器的一部分,list就不会令指向那些元素的迭代器失效。vectors则不然,一旦超过其容量,它的所有iterators、pointers、references都会失效;执行安插或移除操作时,也会令一部分iterators、pointers、references失效。置于deque,当它的大小改变,所有的iterators、pointers、references都会失效。
4、如果你要的容器是这种性质:“每次操作若不成功,变无效用”(并以此态度来处理异常),那么你应该选用list,或是采用关联式容器。
5、如果你经常需要根据某个准则来搜寻元素,那么应当使用“以该排序准则对元素进行排序”的set或multiset。hash table的搜寻速度更快,应考虑使用其进行元素搜寻,但是它的元素并未排序,所以如果元素必须排序,它用不上。
6、如想处理key/value pair,请采用map或multimap(可以的话请采用hash table)。
7、如果需要关联式数组,应采用map。
8、如果需要字典结构,应采用multimap。
有个问题比较棘手:如何根据两种不同的排序准则对元素进行排序?如存放元素时一种排序准则,而搜寻元素时使用另外一种排序准则。这时你可能需要两个sets或maps,格子拥有不同的排序准则,但共享相同的元素。(即使用前面的引用计数指针,容器中存放该类型的智能型指针)
关联式容器拥有自动排序能力,但并不意味它们在排序方面的执行效率更高。事实上由于关联式容器每安插一个新元素,都要进行一次排序,所以速度反而不及序列式容器经常采用的手法:先安插所有元素,然后调用排序算法进行一次完全排序。









原创粉丝点击