《Generic Programming and the STL》读书笔记

Generic Programming and the STL

 

最后还需要把没搞懂的地方一一记下列出搞懂,争取一次把STL吃透,并在工作中学以致用!!别囫囵吞枣!

译序

作为一个广为人知的面向对象程序语言,C++的另一面-范型编程思维-被严重的忽略了。不能说面向对象思维和泛型思维有什么主从之分,但是红花绿叶相辅相成,肯定能对程序开发有更大的突破。
泛型思维在C++身上主要以templates及相关特性来表现。许多人以为template是多么高阶的技巧,望之俨然;接触后才发现,即之也温。更深入演戏泛型思维后又有另一层体会:其言也厉。
泛型编程和C++ templates和STL不该并为一谈。然以现今发展来看,三者又几乎被划上等号。
人们对于STL的最大误解是效率。事实上,STL提供的是一个不损及效率的抽象性。泛型编程和面向对象编程不同,它并不要求你通过额外的间接层来调用函数;它让你撰写完全一般化并可重复运用的算法(注意算法二字,即泛型编程主要是为算法所用,不要误解成泛型编程在什么地方都能用上,常用的算法已经被人写出来了,比如STL,需要我们自己去写算法的可能性不大,因为泛型意味着通用,通用的算法已经有了,我们关注的重点还是STL的使用上,我猜想),其效率和[针对特定数据类型而设计]的算法旗鼓相当。每一个算法、每一个容器的操作行为,其复杂度都有明确规范-通常是最佳效率或极佳效率。在接受规格书明定的复杂度之后,我想你不会认为自己亲手撰写的代码,能够更胜通过严格检验的世界通用程序库。
STL的严重缺点在于,它尚未支持persistence(对象持久性)。在良好的解决方案尚未开发出来之前,persistence必须由用户自行完成。
永远记住,面对新技术,程序员最大的困难在于心中的怯弱。不要和哈姆雷特犹豫不决,当你面对一个有用的技术时,应该果断。(很有道理,有时我就害怕去学新东西,但真正的学起来也没有想象的困难)
前言

也可能听过别人把面向对象与C++当同一件事来讲,但是这并非C++语言的唯一用途。基本上,C++支持多种不同的设计思维模式(paradigms),其中,最新也最鲜为人知的一项就是泛型编程(genericprogramming)。
STL是一种高效、泛型、可交互操作的软件组件;巨大,而且可以扩充。它包含计算机科学中的许多基本算法和数据结构,而它把算法和数据结构完全分离开来,互不耦合。STL不只是一个容器类程序库,更精确地说它是一个泛型算法库;容器的存在使这些算法有东西可以操作。
STL的设计具有扩充性;也就是说,你可以自行撰写一些组件,与STL组件交互作用,就像不同的STL组件之间可以交互作用一样。有效地运用STL,意味着对它进行扩充。
STL并非只是一些有用组件的集合(对目前的我来说,它确实只是一些有用组件的集合)。它还有鲜为人知而未被了解的一面:它是描述软件组件抽象需求条件的一个正规而有条例的阶层结构(formalhierarchy)。由于所有STL组件都是精确符合某些特定条件而写成,所以STL组件可以相互作用,可以扩充,你可以在增加新算法和容器的同时,对新旧代码之间的系统作用抱巨大信心。
STL的创新在于认知这些类型条件可以被具体指明并加以系统化。也就是说,我们可以定义一组抽象概念(所谓concepts),只要某个类型满足某组条件,我们就说此类型符合某个concept。这些concepts非常重要,因为算法对于其引数类型的大部分假设,都可以藉由[符合某些concepts]以及[不同concepts之间的关系]来陈述。此外,这些concepts形成一个明确定义的阶层架构,这让人联想到传统面向对象中的继承,只不过它是纯然的抽象。
这个concepts阶层架构是STL的一个概念性结构,也是STL最重要的部分。由于它,使得重复运用和交互操作变得可能。此一概念结构作为软件组件的正规分类法也十分重要-即使没有具体代码。STL确实包含具体数据结构,如pair和list,但是要有效率地运用这些数据结构,你必须了解其所依据的概念结构。
定义抽象的concepts,并根据抽象的concepts来撰写算法与数据结构,是泛型编程的本质。
谁该阅读这本书
更明确地说,由于STL对templates的使用方式迥异其他C++程序,所以本书讨论了某些templates高阶技术。本书不应该是你的第一本C++书籍,也不应该是你的第一本算法分析入门书。你应该知道如何撰写基本的C++,同时也应该知道O(N)表示法的意义。

第一篇 泛型编程导入

第1章 STL巡礼

1.2 总结

正如这两个简单例子所示,我们已经勾勒出STL的概观了。STL包含各种泛型算法,如sort、find和lexicographical_compare;泛型指针(iterators)istream_iteraor和ostream_iterator;泛型容器如vector;以及函数对象如less和greater。

这些例子同时也显示运用STL时的几个最重要的观念。所谓使用STL,就是去扩充它。上述两个例子中,我们各自完成了自己的STL兼容组件:第一个例子完成的是个新品种的iterator,第二个例子则完成两个新型的functionobjects。

·                   STL的算法和容器是独立分离的。任何新增的算法并不要求你改写任何容器,我们可以随心所欲地将任何算法与任何容器匹配混用。假设有N个不同的算法,M种不同的容器,你不需要负担N乘M个不同的实现代码。

·                   无需继承,STL便具备可扩充性、可定制的特性。新的iterator(如上例的line_iterator)与新的functionobjects(如上例的strtab_cmp)无须继承自任何特殊基类。它们只需要满足某些抽象条件。你无需承担[继承方面之任何实现细节所造成的额外开销],便可以扩充STL,并且不必为造成[fragile

·                    baseclass]的问题大伤脑筋。

·                    抽象化并不意味着效率低。泛型算法如sort和copy,以及泛型容器如vector,其效率-如你手工精制一般。

STL提供一种新的程序设计思维方式,其中算法与抽象条件组(abstract sets of requirement)居于中心地位。抽象条件是STL与这本书的基础所在。

 

本章最重要的是把例子搞懂,然后体会总结中的几个观念。

第2章 算法和区间

Iterator可说是STL最重要的一个创新发明,它使[将算法与其相关数据结构之间的关系切割分离]一事变得可能。

为什么我们使用如此奇怪而不对称的range定义,让左端涵盖端点而右端不涵盖其端点呢?会这么定义,其实是为了便利性。正如Andrew Koenig指出,非对称性

range有助于避免[相差一个off-by-one]的错误:[first,last)中的元素个数,正如你所期望的是last-first.(你想想,要是都是闭区间,你要表示一个空区间怎么表示?呵呵,是不是很不好表示,因为你的后端点减去前端点并再加上1才行,也就是说表示0个元素的区间时,前面相减得为-1,怎么可能会减出-1来,呵呵)。所以,[p,p)根据元素个数的计算方法,得出它有0个元素,即是一个空区间,也就是右括号具有更高的优先级。

 

如果first是int_node的指针,则find会试图以指针运算++first来取得下一个元素的位置,这会引发灾难,因为我们此刻对付的是链表,而非数组。如果p是int_node的指针,那么下一个节点应该是p->next,而非p+1.这个问题很快就有了答案:C++允许操作符重载。如果operator++的行为不符和需要,我们必须重新定义一个,使非find可以正确走过链表。

重新定义[参数类型为int_node*]的operator++操作是不可能的。尽管如此,我们所能够做的,还是非常直接易懂。我们可以写一个简单的wrapperclass(包装类),使它看起来像int_node*(即看起来行为像指针),而其operator++,有更好(更适当)的定义。(即用一个iterator类把真正的元素地址信息包装起来,只要iterator通过重载操作符,实现指针所具有的++,--,*等语义就行,这样就把遍历range方法的差异隐藏起来了,我们就可以用一个算法操作不同的range了)

众所周知,计算机科学里头的许多问题,常常借着[增加间接层]的方式获得解决。本例,我们就是这样,增加间接层iterator。

我们仅仅要求:iterator能够以某种线性顺序遍历某个数据结构,以访问其中的所有的元素。

经过2.2节的讨论之后,iterator似乎需要定义为一个concept,而指针便是concept的一个model。这差不多是正确的,但却不尽然。换句话说,有许多不同的方法可以将指针一般化,每换一个不同的方法便是一个不同的concept。Iterator不单单只是一个concept,而是五个不同的concepts:InputIterator、Ouput Iterator、Forward Iterator、Bidirectional Iterator和Random AccessIterator。(第六个concept-Trivial Iterator-的存在目的是为了用来厘清其他iterator concept的定义)

2.3.1Input Iterators

·InputIterators用来指向某对象,但不需要提供任何更改该对象的方法。比如,你可以int n = *itor,但你不一定可以*itor = 6.

·InputIterators可以累加,但并非一定可以递减,同样道理itor+5和itor1-itor2这种表达式也并非一定有效。Input Iterators唯一需要的运算形式是itor++。

·你可以测试两个InputIterators是否相等,但你不能测试谁在谁之前,即不能>或<比较。

·InputIterators只遍历range[first,last)一次,并对range中的值最多读取一次。这是使用Input Iterators的唯一正确方式。

2.3.2Output Iterators

同InputIterators类似,

它也只能累加,另外还有其他两个重要限制.第一个很明显:正如InputIterators具有只读性一样,Output Iterator具有只写性。所以,你可以*itor = x,但不能写x=*p。第二个限制就不那么显而易见了,但我们可以利用copy加以说明。

2.3.3Forward Iterators

同InputIterators和Output Iterator一样只能累加,但既可以读也可以写。如果是const的Forward Iterator,则只能取出它所指的对象,但不能经由它赋值;如果是mutableForward Iterator,则我们可以取出其所指的值,或是对它做赋值动作。

2.3.4Bidirectional Iterators

和ForwardIterator一样,Bidirectional Iterators允许multipass算法,而且它也可以是constant或mutable。

正如BidirectionalIterators名称所指的那样,它支持双向移动。Bidirectional Iterators可以累加以取得下一个元素,或是递减以取得前一个元素。

凡选择运用BidirectionalIterators的算法,通常都是需要在某个区间内反向移动。例reverse_copy.

2.3.5Random Access Iterators

它涵盖指针的所有运算形式。

RandomAccess Iterators对于sort这类算法很重要,因为排序suanfa 必须有能力比对并交换相隔甚远的元素(而非只是相邻元素)。
Random Access Iterators真正独特的性质是它能够以固定时间随机访问任意元素。

 

2.5 总结

InputIterator       Output Iterator

          Forward Iterator

        Bidirectional Iterator

        Random Access Iterator

这就是迭代器的conceptshierarchy图

 

本章的内容都是概念东西,理论性很强,目前基本上没有搞懂,需要努力!

 

第3章 再论Iterators

 

第4章 Function Objects

FunctionObjects在很多不同的场合中都很有用。在STL中,它们主要的用途,正如Stoustrup所说:[以template参数来指明要采用的策略]。

最简单的FunctionObjects当推一般的函数指针。(当初还以为函数指针不是函数对象呢,呵呵,原来它也是)

不一定非得使用函数指针不可。是的,functioncall操作符operator()可以被重载,所以只要某个型别有妥善定义的operator(),你便可以将该对象传递给find_if当作参数。

例如:

template<class Number>struct even

{

    bool operator()(Number x) const{return (x& 1)== 0;}

}

使用时:

find_if(f,l,even<int>());

一个疑问,为什么算法即能传递函数指针又能传递对象进去,形参的类型是什么呢?

解释:

通过查看find_if的代码

template<class _InIt,

    class _Pr> inline

    _InIt find_if(_InIt _First, _InIt _Last, _Pr _Pred)

    {   // find firstsatisfying _Pred

    _ASSIGN_FROM_BASE(_First,

       _Find_if(_CHECKED_BASE(_First), _CHECKED_BASE(_Last), _Pred));

    return (_First);

    }

注意,形参的类型是一个模板参数,也就是说当你的实参是函数指针时,那么形参的类型就是传进来的函数指针的类型,当实参是一个对象时,形参的类型就自然成了对象那个类型了,即形参的类型是根据实参定的,而不是固定的,固定的当然实现不了,根本就不存在什么类型能同时接纳函数指针和类对象,终于理解Stoustrup说的那句话了。

 

even<int>是个类,所以我们必须将该种classobject传给find_if。所以我们必须调用其构造函数。(当初还以为第三个参数为一个函数名(),现在才发现错了,呵呵)

classeven并不是什么有趣的function object。它的确是比函数is_even更有弹性(可搭配使用任何整数型别,而非仅仅int而已),也更有效率(因为operator()被声明为inline),不过除此之外似乎就没有多少特别了。它只是个以class形式包装的函数罢了。

FunctionObject并不局限于这般简单的形式。它可以像一般class一样拥有成员函数和成员变量。也就是说,你可以利用函数对象来表现[具有局部状态的函数]。

