C++学习笔记-第11单元 标准模板库介绍

第11单元 标准模板库介绍

文章目录

  • 第11单元 标准模板库介绍
    • 单元导读
    • 11.1 标准模板库(STL)基础
    • 11.2 STL容器简介
    • 11.3 STL迭代器简介
      • 11.3.1 使用迭代器访问容器中的元素
      • 11.3.2 迭代器类型
      • 11.3.3 迭代器支持的运算符操作
    • 11.4 顺序容器
      • 11.4.1 顺序容器的逻辑结构
      • 11.4.2 代码展示```std::vector```
      • 11.4.3 代码展示```std::deque```
      • 11.4.4 代码展示```std::list```
    • 11.5 关联容器
      • 11.5.1 关联容器的逻辑结构
      • 11.5.2 代码展示```std::multiset```
      • 11.5.3 代码展示```std::map```
    • 11.6 容器适配器

注:本部分内容主要来自中国大学MOOC北京邮电大学崔毅东的 《C++程序设计》课程。
注:94条 C++程序规范。


单元导读

  本单元重点是对标准模板库中的顺序容器关联容器的使用,以及如何创建迭代器以遍历容器。在使用容器时要注意不同容器的实现方式对遍历、搜索、删除、插入容器元素的影响。此外还要注意,如果创建针对一个容器的迭代器后,又向容器插入或者删除了元素,那么大部分情况下,这个已经创建的迭代器是会失效的。很多同学在遍历容器时遇到这个问题而不知道原因,花费了大量的时间调试代码。

11.1 标准模板库(STL)基础

  本节来介绍C++标准模板库(Standard Template Library, STL),STL并不是随着C++的诞生而来,而是由STL之父Stepanov(合作者Meng Lee)于上世纪八十年代末九十年代初创造,并于1994年2月正式成为ANSI/ISO C++的一部分(有修改)。但是要注意STL并不是“C++标准库(C++ Standard Library)”,STL只是C++标准库中重要的一部分,使用STL可以极大地简化编程语句。顾名思义STL使用了大量的模板,其由五部分组成:容器(Containers)、迭代器(Iterators)、 算法(Algorithms)、函数对象(Function Objects)、空间分配器(Memory Allocation),下面简要的介绍前三个组件。

  • 参考stackoverflow文章:“STL”和“C++标准库”的区别?

1. 容器(Containers)
  容器类似于“储物柜”,用于保存一组任意类型的数据,数据个体被称为“元素”。“容器”分为三类:

  1. 顺序容器(Sequence containers):是一种多个元素的有序集合的线性数据结构,有头有尾、有前有后,例如向量类vector列表list双端队列deque(double end queue)。
  2. 关联容器(Associative containers):是一种可快速定位元素的非线性数据结构,这类容器主要用于存储“键值对”,例如集合setmultiset映射mapmultimap
  3. 容器适配器(Container adapters):并不是一个完整的容器,而是依赖于前面已有的容器进行构建,是顺序容器的受限版本,所以也被称为“二级容器”。主要用于处理特殊情况,例如栈stack队列queue优先队列priority_queue
表11-1 STL所有的容器类
STL容器类 头文件 作用
一级容器 顺序容器 vector 直接访问任意元素,快速插入、删除尾部元素
deque 直接访问任意元素,快速插入、删除头部和尾部元素
list 快速插入、删除任意位置元素
关联容器 set 快速查询元素,无重复关键字
multiset 与set相同,但允许重复关键字
map 键值对映射,不允许重复关键字,使用关键字快速查询元素
multimap 与map相同,但允许重复关键字
二级容器 容器适配器 stack 后进先出容器
queue 先进先出容器
priority_queue 高优先级元素先删除

上表中:

  1. vector:“快速”指的是执行效率。
  2. deque:看起来功能比vector强,但实现时的开销比vector略微大一些,所以尽量使用vector
  3. list:不能直接访问任意元素,因为是使用“链表”这种数据结构实现。

2. 迭代器(Iterators)
  迭代器用于访问(遍历)容器中的元素,主要是解引用*->++等指针相关操作进行重载的类模板,比如for(int i=0; i<10; i++)中的i就可以看成是一个类似指针的东西,也就是迭代器。每个容器都有自己专属的迭代器,只有容器才知道如何遍历自己的元素。

  那迭代器作为一种泛型指针,不仅可以通过[]访问元素,还可以通过自增运算++指向下一个元素。一般情况下,迭代器不会单独使用,而是与容器共同使用,但总有一小部分例外:有些迭代器与容器无关,如istream iteratorostream iterator

  • 11.4节、11.5节将演示如何使用迭代器遍历顺序容器、关联容器。

3. 算法(Algorithms)

  算法(algorithms)就是操作容器中数据的方法,并且在术语上“算法”经常可以和操作(operations)、函数(functions)、方法(methods)相替换。STL中提供了大约80种独立于容器的算法:查找、排序、比较、替换……可自行查找相关书籍。

