C++笔记-STL简析


1.什么是模板

C++笔记-STL简析_第1张图片

模板的引入:如图,如果编写了一个两个int 型相加的 add() 函数,那么这个函数就只能实现 int 型加法,对 float 等其他类型无法实现,要实现这些其他类型的加法就要重新编写add()函数。结果导致这样的情况出现:即使拥有同一个函数名,相同的函数体,却因为参数类型和返回值类型不一样,仍然不得不为每一个函数编写一组函数体完全相同的代码。如上图就出现了3个不同的函数,即使它们是二元加法的重载函数。如果能从这些函数中提炼出一个通用函数,而它又适用于多种不同类型的数据,就会使代码的重用率大大提高,C++通过模板来解决这样的问题。

模板(Template)是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

模版是一种解决方案,其目的就是为了减少重复代码,提供参数化容器类和通用的算法(函数),从而真正地实现代码可重用性,能够让程序员编写与类型无关的代码解放C++程序员生产力。

模板常有两种形式:函数模板类模板。而把函数模板的具体化称为模板函数,把类模板的具体化成为模板类。

  • 函数模板:针对仅参数类型不同的函数;
  • 类模板:针对仅数据成员和成员函数类型不同的类。

模板可以认为是类的类,我们知道一个类可以实例化为一个对象,那么类其实可以认为是一个模板的一份实例,我们可以让一个模板产生多份类实例,只需要简单的指定不同的模板参数,这样可以进一步提高代码的抽象性。

注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。


 

2.什么是STL

STL,即 Standard Template Library,中文译为标准模板库。其包含有大量的模板类和模板函数,是 C++ 提供的一个基础模板的集合,用于完成诸如输入/输出、数学计算等功能。该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。

从根本上说,STL 是一些容器、算法和其他一些组件的集合。所有容器和算法都是总结了几十年来算法和数据结构的研究成果,汇集了许多计算机专家学者经验的基础上实现的,因此可以说,STL 基本上达到了各种存储方法和相关算法的高度优化。注意,这里提到的容器,本质上就是封装有数据结构的模板类,例如 list、vector、set、map 等。

STL 最初由惠普实验室开发,于 1998 年被定为国际标准,正式成为 C++ 程序库的重要组成部分。值得一提的是,如今 STL 已完全被内置到支持 C++ 的编译器中,无需额外安装,这可能也是 STL 被广泛使用的原因之一。

STL 就位于各个 C++ 的头文件中,即它并非以二进制代码的形式提供,而是以源代码的形式提供


 

3.STL的特性

STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但这种分离确实使得STL变得非常通用。例如,由于STL的sort()函数是完全通用的,你可以用它来操作几乎任何数据集合,包括链表,容器和数组;

STL另一个重要特性是它不是面向对象的。为了具有足够通用性,STL主要依赖于模板而不是封装,继承和虚函数(多态性)—OOP(面向对象编程)的三个要素。你在STL中找不到任何明显的类继承关系。这好像是一种倒退,但这正好是使得STL的组件具有广泛通用性的底层特征。另外,由于STL是基于模板,内联函数的使用使得生成的代码短小高效.

从逻辑层次来看,在STL中体现了泛型化程序设计的思想。引入了诸多新的名词,比如像需求(requirements),概念(concept),模型(model),容器(container),算法(algorithmn),迭代子(iterator)等。与OOP(object-oriented programming)中的多态(polymorphism)一样,泛型也是一种软件的复用技术;从实现层次看,整个STL是以一种类型参数化的方式实现的,这种方式基于一个在早先C++标准中没有出现的语言特性--模板(template)。

STL 中加入了一个封装内存模式信息的抽象模块,也就是 STL 中的 allocator(内存分配器),它使 STL 的大部分实现都可以独立于具体的内存模式,从而独立于具体平台。


 

4.STL的基本组成

通常认为,STL 是由容器、算法、迭代器、函数对象、适配器、内存分配器这 6 大组件组成,其中后面的 4 个组件可以认为是为前 2个组件服务的,C++ STL基本组成为:6大组件+13个头文件