很显然,任何functionobject concept的基本条件只是:如果f是个function objects,我们可以将operator()施行于f身上。

 

我们已经看过了,有的算法使用单一参数的functionobject,有的使用两个参数的function object。另有一些算法(如generate)使用的是不需要引数的function object.因此,基本的functionobject concepts是Generator、Unary和Binary Function。这些concepts所描述的是能够以f(),f(x)和f(x,y)调用的对象(其实还可以扩充为三元或多元,但实际上目前的STL算法并未用到两个参数以上的函数对象)。STL所定义的functionobject concepts,都是这三个concepts的refinements(加强、精炼)。

4.3Function Object Adapters(函数对象适配器)

Adapter是一种[将某接口转换成另一接口]的组件。

比如说,我们刚才求的是第一个偶数,当我们要求第一个奇数的时候,我们就没有必要重新定义一个求奇数的函数对象,因为求奇数和求偶数之间有简单的关系,偶数的反面就是奇数,故我们可以以预定义(如果不是预定义,我们也捡不了什么便宜,还不如专门写一个求奇数的呢)的少量组件(可以表明那种关系),构筑

出复杂的函数对象,而不必撰写新的函数对象或类。

例:

       // TEMPLATE CLASS unary_negate

template<class _Fn1>

    class unary_negate

    : public unary_function<typename _Fn1::argument_type, bool>

    {   // functor adapter!_Func(left)

public:

    explicit unary_negate(const _Fn1& _Func)

       :_Functor(_Func)

       {   // construct fromfunctor

       }

 

    bool operator()(const typename _Fn1::argument_type&_Left) const

       {   // apply functor tooperand

       return (!_Functor(_Left));

       }

 

protected:

    _Fn1 _Functor;    // the functor toapply

    };

 

       // TEMPLATE FUNCTION not1

template<class _Fn1> inline

    unary_negate<_Fn1>not1(const _Fn1& _Func)

    {   // return aunary_negate functor adapter

    return (std::unary_negate<_Fn1>(_Func));

    }

使用时,

find_if(first,last,not1(even<int>()))

通过代码可以看出,实际上是not1根据even构造出了另外一个预定义的函数对象。

 

STL还定义有其他数个functionobject adapters。其中最重要的是:(1)binder1st与binder2nd,可以将Adaptable Binary Function转换成UnaryFunction;(2)mem_fun_t及其变形,它们除了[作用与成员函数而非全局函数]之外,极类似pointer_to_unary_function;(3)unary_compose,能将两个函数对象f和g转换成一个函数对象h,使得h(x)等于f(g(x))。

 

4.5 总结

单独运用函数对象,用途并不大。函数对象tongchang只是小型的class,用来执行单一而特定的行为。函数对象常常只有一个成员函数(),没有任何成员变量。

函数对象之所以有用,因为它们使泛型算法变得更一般化。

 

没学一个东西的时候,最好考虑在工作中哪儿可以用到,否则学了和没学一样。

 

 

第5章 Containers

Iterator的抽象化过程让我们可以将算法与其所操作之元素分离(解除耦合关系,decouple),这使得我们得以撰写[能操作于任意线性序列之上]的算法。

 

我们可以根据containers提供的iterator种类,来对container分类。

Container(InputIterator)

          |

ForwardContainer(Forward Iterator)

          |

ReversibleContainer(Bidirectional Iterator)

          |

RandomAccess Container(Random Access Iterator)

 

当你初次学习使用STL,最容易犯的错误便是类似以下的写法:

intA[5] = {1,2,3,4,5};

vector<int>V;

copy(A,A+5, V.begin());//其实这样完全错误,如果你试着执行它,程序可能完全会完蛋。你可以想想,我们在使用memcpy的时候,都需要目标地址有足够的空间,在这个地方也是一样,拷贝的语义中不会包含为目标位置分配空间的行为。

 

以copy将新值安插到vector的正确写法:

copy(A,A+5, back_iterator(V));

//译注:back_iterator()是一种insertiterator adapter function,

//用来产生某个container的back_insert_iterator.

 

 

 

 

第二篇 参考手册:STL Concepts

第6章 基本概念

本章所谈的concepts都是非常一般性的概念。它们适用于任何泛型程序库。大多数C++内建类型以及STL定义的许多类型都是这些concepts的models。

所谓正规型别(rugulartype),是同时兼具Assignable,Default Constructible和Equatible Comparable的一个model。除非有合理的因素,否则任何自定义类型(user-definedtype)都应该是rugular type。
6.1 Assignable

如果我们能够赋值某个类别的object,并且可以将值赋予该类别的变量身上,这个类别便是Assignable。

几乎所有C++内建类型都是Assignablemodel,值得注意的是,const T是个例外。因为如果x声明为const int,那么x = 7是不合法的。换句话说你可以复制const int变量,但你不能对它复制。

6.2Default Constructible

如果某类型具有一个默认构造函数,也就是说我们不需要制定任何初值,就可以构造该型别的object,那么这个型别便属于DefaultConstructible。

6.3Equlity Comparible

如果我们可以使用operator==对某个型别的对象做相等性比较,而且operator==系表示等价关系的话,该类型便是EqulityComparible。

一般而言,当且仅当T是EqulityComparible,则vector<T>是Equlity Comparible。

6.4 可序性(Ordering)

如果某个型别能够以某种次序排序,它便隶属于LessThanComparible。它必须能够以operator<作为比较动作。

技术上,operator<一定会导致所谓的partialordering(部分可序),不过这不是个苛刻条件。LessThan Comparible的一个refinement Strict WeaklyComparible,会在[以operator<导出的可序性]上加入更严格的限制。

如果某个类型是LessThanComparible,而operator<除了满足partial ordering条件,还满足更严格的stick weak ordering条件,那么这个型别便隶属于StrictWeakly Comparible。

StrictWeakly Comparible的正式定义如下:如果两元素具有[任何一个都不小于另一个]的性质,那么它们视为具备[某种程度之等价关系]是合理的。

任何比较行为如果只着眼于object中的某部分,都可能是个strictweak ordering,不在乎大小写的字符串比较动作,就是一种strict weak ordering。同样道理,如果某个calss具有两个字段first_name和last_name,而其operator<只针对last_name,那么这个class便是strictweakly comparible的一个model。

 

 

第7章 Iterators

7.1Trivial Iterator

TrivialIterator是一种可dereference,使得以取用某个object的concept。它不必支持数值运算如累加与比较。Trivial Iterator之所以称为“Trivial Iterator”,乃因为它无法提供迭代行为。

STL定义的所有迭代器类型都具有累加性,也就是说STL的所有迭代器类型不但是TrivialIterator的model,也是本章所描述之其他迭代器concepts的model。

7.2Input Iterator

隶属InputIterator之类的迭代器可被dereference,使能取用某个object。它也可以累加,以获得序列中的下一个迭代器。

所有STL迭代器几乎都是InputIterator的model,传统指针亦然。

istream_iterator是[隶属于InputIterator,但却不是其他迭代器concepts的model type]的例子之一。此一型别的确具有Input Iterator奇妙而受到束缚的语义。

7.3Output Iterator

OutputIterator在某些情况下恰与Input Iterator相反,不过它们的接口更受束缚。它们不必一定得支持成员访问或相等比较,也不必拥有相关型别differencetype或value type。

直觉上你可以把OutputIterator想象为磁带:可以写数值到目前位置上,然后前进到下一个位置,但是不能读出,也不能回转或倒带。

以下型别都是OutputIterator:

front_insert_iterator

back_insert_iterator

insert_iterator

ostream_iterator

7.4Forward Iterator

顾名思义,ForwardIterator不允许在序列中回头移动,只能向前(向range尾端)移动。

7.5Bidirectional Iterator

BidirectionalIterator是一种可累加也可递减的迭代器。可递减正是Bidirectional Iterator与Forward Iterator之间的唯一区别。

以下型别都是BidirectionalIterator:

list<int>::iterator

set<string>::iterator

char*(它同时也是一个RandomAccess Iterator)

7.6Random Access Iterator

RandomAccess Iterator是一种可累加也可递减的迭代器(这一点和Bidirectional Iterator一样),并在向前或回头移动任意步数时,以及在比较两个iterator时,耗用固定时间。

指针可算是RandomAccess Iterator中最重要的一个model了。此外,下列型别也都是Random Access Iterator:

vector<int>::iterator

vector<int>::const_iterator

deque<int>::iterator

deque<int>::const_iterator

 

第8章 Function Objects

8.1 基本上FunctionObjects

8.1.1Generator

Generator是一种不需要任何参数的functionobject。

凡是一个函数能返回某值,而且不需要任何参数,它便是一个Generator。

8.1.2Unary Function

 Unary Function是一种只需单个参数的functionobject。

凡是一个函数能返回某值,而且需要单一参数,它便是一个Unary Function。

8.1.3 Binary Function

Binary Function是一种需要两个参数的function object。

凡是一个函数能返回某值,而且需要两个参数,它便是一个Binary Function。

8.2 Adaptable Function Objects

8.2.3 Adaptable Generator

所谓Adaptable Generator,是一种Generator并嵌套定义其result type。

STL并没有定义出Adaptable Generator的任何model。下面是我自行定义的一个例子:

struct counter

{

    typedef int result_type;

 

    counter(result_type init =0):n(init){}

    result_type operator(){returnn++;}

    result_type n;

};

8.2.2 Adaptable Unary Function

例:

negate

identity

pointer_to_unary_funtion

pointer_to_unary_funtion非常有趣,因为它可以把函数指针当作Adaptable Unary Function使用。

 

8.2.3 Adaptable Binary Function

Adaptable Binary Function是一种Binary Function,并嵌套定义其argument type和resulttype。

例:

plus

multiplies

less

equal_to

pointer_to_binary_function

 

8.3 Predicates

8.3.1 Predicate

所谓Predicate是一种Unary Function,其结果表现出某个条件的真与假。Predicate是个函数,需要一个int参数,如果该参数为正值,就返回true。

8.3.2 Binary Predicate

所谓Predicate是一种Binary Function,其结果表现出某个条件的真与假。Predicate是个函数,需要两个参数并测试它们是否相等。

8.3.3 Adaptable Predicate

它是一个[返回值类型可转换为bool]的Unary Function,并且嵌套定义了argument type和return type。

例:

logical_not<bool>

binder1st<equal_to<int> >

8.3.4 Adaptable Binary Predicate

它是一个Binary Function,其返回值型别可转换为bool,而且嵌套定义有argument type与return type。

下面是STL定义的一些Adaptable Binary Predicate calasses:

less<T>

greater<T>

equal_to<T>

logical_and<T>
8.3.5 Strict Weak Ordering

Strict Weak Ordering是一种Binary Predicate,可用来比较两个objects:如果第一个object位于第二个object之前,就返回true。

以下型别都是Strict Weak Ordering的实例:

less<int>

greater<int>

less<string>

greater<string>

8.4 特化的Concepts

8.4.1 Random Number Generator

Random Number Generator是一种function object,可用来产生整数随机数数列。如果f是一个RandomNumber Generator并且N是ge 正数,则f(N)返回一个小于N并且大于等于0的整数。如果以同一个N调用多次调用f,产生的数列在range[0,N)中将是均匀分布(uniformlydistributed)。

Random Number Generator(乱数产生器)是个微妙的主题。一个好的Random Number Generator必须满足均与分布以外的许多统计学性质。

8.4.2 Hash Function(散列函数)

Hash Function是一种Unary Function,用在Hashed Associative Container中。它接受单一参数,并将该参数映射至型别为size_t的结果。HashFunction必须具备注定性(deterministic),意味着其返回值必须只和其参数有关,而且同一个参数会导致相同的结果。

STL唯一定义的一个HashFunction是class hash。

 

第9章 Containers

所谓container是一种object,可以包含并管理其他objects,并提供iterators用以定位其所包含之object(元素)。

STL的containerconcepts可归属为三种广泛类型。第一种是极为一般化的Container concept,以及另三个稍微不那么一般化的concepts、,它们系以其所提供的iterators型别作为分类依据。STL的所有containers都是Containerconcept的models。第二种是Sequence concept,这是以中可动态调整大小的container,可以在任何位置安插或删除元素。第三种是AssociativeContainer concept,特别针对[以值查询元素]的情况下进行优化。

某些container以其内存分配方式作为参数,其中很多使用Allocatorconcept。Allocator并不是Container的强化(refinement),但因为它与container的关系密切,所以本章也纳入讨论。

 

9.4Allocator(空间配置器)

Allocator是一个class,用来封装内存分配与归还的某些细节。STL预定义的所有containers都使用Allocator来做内存管理。举个例子,当你实例化(instantiate)vectortemplate时,你必须指定使用那一个Allocator class。

关于allocator,有三个事实比任何技术细节都来得重要。