11.2 STL容器简介

  上一节对于“容器”做出了简单的介绍,本小节就来介绍STL容器中一些通用的操作方法。下表11-2、表11-3给出了容器中一些常见的操作:

表11-2 所有容器类的共同函数
共同函数 描述
无参构造函数 构造一个空容器
带参构造函数 每个容器都有多个带有参数的构造函数
拷贝构造函数 创建一个容器,从一个已有的同类型容器中复制元素
析构函数 容器销毁后执行清理工作
empty() 若容器中没有元素则返回空
size() 返回容器中的元素数目
赋值运算符= 将容器内容复制到另一个容器
关系运算符(<,<=,>,>=,==,!=) 顺序比较两个容器中的对应元素,来确定大小关系
表11-3 一级容器的通用函数
通用函数 描述
cl.swap(c2) 交换两个容器c1和c2的内容
cl.max_size() 返回一个容器可以容纳的最大元素数量
c.clear() 删除容器中的所有元素
c.begin() 返回容器首元素的迭代器
c.end() 返回容器尾元素之后位置的迭代器
c.rbegin() 返回容器为元素的迭代器,用于逆序遍历
c.rend() 返回容器首元素之前位置的迭代器,用于逆序遍历
c.erase(beg, end) 删除容器中从beg到end-1之间的元素,beg和end都是迭代器

最后使用代码简单展示一下容器类的创建和操作,基本思路就是“创建容器类对象–>存入数据–>显示元素数量–>比较两个容器对象大小”:
源文件SimpleSTLDemo.cpp

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using std::cout;
using std::endl;
int main() {
    //创建容器对象
    std::vector<int>     vector1,    vector2;
    std::list<int>       list1,      list2;
    std::deque<int>      deque1,     deque2;
    std::set<int>        set1,       set2;
    std::multiset<int>   multiset1,  multiset2;
    std::stack<int>      stack1,     stack2;
    std::queue<int>      queue1,     queue2;

    //测试方法:存入数据、显示元素数量、比较两个容器对象大小。
    //针对vector的测试代码
    cout << "针对vector的测试" << endl;
    vector1.push_back(1);   //存入1
    vector1.push_back(2);   //存入2
    vector2.push_back(30);  //存入30
    cout << "vector1当前元素数量:" << vector1.size() << endl;
    cout << "vector2当前元素数量:" << vector2.size() << endl;
    cout << "vector1最大容量:" << vector1.max_size() << endl;
    cout << "vector2最大容量:" << vector2.max_size() << endl;
    vector1.swap(vector2);//交换元素
    cout << "vector1当前元素数量:" << vector1.size() << endl;
    cout << "vector2当前元素数量:" << vector2.size() << endl;
    cout << "vector1 < vector2 : " << std::boolalpha << (vector1 < vector2) << endl << endl;

    //针对list的测试代码
    cout << "针对list的测试" << endl;
    list1.push_back(1);   //存入1
    list1.push_back(2);   //存入2
    list2.push_back(30);  //存入30
    cout << "list1当前元素数量:" << list1.size() << endl;
    cout << "list2当前元素数量:" << list2.size() << endl;
    cout << "list1最大容量:" << list1.max_size() << endl;
    cout << "list2最大容量:" << list2.max_size() << endl;
    list1.swap(list2);//交换元素
    cout << "list1当前元素数量:" << list1.size() << endl;
    cout << "list2当前元素数量:" << list2.size() << endl;
    cout << "list1 < list2 : " << std::boolalpha << (list1 < list2) << endl << endl;

    //针对deque的测试代码
    cout << "针对deque的测试" << endl;
    deque1.push_back(1);   //存入1
    deque1.push_back(2);   //存入2
    deque2.push_back(30);  //存入30
    cout << "deque1当前元素数量:" << deque1.size() << endl;
    cout << "deque2当前元素数量:" << deque2.size() << endl;
    cout << "deque1最大容量:" << deque1.max_size() << endl;
    cout << "deque2最大容量:" << deque2.max_size() << endl;
    deque1.swap(deque2);//交换元素
    cout << "deque1当前元素数量:" << deque1.size() << endl;
    cout << "deque2当前元素数量:" << deque2.size() << endl;
    cout << "deque1 < deque2 : " << std::boolalpha << (deque1 < deque2) << endl << endl;

    //针对set的测试代码
    cout << "针对set的测试" << endl;
    set1.insert(1);   //存入1
    set1.insert(1);   //存入1
    set1.insert(2);   //存入2
    set2.insert(30);  //存入30
    cout << "set1当前元素数量:" << set1.size() << endl;
    cout << "set2当前元素数量:" << set2.size() << endl;
    cout << "set1最大容量:" << set1.max_size() << endl;
    cout << "set2最大容量:" << set2.max_size() << endl;
    set1.swap(set2);//交换元素
    cout << "set1当前元素数量:" << set1.size() << endl;
    cout << "set2当前元素数量:" << set2.size() << endl;
    cout << "set1 < set2 : " << std::boolalpha << (set1 < set2) << endl << endl;

    //针对multiset的测试代码
    cout << "针对multiset的测试" << endl;
    multiset1.insert(1);   //存入1
    multiset1.insert(1);   //存入1
    multiset1.insert(2);   //存入2
    multiset2.insert(30);  //存入30
    cout << "multiset1当前元素数量:" << multiset1.size() << endl;
    cout << "multiset2当前元素数量:" << multiset2.size() << endl;
    cout << "multiset1最大容量:" << multiset1.max_size() << endl;
    cout << "multiset2最大容量:" << multiset2.max_size() << endl;
    multiset1.swap(multiset2);//交换元素
    cout << "multiset1当前元素数量:" << multiset1.size() << endl;
    cout << "multiset2当前元素数量:" << multiset2.size() << endl;
    cout << "multiset1 < multiset2 : " << std::boolalpha << (multiset1 < multiset2) << endl << endl;

    //下面是容器适配器的测试
    //针对stack的测试代码
    cout << "针对stack的测试" << endl;
    stack1.push(1);   //压入1
    stack1.push(1);   //压入1
    stack1.push(2);   //压入2
    stack2.push(30);  //压入30
    cout << "stack1当前元素数量:" << stack1.size() << endl;
    cout << "stack2当前元素数量:" << stack2.size() << endl;
    cout << "stack1 < stack2 : " << std::boolalpha << (stack1 < stack2) << endl << endl;

    //针对queue的测试代码
    cout << "针对queue的测试" << endl;
    queue1.push(1);   //存入1
    queue1.push(1);   //存入1
    queue1.push(2);   //存入2
    queue2.push(30);  //存入30
    cout << "queue1当前元素数量:" << queue1.size() << endl;
    cout << "queue2当前元素数量:" << queue2.size() << endl;
    cout << "queue1 < queue2 : " << std::boolalpha << (queue1 < queue2) << endl << endl;

    return 0;
}

