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

《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 lists
	list<int> list1, list2;


	// fill both list with elements
	for (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 3
	list2.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 end
	list2.splice(list2.end(), list2, list2.begin());
	printLists(list1, list2);


	// sort second list, assign to list1 and remove duplicates
	list2.sort();
	list1 = list2;
	list2.unique();
	printLists(list1, list2);


	// merge both sorted lists into the first list
	list1.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 criterion
template <class T>
class RuntimeCmp
{
public:
	enum cmp_mode {normal, reverse};
private:
	cmp_mode mode;
public:
	// constructor for sorting criterion
	// -- default criterion uses value normal
	RuntimeCmp(cmp_mode m = normal) : mode(m) {}


	// comparision of elements
	bool operator()(const T& t1, const T& t2) const
	{
		return mode == normal ? t1 < t2 : t2 < t1;
	}


	// comparision of sorting criteria
	bool operator==(const RuntimeCmp& rc)
	{
		return mode == rc.mode;
	}
};


// type of a set thar uses this sorting criterion
typedef set<int, RuntimeCmp<int> > IntSet;


// forward declaration
void fill(IntSet& set);


int main()
{
	// create, fill, and print set with normal element order
	// -- uses default sorting criterion
	IntSet coll1;
	fill(coll1);
	PrintElements(coll1, "coll1: ");


	// create sorting criterion with reverse element order
	RuntimeCmp<int> reverse_order(RuntimeCmp<int>::reverse);


	// create, fill, and print set with reverse element order
	IntSet coll2(reverse_order);
	fill(coll2);
	PrintElements(coll2, "coll2: ");


	// assign elements and sorting criterion
	coll1 = 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 order
	set.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 element
coll["new_key"] = coll["old_key"];
// remove old element
coll.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 conversion
coll.insert(pair<string, float>("otto", 22.3));
// use no implicit conversion
coll.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 key
StringFloatMMap::iterator pos;
pos = coll.find(key);	// do not use STL algorithm find(), as it is slow
if (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 value
for (pos = coll.begin; pos != coll.end();)
{
	if (pos->second == value)
	{
		// coll.erase(pos); // Runtime error! if ++pos
		coll.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 containers
void 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 T


public:
	// type definition
	typedef T 				value_type;
	typedef T*				iterator;
	typedef const T*		const_iterator;
	typedef T&				reference;
	typedef const T&		const_reference;
	typedef std::size_t		size_type;
	typedef std::ptrdiff_t	difference_type;


	// iterator support
	iterator begin() { return v; }
	const_iterator begin() const { return v; }
	iterator end() { return v + thesize; }
	const_iterator end() const { return v + thesize; }


	// direct element access
	reference operator[](std::size_t i) { return v[i]; }
	const_reference operator[](std::size_t i) const { return v[i]; }


	// size is constant
	size_type size() const { return thesize; }
	size_type max_size() const { return thesize; }


	// conversion to ordinary array
	T* 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_H


template <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,格子拥有不同的排序准则,但共享相同的元素。(即使用前面的引用计数指针,容器中存放该类型的智能型指针)
关联式容器拥有自动排序能力,但并不意味它们在排序方面的执行效率更高。事实上由于关联式容器每安插一个新元素,都要进行一次排序,所以速度反而不及序列式容器经常采用的手法:先安插所有元素,然后调用排序算法进行一次完全排序。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值