第一(同时也是最重要的),你或许丝毫不必知道Allocator的任何事。使用STL预定义的container时,你不需要知道Allocator的存在,因为这些containerclasses使用缺省的template参数来隐藏对Allocator的使用。例如,缺省情况下,vector<int>意味vector<int,allocator<int> >.你甚至不知道Allocator便可以撰写新的container class。container class不必使用精致复杂的内存管理技术。

第二,allocator是STL中最不具有移植性的一个主题。虽然C++Standard的确提到了Allocator concept,但1998年仍然有很多非标准的allocator被广泛应用。

第三 虽然container以allocator作为参数之一,但这并不表示你得以控制每一个想象可及的内存管理层面。Allocatorconcept与以下各点毫无关系:deque节点中包含多少元素、container的元素是否连续存储、vector重新分配时如何增长内存。

最后提醒你。你很可能完全不必担心allocator的任何事,甚至根本不必使用[另一种内存模型]以及[不同的allocatorinstances]等高级特性。如果你真打算使用这些allocator高级特性,应该严密研读你的编译器说明文档,因为C++Standard[ISO98]20.1.5节有一个重要警告:

本国际标准文档所描述的container实现法,得假设其Allocator之template参数不仅符合表32所列条件,还需符合一下两个额外条件:

略。

 

本章都是些概念,好多东西都和前面的一些章节内容是一样的,所以,本章仅作参考,不要作重点学习,我觉得学习STL的重点方法还是多看看实例,多在工作中使用,等使用熟了之后,再去理解,可能比先理解后使用容易得多。

第三章 参考手册:算法与类

第10章 基本组件

本章所涵盖的类型和函数,是程序库在许多不同地方用到的一些基本素材。大部分都极为简单,不过其中某些十分低阶,你只有在实现一个新的容器时才会用到它们。

 

pair

凡是需要返回两个值的函数,通常可以利用pair。

 

 

第11章 [不改变操作对象之内容]的算法

 

算法find会在iterators range内执行现行查找,寻找指定之值value。更明确地说,它会返回在[first,last)中第一个满足[*i== value]的iterator i.如果没有这样的iterator,就返回last。

// alg_find.cpp

// compile with: /EHsc

#include <list>

#include <algorithm>

#include <iostream>

 

int main() {

    using namespace std;

 

    list <int> L;

 

    L.push_back( 3);

    L.push_back( 1);

    L.push_back( 7);

    result = find( L.begin( ), L.end( ), 7);

    assert( result == L.end( ) || *result== 7);

}

 

除了找带迭代器的容器,数组也是可以的。

int main() {

    usingnamespace std;

 

    inta[3] = {1, 2, 3};

 

    int* result = find(a, a + 2, 4);

    //

    assert(result== a + 2);

 

}

 

find_if

 

找出iterators范围内满足某个条件的元素,注意,这个返回值是一个迭代器,相当于一个指向被找到元素的指针。

template<class _II, class _Pr> inline

    _II find_if(_II _F, _II _L, _Pr_P)

    {for (; _F != _L; ++_F)

       if (_P(*_F))

           break;

    return (_F); }

如何理解int* p = find_if(vec.begin(), vec.end(),bind2nd(equal_to<int>(), 3));

很显然,第3个参数是一个函数指针,如果我们要完成这个功能的话,必须得把满足某个条件的那个条件传进来,条件?那就传函数进来呀,只要我们把iterators范围的每个元素传给条件函数,接着再把另外的从外部传进去的另外的参数(至少1个,也许有几个,现在不敢确定)也传进去,在里面该怎么比较就怎么比,返回一个bool类型的变量表示满足或不满足就行了。

equal_to<int>()就是那个条件函数,注意equal_to是一个从unary_function继承下来的函数对象,()是她的一个成员函数,有两个参数,不过这儿是看不到的,只有在调它的时候才需要,但是,在调用判断是否满足条件函数的时候,它只需要传递迭代器范围内的一个元素进去就行了,所以,我们需要将从外部传进去的函数再次包装一下,让它只需要一个迭代器元素作为参数就行了,所以,我们先调用bind2nd,把最终的条件函数传进去,再把另外的参数传进去,返回find_if会直接调用的函数就行了。

 

这儿表现出来的道理就是:1.变量类型可以当作变量那样传过去传过来的,可以像变量那样适时地固化下来 2.函数(即操作)可以当作参数传过去传过来的,并且可以灵活的很,比如,这儿就是传入了一个具有两个参数的函数和一个参数,返回一个只需要一个参数的函数。

 

其实它这儿绕了个弯,要是find_if设计为需要3个参数的话4个参数的话,就好理解多了,把equal_to<int>()当作第三个参数传进去,3当作第四个参数传进去,在find_if中直接调用传进去的函数,实参为迭代器中的元素,还有3,那么就完成功能了。

 

由此可见,第三个参数没有什么神秘的,实际上就是一个只有一个参数,返回bool值的函数指针,只要我们定义一个这样的函数传进去就行了,不一定要调用别人写好的那些函数。

 

看看别人的这些抽象,提炼公共的东西当作算法,要是按我想的那样多传几个参数进去,算法函数岂不是很多,每个条件都需要一个,不可能嘛,所以我们需要把差异性转换为没差异的传进去,转换的方法也就是像bind2nd那种,传入将要转换的函数,差异性的参数(个数可能不同),返回需要的函数。

 

adjacent_find

int main() {

    using namespace std;

 

    int a[] = {1, 2, 3, 5, 6, 6,90};

    int N = sizeof(a)/sizeof(int);

    int* result = adjacent_find(a,a + N);

    assert(result == a + 4);

 

}

版本1:找出序列中紧邻的相等元素。

 

bool Pret(int a, int b)

{

    return a > b;

}

int main() {

    using namespace std;

 

    int a[] = {1, 2, 3, 5, 8, 6,90};

    int N = sizeof(a)/sizeof(int);

    int* result = adjacent_find(a,a + N, Pret);

    assert(result == a + 4);

}

版本2:找出连续的两个元素,这两个元素满足某个条件。显然,因为比较的是两个元素,也就是肯定需要一个有两个参数,返回值为bool型的条件函数,所以,我直接定义一个Pret函数进去,没想到还真是这样的。当然,除了我们自己定义的函数可以,STL中已经定义好的更可以了,比如

int* result = adjacent_find(a, a + N, greater<int>());

效果和我们自己定义的一样。

 

find_first_of

int main() {

    using namespace std;

 

    int a[] = {1, 2, 3, 5, 8, 6,90};

    int N = sizeof(a)/sizeof(int);

   

    int b[] = {2, 8};

    int M = sizeof(b)/sizeof(int);

 

    int* result = find_first_of(a,a + N, b, b + M);

    assert(result == a + 1);

}

版本1:找出列序1中第一个出现在序列2中的元素。

 

bool twice(int a, int b)

{

    return a * 2 == b;

}

int main() {

    using namespace std;

 

    int a[] = {1, 2, 3, 5, 8, 6,90};

    int N = sizeof(a)/sizeof(int);

   

    int b[] = {1, 6};

    int M = sizeof(b)/sizeof(int);

 

    int* result = find_first_of(a,a + N, b, b + M, twice);

    assert(result == a + 2);

}

版本2:找出序列1中第一个和序列中的任何一个元素有一定关系的元素,这儿是二倍关系,照推测,因为在内部会取出序列1中的一个元素和序列2中的一个元素进行判断,故需要两个参数的条件函数,我就按这种参数定义函数,还真的可以。以后的地方我都可以这样推测,呵呵。

 

search

版本1:找子序列,就和找子字符串差不多

int main() {

    using namespace std;

 

    char a[] ="abc123789";

    char b[] = "123";

   

    int N = strlen(a); //千万不要用sizeof求,那样相当于找"123\0",即子序列有四个字符,当然找不到了:)

    int M = strlen(b);

    char* result = search(a, a + N,b, b + M);

 

    assert(result == a + 3);

    system("pause");

}

 

版本2:

按一定的条件进行匹配,不像版本一默认进行相等匹配。如果把版本1改成版本2:

int main() {

    using namespace std;

 

    char a[] ="abc123789";

    char b[] = "123";

   

    int N = strlen(a);

    int M = strlen(b);

    char* result = search(a, a + N,b, b + M, equal_to<int>());

 

    assert(result == a + 3);

    system("pause");

}

注意,在一次比较过程中,需要调用条件函数M次,即a和1比,b和2比,c和3比,只有都返回true,才算找到了,否则主序列移动一个位置继续按前面的规则比较。看来最主要的是记住查找过程,而不是死记硬背。

 

find_end

功能和过程和search类似,也有两个版本,只不过是寻找的是满足条件的最后一个子序列。

即如果在”123abc123”中找”123”的话,返回的是第二个”123”的地址。

 

search_n

道理和search一样,只不过子序列是又n个value组成的,比如这儿的子序列就是由2个’1’组成的,注意是’1’,而不是1,否则找不到的,别糊涂了。

int main() {

    using namespace std;

 

    char a[] = "abc1123789123";

    int N = strlen(a);

 

    char* result = search_n(a, a + N, 2, '1');

//由search也很容易实现类似功能,只要把子序列换成”11”就行了。

 

    assert(result == a + 3);

    system("pause");

}

 

count

返回序列中等于某个值的元素的个数

int main() {

    using namespace std;

 

    char a[] = "abc1123789123";

    int N = strlen(a);

 

    int result = count(a, a + N, '1');

 

    assert(result == 3);

    system("pause");

}

 

count_if

它和count的关系相当于find和find_if的关系:

int main() {

    using namespace std;

 

    int a[] = {1, -1, 6, -9, 50, 5};

    int N = sizeof(a) / sizeof(int);

 

    int result = count_if(a, a + N,bind2nd(less<int>(), 0));

//如果要简单的记bind2nd的话,就是说将0作为less<int>()的第二个参数(即是绑定到第二个参

//数),第一个参数为序列中的元素留着。

 

    assert(result == 2);

    system("pause");

}

 

 

for_each

看一个例子就明白了,遍历序列,取出每个元素作为参数调用我们传给for_each的函数,显然我们传进去的函数只需要1个参数就行了。

voidprint(int a)

{

    cout<< a <<endl;

}

 

int main() {

    using namespace std;

 

    int a[] = {1, -1, 6, -9, 50, 5};

    int N = sizeof(a) / sizeof(int);

   

    for_each(a, a + N, print);

 

    system("pause");

}

 

这是我的一个试验,呵呵

voidprint(int a, int plus)

{

    cout<< a + plus<<endl;

}

 

int main() {

    using namespace std;

 

    int a[] = {1, -1, 6, -9, 50, 5};

    int N = sizeof(a) / sizeof(int);

   

    for_each(a, a + N, bind2nd(print, 5));

//这样绑定编译不能通过,看来异想天开还是不行的

 

    system("pause");

}

 

equal

版本1:比较两个序列是否完全相等

int main() {

    using namespace std;

 

    int a[] = {1, -1, 6, -9, 50, 5};

    int b[] = {1, -1, 6, -9, 50, 5};

    int N = sizeof(a) / sizeof(int);

   

    bool bResult = equal(a, a + N, b); //返回true

//如果从b序列中去掉一个元素,那么返回false,相等的首先条件肯定是元素个数必须相等

 

    system("pause");

}

版本2:

道理同find和find_if

 

mistatch

版本1:返回两个序列中第一个不同值的元素的指针,因为返回值需要存储两个位置,故返回值是一个pair,注意了。

int main() {

    using namespace std;

 

    int a[] = {1, -1, 6, -9, 50, 5};

    int b[] = {1, -1, 6, -9, 50};

    int N = sizeof(a) / sizeof(int);

   

    pair<int*, int*> pos = mismatch(a, a +N, b);

//其中pair中第一个值指向5那个元素,第二个值指向50后面的那个元素,是一个未定义的值。

 

    system("pause");

}

版本2:道理同find和find_if

找出满足条件的那两个元素的指针的pair。注意,比较时元素是根据位置一一对应的。

 

lexicographical_compare

测试第一个序列是否小于第二个序列(即字典比较),注意,这儿要分三种情况,

1.  对应的两个元素不等,第一个序列小的话就返回true,否则false

{1, -1, 6, 3}和{1,-1, 6, -9, 50},返回false

2.  发生比较了的都相等,第一个序列元素少就返回true,否则返回false

{1, -1, 6,-9}和{1, -1, 6, -9, 50},返回true

3.  发生了比较的都相等,两个序列元素个数相同,返回false。

{1, -1, 6, -9,50}和{1,-1, 6, -9, 50},返回true

版本2:道理同find和find_if

 

min,max

太简单,不用举例了:)

 

min_element

返回序列中最小的那个元素的指针