运行结果:

针对vector的测试
vector1当前元素数量:2
vector2当前元素数量:1
vector1最大容量:1073741823
vector2最大容量:1073741823
vector1当前元素数量:1
vector2当前元素数量:2
vector1 < vector2 : false

针对list的测试
list1当前元素数量:2
list2当前元素数量:1
list1最大容量:357913941
list2最大容量:357913941
list1当前元素数量:1
list2当前元素数量:2
list1 < list2 : false

针对deque的测试
deque1当前元素数量:2
deque2当前元素数量:1
deque1最大容量:1073741823
deque2最大容量:1073741823
deque1当前元素数量:1
deque2当前元素数量:2
deque1 < deque2 : false

针对set的测试
set1当前元素数量:2
set2当前元素数量:1
set1最大容量:214748364
set2最大容量:214748364
set1当前元素数量:1
set2当前元素数量:2
set1 < set2 : false

针对multiset的测试
multiset1当前元素数量:3
multiset2当前元素数量:1
multiset1最大容量:214748364
multiset2最大容量:214748364
multiset1当前元素数量:1
multiset2当前元素数量:3
multiset1 < multiset2 : false

针对stack的测试
stack1当前元素数量:3
stack2当前元素数量:1
stack1 < stack2 : true

针对queue的测试
queue1当前元素数量:3
queue2当前元素数量:1
queue1 < queue2 : true

11.3 STL迭代器简介

11.3.1 使用迭代器访问容器中的元素

  迭代器的主要用途是访问和处理一级容器(顺序容器+关联容器)中的元素,并且一级容器中的某些函数也与迭代器有关,比如begin()返回首元素的指针、end()返回尾元素后面一个位置的指针。下面使用一段代码展示如何使用迭代器访问容器中的元素,并根据运行结果讨论不同容器对于元素的输入输出次序的影响:
源文件IteratorDemo.cpp

#include 
#include 
#include 
using std::cout;
using std::endl;
int main() {
    //创建vector对象并存入一些数据
    std::vector<int> intVector;
    intVector.push_back(10);
    intVector.push_back(40);
    intVector.push_back(50);
    intVector.push_back(20);
    intVector.push_back(30);
    //创建vector迭代器对象,并遍历vector所有元素
    std::vector<int>::iterator p1;
    cout << "遍历vector元素:";
    for (p1 = intVector.begin(); p1 != intVector.end(); p1++) {
        cout << *p1 << " ";
    }

    //创建set对象并存入一些数据
    std::set<int> intSet;
    intSet.insert(10);
    intSet.insert(40);
    intSet.insert(50);
    intSet.insert(20);
    intSet.insert(30);
    //创建set迭代器对象,并遍历set所有元素
    std::set<int>::iterator p2;
    cout << "\n遍历set元素:";
    for (p2 = intSet.begin(); p2 != intSet.end(); p2++) {
        cout << *p2 << " ";
    }
    return 0;
}

运行结果:

