一、类模板简介:
在现今的C++标准模板库中,几乎所有的东西都被设计为template形式,不支持模板,就无法使用标准程序库。模板库可以认为是针对一个或多个尚未明确的类型而编写一套函数或类型。模板是C++的一个新特性。通过使用模板,C++允许推迟对某些类型的选择,直到想使用模板或者对模板进行专门化处理时,使用模板,可以让程序员面对相似而又略有不同特性时,更快捷地编写代码,提高开发效率。
C++模板功能思想:
泛型编程完美思路:
设想能否研制一种编程机制,让一个函数适应所有的数据类型,包括自己定义的。实现这种机制,既然适应所有的数据类型,称为泛型编程(Generic Programming)。
泛型编程最初提出时的动机很简单直接,就是发明一种语言机制,能够实现:
- 一个通用的标准容器库。所谓通用的标准容器库,就是要能够做到,如一个list类存放所有可能类型的对象;
- 泛型编程让编写完全一般化并可以重复使用的算法,其效率与针对某特定数据类型而设计的算法相同。泛型即是指具有在多种数据类型上皆可操作的含义,这样算法与数据结构完全分离。其中算法是泛型的,不与任何特定数据结构或对象类型联系在一起。
泛型编程的代表作品STL是一种高效、泛型、可交互操作的软件组件。STL以迭代器(iterators)和容器(containers)为基础,是一种泛型算法(generic algoritms)库,容器的存在这些算法又东西可操作。STL包含各种泛型算法(algoritms) 、泛型迭代(iterator)、泛型容器(containers)以及函数对象(function objects)。STL并非只是一些有用组件的集合,它是描述软件组件抽象需求条件的一个正规而有条理的架构。
泛型的第一好处是编译时的严格类型检查。这是集合框架最重要的一个特点。此为,泛型消除了绝大多数的类型转换。如没有泛型,当使用集合框架时,不得不进行类型转换。
以上概括为:它是把数据类型(包括自己定义的class类型)作为一种参数传递进来的机制。
C++模板:
让程序更加智能,C++很需要泛型这种新的编程模式,于是引用了模板这个功能。也就是说在C++中,引入了关键template。
使用模板是为了实现泛型,可以减轻编程的工作量,增加函数的重用性。注意重用性几乎是编程的基本思路,不然也不会发明函数这种功能了。
C++STL的渊源:
有了泛型编程思想,有了模板功能,C++程序员就可以编写出很多通用的针对不同数据类型的算法,其中,STL脱颖而出,称为C++标准,只需掌握这个标准,就不用自己费心费力从头编写一些算法。这就是所谓的开发基本思想:不要重复发明轮子。
简单的说,STL(Standard Template Libarary)标准模板库,是一个具有高可用性的、高效的C++程序库。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法,为广大C++程序员提供了一个可扩展的应用框架,高度体现了软件的可复用性。刚开始可以理解为C++内置的函数库、类算法框架库等。
C++基本概念通览:
- 命名空间:name space是指标识符的可见范围或者有效范围。定义命名空间的目的是防止出现名称冲突现象。命名空间是C++中一项较新的特性。为了将多个程序员开发(或厂商)开发的代码便捷、高效地组合起来,防止重复的函数名、类名等,命名空间可将不同的代码封装在有效的范围内;
- 头文件:头文件的功能主要是将原程序片段收集到一起,形成一个提供给编译程序的文件,一般情况下,头文件中只包含各种声明、常量定义、预编译、注释、类型定义、模板定义等。常规的函数定义、数据定义、导出的模板定义等不应该出现在头文件中。
- 面向对象的程序设计:面向对象时C/C++语言解决问题的一种方法。原来的程序开发是面向过程的。面向过程的程序设计主要考虑解决问题的先后顺序、措施安排等,具有典型的过程性。而面向对象的程序设计主要是建立在各种对象基础的软件开发,每一个具有独立特性和相应功能的个体均可以作为一个对象加以处理。
- C++中的声明和定义:在程序开发领域,声明是指当一个计算机程序需要调用内存空间时,对内存发出的“占位”指令;定义是指声明的变量、函数、类等呈现或描述出来,为其提供一个意义相当的表达,并表明其与众不同之处,最终目的是显示其内涵。
- 指针:指针是C语言中一个重要概念,也是C语言的重要特色。C++沿袭了C语言的这一特色。正确而灵活地使用指针,可以有效地表示复杂的数据结构、动态分配内存、便捷地使用字符串、方便地使用数组、直接处理内存、函数可以返回多个数值等;在内存中,变量存储在特定的地址,通过地址可以找到所需的变量,变量在内存中的地址“指向”该变量单元。一个变量的地址称为该变量的“指针”。如有一个变量用来存储另一个变量的地址,则称它为“指针变量”;在学习过程中,要注意区分指针和指针变量。指针指的是该变量在内存中的存储地址;而指针变量指的是该变量的值是一个指向其他变量的指针值。
- 函数:C/C++程序至少需要一个main()函数。程序往往由多个函数组成。函数是C++源程序的基本模块,通过调用相应的函数,可以实现特定的功能。C/C++语言提供了强大的、极为丰富的库函数。另外,用户或程序开发者可以把自己的思想或算法编写成一个相对独立的函数模块。
- 函数中变量:局部变量和全局变量;变量的存储类别,在使用函数时涉及不同的存储方分为静态存储方式和动态存储方式;全局变量使用静态存储方式;函数的形参和普通局部变量使用动态存储方式:
- auto类型是默认类型,可以省略不写;
- static类型属于静态存储类型,当希望函数中的局部变量的值在函数调用结束后不消失而保留原值时,可用关键字static声明该局部变量;
- C语言中将局部变量的值放在CPU中的寄存器中,这种变量称为“寄存器变量”,用关键字register作声明;
- 如外部变量不存在文件的开头定义,其有效的作用范围只限于定义到文件末尾。函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部声明”,表示该变量是一个已经定义外部变量。有了次“声明”起,合法地使用该外部变量。
- 文件:C/C++中的文件一般是指,存储在外部介质上数据的集合,并且是一组相关数据的有序集合。当文件被使用时,将其调入内存中。文件可分为两种:一种是驻留在磁盘或其他外部介质的一个有序数据集,可以是数据文件、可执行程序;另一种是设备文件,是指和主机相联的各种设备,像显示器、键盘、打印机等;外部设备一般可以看作一个文件来进行管理,它们的输入、输出等同于对磁盘文件的读和写。
- 程序的编译和链接:
- 程序编译:是指将编辑好的源文件翻译成二进制目标代码的过程,编译过程时使用C语言提供的编译程序完成的,不同操作系统下的各种编译器的使用命令不完全相同,使用时应注意计算机环境。编译时,编译器首先要对源程序中的每一个语句检查语法错误。当发现错误后,会提示错误的位置和错误类型的信息。要再次调用编辑器进行查错修改,之后再编译,直到排除所有语法和语义错误。正确的源程序文件经过编译后在磁盘上生成目标文件
- 程序的链接:编译后产生的目标文件是可重定位的程序模块,不能直接运行。链接程序是把目标文件和其他分别进行编译生成的目标程序木块及系统提供的标准库函数链接在一起,生成可运行的可执行文件的过程。
- 程序的启动和终止:对于任何一个C/C++程序,程序执行时总是从main()函数开始,之后一次或者顺序执行main()函数中的程序代码,实现其中其他函数的各种功能。
- 异常处理:异常处理机制可看作是编译时的类型检查和歧义性控制机制在运行中的对应物。
类模板定义:
- 模板库(STL):STL另一个重要特性是它不是面向对象的。STL主要依赖于模板。这使得STL的组件具有广泛通用性和底层特性。由于STL是基于模板的,内联函数的使用使得生成的代码短小高效。
- 类模版实例化:类模版仅仅是模板,如何使用模板就涉及类模板的实例化问题。模板实例化一般指使用模板类和模板参数生成一个类声明的过程。
- 类模板的成员函数:可被类模板实例化产生的类所拥有。每个类模板都拥有自己相应的成员函数,这些函数可被模板的实例调用。
- 类模板的静态数据成员:被static修饰,意味着它为该类的所有实例所共享,即当某个类的实力修改了改静态成员变量,其修改值为该类的其他所有实例所见。
成员模板:
一个模板可以在一个类或类模版中声明,这样的类模版称为成员模版;成员模版的定义既可以在类(或类模版)定义内部,也可以在类(类模版)定义的外部;当类模版的成员模板在类模版定义的外部定义时,应该完整地指出类模版的参数和成员模版的参数。
友元模版:
- 非模版函数、类作为实例的友元;
- 模版函数、模版类作为同类型实例类的友元;
- 模版函数、类作为不同类型实例类的友元。
函数模版:
函数模版定义一个无限的相关函数集合。当函数除了数据类型不一致外,其余的处理全部相同,此时函数模版随之诞生,这也是创建函数模版的原因;
函数模版可以定义参数化的非成员函数,能够不同类型的参数调用相同的函数,由编译器决定该采用哪种类型,从模板函数中生成相应的代码。
类模版的参数:
模版的参数可以是类型参数,也可以由常规类型的参数。并且一个模板可有多个参数。
模板库简介:
STL的代码从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),几乎所有的代码都采用模板类和模板函数的方式,在C++标准中,STL被组织为13个头文件:<algorithm>、<deque>、<functional>、<iterator>、<verctor>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack>和<utility>。
STL的组件:
STL是C++标准程序库的核心。STL内所有组件都由模板构成,其元素可以是任意型别。程序通过选用恰当的群集类别,调用其成员函数和算法数据,就万事大吉了,代价是STL并不好理解。
- 容器:用来管理某类对象的集合;
- 迭代器:用来在一个对象群集的元素上进行遍历动作。对象集可能是容器,也可能是容器的一部分。迭代器的主要用途是为容器提供了一组很小的公共接口,利用这个接口,某项操作可以行进至群集内的下一个元素。每种容器都提供了各自的迭代器,迭代器了解该容器的内部结构,所以能够正确行进。迭代器的接口和一般指针差不多;
- 算法:用来处理群集内的元素。可以出于不同目的的搜寻、排序、修改、使用那些元素。所有容器的迭代器都提供一致的接口,通过迭代器的协助,算法程序可以用于容器。
- 仿函数:可以理解为函数的一般形式。仿函数有几种不同的约束,对于编程来说,仿函数非常重要。
- 内存配置器:配接器可以实现不同类之间的数据转换。
二、C++中的字符串:
C++标准程序库中的字符串处理类string,在最初的C语言中,头文件string.h提供了一系列字符串函数,早期的C++为处理字符串提供了各自的类,string类由头文件<string>支持,该类包含了大量方法,并且包括了若干构造函数,用于字符串赋给变量、合并字符串、比较字符串和访问各个元素的重载操作符、查找字符和子字符串的方法等。
在STL中还提供了另一个模版类:auto_ptr类。主要用于管理动态内存分配。如使用new函数分配堆中的内存,而又不记得回收这部分内存,在会导致内存泄漏。必须使用delete语句释放该内存块。即使在函数末端添加了delete语句释放内存,还需要再任何跳出该函数的雨具(如抛出异常)之前添加释放内存的处理,例如,goto语句,throw语句。auto_ptr模版定义了类似指针的对象,将new获得的地址赋给该对象。当auto_ptr对象过期时,析构函数将使用delete来释放内存。将new返回的地址赋值给quto_ptr对象时,无须记住还需要释放这些内存。在auto_ptr对象过期时,内存将自动释放。
三、容器:
容器是管理序列的类,通过容器类提供的成员函数,实现各种对序列中元素的操作,是一种高度的泛型抽象。部分算法也可以应用于容器序列的控制。
STL提供了各种容器的模版类,通常这些模板类包括:向量(vector)、列表(list)、双队列(deque)、集合(set)、多重集合(multiset)、映射(map)、多重映射(multimap)。通常,
- 向量(vector)可以认为是包含一个或者N个更多元素的数组;
- 列表(list)是由节点组成的双向链表,每个节点包含一个元素;
- 双队列(deque)是包含N个连接的指向不同元素的指针组成的 数组;
- 集合(set)是由节点组成的,每个节点包含一个元素,节点之间以某种谓词排序;
- 多重集合(multiset)是允许存在两个次序相等的元素集合;
- 映射(map)是由{键,值}对组成的几个,同样以某种谓词排序,谓词和数据对有某种作用;
- 多重映射(multimap)是允许键对包含相等次序的映射。
STL在实现诸多容器类的同时,还实现了部分序列式容器的适配器(adapter)。容器的适配器是针对原有的基本容器的不足,对原有基本容器功能的补充。所有的适配器都不提供迭代器,元素访问通过专有的接口函数实现。
容器的概念:
在编程实现过程中,可以认为“容器是用来存储和组织其他对象的对象”。容器适配器严格地说并不是容器,而是使用容器的对象,是在容器的基础上发展起来的。
容器的成员:
作为容器的成员,必须满足三个条件:
- 元素必须是可复制的。所有容器均会产生一份元素副本,不会发生alias现象,在所有容器操作行为传回的均是其元素的副本。这导致复制构造函数的执行非常频繁;
- 元素必须是可指派(assign)的,容器的成员函数以及STL的各种算法(algorithm)均可利用assign()函数为元素设定新值。
- 元素必须是可释放的(经过析构函数释放内存)。使用者从容器中将元素删除时,容器必须释放其元素所占的内存。按这种需求,析构函数不能设置为private类型
容器的函数:
作为容器的成员函数(操作),也具有一定共同具备的能力:
- 容器均能提供value,而非reference(引用)。涉及元素操作时,元素满足成员的三个条件;
- 所有的元素自动形成顺序自动形成顺序。即按此顺序可以多次遍历所有元素。这些容器均包含可返回迭代器的函数,使用这些迭代器可遍历元素,这是算法和容器的关键接口实现;
- 函数使用者必须确保传递的参数符合要求,否则,可能会导致未定义的行为,通常STL不会抛出异常。
容器的种类:
- 序列式容器(sequence容器),容器中的元素时有序的,但未排序。包含:vector动态数组、deque双向队列、list双向串行;
- 关联性容器,容器中的元素都经过排序。包含:set、multiset、map、multimap和hash(哈希)table;
- 容器配接器,是以某种STL容器作为底,修改其接口,具备各自的特点。包含:stack、queue、priority_queue.。
容器的数据结构:
- string字符串,其内保存字符。string并不是真正的类,而是一个basic——string类的typedef。
- bitset,其内保存bitset的结构体,每个bit表示一个标志(flag)。其长度便是模板的自变量。
- valarray,是数学中的线性数列的呈现,其好处是可以像处理单一数值一样,对整个valarray中每个元素实现运算。
序列式容器:
对于基本容器,通过增加要求来实现对其的改进。序列是一种重要的改进。6种STL容器(deque、list、queue、priority_queue、stack、vector)都是序列。序列最重要的特点就是可以在首端删除元素,在尾端添加元素。尤其是双向序列,允许在两端添加和删除元素。序列中包含至少一种迭代器。从而保证元素按特定的顺序排列,不会在两次迭代间发生变化。
- vector类模版:就像一个动态数组,是典型的“将元素置于动态数组中加以管理”的抽象概念。常用API有push_back()、pop_back()、size()、capacity()、max_size()、reserve()、resize()、empty()等;
- list类模版:由双向链表来实现,每个节点存储一个元素。list支持前后两种移动方向。list和vector类似,提供了对元素的随机访问。list的优势在于任何位置执行插入和删除动作都非常迅速,因为改变的仅仅是链接而已。在list中移动元素要比在vector和deque中快得多。
- deque(双端队列)类模版:提供了对序列随机访问的功能,可实现在序列两端进行快速插入和删除操作的功能,并可以在需要时修改自身大小。采用动态数组来管理序列中的元素,提供随机存取,和vector具有几乎类似接口。其最重要的特征在两端放置元素和删除元素都是高效的,原因在于deque型序列开放了序列的两端,即头尾均开放,可以实现在序列两端进行快速的插入和删除。当需要向序列两端频繁地插入或删除数据时,最佳的容器为deque。
关联式容器:
关联式容器依据特定的排序准则,自动为其元素排序。关联式容器中的元素都经过排序,是已排序的。所有关联式容器都有一个可供选择的template参数,指明排序原则。排序准则以函数形式呈现,用于比较元素值或元素键。
关联式容器由二叉树实作出来。在二叉树中,每个元素都有一个父节点和两个子节点;左子树的所有元素都比自己小,右子树的所有元素都比自己大。关联式容器的差别在于元素的类型以及处理重复元素的方式。
- set是一种随机存取的容器,其关键词和数据文件是同一个值。set对象中的所有元素必须具有唯一值,即不能包含重复的元素。set对象可以使程序按照次序存储一组数值,在一个集合中元素既作为存储的数据又作为数据的关键值。集合的本质就是一个有序的排列。
- multiset是另一种类型的容器,其关键词和数据元素是同样的值。与set不同的是,它可以包含重复的元素。multiset对象可以使程序按照次序存储一组数值。
- map是一种关联数组容器,包含成对数据的容器,一个值是实际数据值,另一个值是用来寻找数据的关键值,一个特定的关键词只能与一个元素相联系。map是排序结构体,键值是独一无二的。事实上,map的内部结构和set是一样的。set可以认为是一种特殊的map,其键值和实值是同一个。
- multimap是一种允许出现重复键关键值的关联数组容器。与map对象不同,一个关键词可以和多个元素相联系,multimap允许键值重复。’
特殊容器
- bitset类模版:bitset创造一个内含位或布尔值且大小固定的数组。当需要管理各种标识,并需要以标识的任意组合表现变量时,即可使用bitset类模版。
- stack类模版:一个栈容器(后进后出,LIFO),使用函数push()将任意数量的元素置入栈(stack)中,使用函数pop()将元素依次反序地从栈中移除。
- 队列queue类模版:FIFO(先进先出)型队列。队列和栈的不同之处在于,队列是双端的,元素压入时从一端,元素移除时是从另一端;而栈是单端口的容器,元素的压入和移除均是从同一端口。通俗的讲,queue是典型的数据缓冲区结构。
- 优先队列Priorty Queues类模版:其元素根据优先级被读取。在此队列中,元素并不是按顺序存储在容器中的,而是按照优先级顺序存储在容器中的。即在此类队列中,被压入的元素已经按优先级进行了自动排序。
四、C++中的算法:
以有限的 步骤解决逻辑或数学上的问题,称为算法。具体的算法是指对解题方案准确而完整的描述,是一系列解决问题的清晰指令。算法是用系统的方法解决问题的策略机制。算法能够实现对于一定规范的输入,在有限时间内获取所要求的输出。一般情况下,算法大致分为基本算法、数据结构的算法、数论与代数算法、计算几何算法、图论算法、动态规划及数值分析、加密算法、排序算法、检索算法、随机化算法、并行算法等一系列算法。
C++标准程序库也提供了一系列的算法。算法一般被设计用来处理迭代器区间。迭代器是一个“可遍历STL容器内全部或部分元素”的对象。迭代器的当前值指向容器中的特定位置。STL中算法概念可以理解为使用迭代器处理容器中元素的方法。
算法库简介:
STL的算法一般采用“覆盖(Overwrite)”模式而不是“安插(Insert)”模式。调用时必须保证目标区间拥有足够的元素空间。当然也可以使用“安插(Insert)”型迭代器访问容器。
为了提高灵活性和功效,STL的算法允许使用者传递自定义的操作(函数),方便STL算法调用。这些操作(函数)既可以是一般函数,也可以是仿函数。如返回值是bool类型,称为条件判断式。
对于算法函数设计,有连个主要的通用部分。首先,这些函数都使用模板来提供通用类型;其次这些函数都使用迭代器来提供访问容器中数据的通用表示。所以,可以将数值存储于数组中,或存储在链表中,还可以将对象在树结构中。
- 非修改式序列算法:不改动容器中元素次序,也不改动元素值。一般通过input迭代器和forward迭代器完成工作,可用于所有的标准容器;
- 修改式序列算法:一般不直接改变容器中元素的值,或者在复制到另一区间的过程中改变元素值。修改式序列算法还包括移除性质的算法。移除一般只是在逻辑上“移除”元素,不改变容器的大小和容器中的元素个数。“移除”和“删除”是不同的算法。
- 排序和相关算法:包括多个排序函数和其他各种函数,包括集合操作等。C++的STL涵盖了所有的变序性算法。排序一般是指通过对容器中元素的赋值和交换,改变元素顺序。排序算法的复杂度通常低于线性算法,要动用随机存取迭代器。变序性算法不能以关联式容器作为目标。因为关联式容器的元素被视为常数,不能变更。
- 通用数字算法:一般包括将区间的内容积累、内部乘积、小计、计算相邻对象差等函数。
非修改性序列算法:
- for each算法:for_each非常方便地同时处理修改每个元素;
- 元素计数算法:STL算法库提供了元素计数的功能,即求得容器中元素的总个数。这个功能是由算法count()来实现。另外还提供了条件计数的算法count_if();
- 最大值和最小值算法:max_element()、min_element();
- 搜寻算法:find()、find_if()、search_n()、find_en()、find_first_of()、adjacent_find();
- 区间比较算法:equal()、mismatch()、lexicographical_compare();
变动性算法:
复制:
- 如要把一个区间复制到前端,应使用copy(),此目标位置应在_First之前;
- 如要把一个区间复制到后端,应使用copy_backward()。目标位置-DestEnd应该在sourceEnd之后;
- STL并没有提供copy_if()算法。如希望实现现有条件地复制容器中的元素,可以算法remove_copy()_if();
- 如希望在复制过程中逆转元素的顺序,可以使用reverse_copy();
- 确保目标区间有足够空间,否则需要使用insert迭代器;
- 如实现两个容器间所有元素的复制,可以不使用copy()和copy_backward(),而使用assign()算法;
- 如希望在复制过程中删除部分元素,可使用算法remove_copy和remove_copy_if();
- 如在复制过程中改变元素的数值,需要使用transform()算法和replace_copy()算法;
- 如目标容器是空容器,需要使用插入型迭代器(InsertIterator)。
转换:
STL中提供了函数transform(),实现了将源区间的元素复制至目标区间,复制和修改元素一气呵成;还可以将两个区间的元素合并,并将结构写入目标区间,
互换:
STL中提供了函数swap()及算法swap_ranges(),用于两个不同容器对象之间的元素交换。
赋值:
STL提供了fill()、fill_n()、generate(),generate_n();
替换:
STL提供了replace()替代容器中需要替换的元素;
逆转:
逆转是指将容器或序列中的元素按逆向顺序反转,第一个元素变成最后一个元素,第二个元素变成倒数第一个元素,依次反转。STL提供了两个可以实现逆转功能的算法函数:reverse()和reverse_copy()。
旋转:
STL提供了对于容器或序列的旋转算法:即rotate()和rotate_copy()。函数rotate()将容器中的元素或序列看作一个环,旋转这些元素直至原来middle处元素到达first位置。旋转运算时使序列中的元素按照一个环的方式旋转,而不是简单的左移。rotate_copy()产生一个旋转后的副本,即在旋转之后会复制参加旋转的元素。
排列:
- 排列元素next_permutation():会改变容器(序列)中的元素次序,排序方式为字典式“正规”排序;
- 重排元素random_shuffle():是指对容器中的所有元素进行随机排序,一般有两种形式,符合均匀分布随机的排序;按指定规则打乱容器(序列)中的元素次序。
- 前向搬移partition():是按指定的一元判断式向前搬移元素,当一元判断式的值为true时,算法会向前移动符合条件的元素,函数返回值是使一元判断式为false的第一个元素位置。
排序及相关操作:
STL提供了多种算法用来对容器(序列)中全部或部分元素进行排序。
- 全部元素排序:算法sort()和算法stable_sort()支持对容器(序列)中所有元素进行排序。算法sort()和stable_sort()需要访问随机存取迭代器,智能适用于vector型和deque型容器。由于list型容器不支持随机存取型迭代器,所以不能使用这两个算法,但是list型容器提供了sort()成员函数,用于自身元素的排序。
- 局部排序:partial_sort()和partial_sort_copy();
- 根据某个元素排序:nth_element(),该算法可以对指定区间内的元素进行排序,并使第N个位置上的元素就位。即所有在位置n之前的元素都小于等于它,所有在位置n之后的元素都大于等于它。因此,可得到根据位置N上的元素分割出两个子序列。第一个序列的元素全部小于第二个子序列的元素。
- 堆(Heap)操作:“堆”属于计算机领域的一个术语。在计算机算法领域,“堆”通常用于指一种组织序列元素的方式。“堆”的第一个元素通常时具有最大值的元素。STL的算法库中提供了部分堆操作算法:push_heap()、pop_heap()、sort_heap()、make_heap()等;堆(Heap)应用与排序,是一种特别的元素组织方式,是以序列式群集实做而成二叉树。它具有两大性质:堆中的第一个元素的值总是最大;能够在对数时间内增加或移除一个元素。
- 合并排序:set_merge两个已序集合的总和(merge);set_union()两个已序集合的并集(union);set_intersection()两个已序集合的交集(intersection)、set_difference()两个已序集合的差集(difference)、inplace_merge()连贯的已序区间合并算法。
- 搜索:binary_search()、includes()、lower_bound()、equal_range()和upper_bound();
删除算法:
- remove()算法:查找并删除容器(区间)中的元素时最基本的操作。
- remove_if()算法:条件删除算法。
- remove_copy()和remove_copy_if():用于复制过程中移除相关的元素。
- unique()移除重复元素:在vector、list、deque、multiset和multimap等类型的容器中,允许元素重复出现,但是有时需要删除重复的元素。
- unique_copy()复制过程中移除重复元素;
五、迭代器(Iterator):
迭代器是链接容器和算法的纽带,它为数据提供了一种抽象的观点,使写算法的人不必关心各种各样的数据结构和具体细节。迭代器提供了一个数据访问的标准模型,缓解了要求容器提供一组更广泛的访问操作的压力。
迭代器用对象序列作为它所支持的数据访问模型。将具体数组和字节形式的低级数据模型映射到高级的对象模型。
迭代器及其特性:
迭代器本身是一种对象,所以,即使不是迭代器,但其行为类似迭代器的东西,也是迭代器。迭代器不是通用指针,而是指向数组的指针概念的抽象。通俗的讲,迭代器就是一个指示器。迭代器技术能够使程序反复地对STL容器内容进行访问。当参数化类型为C++内部类型时,迭代器是C++的指针。
- 输入迭代器:主要用于为程序中需要的数据源提供输入接口,此处数据源可以是容器、数据流等。输入迭代器只能够从一个序列中读取数据,此类迭代器可以被修改、引用并进行比较;
- 输出迭代器:主要用于输出程序中已经得到的数据结果,此处结果数据指的是容器、数据流等。输出迭代器只能向一个序列写入数据,此类迭代器可以被修改和引用。
- 前向迭代器:可以随意访问序列中的元素,许多STL算法函数需要提供前向迭代器。前向迭代器可以用来读也可以用来写,结合了输入和输出迭代器的功能,并能够保存迭代器的值,以便其原先位置开始重新遍历。
- 双向迭代器:既可用来读也可用来写,双向迭代器可以被增值和减值,并且可以同时进行前向和后向元素的操作。所有的STL容器都提供了双向迭代器功能,以有利于数据的写入和读出。
- 随机访问迭代器:可以通过跳跃的方式访问容器中的任意数据,从而使数据的访问非常灵活。随机访问迭代器作为功能最强大的迭代器类型,具有双向迭代器的所有功能,能够使用算法和所有的迭代器比较功能。
迭代器类型详述:
- 输入型迭代器(InputIterator):只能一次一个地向前读取元素,并按此顺序传回元素值。InputIterator迭代器只能读取元素一次。如复制输入型迭代器,原InputIterator迭代器和新产生的副本都向前读取,会遍历到不同的值。所有的迭代器都具备输入型迭代器的能力,纯粹代器的迭代器的典型例子是“从标准输入装置读取数据”的迭代器。同一个值不会被读取两次,一旦输入流读入一个字后,下一次读取时就会回传另一个字。
- 输出型迭代器(OutputIterator):其作用是将元素值逐个写入,即只能逐个元素进行赋值,不能使用OutputIterator型迭代器对同一序列进行两次遍历。
- 前向迭代器:是输入型迭代器和输出迭代器的结合,具有输入型迭代器的全部功能和输出型迭代器的大部分功能。
- 双向迭代器:在前向迭代器的基础上增加了回头遍历的功能。可支持递减运算符,可以实现一步一步的后退操作。
- 随机存取迭代器:在双向迭代器的基础上增加了随机存取能力。因此,必须增加(提供)迭代器算数运算。即可以实现迭代器加减某个偏移量、能处理距离问题。
迭代器配接器:
C++标准程序库提供了多个预先定义的特殊迭代器,即所谓迭代器配接器(iterator adapters)。迭代器配接器使算法能够以逆向模式、安插模式进行工作,还可以和流配个。迭代器配接器不仅能起辅助作用,还能赋予整个迭代器抽象概念更加强大的能力。
- 逆向迭代器(Reverse):能重新定义递增运算和递减运算,使其行为正好倒置。算法以逆序次序来处理元素。所有标准容器都允许使用Reverse迭代器来遍历元素。
- 插入型迭代器(Insert):用来将“赋值新值”操作转换为“安插新值”操作。通过迭代器,算法可以执行安插行为而非覆盖行为。所有Insert迭代器都隶属于Output迭代器类型,它只提供赋值新值的能力。
- 流迭代器:是一种迭代器配接器,可以把流当成算法的原点和终点。流迭代器是特殊用途的输入和输出迭代器,程序能管理与I/O流相关的数据。插入和反向迭代器由迭代器适配器形成。一个istream迭代器可以用来从input stream中读取元素,而ostream迭代器可以用来对output stream写入元素。流迭代器的特殊形式是所谓的stream缓冲区迭代器,用来stream缓冲区进行直接读取和写入操作。
迭代器辅助函数:
- advance()迭代器前进函数:可使相关的迭代器前进和后退,增加的速度由参数决定,可以使迭代器前进和后退一个或多个元素。
- distance()迭代器距离:用来处理两个迭代器之间的距离。
- iter_swap()交换两个迭代器所指内容:用来交换两个迭代器的元素值,迭代器的型别不一定相同,但所指向的两个数必须可以互相赋值。
六、STL的数值计算:
C++ STL标准程序库的数值相关组件,其中包括复数(complex)、数值数组(value arrays),以及从C标准程序库继承而来的全局数值函数。
复数运算:
C++标准库中提供了一个template class complex<>,用于实现复数操作。复数是由实部和虚部组成的数值。虚部特点是“其平方值为负数”。即复数虚部带着i,i是-1的平方根。
复数类complex的成员函数包括:构造函数、实部函数(real)和虚部(imog),运算符函数。
复数还可以进行其他一些简单的运算,如:绝对值函数(abs)、绝对值的平方(norm)。复数的相位(arg)、输入与输出、共轭函数(conj)、极坐标函数(polar)等。
数组(向量)运算:
STL提供了一个数组类,class valarray。类valarray用于实现数值的运算。valarray代表一个数学概念:数值线性序列。该序列是一维的,但可以通过运用特殊技巧得到多维效果。所谓的特殊技巧,即使用“索引”能力和强大的“子集”能力。因此,valarray是向量和矩阵运算的基础。数值计算工作多依赖于浮点数值的一维向量,下标从零开始。类valarray的设计目的就是加速常用数值向量的计算。
通用数值计算:
在STL中,按照<algorithm>中非数值算法的风格,提供了4个通用的数值算法。这4个算法的声明在头文件<numeric>中。包括:accumulate()(求和)、inner_product()(内积)、partial_sum()(部分和)和adjacent_difference()(序列相邻差)。
全局性数学函数:
C++全局函数在头文件<cmath>和<cstdlib>中。
七、输入/输出流:
标准程序库中的IO classes不仅局限于文件、屏幕或键盘,它形成了一套富有弹性的框架,用于任意数据格式化,处理(存取)任意外部表述。
IOStream简介:
Stream对象:
- 输入流和输出流:C++ IO由stream完成。所谓stream是一条数据“流”,字符序列在其中“川流不息”。按面向对象的原则,stream是由某个类别定义出来的具有特定性质的对象。
- 文件流对象:用于文件操作时,程序需要实例化文件流类的对象,文件流类包括3个,ofstream、ifstream和fstream 。这3个对象分别支持文件输入、输出以及兼具输入和输出。程序需要再流类的构造函数中包括实参,或者实例化未初始化的文件流对象。
Stream类别:
- class istream,定义input stream,可用来读取数据;
- class ostream,定义output stream,可用来写出数据;
- class ofstream,实现文件输入和输出。
IOStream基本类和标准IOStream对象:
- 输入函数:get(s,num)、get(s,num,t)、getline(s,num)、getline(s,num,t)、read(s,num)、readsome(s,num)。
- 输出函数:write()和flush()。
格式化:
对于格式化IO输出,最重要的是格式标志,可定义诸如:数字精度、充填字符、数字进制等。
StreamBuffer缓冲区类:
通常,stream并不负责实际读写操作,而是委托给stream buffer实现。输出流将某些字符放入缓冲区,之后某个时刻,这些被写到输出设备上。此类缓冲区可被称为Streambuf。类Streambuf为缓冲区管理内存,并提供用于填充的缓冲区、访问缓冲区内容、刷新缓冲区和管理缓冲区内存的类方法。
基于字符串的流:
在STL的头文件<sstream>中定义了4个类模版和6个类型,均是和basic_string相关的流缓冲区类型。其功能主要是实现将流附着在string上,即通过流所提供的格式化功能,从string中读取或写入string。
- streambuf类:
- 类模版basic_istringstream:支持读取basic_string类的对象。使用basic_stringbuf类对象控制相关的存储区域。
- 类模版basic_ostringstream:支持写入basic_string类的对象。使用basic_stringbuf类对象控制相关的存储区域。
- 类模版basic_stringstream:支持读取和写入basic_string类对象。
基于文件的流:
- template class basic _ifstream<>及其特化版本ifstream和wifstream,用于读取文件;
- template class basic _ofstream<>及其特化版本ofstream和wofstream,用于将数据写入文件。
- template class basic _fstream<>及其特化版本fstream和wfstream,用于读写文件。
- template class basic _filebuf<>及其特化版本filebuf和wfilebuf,被其他的file stream classes用于进行实际字符的读写工作。
八、异常处理:
异常处理是一种C++特性。异常处理将会使程序按照有序、有组织和一致的方式截取和处理异常条件——错误。异常处理允许使用一段程序来感知和分发错误条件,用另外一段程序来处理错误。检测错误的代码可能不知道处理的策略,代码可以是库中的类和函数,也可能包含其他种类的处理错误的代码。通常错误来源于程序的隐含错误或致命错误(无效等)。函数库不可能知晓所有能检测出的可能发生的异常。函数将错误汇报给正在使用的程序,需要一定的前提。
异常概念及基本思想:
异常的分类:
- C++标准程序库可以在“不污染”函数接口的情况下处理异常。一旦遭遇异常情况,通过“抛出异常”停止正常的处理过程。语言本身或标准程序库所抛出的所有异常,均派生于基类exception。
- 语言本身支持的异常:bad_alloc异常、dynamic_cast通常抛出bad_cast异常、typeid会抛出bad_typeid异常、非预期的异常bad_exception会及时处理,函数bad_exception会调用unexpected()。
- C++标准程序库发出的异常:派生于logic_error,逻辑异常类别,invalid_argument、length_error、out_of_range、domain_error。
- 程序作用域(scope of program)之外发出的异常:派生于runtime_error的异常,用来指出“不再程序范围内,且不容易回避”的事件。
异常的捕捉和处理:
- 全局宏errno表里、零或错误函数的返回值来报告错误种类,方法可靠但非常繁琐。
- 使用setjmp()和longjmp()函数:能够依次、自动地把堆栈还原至函数调用层次结构中较高层位置所记录的状态。会截取和处理不需要瞬时终止程序的错误条件。
- 使用try块和catch块处理异常的捕捉和处理;
- 资源管理:当函数申请了其所占有的资源时,为保证系统的运行,必须正确地释放此资源。
- 构造函数和析构函数的使用:当某个对象的构造执行完毕时,此对象才被认为已经明确建立。之后,堆栈回退时才为该对象调用析构函数。由子对象组成的对象的构造函数需要等待所有子对象的构造函数均完成执行才完毕;
- STL的auto_ptr类:可以使用指针去初始化,且能够以与指针同样的方式间接访问。在auto_ptr类对象退出作用域时,所指的对象被隐式地自动删除。
- 构造函数中的异常和new:“资源申请即初始化”技术是最安全最优秀的方法。从根本上讲,该技术把处理多种资源的问题,归结为一种反复应用处理单一资源技术的问题。
- 析构函数中的异常:不能让析构函数抛出异常,如真的在析构函数中发生了异常的抛出,即是异常处理机制的重大失败。
- 资源耗尽:
- 异常的描述:异常描述检查、未预期的异常、异常映射。
- 未捕捉的异常:未捕捉的异常是没有为其准备catch()处理函数的异常,或是throw()所执行的析构函数抛出异常。此异常将造成terminate()函数被调用,该函数调用abort()以终止程序。
异常类及几个重要问题:
类exception,函数abort(),堆栈解退,错误代码,异常的迷失,异常处理的局限性。
类exception:
类exception除了提供构造函数、析构函数之外,仅有一个虚成员函数what()。函数what()用以获取“型别本身以外的附加信息”,函数返回值是一个以null结束的字符串。
- domain_error类:数学函数通常有定义域和值域。定义域由参数的可能值组成,值域由函数可能的返回值组成。
- invalid_argument类:指出给函数传递一个意外的值。
- length_error类:用于指出没有足够的空间来执行所需的操作。
- out_of_range类:指示索引错误,即索引的数值超出了正常容量的上下限。
- runtime_error:描述了可能在运行期间难以预计和防范的错误,每个类的名称指出其报告的错误类型。
- bad_allloc异常和new:
调用abort():
终止程序,之后函数的返回值会通知操作系统处理失败,abort()是否要刷新文件缓冲区取决于实现。有时,也可使用函数exit()。函数exit完成刷新缓冲区的功能,但不显示消息。
堆栈解退:
假定函数出现异常而终止,则程序将释放堆栈中的内存,但不会释放堆栈内第一个返回地址,而是继续释放堆栈,直至寻找到try块中返回的地址。之后,控制权将转到块尾的异常处理程序,而不是函数调用后面的第一条语句。该过程称为堆栈解退。
异常处理的局限性:
处理异常详述:
异常处理的基本思想是简化程序的错误代码,为程序健壮性提供一个标准检测机制。
异常机制的规则:
- 异常机制只能用于处理错误;
- 通常不要使用goto语句或switch语句跳转至try块或catch块内;
- 不要显示地抛出NULL;
- 如函数声明时,指定了具体的异常类型,只能抛出指定类型的异常;
- 如函数声明时指定了异常类型,在编译单元中该函数的声明必须有同样的指定;
- 异常只能在初始化之后且程序结束之前抛出;
- throw语句只能出现在catch语句块中;
- 空的throw语句本身不允许引发新的异常;
- 所有的流程中显式地抛出异常应该有一个类型兼容的处理程序;
- 至少需要有一个程序处理所有其他针对处理的异常;
- 若try-catch语句块中有多个处理程序,或派生类和部分或全部基类的function-try-bloack块有多个处理程序,处理程序的顺序应该是先派生类后基类;
- 如try-catch语句块或function-try-bloack块有多个处理程序时,catch(....)处理程序(捕捉获取所有异常)应该放在最后;
- 若异常对象为类的对象时,应该通过引用来捕获;
- 若构造函数和析构函数时function-try-bloack结构的,在catch处理程序中不能引用该类或基类的非静态成员;
- 析构函数退出之后,不允许还有未处理的异常。
异常处理的特殊函数:
函数terminate():
下述情况下,异常处理必须被放弃,而采用较弱的错误处理方式:
- 当异常处理机制调用的用户函数包括一个uncaugth_exception时,在抛出完整的信息之后,异常被捕获之前的时间段内;
- 当异常处理机制不能发现抛出的异常句柄时,堆栈中退出的对象的析构函数存在一个异常时;
- 当函数被执行时,而此函数使用atexit注册过;
- 当一个抛出的表达式尝试重新抛出异常并且没有异常被处理时;
- 当unexception函数抛出一个异常,而该异常不被前面的异常说明允许,并且std::bad_exception不包括在异常描述中;
- 当默认的unexception_handler被调用时。
unexceptected()函数:
如一个函数带有一个异常描述,抛出异常时,异常描述没有被列表,则函数void unexceptected()被迅速调用。unexceptected()函数不应该返回,但它能抛出一个异常。
函数uncaught_exception():
此函数放在析构函数中。返回为true,被抛出的对象被评估之后,直到相匹配的句柄完成异常声明初始化。它包括了退栈的过程。如异常被重复抛出,重新抛出异常之处,uncaught_exception()返回为true,直到重新抛出的异常被再次捕获。
九、通用工具:
C++标准库的通用工具由短小精悍的类和函数构成,执行最一般化的功能。头文件<unility>是很小的头文件。
动态内存管理:
在STL中,头文件<memory>以非常杰出的方式为容器中的元素分配存储空间,同时也为某些算法执行期间产生的临时对象提供机制。在C++标准中,提供了内存分配器(allocator)。
- 默认分配器:在名称空间std中,声明了一系列和内存管理相关的模板类、模版函数、缓冲区模版类、迭代器和算法。
- raw storage iterator:用于使算法可以存储其结果至非初始化的内存。
- temorary buffers(临时缓冲区):
堆的内存分配:
C++程序从存储器的全局存储区分配和释放动态存储块;全局存储区有时称为自由存储器(free store),更通用的称呼是堆(heap)。运用运算符new从对配存储器,用运算符delete把存储器返回给堆。
- new和delete运算符:new运算符和数据类型、类、结构或数组的名字一起使用。会为新建项目分配存储器并返回存储器位置,程序可以把返回的地址赋予指针。delete把之前分配的存储器返回给堆,操作数必须是以前分配的存储器地址。存储器返回之后可以运算符重新分配。
- 分配固定维数的数组:
- 分配动态内存数组:
- 处理堆耗尽:通过将try块和catch块放在main()函数中,至少可以将异常截获。
辅助功能:
- 数值极限:数值型别的极值是与平台相关的特性;
- 最大最小值(较大较小值):max()、min();
- 两值交换:swap();
- 辅助性比较;
日期和时间:
-
asctime():用于将指定的时间以字符串(英文简写)形式输出;
-
ctime():用于将指定的时间以字符串形式输出,并遵循从本地时区设置;
-
clock(0:返回值是硬件滴答数,需要换算成秒或毫秒;
-
difftime():返回两个time_t类型参数(时刻)之间的时间差;
-
gmtime():用于将参数传递的时刻转换为格林尼治时间;
-
localtime():把从1970年1月1日零时零分到当前时间系统所偏移的秒数时间转换为日历时间;
-
mktime():将当前的日历时间转换为从1970年1月1日零时零分起至今的UTC时间经过的秒数;
-
time():获取当前系统(电脑)时间;
-
strtime():根据区域设置格式化本地时间、日期,函数的功能将时间格式化。
模版类auto_ptr:
使用auto_ptr使用函数new()将指针存储在一个对象中,当需要被破坏此指针时,使用delete()函数。模版auto_ptr_ref保持了对类auto_ptr的一个引用。该引用用于允许auto_ptr类型对象的传递和从函数中传递出来。
十、语言支持:
十一、检测库详情:
异常类Exception:
标准C++库提供诸多的类,用于在C++程序中报告某些确定的错误。这些错误的模型反映(折射)了这些类,错误被分成两个大类:逻辑错误和运行错误。逻辑错误的特性是:这些错误的 原因是程序的内部逻辑错误。从理论上讲,这些错误是可以被预防的。相反,运行错误的原因上通常时一些超出程序范围的事件。此类错误不容易被提前预知。
- 类logic_error:定义了异常对象的类型,这些异常对象被抛出用于报告程序执行过程中发生的错误。
- 类domain_error:是类logic_error的派生类。因此继承了logic_error类的what()函数。类domain的作用是定义了异常对象的类型,便于在程序执行时报告“域”错误;
- 类invalid_argument():派生于logic_error,定义异常对象的一种类型,该异常对象被抛出时用于汇报一个无效参数错误;
- 类length_error:定义了异常对象的类型,在程序执行时,这些异常被抛出用于长度错误。所谓长度错误是对象的长度超越了其最大允许的大小;
- 类out_of_range:定义了异常对象的类型,该对象在程序执行过程中被抛出,用于报告参数值错误(该参数不再期望的范围内);
- 类runtime_error:定义了异常对象类型。这些异常对象在程序执行时被抛出,用于报告检测到的错误;
- 类range_error:在程序执行时,尤其是内部计算时,这些异常对象被抛出用于报告范围类型的错误;
- 类overflow_error:这些异常对象会在发生算术运算溢出错误时被抛出;
- 类underflow_error(下溢出):程序执行过程中,一旦发生算术underflow错误,这些异常对象被抛出;
assertions(断言):
宏assertions会判断程序中是否出现明显非法数据,若出现了则终止程序以免导致严重后果,便于查找错误。
使用assert()缺点是会影响程序的性能,增加额外的开销。
assert()需要注意一下几个问题:
- 使用断言捕捉不应该发生的非法情况。不混淆非法情况与错误之间的区别,后者必然存在的并且是一定要进行处理;
- 使用断言时,应对函数的参数进行确认;
- 编写函数时,要反复考查,堆设定的假定需要使用断言进行检查;
- 通常程序员都熟悉部分放错误性的程序设计,记住此风格会隐藏错误。当进行防错误性编程时,若不可能发生的事情却发生了,需要使用断言进行报警;
- 每个断言需要详细、清楚;
- 使用不同的算法对程序结果进行确认;
- 在错误发生之前,即使用初始检查程序。
错误编码:
在头文件<cerror>中,包含了多个错误编码。变量errno的值是常量,在不同的错误条件下或事件中,诸多的错误代码被赋值给变量errno。
十二、国际化库详解:
十三、仿函数:
仿函数也称为函数符,是以函数方式与括号()结合使用的任意对象。在STL的标准中,仿函数的英文名称为FunctionObjects,即函数对象。更通俗地说,仿函数是将函数作为参数传递的使用方式。
仿函数的概念:
所谓仿函数是一个定义了operator()的对象。仿函数可以视为一般函数,不同之处在于:仿函数的功能是在其成员函数operator()中实现的。虽然仿函数的定义形式比较复杂,但仿函数的有点有三个方面:
- 仿函数比一般函数更灵巧,主要是因为仿函数拥有状态(state)。对于仿函数,可以拥有两个状态不同的实体。通常的普通函数是不能实现该目的的;
- 每个仿函数都有其型别。因此仿函数的型别可以作为模板参数,实现指定某种行为的目的,容器型别不会和具体仿函数有关,而仅仅是该型别的仿函数均可使用;
- 仿函数要比函数指针的执行速度快得多。在C++规范中,函数调用通常使用指针,当需要调用函数时,只需要函数的地址(名称)即可。地址调用方法的缺陷是效率非常低,为提高效率需要使用仿函数的形式。定义的仿函数被调用时,是使用运算符operator()的 。通过对运算符进行自定义,能显著提高效率。
仿函数的作用:
-
作为排序规则;
-
拥有内部状态;
-
算法for_each()的返回值;
-
作为判断式。
辅助用仿函数:
仿函数的组合能很重要,可以从一些软件组件构造出另一些组件。最简单的仿函数可以构造出非常复杂的仿函数。一般而言仿函数行为均可借由仿函数的组合而实现。
- f(g(elem)):这是一元组合函数的最一般形式。一元判断式被嵌套调用,g()的执行结果作为f()的参数。整个表达式的操作类似一个一元判断式;
- f(g(elem1,elem2)):两个元素elem1和elem2作为参数传递给二元判断式g()。其结果作为参数传给一元判断式f()。整个表达式雷士一个二元判断式;
- f(g(elem),h(elem)):参数elem作为参数被传递给两个不同的一元判断式g()和h(),两者的结果由二元判断式f()处理;
- f(g(elem1),h(elem2)):此处参数elem1和elem2作为唯一参数传递给两个不同的一元判断式g()和h(),两个结果共同被二元判断式f()处理。
关系仿函数:
- 等于(equal_to<type>);
- 不等于(not_equal_to<type>);
- 小于(less<type>());
- 大于(greater<type>());
- 大于等于(greater_equal)和小于等于(less_equal);
逻辑仿函数:
返回bool类型的仿函数称为谓词。
- 逻辑与(logic_and<T>);
- 逻辑或(logic_or<T>);
- 逻辑非(logic_not<T>);
算术仿函数:
- 加减乘除仿函数(plus<T>、minus<T>、multipies<T>、divides<T>);
- “求余”仿函数(modulus<T>)和“求反”仿函数(negate<T>);
其他类型的仿函数:
- 证和映射(identity<T>):使用时需要一个参数,返回的是未经任何变化的原参数。
- 仿函数project1st<Arg1,,Arg2>和project2and<Arg1,,Arg2>:这两个仿函数接受两个参数。project1st返回第一个参数并忽略第二参数;project2and返回第二个参数并忽略第一个参数。
- 仿函数select1st<pair>和select2and:<pair>:select1st返回该pair的第一个元素,select2and返回该pair的第二个元素。
- 仿函数hash<T>:运算符函数会返回参数为x的hash值;
- 仿函数subtractive_rng<T>:是一种随机数发生器。
适配器:
- 绑定器(binder):通过吧二元函数对象一个实参绑定到一个特殊值上,将其转换成一元函数对象;
- 取反器(negator):是将函数对象的值反转的函数适配器。
- 成员函数适配器:是成员函数可以被用作算法的参数。当算法需要调用一个标准的操作或自定义操作时,成员函数的使用可以是非常简便的;
- 函数指针适配器:使函数指针可以被用作算法的参数。
十四、配置器:
空间配置器(allocator)简称配置器。空间配置器代表一种特定的内存模型,并提供一种抽象概念,便于将内存的申请转变为对内存的直接调用。
配置器主要用于将算法和容器的实现隔离于物理存储细节之外。每个配置器均提供了一套分配和释放存储的标准方式、一套用于指针类型和引用类型的标准名字。
配置是一种纯粹的抽象(概念)。C++ STL提供了一个标准配置器。主要目的是为程序员提供更好的服务。程序员还可以根据自己需要,设计和提供自定义的分配器。
标准器容器和算法均通过分配器提供的功能获取和访问存储。通过提供新的分配器,为标准容器提供新的不同的存储使用方式。
使用配置器可以实例出容器和其他组件。配置器提供一个接口包括:
- allocate(int num)(分配);
- construct(p)(生成);
- destroy(销毁);
- deallocate(p,num)(回收对象)。
标准配置器:
使用全局的函数opertator new和operator delete分配和回收内存,函数allocate()有可能会抛出bad_alloc类型的异常。
对于rebind()类型,是使一个配置器能分配任意类型的对象。
自定义配置器:
实现一个配置器最主要的是:分配和回收存储空间。在原有的标准配置器的基础上,修改配置器的几个成员函数即可,像max_size()、alllocator()、deallocato()。
可将自己在内存分配方面策略体现在3个函数内,像重新运行内存、使用共享内存,或内存映射到面向对象的数据库中。
未初始化的内存:
- uninitialized_fill(ForwardIerator beg,ForwardItertor end,const T& value):以数值value初始化范围[beg,end]区间内的元素;函数要么成功,要么没有影响。
- uninitialized_fill_n(ForwardIerator beg,size num,const T& value):以数值value初始化从beg开始的num个元素。同样,函数要么成功,要么没有影响。
- uninitialized_copy():以[sourcebeg,sourceend]区间内的元素为根据,将destbeg起始的元素加以初始化。函数要么成功,要么没影响。