int main() {

    using namespace std;

 

    int a[] = {1, -1, 6, -9};

    int N = sizeof(a) / sizeof(int);

 

    int* p = min_element(a, a + N); //返回-9

    system("pause");

}

 

 

max_element

返回序列中最小的那个元素的指针

 

本章总结:

其实本章的那些算法都是些常用的,并且非常简单的,让我们自己写的话,也能写出来,但是别人已经写好了,我们为什么还要花功夫去写呢,并且我们自己写的不一定正确,还需要花时间测试,所以,如果没有特殊要求,我们尽量用STL提供的算法。

今天学习过程中有个感悟,好多东西看起来很难,但是你耐住性子搞懂一个,其他的就都好理解了,如果一遇到困难就跳过的话,什么都学不到。

这章没有什么难度,平常多用就会了,关键是平常写程序的时候要有使用算法的意识,否则我们始终停留在自己写代码的级别上。

本章还用到了许多函数子,这些定义在functional文件中的函数子,其实,他们一点都不高深,仅仅是重载了一些常用的运算操作函数,比如加,减,乘,除,求反等,这些操作只需要两个操作数或者一个,所以只有binary_function<ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_vcstdlib/html/79b6d53d-644c-4add-b0ba-3a5f40f69c60.htm>和unary_function<ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_vcstdlib/html/04c2fbdc-c1f6-48ed-b6cc-292a6d484627.htm>基类,它们的主要作用是辅助算法函数。

说白了,函数子就是从binary_function<ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_vcstdlib/html/79b6d53d-644c-4add-b0ba-3a5f40f69c60.htm>和unary_function<ms-help://MS.VSCC.v80/MS.MSDN.v80/MS.VisualStudio.v80.chs/dv_vcstdlib/html/04c2fbdc-c1f6-48ed-b6cc-292a6d484627.htm>基类继承下来的一些结构体,并且重载了()操作符,并根据语义实现了()函数,在调用算法函数时,我们需要把()函数传进去,让算法代码调用就行了。

 

复习时只需要看看函数名,想想我们是否会用就OK了。

 

第12 [改变操作对象之内容]的算法

本章所叙述的基本算法作用于一个iterator或多个iterator所形成的ranges上,并更改某些iterators所指向的元素。这些算法包括:(1)从某个range复制元素至另一个range;(2)为range中的每个iterators赋值;(3)将某个值覆盖为另一个值。

 

本章的算法有一个相同的特质:它们更改的是iterator所指之值,而非iterator自身。或者说不能改变[first,last)的元素个数

 

copy

将一个range中的元素拷贝到另一个range,语义跟memcpy差不多。

注意,一定要保证目的range的元素个数至少有源range那么多,否则就跟memcpy拷贝越界了一样,程序就飞掉了。

对于vector、list之类的容器,如果发生这种情况,会立刻发生内存访问错误或断言,如果是数组的话,基本上不会发生严重错误,仅仅是把其他地方的内存覆盖掉了,这种错误很难发现的,谨记!!!

    list<int> list1;

    list1.push_back(1);

    list1.push_back(2);

//  list1.push_back(3);

 

    list<int> list2;

    list2.push_back(4);

    list2.push_back(5);

 

    copy(list1.begin(), list1.end(), list2.begin());

 

那么,怎样将元素追加到一个容器的后面呢?

copy(list1.begin(), list1.end(), back_inserter(list2));

back_inserter会在输入容器list2的后面添加一个元素,即copy每次赋值给的都是back_inserter添加在list2后面的那个元素。

 

下面的例子是将list内的所有元素复制到标准输出设备上,即打印出来。

    list<int> list1;

    list1.push_back(1);

    list1.push_back(2);

    list1.push_back(3);

 

    copy(list1.begin(), list1.end(), ostream_iterator<int>(cout, "\n")); //对ostream_iterator还不会使,其函数原型为:

    ostream_iterator(ostream_type&_Ostr,

       const _Elem *_Delim = 0)

       : _Myostr(&_Ostr),_Mydelim(_Delim)

       {   // construct fromoutput stream and delimiter

       }

现在我们大概知道,int代表输出的元素的类型,cout代表输出到什么地方,"\n"对应_Delim参数,也就是分隔符的意思,这个用法只有多记几个,用多了就理解了。

 

copy_backward

copy_backward和copy的关系就像memcpy和memmove的关系一样,只不过使用上有点差异,例:

    int a[3] = {12, 5,7};

    int b[3] = {3, 4,8};

 

    copy_backward(a, a + 3, b + 3); //注意:第3个参数不是b,而是目标range的last)。这是最大的不同:(

 

swap

简单,不用举例

 

swap_ranges

将大小相同的两个ranges的内容互换。

    int a[3] = {12, 5,7};

    int b[3] = {3, 4,8};

 

    swap_ranges(a, a + 3, b);

 

transform

它在range内的各元素身上执行某种运算,比便将某个range[转换]成另一个range。

版本1是将源range的每个元素执行一定运算后赋值给目的range的对应元素。

int Inc(int i)

{

    return i + 1;

}

    int a[3] = {12, 5,7};

    int b[3] = {3, 4,8};

 

    transform(a, a + 3, b, Inc); //b的最终结果为13,6, 8

版本2是将源range的每个元素和另外一个range(不一定是目的range)对应元素一块儿传递给一个函数,然后将返回值赋值给目的range的对应元素。

 

int Add(int i,int j)

{

    return i + j;

}

    int a[3] = {12, 5,7};

    int b[4] = {3, 4, 8,9};

 

    transform(a, a + 3, b + 1, b, Add); //b的最后结果为16,13,16, 9,参数3除了传递b之外,也可以传递其他的range

 

replace

将一个range中所有值为XX的元素都修改为YY。

    int a[4] = {12, 5,7, 5};

 

    replace(a, a + 4, 5, 10); //a的最终结果为12,10,7,10

 

replace_if

就如find和find_if的关系。

//将所有为偶数的元素都替换为

bool even(int i)

{

    return (i % 2) == 0;

}

 

int main() {

 

    int a[4] = {12, 5,7, 5};

 

    replace_if(a, a + 4, even, 10);

 

    system("pause");

}

 

repalce_copy

将一个range的元素复制到目的range,复制过程,如果发现源range中某个元素的值为XX,则用YY赋值给目的range中的对应元素。注意,它不会修改源range的任何东西。这儿即说明了一个道理,只要是复制,就不该对源发生任何影响。

    list<int> list1;

    list1.push_back(1);

    list1.push_back(1);

    list1.push_back(2);

    list1.push_back(3);

   

    list<int> list2(4);

    replace_copy(list1.begin(), list1.end(), list2.begin(), 1, 10);

 

 

repalce_copy_if

见上红色部分。

简单,不举例了。

 

fill

填充一个区间,行为有点像memset

    int a[4];

    fill(a, a + 4, 1);//a的最终值为1,1,1,1

 

fill_n

    int a[4];

    fill_n(a, 2, 1);//将a前两个元素赋值为1

 

    vector<int> vec1(2);

    fill_n(vec1.begin(), 3, 5); //注意,一定要保证不能越界,vec1一共才2个元素,我们缺要赋值3个元素,所以程序飞掉。

 

generate

template<classForwardIterator, class Generator>

   void generate(

      ForwardIterator _First,

      ForwardIterator _Last,

      Generator _Gen

   );

一看声明就知道是怎么回事了,即调用_Last- _First次_Gen函数,将函数返回值赋值给range中的对应元素。

 

generate_n

道理同fill和fill_n的关系,注意不要越界就行了。

template<classOutputIterator, class Size, class Generator>

   void generate_n(

      OutputIterator _First,

      Size _Count,

      Generator _Gen

   );

 

remove

将值为_Val的元素从序列中移除,其实移除的那些元素还在,只不过被移到序列的最后面去了,从下面的例子可以看到,移除后元素的个数仍然未发生变化。

template<classForwardIterator, class Type>

   ForwardIterator remove(

      ForwardIterator _First,

      ForwardIterator _Last,

      const Type& _Val

   );

 

    int a[4] = {1, 1, 2,3};

    remove(a, a + 4, 1); //2,3,2,3

 

    list<int> list1;

    list1.push_back(1);

    list1.push_back(1);

    list1.push_back(2);

    list1.push_back(3);

    remove(list1.begin(), list1.end(), 1);

    assert(list1.size() == 4); //2,3,2,3

要真正的移除元素,可以:

    list1.erase(remove(list1.begin(), list1.end(), 1), list1.end());//remove返回有效元素的下一个元素即第一个无效元素,所以我们可以这样做

    assert(list1.size() == 2); //2,3

特别注意: 算法的remove和成员函数的remove是不一样的,成员函数的remove跟erase一样,确实会删除掉元素。例:

    list<int>list1;

    list1.push_back(1);

    list1.push_back(2);

    list1.push_back(3);

    list1.remove(2);

    _ASSERT(list1.size() == 2);

 

remove_if

同find与find_if

简单,不举例。

 

remove_copy

    list<int> list1;

    list1.push_back(1);

    list1.push_back(1);

    list1.push_back(2);

    list1.push_back(3);

   

    list<int> list2(2);

    remove_copy(list1.begin(), list1.end(), list2.begin(), 1);//list1仍然为1,1,2,3,即对list1没有真正的remove,而是赋值过程中做了remove,刚开始我还理解错了,以为对list1会有影响。list2为2,3

 

remove_copy_if

关系同find与find_if.

简单不举例。

 

unique

注意,unique只移除相邻的重复元素。如果你想要移除所有重复元素,你必须确定所有重复元素都相邻。基于这个理由,unique和sort搭配特别管用。

unique同remove一样,实际上不会改变序列中元素的个数。

    list<int> list1;

    list1.push_back(1);

    list1.push_back(5);

    list1.push_back(5);

    list1.push_back(3);

    list1.push_back(5);

    list1.unique();//1,5,3,5

 

    list<int> list2;

    list2.push_back(1);

    list2.push_back(5);

    list2.push_back(5);

    list2.push_back(3);

    list2.push_back(5);

    list2.sort();

    list2.unique();//1,3,5

 

unique_copy

同replace和replace_if,不会修改源range的值。

    list<int> list1;

    list1.push_back(1);

    list1.push_back(5);

    list1.push_back(5);

    list1.push_back(3);

    list1.push_back(5);

 

 

    list<int> list2(4);

    unique_copy(list1.begin(), list1.end(), list2.begin());//1,5,3,5

版本2:

template<class InputIterator, class OutputIterator,class BinaryPredicate>

  OutputIterator unique_copy(

     InputIterator _First,

     InputIterator _Last,

     OutputIterator _Result,

     BinaryPredicate _Comp,

      );

将相邻的两个元素传递给函数_Comp,如果返回真则把第二个元素拷贝到目的序列。(版本相当于默认比较的是两个元素是否相同)

 

reverse

将序列中的元素反序。简单,不用举例。

 

reverse_copy

将反序后的拷贝到目的序列,注意,不会影响源序列。

 

rotate

将[first,middle)和[middle,last)互换。

template<classForwardIterator>

   void rotate(

      ForwardIterator _First,

      ForwardIterator _Middle,

      ForwardIterator _Last

   );

    int a[4] = {1, 2, 3,4};

 

    rotate(a, a + 3, a + 4);//4,2,3,1

a+3即是指向4这个元素,也就是说将序列掰成两半,4被划到后面一半,1,2,3和4,然后一一交换。

我就想不通,这个函数平常我们能用到吗,用来干嘛:)

 

rotate_copy

同replace和replace_copy的关系,简单,不举例。

 

next_permutation

根据当前排列求下一个排列,下一个排列必须要比当前序列大(默认比较大小时是根据字典比较的,当然我们可以按自己的方式比较大小,只要我们传递一个比较函数进去,版本2的做法)。

    int a[] = {1, 5, 3};

    const int N = sizeof(a) / sizeof(a[0]);

    do

    {

       copy(a, a + N, ostream_iterator<int>(cout, " "));

       cout <<endl;

    } while(next_permutation(a, a + N));

 

结果为:

1 5 3

3 1 5  

3 5 1

5 1 3

5 3 1

算法就是每次从后面取最小的往前面放,然后逐渐变大,依次类推。

 

prev_permutation

和next_permutation刚好相反,它是根据当前排序求下一个更小的序列。

 

partition

template<class BidirectionalIterator, classPredicate>

  BidirectionalIterator partition(

     BidirectionalIterator _First,

     BidirectionalIterator _Last,

     BinaryPredicate _Comp

   );

算法partition会依据_Comp重新排列[first,last)的元素,使满足pred的元素排在不满足pred的元素之前。

//将所有偶数元素排到前面

bool even(int i)

{

    return (i % 2) == 0;

}

 

int main() {

 

    int a[] = {1, 2, 3,4, 5, 6, 7, 8, 9, 10};

    const int N = sizeof(a) / sizeof(a[0]);

   

    partition(a, a + N, even);

 

    system("pause");

}