遍历vector元素:10 40 50 20 30
遍历set元素:10 20 30 40 50

编程感想:

  1. 迭代器终止条件:注意在for循环中遍历迭代器时,因为迭代器类型是指针所以不能用小于<,而是只能用!=来判断迭代器何时到达末尾。
  2. set存放次序:set中存放的值称为“键(key)”,其会按照一定的顺序将输入数据进行排序。所以set是关联容器,而vector是顺序容器。

11.3.2 迭代器类型

Input iterators
Forward iterators
Output iterators
Bidirectional iterators
Random access iterators
图11-1 迭代器分类方式

  本小节来讨论一下迭代器类型,每种容器都有自己的迭代器类型,如上图一般可分为5类。上面两个是根据迭代器只读/只写的内容来进行分类的;下面三个则是按照迭代器的变化方式来进行分类的:

  1. 输入迭代器(Input iterators):可读取所指内容(易混淆)。
  2. 输出迭代器(Output iterators):可修改所指内容(易混淆)。
  3. 前向迭代器(Forward iterators):只能单步向前移动。同时包含了输入迭代器、输出迭代器的功能,但是“虚线”表示前向迭代器不见得能完成输出迭代器的所有功能。
  4. 双向迭代器(Bidirectional iterators):只能单步前后移动。
  5. 随机访问迭代器(Random access iterators):类似普通指针可相加相减、任意跳转,功能最强大。

下面给出容器支持的迭代器类型:

表11-4 容器支持的迭代器类型
STL容器类 支持的迭代器类型 说明
一级容器 vector 随机访问迭代器 元素严格有序
(类似数组)
deque 随机访问迭代器
list 双向迭代器 仅能通过某个元素
找到其直接前驱和直接后继
(类似链表)
set 双向迭代器
multiset 双向迭代器
map 双向迭代器
multimap 双向迭代器
二级容器 stack 都不支持 ——
queue 都不支持
priority_queue 都不支持

11.3.3 迭代器支持的运算符操作

  那上一小节介绍了迭代器的不同类型,主要关注一级容器所包括的“随机访问迭代器”和“双向迭代器”,本小节就给出不同的迭代器都支持哪些运算符操作。下面给出了表11-5及迭代器的代码示例,注意关注代码中的p1都表现出了哪些类型的迭代器特征:

表11-5 迭代器支持的运算符
类别 描述
所有迭代器都是
前向迭代器
++p 前置自增
p++ 后置自增
输入迭代器 *p 迭代器解引用(仅做右值)
p1 == p2 判断所指元素是否相等
p1 != p2 判断所指元素是否不等
输出迭代器 *p 迭代器解引用(仅做左值)
双向迭代器 --P 前置自减
p-- 后置自减
随机访问迭代器 p += i 将迭代器p增加i个位置
p -= i 将迭代器p递减i个位置
p + i 返回一个迭代器,位于p之后的第i个位置
p - i 返回一个迭代器,位于p之前的第i个位置
p1 < p2 如果p1在p2之前,则返回true
p1 <= p2 如果p1在p2之前或等于p2,则返回true
p1 > p2 如果p1在p2之后,则返回true
p1 >= p2 如果p1在p2之后或等于p2,则返回true
p[i] 返回偏移i的位置p处的元素

源文件IteratorOperatorDemo.cpp

#include 
#include 
using std::cout;
using std::endl;
int main() {
    std::vector<int> intVector;
    intVector.push_back(10);
    intVector.push_back(20);
    intVector.push_back(30);
    intVector.push_back(40);
    intVector.push_back(50);
    intVector.push_back(60);
    //遍历所有vector对象,并将迭代器指向.end()
    std::vector<int>::iterator p1 = intVector.begin();
    cout << "intVector: ";
    for (; p1 != intVector.end(); p1++) {
        cout << *p1 << " ";
    }
    //迭代器运算符操作
    cout << endl << "intVector[5]: " << *(--p1) << endl;
    cout << "intVector[2]: " << *(p1 - 3) << endl;
    cout << "intVector[2]: " << p1[-3] << endl;
    *p1 = 1234;
    cout << "intVector[5]: " << *p1 << endl;
    return 0;
}

运行结果:

intVector: 10 20 30 40 50 60
intVector[5]: 60
intVector[2]: 30
intVector[2]: 30
intVector[5]: 1234

11.4 顺序容器

11.4.1 顺序容器的逻辑结构

C++学习笔记-第11单元 标准模板库介绍_第1张图片
图11-2 顺序容器的逻辑结构

  本小节介绍顺序容器的逻辑结构。顺序容器主要包含三个容器:随机访问迭代器std::vectorstd::deque,以及双向迭代器std::list。下面来看一下这三个容器的一般特征:

  1. std::vector相当于从一端操作的数组,当数组容量满了之后,就会申请新的更大空间,并将原有内容拷贝到这个新的空间之中。
  2. std::deque则是双端队列,会不断地向两端增加数据,存满了就申请新的空间。
  3. std::list是使用链表来实现的,“链表”可以简单的看成一些节点的链接,每个节点都包含两个指针,分别指向其前面和后面的元素,所以每个节点都有前驱和后继。list也是可以在两端进行操作的,并且由于使用链表实现,所以在list中放入和删除元素,其速度和效率都比vectordeque要快。
