[C++] STL标准模板库

目录

一、STL简介

二、STL的组件 

三、STL头文件与命名空间 

四、STL三大组件之 —— 容器   

4.1  容器概述

4.2  序列式容器 

4.3  排序式容器  

五、STL三大组件之 —— 迭代器  

5.1  迭代器概述  

5.2  五种迭代器 

5.3  迭代器的定义

5.4  迭代器的使用方法

一、STL简介

        STL(Standard Template Library)标准模板库,最早由惠普实验室开发,是一个基于泛型编程思想(模板)的程序库,内嵌于C++编译器中。它提供了几乎所有常见的数据结构类型及其算法,是对基础数据结构的补充与强化。它有两个特征:

        特性1:数据结构与算法的分离。基于泛型编程思想,STL中算法并不依赖于数据结构进行实现。换句话说,STL中的一个算法可以操作几种不同的容器数据。例如,sort()可作用于动态数组、栈和队列,甚至是链表。实际上,这主要是通过迭代器实现的。 

        特性2:STL并非面向对象(OOP)。STL中的数据结构与算法皆不具有明显的继承关系,各部分之间相互独立。“采用非面向对象的方法在逻辑上降低了各部分之间的耦合关系,以提升各自的独立性、弹性、交互操作性”。这符合泛型编程设计原理。不过,STL也采纳了一些封装的思想,令使用者不必知晓其底层实现也能应用自如。

二、STL的组件 

        要设计一个复杂的系统,当然不是用一个 .cpp 就能完成的。工程师的方法当然是将其拆分成组件,从而分别进行设计,再将这些组件按照一定规则组合起来。STL在整体上就遵循这样的设计思路。STL由6个组件组成:容器算法迭代器仿函数容器适配器空间配置器; 

  • 容器:各种数据结构,如vector、list、deque、set、map。以模板类的方式提供。
  • 算法:对容器中的数据执行操作的模板函数,如sort()、find()。在 std 命名空间中定义。
  • 迭代器:一种访问容器的方法,可理解为指针。算法对容器的操作要借助迭代器才能实现。
  • 仿函数:一种类,可以像使用函数一样使用它,可理解为更高级的运算符重载。
  • 容器适配器:一种接口类,封装了一些基本容器,如stack、queue等。
  • 空间配置器:自动配置与管理内存空间。使用STL时无需手动管理内存。
     

* 实际使用时,我们只要掌握前三个即可! 

        STL组件的交互关系:容器通过空间配置器取得数据存储空间,算法通过迭代器操作容器中的内容,仿函数可以协助算法,适配器可以修饰仿函数。如图: (了解即可) 

33c465d0668043ceb375d16071690453.png 

三、STL头文件与命名空间 

         STL被定义在命名空间 std 中,头文件如下: 

 

四、STL三大组件之 —— 容器   

4.1  容器概述

        要操作任何数据,首先要解决数据的存储问题。简单地说,容器就是一个能够容纳各种数据的“桶”。不过,容器中不仅储存数据,还用各种组织方式将数据组织起来,就形成了数据结构。容器就是一个高级的桶,它里面还存放了成员函数。

        根据组织数据的方式不同,STL 提供的标准容器可分为 3 类:序列容器、排序容器、哈希容器。其中后两类容器也统称为关联容器。 

  • 序列容器:主要包括 vector 向量容器、list 列表容器以及 deque 双端队列容器。元素在容器中的位置同元素的值无关,即容器不是排序的。类似数组,序列容器随机存储性能较好。
  • 排序容器:包括 set 集合容器、multiset多重集合容器、map映射容器以及 multimap 多重映射容器。排序容器中的元素是排序好的(按键排序)。类似函数映射,排序容器查找性能较好。
  • 哈希容器:C++11 新加入 4 种关联式容器,分别是 unordered_set 哈希集合、unordered_multiset 哈希多重集合、unordered_map 哈希映射以及 unordered_multimap 哈希多重映射。哈希容器中的元素是未排序的,元素的位置由哈希函数决定。哈希容器查找性能比排序容器更好,而遍历效果较差。
     

4.2  序列式容器 

  • array<T,N> (数组容器):可以存储 N 个元素的高级数组。容器一旦建立,其长度就固定不变;
  • vector<T> (向量容器):一个长度可变的序列容器,你常常会在LeetCode使用它。使用此容器,在尾部增加或删除元素的效率最高O(1) ,在其它位置插入或删除元素效率一般O(n) ;
  • deque<T> (双端队列容器):和 vector 非常相似,区别在其在头部插入或删除元素也同样高效O(1);
  • list<T> (链表容器):一个长度可变的序列容器,底层为双向链表。在这个序列的任何地方都可以高效地增加或删除元素O(1),但随机访问的效率一般O(n);
  • forward_list<T> (正向链表容器):和 list 容器非常类似,但底层为单链表,只能正向进行访问。它比链表容器快、更节省内存。

