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

news2024/9/24 1:15:29

第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<vector>直接访问任意元素,快速插入、删除尾部元素
deque<deque>直接访问任意元素,快速插入、删除头部和尾部元素
list<list>快速插入、删除任意位置元素
关联容器set<set>快速查询元素,无重复关键字
multiset<set>与set相同,但允许重复关键字
map<map>键值对映射,不允许重复关键字,使用关键字快速查询元素
multimap<map>与map相同,但允许重复关键字
二级容器容器适配器stack<stack>后进先出容器
queue<queue>先进先出容器
priority_queue<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 <iostream>
#include <vector>
#include <list>
#include <deque>
#include <set>
#include <stack>
#include <queue>
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 <iostream>
#include <vector>
#include <set>
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 <iostream>
#include <vector>
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 顺序容器的逻辑结构

图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 <iostream>
#include <vector>
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 <iostream>
#include <deque>
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 <iostream>
#include <list>
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<int> 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<T>::iterator就是从属类型,其类型也是不确定的,就需要加上typename关键字变成:typename std::list<T>::iterator p;。具体可以见CSDN博文“C7510:类型从属名称的使用必须以“typename”为前缀”。

11.5 关联容器

11.5.1 关联容器的逻辑结构

图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 <iostream>
#include <set>
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<int, std::greater<int> > 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 <iostream>
#include <map>
#include <string>
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/728109.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue组件库Element-快速入门

目录 什么是Element 快速入门 什么是Element Element&#xff1a;是饿了么团队研发的&#xff0c;一套为开发者、设计师和产品经理准备的基于Vue2.0的桌面端组件库组件&#xff1a;组成网页的部件&#xff0c;例如超链接、按钮、图片、表单、表格、分页条等官网&#xff1a;…

avoidLabelOverlap无效果,echarts环形标签重叠,echarts数据重叠

在开发echarts环形图时,会出现数据重叠的现象.比如下面这情况.不可能为了数据将环形图变得太小,这样环形图太小也不美观.后来在官网上查找有一个avoidLabelOverlap属性,结果加上去后也没啥用 解决方法: 除了加上面:avoidLabelOverlap:true以外,还要加另外一个属性:minAngle:1…

win10系统中Pensieve(Sigcomm17)算法部署与运行

win10系统中Pensieve算法部署与运行 一、环境配置1、Anaconda环境配置2、Pycharm安装3、Pensieve安装包配置4、Pensieve数据集生成5、Pensieve代码运行6、Pensieve代码修改 一、环境配置 1、Anaconda环境配置 下载并且安装 Anaconda 官网下载地址&#xff1a;https://www.ana…

【Java基础教程】(六)程序概念篇 · 末:全面讲解Java方法的定义及应用、方法重载及递归~

Java基础教程之程序概念 末 本节学习目标1️⃣ 方法的定义与使用1.1 概念&#x1f50d; 在编写代码时&#xff0c;怎么判断什么情况下应该定义方法? 1.2 方法重载1.3 方法递归 &#x1f33e; 总结 本节学习目标 掌握Java中方法的定义结构、方法重载的概念与应用&#xff1b;…

EulerOS2.0SP10操作系统Esxi虚拟机安装手记

本文记录了在 Esxi7.0平台上新建虚拟机安装华为EulerOS2.0SP10国产操作系统的过程&#xff0c;仅供参考。 一、新建虚拟机 1、在VCenter上选中集群&#xff0c;右键选择新建虚拟机 2、在新建虚拟机视图点NEXT 3、输入虚拟机名称&#xff08;自定义&#xff09;&#xff0c;选…

Apikit 自学日记:如何引用全局变量

引用全局变量 一、什么是全局变量 全局变量可以在测试过程中动态取值以及赋值&#xff0c;比如&#xff1a;使用登录接口获取 token&#xff0c;将 token 值赋值给自定义全局变量 global_token&#xff0c;然后在另一个需要使用该token的接口中&#xff0c;使用 {{global_toke…

css,less,scss中的深度选择器,结合elementUi使用

css中深度选择器用到的是 >>>加类名 可以很方便的找到自己想要修改的样式 lees中用到 /deep/ 加类名 scss中用到::v-deep 加类名

【软件工具使用体验NFC】读取nfc里的内容和写入nfc的内容 tagInfo和TagWritter两款软件的使用和下载

教程目录 教程简介所需环境和版本资源免费下载执行过程tagInfo的使用&#xff1a;tagWritter的使用&#xff1a;my dataset的解释&#xff1a; 结束语 教程简介 这篇内容主要是前段时间开发了一个小程序的时候涉及到了通过小程序读写nfc标签的内容&#xff0c;当时还特地和我们…

特征模型仿真例1:参数辨识

