[TOC]

1.STL的介绍

STL,标准模板库,有惠普实验室开发的一系列标准化组件,目前是 C++ 的一部分,内建在编译器中。STL 一个重要特点是数据结构与算法的分离,这使得 STL 变得非常通用。

STL 具有高可重用性、高性能、高移植性、跨平台的优点。

STL 代码广义上分为三类:container(容器)、iterator(迭代器)和 algorithm(算法),容器和算法通过迭代器进行无缝连接。

2.容器概览

一个容器就是一些特定类型对象的集合,标准库中容器根据逻辑结构不同分为两大类,顺序容器关联容器

  1. 顺序容器主要有以下几种:

    容器类 解释 特点
    vector 可变大小数组 支持快速随机访问,在尾部之外的位置插入或删除元素可能较慢
    deque 双端队列 支持快速随机访问,在头尾位置插入/删除速度较快
    list 双向链表 只支持双向顺序访问,在任何位置插入/删除速度较快
    forward_list 单向链表 只支持单向顺序访问,在任何位置插入/删除速度较快
    array 固定大小数组 支持快速随机访问,不能添加/删除元素
    string 与 vector 类似 专门用于保存字符,随机访问快,在尾部插入/删除快

    顺序容器中,通常 vector 是最好的选择,除非有更好的理由选择其他容器。

  1. 关联容器中元素是按关键字来保存和访问的,分为两类,有序关联容器和无序关联容器。

    其中有序关联容器主要有以下几种:

    容器类 解释与特点
    map 关联数组:保存关键字 – 值对
    set 关键字即值,即只保存关键字的容器
    multimap 关键字可重复出现的 map
    multset 关键字可重复出现的 set

3.vector 容器

  1. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    // 1. 包含容器对应的头文件,并使用名称空间
    #include <iostream>
    #include <vector>
    using namespace std;
    int main()
    {
    // 2. 因为是一个模板,所以需要在实例化时指定类型
    // 初始化 10 个元素大小
    vector<int> vec1(10);
    // 初始化 10 个 2
    vector<int> vec2(10, 2);
    // 使用后面的值进行初始化
    vector<int> vec3 = { 1, 2, 3, 4, 5 };
    // 拷贝构造函数
    vector<int> vec4(vec3);

    // 3. 增加容器的元素
    // 在末尾添加元素
    vec3.push_back(10);
    // 在开头位置添加元素
    vec3.insert(vec3.begin(), 1);

    // 4. 删除元素
    // 删除结尾的元素
    vec3.pop_back();
    // 删除第二个元素
    vec3.erase(vec3.begin() + 3);

    // 5. 如何查看和修改元素
    // 使用下标修改
    vec3[0] = 100;
    // 修改首元素的值
    vec3.front() = 200;
    // 修改尾元素的值
    vec3.back() = 300;
    // 获取对应位置的元素,但是会判断是否越界
    vec3.at(1) = 400;

    // 6. 遍历容器的几种方式

    // 6.1. 常规遍历,用下标
    // 这个方式一般只用于显示和修改元素数据(不增加或删除)
    for (int i = 0; i < vec3.size(); ++i)
    printf("%d ", vec3[i]);
    printf("\n");

    // 6.2. 常规遍历,用迭代器 auto 自动类型识别
    //根据 begin 函数返回值类型推测变量类型
    for (auto i = vec3.begin(); i != vec3.end(); ++i)
    printf("%d ", *i);
    printf("\n");

    // 6.3. 使用列表循环,将 vec3 的所有元素依次赋值给 i
    // 不添加引用就是值拷贝,不能修改原来的数据
    // 这个方式一般只用于显示和修改元素数据(不增加或删除)
    for (auto & i : vec3)
    printf("%d ", i);
    printf("\n");

    // 6.4. 使用 while 的方式进行遍历
    auto begin = vec3.begin();
    while (begin != vec3.end())
    {
    printf("%d ", *begin);
    ++begin;
    }
    printf("\n");

    // 在循环的过程中,不能随意的增加和删除元素
    // 会导致迭代器无效,使程序崩溃
    for (auto i = vec3.begin(); i != vec3.end(); ++i)
    {
    // 删除时需要接收返回的新迭代器
    i = vec3.insert(vec3.begin(), 10);
    }

    // 1 2 3 4 5 6 7
    // b e
    // end 迭代器不能用于解引用

    return 0;
    }
  1. 其他方法

    方法名 作用
    vec.clear() 清空所有元素
    vec.reserve() 改变容量大小
    vec.resize() 改变元素个数
    vec.rbegin() 返回一个反向 iterator,指向最尾端元素
    vec.rend() 返回一个 iterator,指向第一个元素
    …… ……