结果:

10 2 8 4 6 57 3 9 1

但对元素的顺序不做任何保证。

 

stable_partition

和partition差不多,但是它会对切分后的部分进行排序,比如上面的换成stable_partition之后,结果就为:

2 4 6 8 10 13 5 7 9

 

random_shuffle            

shuffle

[5FQfl]

n.

拖着脚走, 混乱, 蒙混,洗纸牌

v.

拖曳, 搅乱, 慢吞吞地走,推诿, 洗牌

算法random_shuffle随机重排[first,last)的顺序。也就是说它在N!种可能的元素排列顺序中随机选出一种,此处N为last-first。即每种排列产生的可能性都是1/N!之一,比起其他的一些算法来,这个函数会产生一个各排列均匀分布情况。

版本2没有完全明白?待理解,比如用途。

比如在SIP适配器中创建call_id的时候,或许就能排上用场。不过它跟rand一样,只要种子相同,产生出来的随机数就相同。例如,只要被随机的参数一样,每次调用random_shuffle产生的结果都一样。

上次验证时,只要参数一致,每次运行产生的结果都是这样:

ebgcafhd

agedcfhb

agcfhdeb

gfhabecd

fbcagdhe

 

random_sample和random_sample_n在标准STL中没有,只存在于SGISTL,它们可以从一个序列中随机抽取几个元素放到另外一个序列中,差别在于前者不保持元素在input range中的相对顺序,而后者会。

 

accumulate

它可以计算init和[first,last)内所有元素的总和(或其他双参运算)。

版本1:

    int a[] = {1, 2, 3};

    const int N = sizeof(a) / sizeof(a[0]);

   

    int i = accumulate(a, a + N, 0); //i==6

注意,accumulate的接口有点笨拙。你一定得提供一个初始值init(这么做的原因是当序列为空时仍可获得一个明确定义的值)。

版本2:

只要了解函数是怎么运作的就好理解了,对于版本一,应该是:init+ 元素1 + 元素2 + ......

对应版本二,相当于把+换成一个函数参数了,比如

int i = accumulate(a, a + N, 2, multiplies<int>());

即相当于2 * 1 * 2 * 3

 

inner_product

求内积

算法为:

_Val + ( a1 * b1 ) + ( a2 * b2 ) +

例1:

    int a[] = {1, 2, 3};

    int b[] = {2, 3, 4};

    const int N = sizeof(a) / sizeof(a[0]);

   

    int i = inner_product(a, a + N, b, 0);    //20

20 = 0 + 1 * 2 + 2 * 3 + 3 * 4

版本2相当于把+和*换成我们自己的函数。

 

partial_sum

 

    const int N = 10;

    int A[N];

 

    fill(A, A + N, 1);

 

    partial_sum(A, A + N, ostream_iterator<int>(cout, " "));

结果:1 2 3 4 5 6 7 8 9

把第三个参数换成B可能好理解些:

int B[N];

partial_sum(A, A + N, B);

即把元素1赋值给B中的第一个元素

即把元素1+元素2赋值给B中的第2个元素

即把元素1+元素2+加元素3赋值给B中的第3个元素

它和accumulate的差别在于accumulate只输出一个最终值,而partial_sum把每次计算的过程sum都输出了,即用来接收输出的参数也不一样,前者是一个元素,后者是一个容器。

版本2:

相当于把+号换成了一个函数。

 

adjacent_difference

道理和partial_sum差不多,例

    const int N = 10;

    int A[N];

    int B[N];

 

    fill(A, A + N, 1);

 

    adjacent_difference(A,A + N, B);

B中的元素为1 0 0 0 0 0 0 0 0 0

即第1个元素赋值给B中的第1个元素

即把第2个元素减去第1个元素的差值赋值给B中的第2个元素

即把第3个元素减去第2个元素的差值赋值给B中的第3个元素

版本2:

相当于把-号换成了一个函数。

 

感觉本章的函数都没多大用,以后可以试图使用一下,看爽不爽。特别是后面的一般化数值算法,可能能用到的时候不多吧,呵呵,虽然没有用,但是以前没搞懂的东西,现在懂了,也是一种进步了。

 

第13章 排序和查找

除了本章所列的算法,STL还包含三种其他形式的排序算法:第一,containerslist和slist都拥有member function sort,可用来对整个container排序;二,Sorted AssocaiativeContainers的元素一定(自动)处于已序状态;第三,你可以使用heap sort算法来对任何range排序:首先调用make_heap构造一个heap,然后调用sort_heap将这个heap转换成一个已序的range。

没有任何一种排序算法(在各种情况下)永远是最好的。由于排序是一种既重要而又潜在性地极耗时间的行为,所以你应该小心选择你的排序算法;通常sort是个好选择,但有时候其他方法也许更恰当。

 

sort

注意,sort不一定是个稳定(stable)排序算法。假设两个元素定价,排序后并不能保证这两个元素的相对位置还和排序前的一致。

版本2在比较时是根据我们传进去的函数完成的。比如我们要进行递减排序,就得用版本2。

    int A[] = {3, 1, 6,2, 9};

    const int N = sizeof(A)/ sizeof(int);

 

    sort(A, A + N, greater<int>());//降序排列

 

stable_sort

和sort比起来,它是个稳定排序,但是比sort要慢。

 

partial_sort

template<class RandomAccessIterator>

   voidpartial_sort(

     RandomAccessIterator _First,

     RandomAccessIterator _SortEnd,

     RandomAccessIterator _Last

   );

保证[_First,_SortEnd)中的元素是整个序列中最小的,并且是递增排序的。

版本2:

比较函数是我们传进去的。

    int A[] = {3, 1, 6,10, 2, 9};

    const int N = sizeof(A)/ sizeof(int);

 

    partial_sort(A, A + 3, A + N);//1 2 3 10 6 9,即保证1 2 3是整个序列中最小的3个,并且是排序的,对其他的元素没有什么保证。

 

partial_sort_copy

template<class InputIterator, classRandomAccessIterator>

  RandomAccessIterator partial_sort_copy(

     InputIterator _First1,

     InputIterator _Last1,

     RandomAccessIterator _First2,

     RandomAccessIterator _Last2

   );

从序列1中取_Last1 - _First1或_Last2 - _First2个(取小的那个,以保证不越界)元素到目的序列中。

好像这儿丢掉了partial_sort的语义,仔细想想,其实也没丢,只不过_SortEnd等于_Last而已,拷贝多少个,虽然前面两个参数没法控制了,但是通过控制后面两个参数仍然可以达到效果。

版本2同其他。

 

nth_element

template<class RandomAccessIterator>

   voidnth_element(

     RandomAccessIterator _First,

     RandomAccessIterator _Nth,

     RandomAccessIterator _Last

   );

保证[_First, _Nth)中的元素不大于[_Nth, _Last)中的元素,各段内元素的顺序没有保证。

    int A[] = {2, 1, 6,2, 2, 9};

    const int N = sizeof(A)/ sizeof(int);

 

    nth_element(A, A + 2, A + N);  

 

is_sorted

是否已经是有序的,标准C++中没有,只存在于SGISTL模板中。

 

以下部分为二分查找法,前提是序列已经是有序的。

binary_search

它试图在已排序[first,last)中寻找元素value。如果有则返回true,否则返回false。

如果传入一个无序或者不是递增的序列,程序出现断言。

如何在一个递减的序列中实现二分查找?(暂时没找到办法,应该有的)

 

    int A[] = {1, 2, 4,9, 13};

    const int N = sizeof(A)/ sizeof(int);

    bool b = binary_search(A, A + N, 9);

版本2:

[first,last)已根据functionobject comp递增排序。这可能是实现在一个递减的序列中实现二分查找的方法,但是我试一下,没成功。

 

lower_bound

template<classForwardIterator, class Type>

   ForwardIterator lower_bound(

      ForwardIterator _First,

      ForwardIterator _Last,

      const Type& _Val

   );

找到序列中第一个等价于_Val的元素,如果没找到则返回第一个不小于_Val的元素。(或者说其返回值是,在不破坏顺序的原则下,第一个可安插_Val的位置,这个好理解)

    int A[] = {1, 2, 4,4, 9, 13};

    const int N = sizeof(A)/ sizeof(int);

    int* p = lower_bound(A, A + N, 4); //指向第一个4

 

    int A[] = {1, 2, 4,4, 9, 13};

    const int N = sizeof(A)/ sizeof(int);

    int* p = lower_bound(A, A + N, 5); //指向9

版本2同binary_search的版本2.

 

upper_bound

template<classForwardIterator, class Type>

   ForwardIterator upper_bound(

      ForwardIterator _First,

      ForwardIterator _Last,

      const Type& _Val

   );

其返回值是,在不破坏顺序的原则下,最后一个可安插_Val的位置。注意,如果按lower_bound的第一种方式理解,upper_bound应该是返回最后一个等于_Val的位置,但实际上不是这样的,即

    int A[] = {1, 2, 4,4, 9, 13};

    const int N = sizeof(A)/ sizeof(int);

    int* p = upper_bound(A, A + N, 4); //返回的是9还是4的位置

 

equal_range

template<classForwardIterator, class Type>

   pair<ForwardIterator, ForwardIterator>equal_range(

      ForwardIterator _First,

      ForwardIterator _Last,

      const Type& _Val

   );

结合了lower_bound和upper_bound,其返回值是一对iteratorsi和j,其中ishi在不破坏顺序的前提下_Val可安插的第一个位置,j则是在不破坏顺序的前提下_Val可安插的最后一个位置。

    int A[] = {1, 2, 4,4, 9, 13};

    const int N = sizeof(A)/ sizeof(int);

    pair<int*, int*> result = equal_range(A, A + N, 4);//返回4,9

 

merge

算法merge可将两个已排序的ranges合并成单一一个已排序的range。这个动作属于稳定行为,意思是能够保持inputrange中每个元素的相对顺序。面对inputs中的等价元素,从第一个range来的元素会排在第二range来的元素之前。

    int A[] = {1, 2, 4,4, 9, 13};

    int B[] = {3,67,100};

    const int N = sizeof(A)/ sizeof(int);

    const int M = sizeof(B)/ sizeof(int);

    int C[N+M];

    merge(A, A+N, B,B+M, C);

 

inplace_merge

如果两个连接一起的ranges[first,middle)和[middle,last)都已排序,那么inplace_merge可将她们结合成单一已序range。

    int A[] = {1, 3, 5,2, 4, 6};

    const int N = sizeof(A)/sizeof(int);

    inplace_merge(A, A+3, A+N);

 

在SortedRanges身上执行集合(Set)相关操作

 

includes

template<classInputIterator1, class InputIterator2>

   bool includes(

      InputIterator1 _First1,

      InputIterator1 _Last1,

      InputIterator2 _First2,

      InputIterator2 _Last2

   );

算法includes可测试某个集合是否为另一集合的子集合,此处两个集合都以SortedRanges来表示。也就是说,并且仅当[_First2,_Last2)中每个元素在[_First1,_Last1)中存在等价元素,那么includes便返回true。

    int A[] = {1, 2, 3,4, 5, 6};

    int B[] = {1, 3, 9};

    int C[] = {1, 3, 6};

 

    bool b1 = includes(A, A + 6, B, B+3); //false

    bool b2 = includes(A, A + 6, C, C+3); //true

 

set_union

算法set_union可构造出两集合之联集。

如果某个值在[first1,last1)出现n次,同样的值在[first2,last2)出现m次,那么该值在outputrange中会出现max(n,m)次。

    int A[] = {1, 2, 3,3, 4, 5, 6};

    int B[] = {1, 3, 9};

    int C[10];

    set_union(A, A+7, B, B+3, C);//结果为1 2 33 4 5 6 9

 

set_intersection

算法set_intersection可以构造出两集合的交集。

如果某个值在[first1,last1)出现n次,同样的值在[first2,last2)出现m次,那么该值在outputrange中会出现min(n,m)次。

 

    int A[] = {1, 2, 3,3, 4, 5, 6};

    int B[] = {1, 3, 9};

    int C[10];

    set_intersection(A,A+7, B, B+3, C); //结果为1,3

 

set_difference

算法set_intersection可以构造出两集合之差。即S1 - S2

如果某个值在[first1,last1)出现n次,同样的值在[first2,last2)出现m次,那么该值在outputrange中会出现max(n-m,0)次。

    int A[] = {1, 3, 3,3, 4, 5, 6};

    int B[] = {1, 3, 9};

    int C[10];

    set_difference(A, A+7, B, B+3, C); 结果为 3 3 45 6

 

set_symmetric_difference

算法set_intersection可以构造出两集合之对称差。即(S1- S2) U (S2 - S1)

    int A[] = {1, 3, 3,3, 4, 5, 6};

    int B[] = {1, 3, 9};

    int C[10];

    set_symmetric_difference(A,A+7, B, B+3, C); //3 3 4 56 9

 