4.1 STL的6大组件

1)容器(Container):是以模板类的方法提供的各种数据结构从实现的角度看,STL容器是一些封装数据结构的class template。例如 vector 向量容器、list 列表容器等。为了访问容器中的数据,可以使用由容器类输出的迭代器。

2)算法(Algorithm):是用来操作容器中的数据的模板函数。从实现的角度看,STL算法是一种function template。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。STL 提供了非常多(大约 100 个)的数据结构算法,它们都被设计成一个个的模板函数,这些算法在 std 命名空间中定义。例如,STL用sort()来对一个vector中的数据进行排序,用find()来搜索一个list中的对象,函数本身与他们操作的数据的结构和类型无关,因此他们可以在从简单数组到高度复杂容器的任何数据结构上使用。

3)迭代器(Iterator):提供了访问容器中对象的方法。在C++ STL 中,对容器中数据的读和写,是通过迭代器完成的。它扮演着容器和算法之间的胶合剂,是所谓的“泛型指针”。从实现的角度来看,迭代器是一种将“operator, operator->, operator++, operator--”等指针相关操作予以重载的class template。所有STL容器都附带有自己专属的迭代器,--是的,只有容器的设计者才知道如何遍历自己的元素。C++的原生指针也是一种迭代器。例如,可以使用一对迭代器指定list或vector中的一定范围的对象。迭代器就如同一个指针。但是,迭代器也可以是那些定义了operator*()以及其他类似于指针的操作符地方法的类对象。

4)仿函数(Functor):行为类似函数,可作为算法的某种策略(policy)。从实现的角度看,仿函数是一种重载了operator()的class 或 class template。一般函数指针可视为狭义的仿函数。例如,如果一个类将 () 运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象(又称对象函数)。

5)配接器(Adapter):一种用来修饰容器、仿函数或迭代器接口的东西。例如,STL提供的queue和stack,虽然看似容器,其实只能算是一种容器配接器,因为它们的底部完全借助 deque,所有的的操作都由底层的deque供应。改变functor接口者,称为 function adapter;改变Container接口者,称为 Container adapter;改变Iterator接口者,称为 Iterator adapter。即,配接器可以使一个类的接口(模板的参数)适配成用户指定的形式,从而让原本不能在一起工作的两个类工作在一起。

6)配置器(Allocator):为容器类模板提供自定义的内存配置与管理功能。从实现的角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。由于往往只有高级用户才有改变内存分配策略的需求,因此内存分配器对于一般用户来说,并不常用。

 

4.2 STL6大组件的关系

C++笔记-STL简析_第2张图片

 

4.3 STL的13个头文件

在惠普实验室最初发行的版本中,STL 被组织成 48 个头文件;但在 C++ 标准中,它们被重新组织为 13 个头文件,如表 1 所示。按照 C++ 标准库的规定,所有标准头文件都不再有扩展名。以 为例,此为无扩展名的形式,而 为有扩展名的形式。

表 1 C++ STL头文件
     

 

5.组件介绍

5.1 容器(Container)

STL中的容器有队列容器和关联容器,容器适配器(congtainer adapters:stack,queue,priority queue),位集(bit_set),串包(string_package)等等。

1 )序列式容器(Sequence containers):

每个元素都有固定位置-取决于插入时机和地点,和元素值无关,vector、deque、list;

  • vector : 将元素置于一个动态数组中加以管理,可以随机存取元素(用索引直接存取),数组尾部添加或移除元素非常快速。但是在中部或头部安插元素比较费时;
  • Deque : 是double-ended queue的缩写,可以随机存取元素(用索引直接存取),数组头部和尾部添加或移除元素都非常快速。但是在中部或头部安插元素比较费时;
  • List : 双向链表,不提供随机存取(按顺序走到需存取的元素,O(n)),在任何位置上执行插入或删除动作都非常迅速,内部只需调整一下指针;

2) 关联式容器(Associated containers)

