C++程序设计——十一

C++ STL(Standard Template Library——标准模板库)

在前面,我们已经学习了C++模板的概念呢。而关于C++模板,C++提供了C++ STL,它是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。

初步认识STL

C++标准模板库的核心主要在于3个组件:

  • 容器(Containers):容器是用来管理某一类对象的集合。C++ 提供了各种不同类型的容器,比如 deque、list、vector、map 等。
  • 算法(Algorithms):算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作。
  • 迭代器(iterators):迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集。

这三个组件都带有丰富的预定义函数,帮助我们通过简单的方式处理复杂的任务。
此外,STL还有三个重要的组件:仿函数(Functor)、适配器(Adaptor)、分配器(allocator)

1、容器

在实际的开发过程中,数据结构本身的重要性不会逊于操作于数据结构的算法的重要性,当程序中存在着对时间要求很高的部分时,数据结构的选择就显得更加重要。

经典的数据结构数量有限,但是我们常常重复着一些为了实现向量、链表等结构而编写的代码,这些代码都十分相似,只是为了适应不同数据的变化而在细节上有所出入。

而这时,STL容器就起到非常重要的作用。它允许我们重复利用已有的实现构造自己的特定类型下的数据结构,通过设置一些模版类,STL容器对最常用的数据结构提供了支持,这些模板的参数允许我们指定容器中元素的数据类型,可以将我们许多重复而乏味的工作简化。

STL中,容器主要由七个部分组成

  • Vector:将元素置于一个动态数组中加以管理,可以随机存取元素(用索引直接存取),数组尾部添加或移除元素非常快速。但是在中部或头部安插元素比较费时;
  • Deque:是“double-ended queue”的缩写,可以随机存取元素(用索引直接存取),数组头部和尾部添加或移除元素都非常快速。但是在中部或头部安插元素比较费时;
  • List:双向链表,不提供随机存取(按顺序走到需存取的元素,O(n)),在任何位置上执行插入或删除动作都非常迅速,内部只需调整一下指针;
  • Set/Multiset:内部的元素依据其值自动排序,Set内的相同数值的元素只能出现一次,Multisets内可包含多个数值相同的元素,内部由二叉树实现,便于查找;
  • Map/Multimap:Map的元素是成对的键值/实值,内部的元素依据其值自动排序,Map内的相同数值的元素只能出现一次,Multimaps内可包含多个数值相同的元素,内部由二叉树实现,便于查找;
顺序容器

其中,可变长动态数组 vector、双端队列 deque、双向链表 list属于顺序容器,因为对这些容器来说,元素在容器中的位置同元素的值无关,即容器不是排序的。将元素插入容器时,指定在什么位置(尾部、头部或中间某处)插入,元素就会位于什么位置。

关联容器

而set、multiset、map、multimap属于关联容器,因为他们的元素是排序的,插入元素时,容器会按一定的排序规则将元素放到适当的位置上,因此插入元素时不能指定位置。
默认情况下,关联容器中的元素是从小到大排序(或按关键字从小到大排序)的,而且用<运算符比较元素或关键字大小。因为是排好序的,所以关联容器在查找时具有非常好的性能。

容器适配器

除了以上两类容器外,STL 还在两类容器的基础上屏蔽一部分功能,突出或增加另一部分功能,实现了三种容器适配器:栈 stack、队列 queue、优先级队列 priority_queue。

栈:后进先出
队列:先进先出
优先级队列:根据优先级排序

为称呼方便起见,本教程后面将容器和容器适配器统称为容器。

容器的一般性质

容器都是类模板。它们实例化后就成为容器类。用容器类定义的对象称为容器对象。

所有容器都有两个成员函数:

  • int size():返回容器对象中元素的个数
  • bool empty():判断容器是否为空

顺序容器和关联容器还有以下成员函数:

  • begin():返回指向容器中第一个元素的迭代器。
  • end():返回指向容器中最后一个元素后面的位置的迭代器。
  • rbegin():返回指向容器中最后一个元素的反向迭代器。
  • rend():返回指向容器中第一个元素前面的位置的反向迭代器。
  • erase(…):从容器中删除一个或几个元素。
  • clear():从容器中删除所有元素。

注意:如果一个容器是空的,则 begin() 和 end() 的返回值相等,rbegin() 和 rend() 的返回值也相等。
顺序容器还有以下常用成员函数:

  • front():返回容器中第一个元素的引用。
  • back():返回容器中最后一个元素的引用。
  • push_back():在容器末尾增加新元素。
  • pop_back():删除容器末尾的元素。
  • insert(…):插入一个或多个元素。

2、STL迭代器

要访问顺序容器和关联容器中的元素,需要通过“迭代器(iterator)”进行。

迭代器(iterator)模式又称游标(Cursor)模式,用于提供一种方法顺序访问一个聚合对象中各个元素, 而又不需知道该对象的内部表示。

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