表11-6 顺序容器中的共同函数
函数 描述
assign(n, elem) 将指定元素elem的n份拷贝加入(赋值)到容器中
assign(beg, end) 将迭代器[beg, end)间的元素赋值给当前容器
push_back(elem) 将元素附加到容器
pop_back() 删除容器尾元素
front() 返回容器首元素,并不删除
back() 返回容器尾元素,并不删除
insert(position, elem) 将元素插入到容器指定位置

11.4.2 代码展示std::vector

下面来看一下std::vector容器的代码示例:
源文件VectorDemo.cpp

#include 
#include 
using std::cout;
using std::endl;
int main() {
    double values[] = { 1,2,3,4,5,6,7 };
    //构造向量,用迭代器[begin, end)间的元素初始化向量
    std::vector<double> doubleVector(values, values + 7);//数组的名字就是指针
        cout << "1. doubleVector的初始内容:";
    for (int i = 0; i < doubleVector.size(); i++) {
        cout << doubleVector[i] << " ";
    }
    //顺序容器通用函数:assign(n, elem)将n份元素拷贝赋值给容器
    doubleVector.assign(4, 11.5);
    cout << "\n2. assign后,doubleVector的内容:";
    for (int i = 0; i < doubleVector.size(); i++) {
        cout << doubleVector[i] << " ";
    }
    //vector/deque特有函数:at(index)返回指定位置的元素
    doubleVector.at(0) = 22.4;
    cout << "\n3. at后,doubleVector的内容:";
    for (int i = 0; i < doubleVector.size(); i++) {
        cout << doubleVector[i] << " ";
    }
    //顺序容器通用函数:insert(position, elem)将元素插入指定位置
    std::vector<double>::iterator itr = doubleVector.begin();//定义迭代器,令其指向向量首位置
    doubleVector.insert(itr + 1, 555);
    //!警告!调用vector的insert之后,所有的迭代器都【可能】会失效
    itr = doubleVector.begin();//保险起见,重新定义迭代器
    doubleVector.insert(itr + 1, 666);
    cout << "\n4. insert后,doubleVector的内容:";
    for (int i = 0; i < doubleVector.size(); i++) {
        cout << doubleVector[i] << " ";
    }
    //一级容器通用函数:erase(beg, end)删除[beg,end)范围的元素
    doubleVector.erase(itr + 2, itr + 4);
    //!警告!调用vector的erase之后,beg及之后所有的迭代器都【可能】会失效
    cout << "\n5. erase后,doubleVector的内容:";
    for (int i = 0; i < doubleVector.size(); i++) {
        cout << doubleVector[i] << " ";
    }
    //一级容器通用函数:clear()删除所有元素
    doubleVector.clear();
    cout << "\n6. doubleVector现有数据数量:" << doubleVector.size() << endl;
    cout << "   doubleVector空了吗?" << std::boolalpha << doubleVector.empty() << endl;
    return 0;
}

运行结果:

1. doubleVector的初始内容:1 2 3 4 5 6 7
2. assign后,doubleVector的内容:11.5 11.5 11.5 11.5
3. at后,doubleVector的内容:22.4 11.5 11.5 11.5
4. insert后,doubleVector的内容:22.4 666 555 11.5 11.5 11.5
5. erase后,doubleVector的内容:22.4 666 11.5 11.5
6. doubleVector现有数据数量:0
   doubleVector空了吗?true

11.4.3 代码展示std::deque

下面来看一下std::deque容器的代码示例:
源文件DequeDemo.cpp