元素位置取决于特定的排序准则,和插入顺序无关,set、multiset、map、multimap;

  • Set/Multiset : 内部的元素依据其值自动排序,Set内的相同数值的元素只能出现一次,Multisets内可包含多个数值相同的元素,内部由二叉树实现,便于查找;
  • Map/Multimap : Map的元素是成对的键值/实值,内部的元素依据其值自动排序,Map内的相同数值的元素只能出现一次,Multimaps内可包含多个数值相同的元素,内部由二叉树实现,便于查找;

容器类自动申请和释放内存,无需new和delete操作
 

5.2 迭代器(Iterator)

Iterator(迭代器)模式又称Cursor(游标)模式,用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。或者这样说可能更容易理解:Iterator模式是运用于聚合对象的一种模式,通过运用该模式,使得我们可以在不知道对象内部表示的情况下,按照一定顺序(由iterator提供的方法)访问聚合对象中的各个元素。

迭代器的作用:能够让迭代器与算法不干扰的相互发展,最后又能无间隙的粘合起来,重载了*,++,==,!=,=运算符。用以操作复杂的数据结构,容器提供迭代器,算法使用迭代器;常见的一些迭代器类型:iterator、const_iterator、reverse_iterator和const_reverse_iterator。

 

5.3 算法(Algorithm)

   STL提供了大约100个实现算法的模版函数,比如算法for_each将为指定序列中的每一个元素调用指定的函数,stable_sort以你所指定的规则对序列进行稳定性排序等等。只要我们熟悉了STL之后,许多代码可以被大大的化简,只需要通过调用一两个算法模板,就可以完成所需要的功能并大大地提升效率。
算法部分主要由头文件组成。

  •  是所有STL头文件中最大的一个(尽管它很好理解),它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。
  •  体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。
  •  中则定义了一些模板类,用以声明函数对象。

STL中算法大致分为四类:

1)非可变序列算法:指不直接修改其所操作的容器内容的算法。
2)可变序列算法:指可以修改它们所操作的容器内容的算法。
3)排序算法:对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作。
4)数值算法:对容器内容进行数值计算。

以下对所有算法进行细致分类并标明功能