迭代器按照定义方式分成以下四种。

  1. 正向迭代器,定义方法如下:
    容器类名::iterator 迭代器名;

  2. 常量正向迭代器,定义方法如下:
    容器类名::const_iterator 迭代器名;

  3. 反向迭代器,定义方法如下:
    容器类名::reverse_iterator 迭代器名;

  4. 常量反向迭代器,定义方法如下:
    容器类名::const_reverse_iterator 迭代器名;

迭代器用法示例

通过迭代器可以读取它指向的元素,*迭代器名就表示迭代器指向的元素。通过非常量迭代器还能修改其指向的元素。

迭代器都可以进行++操作。反向迭代器和正向迭代器的区别在于:

  • 对正向迭代器进行++操作时,迭代器会指向容器中的后一个元素;
  • 而对反向迭代器进行++操作时,迭代器会指向容器中的前一个元素。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> v;  //v是存放int类型变量的可变长数组,开始时没有元素
    for (int n = 0; n<5; ++n)
        v.push_back(n+1);  //push_back成员函数在vector容器尾部添加一个元素
    vector<int>::iterator i;  //定义正向迭代器
    for (i = v.begin(); i != v.end(); ++i) {  //用迭代器遍历容器
        cout << *i << " ";  //*i 就是迭代器i指向的元素
        *i *= 3;  //每个元素变为原来的3倍
    }
    cout << endl;
    //定义反向迭代器
    vector<int>::reverse_iterator j;
    for (j = v.rbegin(); j != v.rend(); ++j)//用反向迭代器遍历容器
        cout << *j << " ";
    cout<<endl;
    return 0;
}

运行结果如下

1 2 3 4 5 
15 12 9 6 3 

分析:
在上面代码中,第 6 行,vector 容器有多个构造函数,如果用无参构造函数初始化,则容器一开始是空的。

第 10 行,begin 成员函数返回指向容器中第一个元素的迭代器。++i 使得 i 指向容器中的下一个元素。end 成员函数返回的不是指向最后一个元素的迭代器,而是指向最后一个元素后面的位置的迭代器,因此循环的终止条件是i != v.end()。

第 16 行定义了反向迭代器用以遍历容器。反向迭代器进行++操作后,会指向容器中的上一个元素。rbegin 成员函数返回指向容器中最后一个元素的迭代器,rend 成员函数返回指向容器中第一个元素前面的位置的迭代器,因此本循环实际上是从后往前遍历整个数组。

注意:如果迭代器指向了容器中最后一个元素的后面或第一个元素的前面,再通过该迭代器访问元素,就有可能导致程序崩溃,这和访问 NULL 或未初始化的指针指向的地方类似。

3、STL算法

在STL中,算法其实就是指函数模板,它通过迭代器来操纵容器中的元素,比如插入、删除、查找、排序等。
STL 中的大部分常用算法都在头文件 algorithm 中定义。此外,头文件 numeric 中也有一些算法。

许多算法操作的是容器上的一个区间(也可以是整个容器),因此需要两个参数,一个是区间起点元素的迭代器,另一个是区间终点元素的后面一个元素的迭代器。例如,排序和查找算法都需要这两个参数来指明待排序或待查找的区间。

有的算法返回一个迭代器。例如,find 算法在容器中查找一个元素,并返回一个指向该元素的迭代器。
有的算法会改变其所作用的容器。例如:

  • copy:将一个容器的内容复制到另一个容器。
  • remove:在容器中删除一个元素。
  • random_shuffle:随机打乱容器中的元素。
  • fill:用某个值填充容器。

而有的算法不会改变其所作用的容器。例如:

  • find:在容器中查找元素。
  • count_if:统计容器中符合某种条件的元素的个数。
常用算法find

我们通过对find算法的介绍来初步认识什么是算法,以及怎么使用它。

find 算法和其他算法一样都是函数模板。find 模板的原型如下:

template <class InIt, class T>
InIt find(InIt first, InIt last, const T& val);

其功能可以是在迭代器 first、last 指定的容器的一个区间 [first, last) 中,按顺序查找和 val 相等的元素。如果找到,就返回该元素的迭代器;如果找不到,就返回 last。
注意![first, last) 这个区间是一个左闭右开的区间,即 last 指向的元素其实不在此区间内。

find函数使用==来判断元素是否相等,所以对特殊的对象,我们需要重载==运算符,使得可以用它来比较两个对象。

值得一提的是,我们前面提到的是“其功能可以是”,而不是“其功能就是”。这是因为模板只是一种代码形式,这种代码形式具体能完成什么功能,取决于程序员对该模板写法的了解及其想象力。按照语法,调用 find 模板时,first 和 last 只要类型相同就可以,不一定必须是迭代器。