13.3 堆的相关操作

堆(heaps)并不是sorted ranges。堆中的元素并不会以递增顺序排列,而是以一种更复杂的方式排列。堆内部事实上是以树状方式来表现一个sequentialrange,该树状结构系以[每个节点小于或等于其父节点]的方式构造。

由于三个理由,使得堆和sorted ranges有密切关系。第一,和sorted ranges一样,堆提供高效率的方式来取得其最大元素。如果[first,last)是一个堆,那么*first便是堆中的最大元素。第二,我们可以在对数时间内以push_heap为堆增加新元素,或以pop_heap为堆移除某元素。第三,有一种简单而又效率的算法,称作sort_heap,可将堆转成sortedrange。

 

堆是一种表现priority queues的方便法门,后者的每一个元素都以任意顺序安插,但移除的顺序是由最大到最小。例如STL有一个containeradapter priority_queue就是以堆实现出来。

也就是不管你以什么顺序往堆中插入一堆元素,删除时(pop)都是以从大到小的顺序进行的。

    priority_queue<int>queue1;

    queue1.push(1);

    queue1.push(10);

    queue1.push(7);

    queue1.push(40);

 

    ASSERT(queue1.top() == 40);

    queue1.pop(); //从堆定弹出一个元素

    ASSERT(queue1.top() == 10);

    queue1.pop();

    ASSERT(queue1.top() == 7);

    queue1.pop();

    ASSERT(queue1.top() == 1);

    queue1.pop();

 

make_heap

可将任意的range[first,last)转换成一个堆。

    int A[] = {1, 2, 6,9, 4};

    make_heap(A, A + 5); // 9 4 6 2 1

 

push_heap

可以为堆增加新元素。其接口有点特别:堆以[first,last-1)表示,新增的元素为*(last-1).

template<class RandomAccessIterator>

   voidpush_heap(

     RandomAccessIterator _First,

     RandomAccessIterator _Last

   );

    int A[] = {1, 2, 6,9, 4, 90};

    make_heap(A, A + 5); // 9 4 6 2 1 90

    push_heap(A, A + 6); // 90 4 9 2 1 6,即相当于把90加入到(A, A + 5)形成的堆中了,变成(A, A + 6)形成的堆。

 

pop_heap

移除堆[first,last)内的最大元素。

    int A[] = {1, 2, 6,9, 4};

    make_heap(A, A + 5); // 9 4 6 2 1

    pop_heap(A, A + 5);  // 64 1 2 (9)

注意,这儿的移除并不是真正的移除,heap相关的函数是不会对被处理容器内的元素做什么的,它仅仅是调整元素的排列顺序而已。如上,pop_heap之后,仅仅是把最大值9移到容器的最后去了而已,要真正移除元素,还得容器自己去办!

 

sort_heap

将堆转换为sorted range。请注意,它并不是个稳定排序法,也就是说它不一定会保持等价元素的相对顺序。

    int A[] = {1, 2, 6,9, 4};

    make_heap(A, A + 5);

    sort_heap(A, A + 5); // 1 2 4 6 9

 

is_sorted

判断是否为一个堆,STL中没有定义,只存在于SGI STL中。

 

 

第14章 迭代器类

第15章 函数对象类

第16章 容器类

除了关联式容器和关联式容器,STL还定义了三种containeradapters。它们自身并不是容器,它们不是Container的models,它们可以提供受限的功能。

16.1序列

C++standard定义了三种序列:(1)vector(一种简单的数据结构,提供元素的快速随机访问能力);(2)deque(一种较为复杂的数据结构,允许高效率地在容器两端安插及移除元素);(3)list(双向链表)。

C++standard并未提供单向链表,不过那是常见的一种扩充物。

16.1.1vector

vector是一种序列容器,支持随机访问元素、常量时间内在尾段安插和移除元素,以及线性时间内在开头或中间安插或移除元素。vector是STL容器类中最简单的一个,很多情况下也是最有效率的。通常它的实现方式是把元素安排在连续的存储块中,这使得vector的迭代器可以是一般的指针。

vector的大小(size,其所包含的元素个数)与容量(capacity,其内存所能容纳元素的个数)之前有很重要的差别,注意区分

vector自动执行内存重新分配动作时,通常以2为因数来增加容量。因此容量的增加与目前的容量成正比,而不是一个固定常量。即不是按每次多分配同样多内存来的,而是根据当前的容量乘2.这一点很重要。如果按固定常量分配,会使一串元素的安插动作呈二次方复杂度,而按2为因数来增加,只呈线性时间的复杂度。

一旦vector的内存重新分配,其iterators便失效了。此外,在vector中间安插或删除元素,也会使指向该安插点或删除点之后元素的所以iterators都失效。这表示如果你以memberfunction reserve来预先分配内存,或如果所有安插或删除动作都在vector尾段进行,那么你就可以使vector的迭代器不失效。(意识到这点很重要,比如我们经常遍历按条件删除容器中的元素时,不小心就把某些元素给跳过了,我们该怎么避免呢?答:通过erase的返回值可以解决

你可以使用reserve来增加vector的容量,但没有任何一个成员函数可以为我们缩小容量,比较简单的办法,例:

template<classT, class Allocator>

voidshink_to_fit(vector<T,Allocator>& v)

{

    vector<T, Allocator> tmp(v.begin(),v.end());

    tmp.swap(v);

}

感觉效率好像也不高,不过我们一般也没有必要收回内存,呵呵。

重要又不熟悉的成员函数:

1.注意反向迭代器和正向迭代器的区别?

int main() {

    vector<int> v;

    v.reserve(4);

    v.push_back(1);

    v.push_back(2);

    v.push_back(3);

    v.push_back(4);

 

    vector<int>::reverse_iterator iterRBegin= v.rbegin();

    vector<int>::reverse_iterator iterREnd   = v.rend();

    for(vector<int>::reverse_iteratoriterR = iterRBegin;iterR != iterREnd;iterR++)

    {

       cout << *iterR<<endl;

    }

 

    system("pause");

}

结果:

4

3

2

1

 

2.size、max_size、capacity有什么区别?

max_size一般为3fffffff,即1G-1个,其他两个现在就好理解了。

 

3.如何创建一个有4个元素的vector,并且每个元素的初始值都是相同的?

如果照我以前的做法,我可能是一个个push_back,好傻,其实可以:

vector<int> v(4, 0);

 

4.尽量用reserve提前分配内存,以前一直没注意这个问题,呵呵

 

5.如何取第一个元素或最后一个元素?

以前都是首先用begin或rbegin求得对应元素的迭代器,再*iterator求值,实际上我们可以直接用front或back就行了,多简单啊!

 

6.除了push_back还有insert也很好使?

比如push_back的内部实现其实就是v.insert(v.end(), 5);

iterator insert(

   iteratorIter,

   const T&x = T( )

);

将元素插入到Iter之前,由此可见,end()跟NULL还有些不一样,至少看起来好像可以保证end()-1后肯定就是最后一个元素,而NULL管你怎么加都不能加到有效的元素那儿。但不知道为什么没人在end()上进行减操作?

 

7.resize相当与一下子建立多少个元素,并赋相同的初值,和前面的构造函数差不多,不过它是在原来的基础上,有可能不用分配内存。

    vector<int> v;

    v.push_back(1);

    v.push_back(2);

    v.push_back(3);

    v.resize(2); //v的元素为:12,即它不会影响没动位置的那些元素,最多只影响增加的那些元素。例

 

    vector<int> v;

    v.push_back(1);

    v.push_back(2);

    v.push_back(3);

    v.resize(5, 6); //1 2 3 6 6

 

8.assign这个函数我以前也用得很少,它的作用:

删除所有的元素,再重新根据输入产生一些元素。它跟resize的最大区别就是以前的元素肯定会被影响。

 

9.所有的标准容器都包含一个默认模板实参class _A = allocator<_Ty>,容器会用_A生成一个成员变量,用来控制内存分配和释放,对于vector,例

    _Aallocator;

    iterator_First, _Last, _End;

    };

为什么要加一个这样的模板参数呢,

For example, an allocator object might allocatestorage on a private heap. It might allocate storage on a far heap, requiringnonstandard pointers to access the allocated objects. It might also specify,through the type definitions it supplies, that elements be accessed throughspecial accessor objects that manage shared memory, or perform automatic garbagecollection. Hence, a class that allocates storage using an allocator objectshould use these types for declaring pointer and reference objects, as thecontainers in the Standard C++ Library do.

也就是说,通过提供我们自己的allocator对象,我们就能实现上述目的。不过,大部分情况下,默认的alloctor已经够用了,真正系统的瓶颈在于内存分配的时候还是比较少的,需要重写allocator的时候和需要重写new的时候是差不多的。

allocate最重要的两个函数是allocate和deallocate,相当于new和delete的功能。我所见到的,都是通过allocator.allocate(_N,(void *)0);调用的,第二个参数总是传0。

 

vector是最简单的一个容器了,其中_First维护着第一个元素的地址,_Last维护着最后一个元素后面的地址,_End维护着所分配内存后面的地址。参考

size_type capacity() const

    {return(_First == 0 ? 0 : _End - _First); }

size_type size() const

    {return(_First == 0 ? 0 : _Last - _First); }

理解了这三个成员,基本上也就理解了整个vector的实现。

 

看看vector的迭代器究竟是什么,如下

typedef _Ty _FARQ *pointer;     // 在allocate中typedef的

// 在vector中typedef的

typedef _A::pointer _Tptr;

typedef _Tptr iterator;

由此可见,vector的迭代器是非常简单的,比如当元素类型是int的时候,那么vector<int>::iterator实际上就是int*,也就是元素类型的指针,其他迭代器以此类推。

iterator begin()。参考

    {return(_First); }

 

 

16.1.2 list

能够以常量时间在list开头、尾端、中间处安插及移除元素。list有一个重要性质:安插与接合(splicing)动作不会造成指向list的迭代器失效,即使是删除动作,也只会令指向被删除元素的那个迭代器失效。这个的意思是,假如我已经记录了一个元素的迭代器为itor1,我在list中进行安插等操作,itor1始终指向原来的那个元素,不像vector重新分配内存后迭代器就失效了。例

int main() {

    list<int> v;

    v.push_back(1);

    v.push_back(2);

    v.push_back(3);

 

    list<int>::reverse_iterator itor1= v.rbegin();

    cout << *itor1<< endl;

 

    v.pop_front(); //即使是删除动作,不会令迭代器itor1失效

    cout << *itor1<< endl;

 

    system("pause");

}

 

 

有时候[只允许forward移动]的单链表也是很有用的。如果你不需要backward移动,slist可能会比list更有效率。

 

理解:

list大部分函数都和vector的差不多,一些需要确认的如下:

1.size_type size( ) const;

返回list的元素个数。注意,size的运行复杂度为O(N).你应该预期它是O(1).参考

size_type size() const

    {return(_Size); }

 

2.splice(接合)

int main() {

    list<int> v;

    v.push_back(1);

    v.push_back(2);

    v.push_back(3);

 

    list<int> v2;

    v2.push_back(100);

 

    v2.splice(v2.begin(), v);//v2为1 2 3 100,v为空,因为被删除掉了,注意两端的定义(back端 100 3 21 front端),遍历时是从front端到back端,注意了!!所以打印出来是1 2 3 100

    system("pause");

}

Removes elements from the argument list and insertsthem into the target list.

void splice(

   iterator_Where,

  list<Allocator>& _Right

);

void splice(

   iterator_Where,

  list<Allocator>& _Right,

   iterator_First

);

void splice(

   iterator_Where,

  list<Allocator>& & _Right,

   iterator_First,

   iterator_Last

);

插到_Where之前,呵呵。

 

3.实现如下

    _A allocator;

    _Nodeptr_Head;

    size_type_Size;      // 保存了节点个数

    };

 

看看它的迭代器究竟是什么东东

    classconst_iterator : public _Bidit<_Ty, difference_type> {

    public:

       //...

       const_iterator(_Nodeptr_P)

           :_Ptr(_P) {}

       const_iterator&operator++()      // 前置++

           {_Ptr= _Acc::_Next(_Ptr);

           return(*this); }

       const_iteratoroperator++(int)    // 后置++

           {const_iterator_Tmp = *this;

           ++*this;

           return(_Tmp); }

       //...

    protected:

       _Nodeptr_Ptr;  // 迭代器内部实际上仅仅就是保存了一个节点指针而已

       };

 

list中定义了节点定义_Node和迭代器定义iterator、const_iterator,然而,在迭代器的成员函数中经常要访问_Node类型且_Node是保护的,以前我的做法都是将这些定义都放到外面去,即list外面去,这样有一个坏处,用户包含list头文件就可以直接访问其内部实现_Node、iteratord等,这是我们不希望的,这个地方的做法是

friend class const_iterator;