#include 
#include 
using std::cout;
using std::endl;
//该模板用于输出deque中的所有元素
template <typename T=double>
void deque_print(std::deque<T>& deque1) {
    for (int i = 0; i < deque1.size(); i++) {
        cout << deque1[i] << " ";
    }
}
int main() {
    double values[] = { 1,2,3,4,5,6,7 };
    //构造deque,用迭代器[begin, end)间的元素初始化向量
    std::deque<double> doubleDeque(values, values + 7);//数组的名字就是指针
    cout << "1. doubleDeque的初始内容:";
    deque_print(doubleDeque);
    
    //顺序容器通用函数:assign(n, elem)将n份元素拷贝赋值给容器
    doubleDeque.assign(4, 11.5);
    cout << "\n2. assign后,doubleDeque的内容:";
    deque_print(doubleDeque);
    
    //vector/deque特有函数:at(index)返回指定位置的元素
    doubleDeque.at(0) = 22.4;
    cout << "\n3. at后,doubleDeque的内容:";
    deque_print(doubleDeque);
    
    //顺序容器通用函数:insert(position, elem)将元素插入指定位置
    std::deque<double>::iterator itr = doubleDeque.begin();//定义迭代器,令其指向向量首位置
    doubleDeque.insert(itr + 1, 555);
    //!警告!调用deque的insert之后,所有的迭代器都【必然】失效!
    itr = doubleDeque.begin();//保险起见,重新定义迭代器
    doubleDeque.insert(itr + 1, 666);
    itr = doubleDeque.begin();//保险起见,重新定义迭代器
    cout << "\n4. insert后,doubleDeque的内容:";
    deque_print(doubleDeque);
    
    //一级容器通用函数:erase(beg, end)删除[beg,end)范围的元素
    doubleDeque.erase(itr + 2, itr + 4);
    //!警告!调用deque的erase之后,beg及之后所有的迭代器都【可能】失效!
    cout << "\n5. erase后,doubleDeque的内容:";
    deque_print(doubleDeque);
    
    //一级容器通用函数:clear()删除所有元素
    doubleDeque.clear();
    cout << "\n6. doubleDeque现有数据数量:" << doubleDeque.size() << endl;
    cout << "   doubleDeque空了吗?" << std::boolalpha << doubleDeque.empty();
    
    //deque/list特有函数:push_front()将元素压入队头
    doubleDeque.push_front(10.10);
    doubleDeque.push_front(20.22);
    doubleDeque.push_front(30.33);
    cout << "\n7. push_front后,doubleDeque的内容:";
    deque_print(doubleDeque);
    
    //deque/list特有函数:pop_front()删除队首元素
    doubleDeque.pop_front();
    //顺序容器通用函数:pop_back()删除容器尾元素
    doubleDeque.pop_back();
    cout << "\n8. pop_front/back后,doubleDeque的内容:";
    deque_print(doubleDeque);
    return 0;
}

运行结果:

1. doubleDeque的初始内容:1 2 3 4 5 6 7
2. assign后,doubleDeque的内容:11.5 11.5 11.5 11.5
3. at后,doubleDeque的内容:22.4 11.5 11.5 11.5
4. insert后,doubleDeque的内容:22.4 666 555 11.5 11.5 11.5
5. erase后,doubleDeque的内容:22.4 666 11.5 11.5
6. doubleDeque现有数据数量:0
   doubleDeque空了吗?true
7. push_front后,doubleDeque的内容:30.33 20.22 10.1
8. pop_front/back后,doubleDeque的内容:20.22

11.4.4 代码展示std::list

下面来看一下std::list容器的代码示例:
源文件ListDemo.cpp

#include 
#include 
using std::cout;
using std::endl;
template <typename T = int>
void list_print(std::list<T>& list1) {
    typename std::list<T>::iterator p;//list的迭代器是双向迭代器
    for (p = list1.begin(); p != list1.end(); p++) {
        cout << *p << " ";
    }
}
int main() {
    int values[] = { 1,2,3,4,5,6,7 };
    //构造list,用迭代器[begin, end)间的元素初始化向量
    std::list<int> intList(values, values + 7);//数组的名字就是指针
    cout << "1. intList的初始内容:";
    std::list<int>::iterator p;//list的迭代器是双向迭代器,不支持随机访问,只能双向访问
    for (p = intList.begin(); p != intList.end(); p++) {
        cout << *p << " ";
    }

    //顺序容器通用函数:assign(n, elem)将n份元素拷贝赋值给容器
    intList.assign(4, 11);
    cout << "\n2. assign后,intList的内容:";
    list_print(intList);
    
    //顺序容器通用函数:insert(position, elem)将元素插入指定位置
    std::list<int>::iterator itr = intList.begin();
    itr++;//迭代器指向第2个11:11 ^11 11 11
    intList.insert(itr, 555);//11 555 ^11 11 11
    //!重要!调用list的insert之后,迭代器不受影响!仍然指向第二个11!!
    intList.insert(itr, 666);//11 555 666 ^11 11 11
    cout << "\n3. insert后,intList的内容:";
    list_print(intList);

    //一级容器通用函数:erase(beg, end)删除[beg,end)范围的元素
    std::list<int>::iterator beg = intList.begin();
    itr++;                  //11 555 666 11 ^11 11
    intList.erase(beg, itr);//11 11
    //!警告!调用list的erase之后,被删除元素的迭代器失效,其它元素迭代器正常!
    cout << "\n4. erase后,intList的内容:";
    list_print(intList);

    //一级容器通用函数:clear()删除所有元素
    intList.clear();
    cout << "\n5. intList现有数据数量:" << intList.size() << endl;
    cout << "   intList为空?" << std::boolalpha << intList.empty();

    //deque/list特有函数:push_front()将元素压入队头
    intList.push_front(10);
    intList.push_front(11);
    intList.push_front(12);
    cout << "\n6. push_front后,intList的内容:";
    list_print(intList);// 12 11 10

    //deque/list特有函数:pop_front()删除队首元素
    intList.pop_front();
    //顺序容器通用函数:pop_back()删除容器尾元素
    intList.pop_back();
    cout << "\n7. pop_front/back后,intList的内容:";
    list_print(intList);//11

    //list特有函数:sort()将元素按升序排序
    int values1[] = { 7,3,2,1 };
    intList.assign(values1, values1 + 4);
    intList.sort();
    cout << "\n8. sort后,intList的内容:";
    list_print(intList);

    //list特有函数:list1.merge(list2)将排好序的list2添加到list1中,并清空list2,
    //合并后的list1也是排好序的。
    std::list<int> intList2(intList);
    //std::list intList2({1,4,5,6});//1 1 2 3 4 5 6 7
    intList.merge(intList2);//1 1 2 2 3 3 7 7
    cout << "\n9. merge后,intList的内容:";
    list_print(intList);
    cout << "\n   intList2为空?" << intList2.empty();

    //list特有函数:reverse()反转本列表
    intList.reverse();//7 7 3 3 2 2 1 1
    cout << "\n10. reverse后,intList的内容:";
    list_print(intList);
    intList.push_back(7);
    intList.push_back(1);//7 7 3 3 2 2 1 1 7 1

    //list特有函数:remove(elem)删除表中与elem相等的元素
    intList.remove(7);//3 3 2 2 1 1 1
    cout << "\n11. remove后,intList的内容:";
    list_print(intList);

    //list特有函数:splice(pos,lis)将lis中所有元素移至本表pos位置,并清空lis
    intList2.assign(7, 2);//2 2 2 2 2 2 2
    cout << "\n12. splice前,intList2的内容:";
    list_print(intList2);
    p = intList2.begin();
    intList2.splice(p, intList);//
    cout << "\n    splice后,intList2的内容:";
    list_print(intList2);
    cout << "\n    intList为空?" << intList.empty();
    return 0;
}