#include <vector>
#include <algorithm>
#include <iostream>
using namespace std;
int main()  
{
   
    vector<int> v;

    for(int i=0;i<10;i++)
    {
        v.push_back(i+1);//向v中放入元素:1到10
    }

    vector<int>::iterator p;//定义迭代器
    p = find(v.begin(),v.end(),3); //在v中查找3,若找到,p被赋值为3,若找不到,find返回 v.end(),p被赋值为v.end()的值
    if(p != v.end()) 
        cout<<"1) " <<  * p << endl;
    else
        cout<<"1) "<<"没找到"<<endl;

    p = find(v.begin(),v.end(),90);
    if(p != v.end())
        cout<<"2) "<<*p<<endl;
    else
        cout<<"2) "<<"没找到"<< endl;

    int a[10] = {10,20,30,40};
    int * pp = find(a,a+4,20);//对普通数组使用find函数
    if(pp == a + 4)
        cout <<"3) "<<"没找到"<< endl;
    else
        cout <<"3) " <<* pp << endl;
}

运行结果如下:

1) 3
2) 没找到
3) 20

代码中,分别对vector对象和普通数组使用了find函数,要注意的是,fand函数若没找到对应元素,会返回最后一个元素。

对算法的初步分类
1)查找算法:判断容器中是否包含某个值
  • 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)排序和通用算法:提供元素排序策略
  • 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)删除和替换算法
  • 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)排列组合算法:提供计算给定集合按一定顺序的所有可能排列组合
  • next_permutation: 取出当前范围内的排列,并重新排序为下一个排列。重载版本使用自定义的比较操作。
  • prev_permutation: 取出指定范围内的序列并将它重新排序为上一个序列。如果不存在上一个序列则返回false。
5)算术算法
  • accumulate:iterator对标识的序列段元素之和,加到一个由val指定的初始值上。
  • partial_sum:创建一个新序列,其中每个元素值代表指定范围内该位置前所有元素之和。
  • inner_product:对两个序列做内积(对应元素相乘,再求和)并将内积加到一个输入的初始值上。
  • adjacent_difference:创建一个新序列,新序列中每个新值代表当前元素与上一个元素的差。
6)生成和异变算法
  • fill: 将输入值赋给标志范围内的所有元素。
  • fill_n: 将输入值赋给first到first+n范围内的所有元素。
  • for_each: 用指定函数依次对指定范围内所有元素进行迭代访问,返回所指定的函数类型。该函数不得修改序列中的元素。
  • generate: 连续调用输入的函数来填充指定的范围。
  • generate_n: 与generate函数类似,填充从指定iterator开始的n个元素。
  • transform: 将输入的操作作用与指定范围内的每个元素,并产生一个新的序列。重载版本将操作作用在一对元素上,另外一个元素来自输入的另外一个序列。结果输出到指定容器。
7)关系算法
  • equal:如果两个序列在标志范围内元素都相等,返回true。重载版本使用输入的操作符代替默认的等于操作符。
  • includes:判断第一个指定范围内的所有元素是否都被第二个范围包含,使用底层元素的<操作符,成功返回true。重载版本使用用户输入的函数。
  • lexicographical_compare:比较两个序列。重载版本使用用户自定义比较操作。
  • max:返回两个元素中较大一个。重载版本使用自定义比较操作。
  • max_element:返回一个ForwardIterator,指出序列中最大的元素。重载版本使用自定义比较操作。
  • min:返回两个元素中较小一个。重载版本使用自定义比较操作。
  • min_element:返回一个ForwardIterator,指出序列中最小的元素。重载版本使用自定义比较操作。
  • mismatch:并行比较两个序列,指出第一个不匹配的位置,返回一对iterator,标志第一个不匹配元素位置。如果都匹配,返回每个容器的last。重载版本使用自定义的比较操作。
8)集合算法
  • set_union:构造一个有序序列,包含两个序列中所有的不重复元素。重载版本使用自定义的比较操作。
  • set_intersection:构造一个有序序列,其中元素在两个序列中都存在。重载版本使用自定义的比较操作。
  • set_difference:构造一个有序序列,该序列仅保留第一个序列中存在的而第二个中不存在的元素。重载版本使用自定义的比较操作。
  • set_symmetric_difference: 构造一个有序序列,该序列取两个序列的对称差集(并集-交集)。
9)堆算法
  • make_heap: 把指定范围内的元素生成一个堆。重载版本使用自定义比较操作。
  • pop_heap: 并不真正把最大元素从堆中弹出,而是重新排序堆。它把first和last-1交换,然后重新生成一个堆。可使用容器的back来访问被"弹出"的元素或者使用pop_back进行真正的删除。重载版本使用自定义的比较操作。
  • push_heap: 假设first到last-1是一个有效堆,要被加入到堆的元素存放在位置last-1,重新生成堆。在指向该函数前,必须先把元素插入容器后。重载版本使用指定的比较操作。
  • sort_heap: 对指定范围内的序列重新排序,它假设该序列是个有序堆。重载版本使用自定义比较操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值