4. list 容器

  1. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    // 002_容器的使用_list.cpp
    #include <iostream>
    #include <list>
    using namespace std;

    int main()
    {
    list<int> listNum;
    // 增
    listNum.push_back(100);
    listNum.push_back(300);
    listNum.insert(listNum.begin(), 200);
    listNum.push_front(0);
    // **注意** : list迭代器只能使用++,或--, 不能使用+运算符


    // 删
    // 只能用迭代器, 但如果要删除第2个元素
    list<int>::iterator itr = listNum.begin();
    // itr = itr + 3;list迭代器只能使用++,或--, 不能使用+运算符
    ++++itr; // 自增2次指向第2个元素
    // 所有容器的erase都会删除迭代器指向的元素
    // 然后删除之后itr指向就无效了, 因此就变成了一个无效迭代器
    // erase会通过返回值返回一个有效的迭代器(一般是删除位置的下一个元素的迭代器)
    itr = listNum.erase(itr);

    // 改
    // 只能通过迭代器去修改
    // 例如:修改第1个元素
    itr = listNum.begin();
    cout << "第1个元素: " << *itr << endl;
    *itr = 111111;
    cout << "第1个元素: " << *itr << endl;
    // 例如:修改第2个元素
    ++itr; // 自增找到第二个元素
    cout << "第2个元素: " << *itr << endl;
    *itr = 111111;
    cout << "第2个元素: " << *itr << endl;

    // 查
    // 通过迭代器来遍历
    for (auto i = listNum.begin(); i != listNum.end(); ++i) {
    cout << *i << ',';
    }
    cout << endl;
    // 通过新式循环来遍历
    for (auto& d : listNum) {
    cout << d << ',';
    }
    cout << endl;

    //访问元素
    //分别取出第一个元素和最后一个元素
    cout << listNum.front() << " " << listNum.back() << endl;

    //获取元素个数
    cout << listNum.size() << endl;

    return 0;
    }

5. map 容器

  1. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    // 003_容器的基本使用_map.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    #include <iostream>
    #include <map>
    #include <string>
    #include <set>
    using namespace std;

    int main()
    {
    // 什么是键值对?
    // 键 : 相当于一个索引.相当于目录中的一下.
    // 值 : 就是一堆真正要保存的数据. 键相当于值的摘要.用于代表这个值.
    // 例如 , 姓名:小明. 姓名:小红, 其中姓名就是键,小明就是值
    // 年龄: 18 , 年龄就是键,18就是值.
    // 4544545 : 小名
    // 4544545就是键,小名值.
    // 总是用键来索引值.

    // <string, string>
    // 第一个string指的是键的类型
    // 第二个string指的是值的类型
    // 当需要将数据保存到map容器中时,
    // 保存的数据的键值对必须都是string类型.
    map<string, string> m1;
    map<int, string> m2;
    // 增
    // pair是一个键值对类
    // map就是用于保存键值对类的平衡二叉树.
    // map是一个平衡二叉树(红黑树)
    // 树中节点的数据类型是 std::pair<typename,typename>
    // 插入数据到map中的时候, 必须先给出键值对来初始化一个pair对象
    // 然后再pair对象插入到map中.
    pair<string, string> p1("姓名", "小明");
    m1.insert(p1);
    //p1是一个键值对对象
    cout << "p1.first=" << p1.first << endl; // 键值对中的键
    cout << "p1.second=" << p1.second << endl;// 键值对中的值


    // 也可以直接在实参中调用pair的构造函数来构造一个pair对象
    m1.insert(pair<string, string>("年龄", "18"));

    // 第三种方法,直接使用`[]`运算符来插入
    // 实际上[]运算符会在二叉树中以"性别"作为键来搜索pair对象
    // 如果没有搜索到, 函数会自动在树中插入一个用"性别"作为键
    // 的pair对象, 然后再将新pair对象值赋值成"男"
    m1["性别"] = "男";

    // m1["姓名"]运算符会在树中以"姓名"作为键找到对应的pair
    // 对象, 然后将pair对象的值返回.
    string str = m1["姓名"]; // str被赋值成"小明"
    str = m1["aaa"]; // 如果获取一个不存在的键的时候,就会插入一个,值有可能会随机(取决于值的类型有无构造函数)

    // 删
    // 直接传入一个键就行了. 对应的键值对就会被删除.
    m1.erase("姓名");

    // 改
    cout << "性别:" << m1["性别"] << endl;
    m1["性别"] = "女";
    cout << "性别:" << m1["性别"] << endl;

    // 查
    // map保存的节点是pair对象
    // 所以map的迭代器指向的元素都是pair对象.
    //
    cout << "遍历map\n";
    for (auto itr = m1.begin();
    itr != m1.end();
    ++itr)
    {
    itr->first; //(*itr).first
    cout << itr->first << "="
    << itr->second << endl;
    }


    // <int, string>
    // 第一个int指的是键的类型
    // 第二个string指的是值的类型
    m2.insert(pair<int, string>(1, "123456"));
    m2.insert(pair<int, string>(2, "456789"));
    m2[100] = "1111";

    cout << "遍历map\n";
    for (auto itr = m2.begin();
    itr != m2.end();
    ++itr)
    {
    itr->first; //(*itr).first
    cout << itr->first << "="
    << itr->second << endl;
    }
    return 0;
    }