运行结果:

1. intList的初始内容:1 2 3 4 5 6 7
2. assign后,intList的内容:11 11 11 11
3. insert后,intList的内容:11 555 666 11 11 11
4. erase后,intList的内容:11 11
5. intList现有数据数量:0
   intList为空?true
6. push_front后,intList的内容:12 11 10
7. pop_front/back后,intList的内容:11
8. sort后,intList的内容:1 2 3 7
9. merge后,intList的内容:1 1 2 2 3 3 7 7
   intList2为空?true
10. reverse后,intList的内容:7 7 3 3 2 2 1 1
11. remove后,intList的内容:3 3 2 2 1 1 1
12. splice前,intList2的内容:2 2 2 2 2 2 2
    splice后,intList2的内容:3 3 2 2 1 1 1 2 2 2 2 2 2 2
    intList为空?true

编程感想:

  1. 函数模板中的typename:本来想着写一个函数模板,方便打印整个std::list的内容。但由于思路是使用迭代器作为遍历元素,所以这个迭代器std::list::iterator就是从属类型,其类型也是不确定的,就需要加上typename关键字变成:typename std::list::iterator p;。具体可以见CSDN博文“C7510:类型从属名称的使用必须以“typename”为前缀”。

11.5 关联容器

11.5.1 关联容器的逻辑结构

C++学习笔记-第11单元 标准模板库介绍_第2张图片
图11-3 关联容器的基本结构

  本小节介绍关联容器的逻辑结构。关联容器包含四个容器std::setstd::multisetstd::mapstd::multimap,并且都只能使用双向迭代器进行访问。也就是说,关联容器仅支持通过对迭代器进行解引用,才能访问元素“key”。并且关联容器使用“key”快速存取元素,元素会按照一定的规则排序(默认升序排序)。下面介绍上图所示的结构说明:

  1. std::setstd::multiset:存放的都是互相独立的“键”(key),只是std::set中的值不能重复,std::multiset可以重复。
  2. std::mapstd::multimap:存放的都是互相独立的“键值对”(key value pair)。同样的,std::map中的键不能重复,但是键所对应的值无所谓;而std::multimap中的键可以重复。
表11-7 关联容器中的共同函数
函数 描述
find(key) 返回指向第一个key元素的迭代器;若没有就返回指向尾元素后一个位置的迭代器
lower_bound(key) 返回指向第一个key元素的迭代器;若没有就返回指向尾元素后一个位置的迭代器
upper_bound(key) 返回指向最后一个key元素后一个位置的迭代器;若没有同上
count(key) 返回容器中具有key的元素的数目

11.5.2 代码展示std::multiset

下面来看一下std::multiset的代码示例:
源文件SetDemo.cpp

#include 
#include 
using std::cout;
using std::endl;
//std::multiset函数模板:打印所有元素
template <typename T, typename Q>
void set_print(std::multiset<T, Q>& multiset1) {//多定义的Q是排序参数,主函数中不写也没事
    typename std::multiset<T>::iterator p = multiset1.begin();
    for (; p != multiset1.end(); p++) {
        cout << *p << " ";
    }
}