题目 考虑被控对象 G ( s ) 3 s 4 s 4 5 s 3 10 s 2 6 s 4 G(s)\frac{3 s4}{s^{4}5 s^{3}10 s^{2}6 s4} G(s)s45s310s26s43s4​ 和特征模型 y ( k ) ϕ T ( k − 1 ) θ ( k ) y(k)\boldsymbol{\phi}^{\mathrm{T}}(k-1)\boldsymbol{\theta}(k) y(k)ϕT(k−1)θ(k) 其…

谁告诉我这是什么文件,怎么了?活不起了?得靠这个冲业绩?

TIM 从一年前就卸载了&#xff0c;今天整理磁盘才发现还有这么多文件&#xff0c;想着没什么用直接删掉得了&#xff0c;没想到他娘的死活删不掉&#xff0c;好多文件一个一个删才找到这玩意&#xff0c;所有能查找运行程序的地方都找遍了&#xff0c;死活没找到在哪打开了&…

第一章 系统服务监控-SpringBootAdmin

前言 本来想用一节就写完SpringBootAdmin的&#xff0c;但随着研究的深入发现一节应该是不够的&#xff0c;网上的资料也不会非常系统&#xff0c;官网的例子有些已经好几年没更新了&#xff0c;所以接下来还是系统性的来写下吧 第一节 完成基础配置&#xff0c;暴露所有端点…

​如何从任何地方访问办公室电脑?

​远程办公已然成为了一种常见的办公方式&#xff0c;在远程工作期间访问办公室电脑获取必要的文件或信息非常重要。与远程访问同一网络中的电脑不同&#xff0c;使用Windows远程桌面从外部访问办公室电脑需要进行端口转发。对于很多人来说&#xff0c;端口转发可能有点复杂。那…

SpringBoot整合RestTemplate用法讲解(完整详细)

前言&#xff1a;本篇主要介绍了RestTemplate中的GET&#xff0c;POST&#xff0c;PUT&#xff0c;DELETE、文件上传和文件下载6大常用的功能&#xff0c;每一个方法和每一行代码都进行了详细的讲解&#xff0c;代码都是亲自测试过的&#xff0c;整篇博客写完以后自己也是受益匪…

缓存穿透、缓存击穿、缓存雪崩详解以及解决方案

缓存穿透、缓存击穿、缓存雪崩详解以及解决方案 一。缓存穿透 查询一个不存在的数据&#xff0c;mysql查询不到数据也不会直接写入缓存&#xff0c;就会导致每次请求都查数据库 方案一&#xff1a; 方案二&#xff1a; 二。缓存击穿 给某一个key设置了过期时间&#xff0…

Mongdb之Robo3T

&#x1f3c6;今日学习目标&#xff1a; &#x1f340;MyBatis详解 ✅创作者&#xff1a;林在闪闪发光 ⏰预计时间&#xff1a;30分钟 &#x1f389;个人主页&#xff1a;林在闪闪发光的个人主页 &#x1f341;林在闪闪发光的个人社区&#xff0c;欢迎你的加入: 林在闪闪发光的…

2023年PTA行业研究报告

第一章 行业概况 精对苯二甲酸&#xff08;Pure Terephthalic Acid&#xff0c;简称PTA&#xff09;是一种主要用于制造聚酯纤维和塑料的关键化学品。PTA是一种白色结晶固体&#xff0c;可通过对苯二酮在催化剂的作用下进行氧化得到。它是聚对苯二甲酸乙二醇酯&#xff08;PET…

机器视觉三维重建

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D开发工具链。 1、Meshroom ⭐4,474 Meshroom是一款基于AliceVision摄影测量计算机视觉框架的免费开源三维重建软件。 https://github.com/alicevision/meshroom 2、Openmvg ⭐2,829 Openmvg库根据三维计算机视觉和结构的运动。…

Web3 通过truffle 脚本进行智能合约测试

上文 Web3 处理智能合约部署到本地区块链&#xff0c;并在本地进行测试中 我们讲解了部署智能合约 然后在终端测试的方法 但上文那种终端测试 其实并不保险 而且也比较不专业 对于这个 有一个mocha测试 这是基于node部署环境的一种环境测试 但是这个需要一定的基础 大家可以专门…

国产BI工具和国外BI工具,差距大不大,用哪种比较好?

自“十四五”以来&#xff0c;我国诸多政策开始推动信创产业的深入&#xff0c;实现关键数字技术自主研发和自主可控。我国信创产业竞争力不断突破&#xff0c;国产化进程稳步推进。2022年开始政策重点提及“数字经济”、“数字政府”和国家信息化。在此背景下&#xff0c;BI产…

el-date-picker 的初始化数据格式理解、报错 “TypeError: date.getFullYear is not a function“

个人理解 如果后端传给前端的时间数据是字符串类型的&#xff0c;那么当触发 el-date-picker 组件时&#xff0c;就会报出很多错误&#xff0c;例如 这是由于初始化赋值格式的问题&#xff0c;需要对初始化值&#xff0c;进行一个 new Date(your_time_data) 前端演示模板&…