4.3  排序式容器  

  • pair<T, T> (键值对类模板):一个类模板,用以创建键值对。
  • map<T, T> (映射容器):其中存储pair对象,存储时会自动根据键的大小进行排序 (内部排序,对使用者是不可见的,除非用迭代器遍历)。容器中的键都是唯一不重复的,且不能被修改。
  •  multimap<T, T> (多重映射容器):与map容器非常相似,区别在于multimap容器可以同时存储多个键相同的键值对。
  • set<T> (集合容器) :类似于map,set容器也存储pair对象,但要求键值对的 键 与 值 必须相等,故只需一个参数T。
  • multiset<T> (多重集合容器):与set容器非常相似,区别在于multiset容器可以同时存储多个键相同的键值对。
     

 4.4  哈希容器 

        哈希容器与排序式容器类似,但其底层依靠哈希表实现;它不会对键值对进行排序,元素的位置由哈希函数决定;

  • unordered_map<T, T> (哈希映射)
  • unordered_multimap<T, T> (哈希多重集合)
  • unordered_set<T> (哈希映射)
  • unordered_multise<T> (多重哈希映射)

五、STL三大组件之 —— 迭代器  

5.1  迭代器概述  

        我们前面说到 “...分别进行设计,再将这些组件按照一定规则组合起来” ,那么,怎么将容器与算法组合起来?算法应该如何去操作容器这种复杂的组件?需要为每个容器都定制算法吗?

        首先,算法要操作容器,需要有一个类似中介的装置。这个中介需要能阅读 (遍历) 容器内的数据,然后提供给算法使用。另外,它还要能对外隐藏容器的内部差异,从而以统一的界面向算法传送数据 (泛型)。在这样的思想下,迭代器应运而生。

        其次,我们不需要为每个容器都定制算法。尽管不同容器的内部结构各异,但它们本质上都是用来存储大量数据的,换句话说,都是一串能存储多个数据的存储单元。因此,诸如数据的排序、查找、求和等操作方法应该在逻辑上是类似的。既然类似,完全可以利用泛型技术,将它们设计成适用所有容器的通用算法,而算法的具体化交给迭代器完成,这将大大降低我们的使用难度。
        总的来说,你可以将迭代器看做适用于所有容器的强大指针,它给算法提供支持。实际上,指针就是一种迭代器。

        * 以下内容不需要记忆,但要理解

        STL标准库为每一种标准容器定义了一种迭代器类型,常见的有:前向迭代器、双向迭代器、随机访问迭代器、输入迭代器、输出迭代器。要注意的是,这五种迭代器都用同一语法定义:

 容器类型::iterator it 

而 it 具体是哪种迭代器,是由容器类型决定的;不同容器的对应的迭代器类型如下: 

 

5.2  五种迭代器 

前向迭代器:ForwardIterator

        ForwardIterator 支持 ++、* 操作,还可以被复制或赋值,可以用 == 和 != 运算符进行比较。此外,两个正向迭代器可以互相赋值;

双向迭代器:BidirectionalIterator
        BidirectionalIterator 在正向迭代器的基础上,添加了 -- 操作;

随机访问迭代器:RandomAccessIterator
         RandomAccessIterator 具有双向迭代器的全部功能,与指针极其相似。它支持以下操作:( i 为整数)

 

        另外,RandomAccessIterator 还支持 <、>、<=、>= 比较运算符。表达式 p2-p1 也是有定义的,其返回值表示 p2 所指向元素和 p1 所指向元素的序号之差。

输入迭代器
        将输入流作为操作对象,了解即可

输出迭代器
        将输出流作为操作对象,了解即可

5.3  迭代器的定义

         前面说到,五种迭代器都是用同一语法定义的,迭代器 it 的类型由容器类型决定。不过,定义迭代器的语法不止 iterator 这一个,还有: 

 

常量迭代器和非常量迭代器的分别在于:非常量迭代器能修改其指向的元素。
反向迭代器和正向迭代器的区别在于对 iterator 进行 ++ 操作时,迭代器会指向容器中的后一个元素。对 reverse_iterator 进行 ++ 操作时,迭代器会指向容器中的前一个元素。
         另外,以上 4 种定义迭代器的方式,并不是每种容器都适用,我们最常用的还是正向迭代器。


5.4  迭代器的使用方法

         一般来说,算法常用 first、last 迭代器作为参数,其作用请顾名思义。另外,你常见到的 a.begin()、a.end()、a.begin() + 1 甚至是 a + 1,它们通通都是迭代器。 

        如果你不幸需要用迭代器遍历容器,你完全可以将迭代器看做指针, *it 就表示迭代器所指向的元素,使用方法如下:

    // 用 iterator 遍历数组
    vector<int> v = {1,2,3,4,5,6,7,8,9,10};
    vector<int>::iterator it;
    for (it = v.begin(); it != v.end(); ++it)
        cout << *i << " ";
    // 用 reverse_iterator 反向遍历数组
    std::reverse_iterator<std::list<int>::iterator> rbegin = values.rbegin();
    std::reverse_iterator<std::list<int>::iterator> rend = values.rend();
    while (rbegin != rend) {
        cout << *rbegin << " ";
        ++rbegin;    // 反向迭代器 ++ 向前移动
    }

* 这里的 v.begin() 以及 v.end() 是 vector<int> 类型的迭代器,故可以相比较;

* 请不要用 it < v.end() 来代替 it != v.end() ,虽然这是正确的;

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ja kar ta

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值