【1】查找算法(13个)判断容器中是否包含某个值

  • adjacent_find:     在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的ForwardIterator否则返回last。重载版本使用输入的二元操作符代替相等的判断。
  • binary_search:    在有序序列中查找value,找到返回true。重载的版本实用指定的比较函数对象或函数指针来判断相等。
  • count:      利用等于操作符,把标志范围内的元素与输入值比较,返回相等元素个数。
  • count_if:  利用输入的操作符,对标志范围内的元素进行操作,返回结果为true的个数。
  • equal_range:  功能类似equal,返回一对iterator,第一个表示lower_bound,第二个表示upper_bound。
  • find: 利用底层元素的等于操作符,对指定范围内的元素与输入值进行比较。当匹配时,结束搜索,返回该元素的一个InputIterator。
  • find_end:   在指定范围内查找"由输入的另外一对iterator标志的第二个序列"的最后一次出现。找到则返回最后一对的第一个ForwardIterator,否则返回输入的"另外一对"的第一个ForwardIterator。重载版本使用用户输入的操作符代替等于操作。
  • find_first_of:  在指定范围内查找"由输入的另外一对iterator标志的第二个序列"中任意一个元素的第一次出现。重载版本中使用了用户自定义操作符。
  • find_if:  使用输入的函数代替等于操作符执行find。
  • lower_bound:  返回一个ForwardIterator,指向在有序序列范围内的可以插入指定值而不破坏容器顺序的第一个位置。重载函数使用自定义比较操作。
  • upper_bound:  返回一个ForwardIterator,指向在有序序列范围内插入value而不破坏容器顺序的最后一个位置,该位置标志一个大于value的值。重载函数使用自定义比较操作。
  • search:  给出两个范围,返回一个ForwardIterator,查找成功指向第一个范围内第一次出现子序列(第二个范围)的位置,查找失败指向last1。重载版本使用自定义的比较操作。
  • search_n: 在指定范围内查找val出现n次的子序列。重载版本使用自定义的比较操作。

 【2】排序和通用算法(14个)提供元素排序策略

  • inplace_merge:      合并两个有序序列,结果序列覆盖两端范围。重载版本使用输入的操作进行排序。
  • merge:                    合并两个有序序列,存放到另一个序列。重载版本使用自定义的比较。
  • nth_element:          将范围内的序列重新排序,使所有小于第n个元素的元素都出现在它前面,而大于它的都出现在后面。重载版本使用自定义的比较操作。
  • partial_sort:            对序列做部分排序,被排序元素个数正好可以被放到范围内。重载版本使用自定义的比较操作。
  • partial_sort_copy:  与partial_sort类似,不过将经过排序的序列复制到另一个容器。
  • partition:                  对指定范围内元素重新排序,使用输入的函数,把结果为true的元素放在结果为false的元素之前。
  • random_shuffle:     对指定范围内的元素随机调整次序。重载版本输入一个随机数产生操作。
  • reverse:                   将指定范围内元素重新反序排序。
  • reverse_copy:        与reverse类似,不过将结果写入另一个容器。
  • rotate:                     将指定范围内元素移到容器末尾,由middle指向的元素成为容器第一个元素。
  • rotate_copy:           与rotate类似,不过将结果写入另一个容器。
  • sort:                         以升序重新排列指定范围内的元素。重载版本使用自定义的比较操作。
  • stable_sort:             与sort类似,不过保留相等元素之间的顺序关系。
  • stable_partition:     与partition类似,不过不保证保留容器中的相对顺序。

 【3】删除和替换算法(15个)

  • copy:                     复制序列
  • copy_backward:    与copy相同,不过元素是以相反顺序被拷贝。
  • iter_swap:              交换两个ForwardIterator的值。
  • remove:                 删除指定范围内所有等于指定元素的元素。注意,该函数不是真正删除函数。内置函数不适合使用remove和remove_if函数。
  • remove_copy:        将所有不匹配元素复制到一个制定容器,返回OutputIterator指向被拷贝的末元素的下一个位置。
  • remove_if:              删除指定范围内输入操作结果为true的所有元素。
  • remove_copy_if:     将所有不匹配元素拷贝到一个指定容器。
  • replace:                  将指定范围内所有等于vold的元素都用vnew代替。
  • replace_copy:         与replace类似,不过将结果写入另一个容器。
  • replace_if:               将指定范围内所有操作结果为true的元素用新值代替。
  • replace_copy_if:     与replace_if,不过将结果写入另一个容器。
  • swap:                      交换存储在两个对象中的值。
  • swap_range:           将指定范围内的元素与另一个序列元素值进行交换。
  • unique:                    清除序列中重复元素,和remove类似,它也不能真正删除元素。重载版本使用自定义比较操作。
  • unique_copy:           与unique类似,不过把结果输出到另一个容器。

【4】排列组合算法(2个)提供计算给定集合按一定顺序的所有可能排列组合

  • next_permutation:    取出当前范围内的排列,并重新排序为下一个排列。重载版本使用自定义的比较操作。
  • prev_permutation:    取出指定范围内的序列并将它重新排序为上一个序列。如果不存在上一个序列则返回false。重载版本使用自定义的比较操作。

【5】算术算法(4个)

  • accumulate: iterator对标识的序列段元素之和,加到一个由val指定的初始值上。重载版本不再做加法,而是传进来的二元操作符被应用到元素上。
  • partial_sum:  创建一个新序列,其中每个元素值代表指定范围内该位置前所有元素之和。重载版本使用自定义操作代替加法
  • inner_product: 对两个序列做内积(对应元素相乘,再求和)并将内积加到一个输入的初始值上。重载版本使用用户定义的操作。
  • adjacent_difference: 创建一个新序列,新序列中每个新值代表当前元素与上一个元素的差。重载版本用指定二元操作计算相邻元素的差。