int main() {
    int values[] = { 3,5,1,7,2,2 };
    //构造multiset,用迭代器[beg, end)间的元素初始化deque
    //升序排列 1,2,2,3,5,7
    std::multiset<int> multiset1(values, values + 6);
    //降序排序 7,5,3,2,2,1
    //std::multiset > set1(values, values + 6);
    cout << "1. set1的初始内容:";
    set_print(multiset1);

    //关联容器通用函数:insert(key)将元素按照一定规则插入
    multiset1.insert(555);   //1,2,2,3,5,7,555
    multiset1.insert(1);     //1,1,2,2,3,5,7,555
    cout << "\n2. insert后,multiset1的内容:";
    set_print(multiset1);

    //关联容器通用函数:lower_bound(key)返回指向第一个key元素的迭代器
    //                  upper_bound(key)返回指向最后一个key元素后一个的迭代器
    std::multiset<int>::iterator p;
    p = multiset1.lower_bound(2);//p指向容器中第一个2
    cout << "\n3. multiset1.lower_bound(2)输出:" << *p;
    p = multiset1.upper_bound(2);//p指向容器中最后一个2的后面一个
    cout << "\n   multiset1.upper_bound(2)输出:" << *p;

    //关联容器通用函数:find(key)返回指向第一个key元素的迭代器
    //                  count(key)返回容器中具有key的元素的数目
    p = multiset1.find(2);
    if (p == multiset1.end()) //若迭代器指向multiset1尾部,则未找到
        cout << "\n4. 当前集合没有“2”!";
    else
        cout << "\n4. 当前集合中2的数量:" << multiset1.count(2);
    
    //一级容器通用函数:erase(beg, end)删除[beg,end)范围的元素
    //                  erase(key)删除集合中所有的key元素
    cout << "\n5. erase前,multiset1的内容:";
    set_print(multiset1);
    //multiset1.erase(p, multiset1.end());
    multiset1.erase(2);
    cout << "\n   erase后,multiset1的内容:";
    set_print(multiset1);
    return 0;
}

运行结果:

1. set1的初始内容:1 2 2 3 5 7
2. insert后,multiset1的内容:1 1 2 2 3 5 7 555
3. multiset1.lower_bound(2)输出:2
   multiset1.upper_bound(2)输出:3
4. 当前集合中2的数量:2
5. erase前,multiset1的内容:1 1 2 2 3 5 7 555
   erase后,multiset1的内容:1 1 3 5 7 555

11.5.3 代码展示std::map

下面来看一下std::map的代码示例:
源文件MapDemo.cpp

#include 
#include 
#include 
using std::cout;
using std::endl;
//std::map函数模板:打印所有元素
template <typename T, typename Q>
void map_print(std::map<T, Q>& map1) {
    typename std::map<T, Q>::iterator p = map1.begin();
    for (; p != map1.end(); p++) {
        cout << p->first << " " << p->second << endl;
        //使用first访问key,使用second访问value
    }
}
int main() {
    //关联容器通用函数:insert(key, value)将元素按照一定规则插入
    std::map<int, std::string> map1;
    map1.insert(std::map<int, std::string>::value_type(101, "李四"));
    map1.insert(std::map<int, std::string>::value_type(100, "张三"));
    map1.insert(std::map<int, std::string>::value_type(103, "好美丽"));
    map1.insert(std::map<int, std::string>::value_type(102, "真潇洒"));
    cout << "1. map1的初始内容:\n";
    map_print(map1);

    //关联容器通用函数:find(key)返回指向第一个key元素的迭代器
    cout << "\n2. 请输入要寻找的key:";
    int key;
    std::cin >> key;
    std::map<int, std::string>::iterator p = map1.find(key);
    if (p == map1.end()) //若迭代器指向map1尾部,则未找到
        cout << "   key " << key << " 未找到!";
    else
        cout << "   已找到键值对:" << p->first << " " << p->second << endl;
    
    //一级容器通用函数:erase(beg, end)删除[beg,end)范围的元素
    //                  erase(key)删除map中所有key对应的键值对
    cout << "\n3. erase前,map1的内容:\n";
    map_print(map1);
    //map1.erase(p, map1.end());
    map1.erase(103);
    cout << "   erase(103)后,map1的内容:\n";
    map_print(map1);
    return 0;
}

运行结果:

1. map1的初始内容:
100 张三
101 李四
102 真潇洒
103 好美丽

2. 请输入要寻找的key:100
   已找到键值对:100 张三

3. erase前,map1的内容:
100 张三
101 李四
102 真潇洒
103 好美丽
   erase(103)后,map1的内容:
100 张三
101 李四
102 真潇洒

11.6 容器适配器

  最后一小节来介绍容器适配器。但很可惜的老师并没有讲这一小节的内容,我也没有找到相关的代码文件StackDemoQueueDemo,所以就将最后两页PPT放在这里,以后有空再补一下。

图11-4 容器适配器PPT

你可能感兴趣的:(#,C++学习,c++,学习,笔记)