class const_iterator : public _Bidit<_Ty,difference_type> {

以后可以借鉴一下。

 

看看如下代码都做了些啥事情

list<int> lst;

lst.push_back(1);

lst.push_back(2);

 

list<int> lst;实际上分配了一个头结点出来,它是不放值的。参考

explicit list(const _A& _Al = _A())

    :allocator(_Al),

    _Head(_Buynode()),_Size(0) {}

_Buynode意思是买一个节点,实际上就是分配一个节点出来,如果不传参数,_Next和_Prev都是指向自己本身,参考

    _Nodeptr_Buynode(_Nodeptr _Narg = 0, _Nodeptr _Parg = 0)

       {_Nodeptr_S = (_Nodeptr)allocator._Charalloc(

           1 *sizeof (_Node));

       _Acc::_Next(_S)= _Narg != 0 ? _Narg : _S;

       _Acc::_Prev(_S)= _Parg != 0 ? _Parg : _S;

       return(_S); }

节点是如何插入的呢,请看

    iteratorinsert(iterator _P, const _Ty& _X = _Ty())

       {_Nodeptr_S = _P._Mynode();

       _Acc::_Prev(_S)= _Buynode(_S, _Acc::_Prev(_S)); // 首先把新节点的_Next和_Prev设置对,并让_P的_Prev指向它

       _S =_Acc::_Prev(_S);  // 让临时指针指向新节点

       _Acc::_Next(_Acc::_Prev(_S))= _S;    // 找到新节点的前一个节点,并它其_Next指向新节点,此时挂接完毕

       allocator.construct(&_Acc::_Value(_S),_X);

       ++_Size;

       return(iterator(_S)); }

首先要搞清楚双链表的结构了,如图,才容易看懂代码。注意

iterator end()

   {return(iterator(_Head)); }

由此可见,头指针就是end。这是以前没想到的,呵呵。

 

16.1.3 slist

单链表,因为不在STL中,故不关注了,呵呵。

 

16.1.4deque

deque类似vector.和vector一样,它也是一种sequence,支持元素随机访问、常量时间内于尾端安插或移除元素、线性时间内于中间处安插或移除元素。

deque和vector的差异在于,deque另外还提供常量时间内于序列开头新增或移除元素(而vector在开头插入就非常费时了,需要把后面的元素都要移动一下,即我们经常需要在序列开头新增或移除元素并且需要随机访问时,就用deque;如果只需要在尾端新增或移除元素并且需要随机访问,则用vector更快;如果需要在中间插入或不需要随机访问,则应该用list)。在deque开头或尾端安插一个元素,需要amortizedconst time,在中间处安插元素的复杂度则于n成线性关系,此处n是安插点距离开头于尾端的最短距离。

另一个差异是deque不会像vector那样会在新增元素时,空间不够的时候会拷贝已有的元素到新分配的内存,再增加新增的元素。对deque而言,类似vector内存重新分配的行为是不会发生的。

一般来说,insert(包括push_front与push_back)会造成指向deque的所有迭代器失效。例

int main() {

    deque<int> d;

    d.push_back(1);

    d.push_back(2);

    d.push_back(3);

 

    deque<int>::iterator itor = d.begin();

    //d.push_front(0);

    //d.push_back(4);

   

    *itor = 7; //经过push_front或push_back之后,之前的itor已经失效,故在这儿会出现断言,itor已经失效了。

 

    system("pause");

}

deque通常内含一个表头,指向一组节点,每个节点包含固定数量并且连续存储的元素,当deque增长时便增加新的元素节点。(即deque不像vector那样是一块连续的内存。)如图所示,见<<The C++Standard Library>>

deque的内部细节对你并不重要。重要的是能够认识到deque远比vector复杂。你应该能够预期,dequeiterator上的任何操作都要比vector iterator上的相同行为慢许多。

除非你需要的功能只有deque才提供(例如在常量时间内于开头做安插或移除动作),否则应该尽量使用vector。同样道理,对deque排序几乎不会是个好主意,你应该将此deque的元素复制到一个vector,然后对此vector排序,再将所有元素复制回deque,这样会比较快。

 

除了deque(双向),STL中还有queue类,实际上它仅仅是把deque(单向)包装了一下,实现一边进一边出的容器,当然函数也会少些,但要记住,它本质就是deque。

 

16.2Associative Containers(关联式容器)

C++Standard提供四种关联式容器类:set、map、multiset和multimap。

C++Standard并未包含Hashed Assocative Containers,然而hash tables(散列表)却是很常见的一种扩充功能。目前最广为流行的

STLhash table是SGI的版本。

 

16.2.1set

template<

   class Key,

   class Traits=less<Key>,

   class Allocator=allocator<Key>

classset

set是一种SortedAssociative Container,能够存储类型为Key的对象。它是一种Simple Associative Container,意指其valuetype和key type都是Key。它同时也是Unique AssociativeContainers,表示没有两个元素相同。

由于set是一个SortedAssociate Container,其元素一定会以递增顺序排列。

往set和multiset中插入元素是一个很快速的行为。(所以,我们想要自动排序的时候,可以考虑用它)

和list一样,set具有数个重要性质:新元素的安插并不会造成既有元素的迭代器失效,从set中删除元素也不会令任何迭代器失效-当然被移除元素的迭代器除外,呵呵。

例:

int main() {

    set<int> d;

    d.insert(20);

    d.insert(3);

    d.insert(2);

    d.insert(2); //最终只能有一个2元素

 

    set<int>::iterator itor = d.begin();

    d.insert(5);

 

    assert(*itor == 2);//迭代器不会失效

 

    system("pause");

}

这个东西用来去掉重复的元素很有用,并且还是排好序的。根本不用像vector那样,需要先排序,再unique,比如在TCC中去掉被选中行中的重复值就可以用它,可惜用的是list,麻烦死了。

 

16.2.2map

比较熟悉,大部分和set差不多,和list一样,map也是,新元素的安插不会造成既有元素的迭代器失效。自map中删除元素也不会造成任何迭代器失效-除了被移除元素的迭代器外。例

    set<int> d;

    d.insert(1);

    d.insert(2);

    d.insert(6);

    d.insert(5);

    d.insert(7);

 

    set<int>::iterator itorBegin= d.begin();

    set<int>::iterator itorEnd   = d.end();

    for(set<int>::iterator itor = itorBegin; itor != itorEnd; )

    {

       set<int>::iterator itorT = itor;

       itor++;

       d.erase(itorT);

    }

要一个个删除元素,可以这样,因为erase不会造成其他迭代器失效,而vector就不行。

 

16.2.3 multiset

它和set唯一的不同是可以容纳两个或多个相同的元素。例

    multiset<int> d;

    d.insert(1);

    d.insert(2);

    d.insert(6);

    d.insert(2);

    d.insert(7); //元素为12 2 6 7

 

    //返回键值为的元素个数

    multiset<int>::size_type count = d.count(2);

 

16.2.4 multimap

道理同multiset与set的关系。例

    multimap<int, char> d;

    d.insert(make_pair(1, 'a'));

    d.insert(make_pair(2, 'b'));

    d.insert(make_pair(3, 'c'));

    d.insert(make_pair(2, 'd'));

 

 

    //返回键值为的元素个数

    multimap<int, char>::size_type count = d.count(2);

    //char t = d[2];因为2可能对应多个值,显然[2]不能被定义,这也是和map的一大差异。

2.

pair <const_iterator,const_iterator> equal_range (

  const Key& _Key

) const;

pair <iterator, iterator>equal_range (

  const Key& _Key

);

功能:找出[键值等价与Key]的所有元素所形成的range。

 

16.2.5 hash_set

在重视快速查找的应用程序中,hash_setclass很有用。然而如果元素必须以特定顺序排列的话,那么set比较适合。

例如

void print ( int elem1)

{

    cout << elem1<< endl;

}

 

 

int main() {

 

    stdext::hash_set<int> s;

    s.insert(4);

    s.insert(3);

    s.insert(4);

    s.insert(9);

    s.insert(-1);

    s.insert(78);

    for_each(s.begin(), s.end(), print);

   

    system("pause");

}

结果:

3

9

-1

4

78

除此之外,hash_set和set还有什么本质区别呢?

一个是二叉树,一个是hash表,大部分情况下,二叉树更稳定更快一些,除非hash函数写得很好,让元素能均匀分布在各个桶中,这样每个桶中元素个数就少了,最好每个桶就一个元素(这样一次hash直接就定位到了),这样,做什么都就快了。从基本用法来说,如果你不显式设置_Comp,hash_set与set、hash_map与map的用法几乎是一模一样的。

12.2.6 hash_map

同hash_set与set的关系

 

对于hash_XXX类,我们使用时最可能会涉及到的就是

template<class_Kty,

    class _Pr = _STD less<_Kty>>    // 排序函数

    class hash_compare

    {   // traits class for hash containers

public:

    enum

        {   // parameters for hash table

        bucket_size= 4,    // 0< bucket_size  the mean(平均的) number of elements per "bucket"(hash-table entry)

        min_buckets= 8};   //min_buckets = 2 ^^ N, 0 < N 桶数,必须是2的N次方,还必须大于0

 

    hash_compare()

        : comp()

        {   // construct with default comparator

        }

 

    hash_compare(_Pr _Pred)

        : comp(_Pred)

        {   // construct with _Pred comparator

        }

 

    size_t operator()(const _Kty& _Keyval) const        // 重载后用来计算hash值,即hash函数

        {   // hash _Keyval to size_t value by pseudorandomizingtransform

        ldiv_t _Qrem = ldiv((long)hash_value(_Keyval), 127773);

        _Qrem.rem = 16807 * _Qrem.rem - 2836 * _Qrem.quot;

        if (_Qrem.rem < 0)

            _Qrem.rem += 2147483647;

        return((size_t)_Qrem.rem);

        }

 

    bool operator()(const _Kty& _Keyval1,const _Kty&_Keyval2) const   // 重载后用来比较两个元素的key值,排序就靠它了,仅仅包装了一下排序函数

        {   // test if _Keyval1 ordered before _Keyval2

        return (comp(_Keyval1, _Keyval2));

        }

 

protected:

    _Pr comp;   // the comparator object

    };

类,它是所有hash_XXX类默认用来排序和hash元素的模板实参。

MSDN上说我们可以从hash_compare继承,通过覆盖某些函数(也就是比较函数和hash函数,其他还有啥啊),或者搞个全新的用来排序和hash元素的类,达到满足我们要求的目的。其实需要我们提供的,最多也就是排序函数hash函数

 

(1)

hash_map<MyStr, MyInt, hash_compare <MyStr, less_str > > hm1; // 看看第三个模板实参传到哪儿去了

 

(2)

template<class_Kty,

    class _Ty,

    class _Tr = hash_compare<_Kty, _STD less<_Kty>>,

    class _Alloc = _STD allocator< _STD pair<const _Kty, _Ty> >>

    class hash_map

        : public_Hash<_Hmap_traits<_Kty, _Ty, _Tr, _Alloc, false> >