【6】生成和异变算法(6个)

  • fill:               将输入值赋给标志范围内的所有元素。
  • fill_n:           将输入值赋给first到first+n范围内的所有元素。
  • for_each:     用指定函数依次对指定范围内所有元素进行迭代访问,返回所指定的函数类型。该函数不得修改序列中的元素。
  • generate:     连续调用输入的函数来填充指定的范围。
  • generate_n:  与generate函数类似,填充从指定iterator开始的n个元素。
  • transform:     将输入的操作作用与指定范围内的每个元素,并产生一个新的序列。重载版本将操作作用在一对元素上,另外一个元素来自输入的另外一个序列。结果输出到指定容器。

【7】关系算法(8个)

  • equal:            如果两个序列在标志范围内元素都相等,返回true。重载版本使用输入的操作符代替默认的等于操作符。
  • includes:        判断第一个指定范围内的所有元素是否都被第二个范围包含,使用底层元素的<操作符,成功返回true。重载版本使用用户输入的函数。
  • lexicographical_compare:  比较两个序列。重载版本使用用户自定义比较操作。
  • max:                        返回两个元素中较大一个。重载版本使用自定义比较操作。
  • max_element:          返回一个ForwardIterator,指出序列中最大的元素。重载版本使用自定义比较操作。
  • min:                          返回两个元素中较小一个。重载版本使用自定义比较操作。
  • min_element:           返回一个ForwardIterator,指出序列中最小的元素。重载版本使用自定义比较操作。
  • mismatch:                 并行比较两个序列,指出第一个不匹配的位置,返回一对iterator,标志第一个不匹配元素位置。如果都匹配,返回每个容器的last。重载版本使用自定义的比较操作。
     

【8】集合算法(4个)

  • set_union:        构造一个有序序列,包含两个序列中所有的不重复元素。重载版本使用自定义的比较操作。
  • set_intersection: 构造一个有序序列,其中元素在两个序列中都存在。重载版本使用自定义的比较操作。
  • set_difference:   构造一个有序序列,该序列仅保留第一个序列中存在的而第二个中不存在的元素。重载版本使用自定义的比较操作。
  • set_symmetric_difference: 构造一个有序序列,该序列取两个序列的对称差集(并集-交集)。
     

【9】堆算法(4个)

  • make_heap:  把指定范围内的元素生成一个堆。重载版本使用自定义比较操作。
  • pop_heap:     并不真正把最大元素从堆中弹出,而是重新排序堆。它把first和last-1交换,然后重新生成一个堆。可使用容器的back来访问被"弹出"的元素或者使用pop_back进行真正的删除。重载版本使用自定义的比较操作。
  • push_heap:   假设first到last-1是一个有效堆,要被加入到堆的元素存放在位置last-1,重新生成堆。在指向该函数前,必须先把元素插入容器后。重载版本使用指定的比较操作。
  • sort_heap:     对指定范围内的序列重新排序,它假设该序列是个有序堆。重载版本使用自定义比较操作。

 

5.4 仿函数(Functor)    

仿函数(functor),就是使一个类的使用看上去象一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了。     有些功能的的代码,会在不同的成员函数中用到,想复用这些代码:       1、公共的函数,可以,这是一个解决方法,不过函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。 2、仿函数,写一个简单类,除了那些维护一个类的成员函数外,就只是实现一个operator(),在类实例化时,就将要用的,非参数的元素传入类中。                                          

算术类仿函数 关系运算类仿函数 逻辑运算仿函数
加:plus 等于:equal_to 逻辑与:logical_and
减:minus 不等于:not_equal_to 逻辑或:logical_or
乘:multiplies 大于:greater 逻辑否:logical_no
除:divides 大于等于:greater_equal  
模取:modulus 小于:less  
否定:negate 小于等于:less_equal  

 

5.5 适配器(Adaptor)    

 STL提供了三个容器适配器:queue、priority_queue、stack。这些适配器都是包装了vector、list、deque中某个顺序容器的包装器。注意:适配器没有提供迭代器,也不能同时插入或删除多个元素。......

 


以上内容来源网络 经过整理而成 

参考链接:

https://www.cnblogs.com/biyeymyhjob/archive/2012/07/22/2603525.html

String类:https://www.cnblogs.com/lanxiang/p/11252404.html

http://c.biancheng.net/stl/algorithms/

 


 

 

 

 

你可能感兴趣的:(C和C++)