    {   // hash table of {key, mapped} values, unique keys

public:

    typedef hash_map<_Kty, _Ty, _Tr, _Alloc> _Myt;

    typedef_Hash<_Hmap_traits<_Kty, _Ty, _Tr, _Alloc, false> > _Mybase;

 

    hash_map()

       : _Mybase(key_compare(),allocator_type())  // 用第三个模板实参产生了一个临时对象传递给了hash_map的父类

       {   //construct empty map from defaults  // 注意,hash_compare(Traits pred )构造也是可以的,也就是说less_str是一个可变的东西,我们可以随时换成其他的,同时也解开了我的迷惑,为什么要构造一个临时hash_compare对象再赋值给hash_compare类型成员_Tr comp,因为它的构造函数是可以带排序函数参数的。

       }

 

(3)

template<class_Traits>

    class _Hash

        : public_Traits    // traits serves as base class _Hmap_traits又成了_Hash的基类

    {   // hash table -- list with vector of iterators for quickaccess

public:

 

    explicit _Hash(const key_compare& _Parg,

        const allocator_type& _Al)

        : _Traits(_Parg), _List(_Al),     // 将第三个模板实参产生的临时对象又传递到父类去了

            _Vec(min_buckets + 1, end(),_Al),

            _Mask(1),_Maxidx(1)

        {   // construct empty hash table

        }

 

    _Mylist _List;  // the list of elements, must initialize before _Vec  // 其实实现hash存储的主要成员变量就这几个。而comp和存储无关,可以简单地把它看做是排序和hash元素的函数指针而已

    _Myvec _Vec;    // the vector of list iterators

    size_type _Mask;    // the key mask

    size_type _Maxidx;  // current maximum key value

    };

 

(4)

template<class_Kty,    // key type

    class _Ty,  // mapped type

    class _Tr,  // comparator predicate type

    class _Alloc,   // actual allocator type (should be value allocator)

    bool _Mfl>  // true if multiple equivalent keys are permitted

    class _Hmap_traits

        : public_STD _Container_base

    {   // traits required to make _Hash behave like a map

 

    _Hmap_traits(const _Tr& _Traits)  //_Traits引用的是第三个模板实参产生的那个临时对象

        : comp(_Traits)

        {   // construct with specified comparator

        }

 

    _Tr comp;   // the comparator predicate for keys 最终第三个模板实参产生的临时对象赋值给它去了,comp实际山也就是hash_map的一个成员变量而已,简单一点说,hash_map中包含有一个hash_compare类型的成员变量。

    };

 

感言:真不适应看模板代码,好抽象,搞得好复杂,我怎么都写不出这种代码来!在hash中如何插入,删除元素我暂时不具体研究,先大概理解和会用吧。

 

12.2.7 hash_multiset

同hash_set与set的关系

 

12.2.8 hash_multimap

同hash_set与set的关系

 

16.3 Container Adapters

stack,queue和priority_queue等Classes并非容器(因为它们没有提供容器都具有的遍历行为),它们只提供container操作行为的有限子集。例如,stack只允许你安插、移除或检查栈顶端的元素。这些classes被称为adapters。因为它们以underlyingcontainer为实现基础。

Stack、queue和priority_queue都是常见的数据结构,但相应的containeradapters的接口可能令人感到陌生。这三种classes都具有成员函数pop,可移除最顶端的元素,而且不返回任何值,你可能会感到奇怪,为何pop()不返回被移除的值呢?

如果pop必须返回被移除的值,那么必须以by value而非by reference的方式返回(因为元素已被移除,没有留下任何东西了)。然而,returnby value很没有效率,因为它至少会调用一次不必要的copy constructor。由于对pop()而言无法有效率而又正确地将返回值返回,所以明确的做法就是不让他返回任何东西。如果你想要检查最顶端元素,可以调用其他成员函数。

16.3.1 stack

它允许安插,移除及审查stack最的顶端元素。这是一种后进先出的数据结构,最顶端元素即是最后新增元素。

除了最顶端元素外,没有任何方法能够访问stack的其他元素;stack不允许遍历其元素。这项限制是stack存在的唯一理由,因为任何Front Insertion Sequence或Back Insertion Sequence都足以胜任栈的要求。例如,以vector而言,栈的行为是成员函数back,push_back,pop_back。使用containeradapter而不直接使用Sequence的唯一理由是,让你清楚知道你正在执行栈的行为。

由于stack是一个container adapter,它实现于某个底部的container之上。缺省的底部类型是deque,但也可以明确指定不同的底部类型。

Suitable underlying container classes forstack include deque, list, and vector, or any other sequence container thatsupports the operations of back, push_back, and pop_back.

 

通过查看其代码,它完全就是对其他容器进行了一层简单包装,减少了一些接口而已,太简单了,没啥研究的。它存在的理由,估计也就是红色部分所示。

 

16.3.2 queue

它是一种先进先出式的数据结构。也就是说允许在queue的尾端新增元素,并在queue的前端将该元素移除。Q.front()分那会的是最早被加入queue的元素。

道理同stack,比如存在的唯一理由,呵呵。

queue默认的底层类型是deque,但也可以指定不同的底部类型。参考MSDN如下

Suitable underlying container classes forqueue include deque and list, or any other sequence container that supports theoperations of front, back, push_back, and pop_front.

 

通过查看其代码,它完全就是对其他容器进行了一层简单包装,减少了一些接口而已,太简单了,没啥研究的。

 

16.3.3 priority_queue优先队列

它提供安插、审视、移除最顶端元素的功能。没有任何机制可以更改priority_queue的任何元素,或是遍历这些元素。

缺省的底层类型是vector,但也可以指定不同的底层类型。

priority_queue是标准的数据结构,可以不同方式实现出来。通常priority_queue是以heap来实现,并以算法make_heap、push_heap和pop_heap来维护heap的状态。

 

这是VC6中的核心成员函数实现:

    value_type&top()

       {return(c.front()); }

    constvalue_type& top() const

       {return(c.front()); }

    voidpush(const value_type& _X)

       {c.push_back(_X);   // 将添加的元素放到容器的最后一个元素位置

       push_heap(c.begin(),c.end(), comp); }   // 将最后一个元素纳入进行堆处理

    voidpop()

       {pop_heap(c.begin(),c.end(), comp);   // 将最大的元素调整到容器的最后一个元素位置

       c.pop_back();}  // 移除最后一个元素,从而实现了移除最大的那个元素

从代码我们可以得到以下几点结论:

1.经过堆处理后,最大的元素确实被排在容器的第一个元素位置。

2.注意,这儿的最大并不是绝对的,它依赖我们提供的比较函数,默认是less,例如,当

priority_queue <int, vector<int>, greater<int> > q3;的时候,情况就刚好相反了,堆以为自己把最大的值放到容器第一个元素位置了,但实际上把最小的那个放过去了。在比较时,它会把两个元素传递给比较函数比较,只要返回true,那么它就认为第一个元素小,第二个大,该怎么调整它就怎么调整。

3.具体内部采用什么容器存储元素,参考MSDN如下

Suitable underlying container classes for priority_queue include dequeClass and the default vector Class or any other sequence container thatsupports the operations of front, push_back, and pop_front and a random-accessiterator.

4.因为内部是采用堆,所以它和map的复杂度都是对数logarithm级别的,二者有没有什么区别和联系呢,在用map的时候可以考虑一下用priority_queue行不行,在都满足应用的时候,两者速度是否有区别呢.我估计priority_queue会快一点点,毕竟它实现较简单,但map支持查找和遍历,所以map应用广泛得多,即priority_queue可以办到的,map都能办到,但是map能办到的,priority_queue就不一定可以办到。

5.不管是priority_queue还是map等,只要涉及比较函数的地方,默认一般都是用less,而在less内部,真正比较的地方,则是用的小于号,这就是为什么map中的元素一定要支持小于符号的真正原因,但是,如果比较函数不用less,比如我用greater,那么内部正真比较就用的是大于符号了,此时map中的元素就不再需要支持小于而必须要支持大于符号了,证明如下

#include <iostream>

#include <map>

#include <algorithm>

using namespace std;

 

struct Node

{

    Node(int i)

    {

       m_i = i;

    }

 

    int m_i;

};

 

bool operator > (Node node1, Node node2)   // 现在我们需要的不是小于号而是大于号了

{

    return node1.m_i >node2.m_i;        // 要是我们在这儿动下手脚,也可以影响排序的:)

}

 

void print(const pair<Node, int>& elem)    // 最好实现为引用,记得加const,否则编译不过,我之前就纳闷,应该支持应用才对啊,否则性能受影响,原来是没加const

{

    cout << elem.first.m_i<< ' ';

}

 

int main( )

{

   map<Node, int,greater<Node> > map1;  // 通过设置比较函数,从而可以影响排序

   map1[Node(2)] = 2;

   map1[Node(5)] = 5;

   map1[Node(6)] = 6;

 

   for_each(map1.begin(),map1.end(), print); // 6 5 2,默认可是2 5 6的:)

 

   return 0;

}

再扩展一下,要是我们既不用less,也不用geater,而是我们自己随便搞的一个函数,函数内部不一定用< 、>去比较,此时我们就连大小于函数都不用重载了,完全可以灵活的玩!

 

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当读者有一定c/c++基础 推荐的阅读顺序: level 1 从<>开始,短小精悍,可以对c++能进一步了解其特性 以<>作字典和课外读物,因为太厚不可能一口气看完 level 2 然后从<>开始转职,这是圣经,请遵守10诫,要经常看,没事就拿来翻翻 接着是<>,个人认为Herb Sutter主席大人的语言表达能力不及Scott Meyers总是在教育第一线的好 顺下来就是<>和<>,请熟读并牢记各条款 当你读到这里,应该会有一股升级的冲动了 level 3 <>看过后如一缕清风扫去一直以来你对语言的疑惑,你终于能明白compiler到底都背着你做了些什么了,这本书要细细回味,比较难啃,最好反复看几遍,加深印象 看完上一本之后,这本<>会重演一次当年C++他爹在设计整个语言过程中的历程 level 4 <>是stl的字典,要什么都可以查得到 学c++不能不学stl,那么首先是<>,它和圣经一样是你日常行为的规范 <>让你从oo向gp转变 光用不行,我们还有必要了解stl的工作原理,那么<>会解决你所有的困惑 level 5 对于c++无非是oo和gp,想进一步提升oo,<>是一本主席这么多年的经验之谈,是很长esp的 一位stl高手是不能不去了解template的,<>是一本百科全书,足够你看完后对于gp游刃有余 <>是太过聪明的人写给明眼人看的 好书有很多,不能一一列举 以上我的读书经历,供各位参考。接下来的无非就是打怪练级,多听多写多看;boost、stl、loki这些都是利器,斩妖除魔,奉劝各位别再土法练钢了。 at last,无他,唯手熟尔。 忘了一本《thinking in C++》 也是经典系列之一 <>这本圣经的作者Scott Meyesr在给<>序言的时候高度的赞赏了Andrei同志的工作:C++社群对template的理解即将经历一次巨大的变化,我对它所说的任何事情,也许很快就会被认为是陈旧的、肤浅的、甚至是完全错的。 就我所知,template的世界还在变化,速度之快就像我1995年回避写它的时候一样。从发展的速度来看,我可能永远不会写有关template的技术书籍。幸运的是一些人比我勇敢,Andrei就是这样一位先锋。我想你会从此书得到很多收获。我自己就得到了很多——Scott Meyers September2000。 并且,Scott Meyers 在最近的Top5系列文章中,评价C++历史里面最重要5本书中、把Modern C++ Design列入其中,另外四本是它自己的effective c++、以及C++ Programming Language、甚至包括《设计模式》和《C++标准文档》。 显然,Scott Meyers已经作为一个顶尖大师的角度承认了<>的价值。 并且调侃地说,可以把是否使用其中模板方法定义为,现代C++使用者和非现代C++使用者,并且检讨了自己在早期版本Effective对模板的忽视,最后重申在新版本Effective第七章节加入大量对模板程序设计的段落,作为对这次失误的补偿。 并且,在这里要明确的是<>并不是一本泛型编成的书,也不是一本模板手册。其中提出了基于策略的设计方法,有计划和目的的使用了模板、面向对象和设计模式。虽然Andrei本人对模板的研究世界无人能敌,但对其他领域的作为也令人赞叹。 任何做游戏的人都不能忽视OpenAL把,你在开发者的名单里能看到Loki的名字:) 最近很忙,无时间写文章,小奉献一下书籍下载地址。虽然经过验证,但是不感肯定各位一定能下: 中文 http://www.itepub.net/html/ebookcn/2006/0523/40146.html 英文 http://dl.njfiw.gov.cn/books/C/Essential%20C
内容简介   许多程序员可能并不知道,C++不仅是一个面向对象程序语言, 它还适用于泛型编程(generic programming)。这项技术可以大大增强你的能力,协助你写出高效率并可重复运用的软件组件(software components)。   本书由知名的C++专家Matthew H.Austern执笔,引导你进入泛型编程思维模型,并将你带往此一模型的最重要成品:C++ Standard Template Library(STL)。本书揭示STL的奥秘,告诉你STL不仅仅是一组方便运用的容器类(container classes)。对于泛型组件和可交互作用的组件而言,STL是一个具备扩充能力的框架(framework)、《泛型编程与STL》阐述了泛型编程的中心思想:concepts、modeling、refinement,并为你展示这些思想如何导出STL的基础概念:iterators、containers、function objects。循此路线,你可以把STL想像为一个由concepts(而非明确之functions或classes)组成的程序库:、你将学习其正式结构并因此获得其潜在威力所带来的完整优势。本书使你能够:   ●以你自己的“可移植组件”及“可交互作用之泛型组件”扩充STL;   ●产生一些算法,让它们和它们所处理之型别(types)及数据结构彻底划清界线;   ●撰写更精致、更高效、更有效力的代码,可跨平台重复使用。 -------------------------------------------------------------------------------- 媒体推荐 书评 《泛型编程与STL》阐述了泛型编程的中心思想:concepts、modeling、refinement,并为你展示这些思想如何导出STL的基础概念:iterators、containers、function objects。循此路线,你可以把STL想像为一个由来的完整优势…… -------------------------------------------------------------------------------- 编辑推荐 《泛型编程与STL》阐述了泛型编程的中心思想:concepts、modeling、refinement,并为你展示这些思想如何导出STL的基础概念:iterators、containers、function objects。循此路线,你可以把STL想像为一个由来的完整优势…… -------------------------------------------------------------------------------- 目录 译序(侯捷) 前言 第一篇 泛型编程导入 第1章 STL巡礼 1.1 一个简单的例子 ……

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值