21. STL模板库

一、什么是STL

  长期以来,软件界一直希望建立一种可重复利用的工具。C++ 的面向对象和泛型编程思想,目的就是提高软件的复用性。大多数情况下,数据结构和算法都未有一套标准,导致程序员被迫从事大量的重复工作。为了建立数据结构和算法的一套标准,STL 就应用而生了。

  STL 的全程为:Standard Template Libray,是一套标准模板库。STL 从广义上分为:容器算法迭代器。容器和算法之间通过迭代器进行无缝连接。STL 几乎所有的代码都采用了模板类或者模板函数。

  STL 大体分为六大组件,分别是:容器算法迭代器仿函数适配器空间配置器

  • 容器:各种数据结构,用来存放数据。
  • 算法:各种常用的算法。
  • 迭代器:扮演了容器与算法之间的胶合剂。
  • 仿函数:行为类似函数,可作为算法的某种策略。
  • 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
  • 空间配置器:负责空间的配置与管理。

  STL 容器就是将运用最广泛的一些数据结构实现出来,常见的数据结构有:数组、链表、栈、队列、集合、映射表、树等。这些容器又分为:序列式容器关联式容器序列式容器 强调值的排序,序列式容器中的每个元素均有固定的位置。关联式容器 的个元素之间没有严格的物理上的顺序。

  算法使用有限的步骤去解决逻辑或数学上的问题。算法又分为:质变算法非质变算法质变算法 是指运算过程中会更改区间内的元素的内容。非质变算法 是指运算过程中不会更改区间内元素内容。

  迭代器是容器和算法之间的粘合剂。它提供一种方法,使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。每个容器都有自己专属的迭代器。

  迭代器主要分为五种:

种类 功能 支持运算
输入迭代器 对数据的只读操作 只读,支持 ++、==、!=
输出迭代器 对数据的只写操作 只写,支持 ++
前向迭代器 读写操作,并能向前推进迭代器 读写,支持 ++、==、!=
双向迭代器 读写操作,并能向前和向后操作 读写,支持 ++、--
随机访问迭代器 读写操作,可以以跳跃的方式访问任意数据 读写,支持 ++、--、[n]、-n、<、<=、>、>=

二、常用容器

2.1、string容器

  string 是 C++ 风格的字符串,它本质上是一个类,内部封装了 char * 的指针。因此,string 是 char * 型的容器。常用的构造函数如下:

string(void);                   // 创建一个空的字符串
string(const char *str);        // 使用字符串s初始化
string(const string & str);     // 使用一个string对象初始化另一个string对象
string(int n, char c);          // 使用n个字符c初始化

  创建 string 容器之后,我们需要对 string 容器进行赋值,常用的赋值操作如下:

string & operator=(const char * str);       // char*类型字符串赋值给当前字符串
string & operator=(const string & str);     // string类型字符串赋值给当前字符串
string & operator=(char ch);                // 字符赋值给当前字符串

string & assign(const char * str);          // char*类型字符串赋值给当前字符串
string & assign(const string & str);        // string类型字符串赋值给当前字符串
string & assign(char * str, int n);         // 字符串的前n个字符赋值给当前字符串
string & assign(int n, char ch);            // 用n个字符ch赋值给当前字符串

  在使用字符串的过程中,我们通常需要拼接字符串,它的拼接方式如下:

string & operator+=(const char * str);                  // 重载+=运算符
string & operator+=(const char ch);                     // 重载+=运算符
string & operator+=(const string & str);                // 重载+=运算符

string & append(cosnt char * str);                      // 把字符串str连接到当前字符串结尾
string & append(cosnt char *str, int n);                // 把字符串str的前n个字符连接到当前字符串结尾
string & append(const string & str);                    // 把字符串str连接到当前字符串结尾
string & append(const string & str, int pos, int n);    // 把字符串str中从pos开始的n个字符连接到当前字符串结尾

  有时,我们需要查找指定字符串是否存在,如果存在的话,我们可能还需要将它替换成别的字符串。string 容器中提供了多种重载的方法来完成这些操作。

int find(const string & str, int pos=0) const;          // 从pos处开始查找str第一次出现位置
int find(const char *str, int pos=0) const;             // 从pos处开始查找str第一次出现位置
int find(const char *str, int pos, int n) const;        // 从pos处开始查找str前n个字符第一次出现位置
int find(const char ch, int pos=0) const;               // 从pos处开始查找ch第一次出现位置

int rfind(const string & str, int pos=npos) const;      // 从pos处开始向前查找str最后一次出现位置
int rfind(const char *str, int pos=npos) const;         // 从pos处开始向前查找str最后一次出现位置
int rfind(const char *str, int pos, int n) const;       // 从pos处开始向前查找str前n个字符最后一次出现位置
int rfind(const char ch, int pos=0) const;              // 从pos处开始向前查找ch最后一次出现位置

string & replace(int pos, int n, const string & str);   // 替换从pos开始的n个字符为str
string & replace(int pos, int n, const char *str);      // 替换从pos开始的n个字符为str

  我们通常需要对字符串之间进行比较,默认是按 ASCII 码字符进行比较,如果两个字符串相等,则返回 0。如果第一个字符串比较大,则返回 1。如果第二个字符比较大,则返回 0。它的函数原型如下:

int compare(const string & str) const;
int compare(const char * str) const;

  有时,我们需要对字符串的单个字符进行操作,C++ 中提供了两种方式。

char & operator[](int n);       // 通过[]方式操作字符
char & at(int n);               // 通过at方法获取字符

  C++ 中提供了 insert() 和 erase() 方法插入和删除字符串,它们的原型如下:

string & insert(int pos, const char *str);          // 插入字符串
string & insert(int pos, const string &str);        // 插入字符串
string & insert(int pos, int n, char ch);           // 在指定位置插入n个字符ch

string & erase(int pos, int n=npos);                // 删除从pos开始的n个字符

  C++ 中还提供了截取子串的方法。

string substr(int pos=0, int n=npos) const;         // 返回由pos开始的n个字符组成的字符串 
#include <iostream>
#include <string>

using namespace std;

int main(void)
{
    string str = "Hello";

    // 拼接字符串
    str += ',';
    str.append("Sakura");
    cout << str << endl;

    str = "abccbaabccbabc";

    // 从索引3开始查找第一次出现cc位置,如果找到返回索引,否则返回-1
    int index = str.find("ccc", 3, 2);
    cout << "index: " << index << endl;

    // 从索引为9处开始向前查找cc最后一次出现位置
    index = str.rfind("cc", 9);
    cout << "index: " << index << endl;

    // 将索引从0到5的字符替换为ABC
    str.replace(0, 6, "ABC");
    cout << str << endl;

    str = "sakura";
    // 字符串比较,返回值:0:相等,-1:小于,1:大于
    int result = str.compare("Sakura");
    cout << "result: " << result << endl;

    // 通过at()方法访问单个字符
    char ch = str.at(0);
    cout << "ch: " << ch << endl;

    // 通过[索引]操作单个字符
    str[0] = 'S';
    cout << str << endl;

    // 在指定索引处插入字符串
    str.insert(0, "Hello, ");
    cout << str << endl;

    // 从指定索引处开始删除指定个数字符
    str.erase(5, 1);
    cout << str << endl;

    // 返回索引为6处开始的6个字符组成的字符串
    string subStr = str.substr(6, 6);
    cout << "subStr: " << subStr << endl;

    return 0;
}

2.2、vector容器

  vector 容器与数组非常相似,不同之处在于数组是静态空间,而 vector 可以动态扩展。vector 容器的动态扩展并不是在原空间之后续接新的空间,而是找到更大的内存空间,然后将原数据拷贝到新空间,释放原来的空间。vector 容器的迭代器是支持随机访问的迭代器。

vector容器

  我们可以使用 vector 容器的构造方法来创建 vector 容器,它的原型如下:

vector<T> v;                    // 采用模板方式实现类实现,默认构造函数
vector(begin(), end());         // 将v[begin(), end())区间中的元素拷贝给本身
vector(n, element);             // 构造函数将n个element拷贝给本身
vector(const vector & vec);     // 拷贝构造函数

  创建 vector 容器后,我们需要对它进行赋值。

vector & operator=(const vector & vec);     // 重载等号操作符
assign(begin, end);                         // 将[begin, end)区间中的数据拷贝到当前vector中
assign(n, element);                         // 将n个element拷贝到当前vector中

  对 vector 容器进行赋值后,C++ 提供了如下方法用来获取容器的大小。

empty();                    // 判断容器是否为空
capacity();                 // 容器的容量
size();                     // 容器中元素的个数
resize(num);                // 重新指定容器的长度
resize(num, element)        // 重新指定容器的长度,若指定长度大于原来,则以element元素填充新位置

  有时,我们需要对 vector 容器进行插入和删除操作,C++ 中提供如下方式:

push_back(element);                                         // 尾部插入元素
pop_back();                                                 // 删除最后一个元素
insert(const_iterator pos, element);                        // 迭代器指向位置pos插入元素element
insert(const_iterator pos, int count, element);             // 迭代器指向位置pos插入count元素element
erase(const_iterator pos);                                  // 删除迭代器指向的元素
erase(const_iterator start, const_iterator end);            // 删除迭代器从start到end之间的元素
clear();                                                    // 清空vector容器中的元素

  有时,我们还需要存取 vector 容器中的数据,C++ 中提供了如下操作:

at(int index);                  // 获取指定位置的元素
opeartor[](void);               // 获取指定位置的元素
front();                        // 获取第一个元素
back();                         // 获取最后一个元素

  有时,我们需要交换两个 vector 容器中的元素,可以通过 C++ 提供的 swap() 函数实现。

swap(v);     // 将v与本身元素互换

  如果我们想减少 vector 在动态扩展的次数,可以使用 reserve(int length) 方法预留 length 个元素,预留位置不可以初始化,元素不可访问。

reserve(int length);     // 容器预留length个元素
#include <iostream>
#include <vector>

using namespace std;

void printVector(const vector<int> &v);

int main(void)
{
    vector<int> v;

    for (int i = 0; i < 10; i++)
    {
        // 将元素放入到vector容器中
        v.push_back(i);
    }
  
    printVector(v);
  
    cout << "empty: " << v.empty() << endl;
    cout << "size: " << v.size() << endl;
    cout << "capacity: " << v.capacity() << endl;

    // 调增容器大小,默认以100填充
    v.resize(15, 100);
    printVector(v);

    // 删除元素
    v.erase(v.end() - 5, v.end());
    printVector(v);

    // 插入元素
    v.insert(v.begin() + 2, 3, 30);
    printVector(v);

    // 删除尾部元素
    v.pop_back();
    printVector(v);

    // 修改索引为0的元素
    v[0] = 100;
    printVector(v);

    // 获取索引为3的元素
    int value = v.at(3);
    cout << "value: " << value << endl;

    cout << "front: " << v.front() << endl;
    cout << "back: " << v.back() << endl;

    vector<int> v1 = {1 ,2, 3};
    vector<int> v2 = {4 ,5, 6};
    // 交换两个容器的元素
    v1.swap(v2);
    printVector(v1);
    printVector(v2);

    return 0;
}

void printVector(const vector<int> &v)
{
    for (vector<int>::const_iterator it = v.begin(); it != v.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
}

2.3、deque容器

  deque 容器也称为双端数据,可以对头端进行插入删除操作。vector 容器对于头部的插入删除效率低,并且数据量越大时,效率越低。deque 相对而言,对头部的插入删除速度比 vector 快。vector 访问元素的速度回比 deque 快,这和两者内部实现有关。deque 容器内部有个中控器,维护每段缓冲区中的内容,缓冲区中存放真是数据。中控器维护的是每个缓冲区的地址,使用 deque 是就像一片连续的内存空间。deque 容器的迭代器也是支持随机访问的。

deque容器

中控器

  我们可以使用 deque 容器的构造方法来创建 deque 容器,它的原型如下:

deque<T> d;                 // 默认构造函数
deque(begin, end);          // 构造函数将[begin, end)区间中的元素拷贝给本身
deque(n, element);          // 构造函数将n个element拷贝给本身
deque(const deque &deq);    // 拷贝构造函数

  创建 deque 容器后,我们需要对它进行赋值。

deque & operator=(const deque & vec);       // 重载等号操作符
assign(begin, end);                         // 将[begin, end)区间中的数据拷贝到当前deque中
assign(n, element);                         // 将n个element拷贝到当前deque中

  对 deque 容器进行赋值后,C++ 提供了如下方法用来获取容器的大小。

empty();                    // 判断容器是否为空
size();                     // 容器中元素的个数
resize(num);                // 重新指定容器的长度
resize(num, element)        // 重新指定容器的长度,若指定长度大于原来,则以element元素填充新位置

  有时,我们需要对 deque 容器进行插入和删除操作,C++ 中提供如下方式:

push_front(element);                                        // 头部插入元素
push_back(element);                                         // 尾部插入元素
pop_front();                                                // 删除第一个元素
pop_back();                                                 // 删除最后一个元素

insert(const_iterator pos, element);                        // 迭代器指向位置pos插入元素element
insert(const_iterator pos, int count, element);             // 迭代器指向位置pos插入count元素element
insert(const_iterator pos, begin, end);                     // 迭代器指向位置pos插入[begin,end)区间元素

erase(const_iterator pos);                                  // 删除迭代器指向的元素
erase(const_iterator start, const_iterator end);            // 删除迭代器从start到end之间的元素

clear();                                                    // 清空deque容器中的元素

  有时,我们还需要存取 deque 容器中的数据,C++ 中提供了如下操作:

at(int index);                  // 获取指定位置的元素
opeartor[](void);               // 获取指定位置的元素
front();                        // 获取第一个元素
back();                         // 获取最后一个元素

  有时,我们需要交换两个 deque 容器中的元素,可以通过 C++ 提供的 swap() 函数实现。

swap(d);     // 将d与本身元素互换
#include <iostream>
#include <deque>

using namespace std;

void printDeque(const deque<int> &d);

int main(void)
{
    deque<int> d;

    for (int i = 0; i < 10; i++)
    {
        // 将元素放入到deque容器中
        d.push_back(i);
    }
  
    printDeque(d);
  
    cout << "empty: " << d.empty() << endl;
    cout << "size: " << d.size() << endl;

    // 调增容器大小,默认以100填充
    d.resize(15, 100);
    printDeque(d);

    // 删除元素
    d.erase(d.end() - 5, d.end());
    printDeque(d);

    // 插入元素
    d.insert(d.begin() + 2, 3, 30);
    printDeque(d);

    // 删除头部元素
    d.pop_front();
    printDeque(d);

    // 删除尾部元素
    d.pop_back();
    printDeque(d);

    // 向头部中新增元素
    d.push_front(10);
    printDeque(d);

    // 修改索引为0的元素
    d[0] = 100;
    printDeque(d);

    // 获取索引为3的元素
    int value = d.at(3);
    cout << "value: " << value << endl;

    cout << "front: " << d.front() << endl;
    cout << "back: " << d.back() << endl;

    deque<int> d1 = {1 ,2, 3};
    deque<int> d2 = {4 ,5, 6};
    // 交换两个容器的元素
    d1.swap(d2);
    printDeque(d1);
    printDeque(d2);

    // 插入元素
    d2.insert(d2.begin(), d1.begin(), d1.end());
    printDeque(d2);

    return 0;
}

void printDeque(const deque<int> &d)
{
    for (deque<int>::const_iterator it = d.begin(); it != d.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
}

2.4、stack容器

  栈是一种 先进后出 的数据结构,它只有一个出口。栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为。栈可以判断容器是否为空。栈也可以返回元素个数。栈中进数据过程称为 压栈,出数据过程称为 出栈

栈

  我们可以使用 stack 容器的构造方法来创建 stack 容器,它的原型如下:

stack<T> s;                 // 默认构造函数
stack(const stack &stk);    // 拷贝构造函数

  创建 stack 容器后,我们需要对它进行赋值。

stack & operator=(const stack& stk);       // 重载等号操作符

  对 stack 容器进行赋值后,C++ 提供了如下方法用来获取容器的大小。

empty();                    // 判断容器是否为空
size();                     // 容器中元素的个数

  有时,我们需要对 stack 容器的数据进行存取,C++ 中提供如下方式:

push(element);                                        // 向栈顶添加元素
pop();                                                // 从栈顶移除第一个元素
top();                                                // 返回栈顶元素
#include <iostream>
#include <stack>

using namespace std;

int main(void)
{
    stack<int> s;

    for (int i = 0; i < 10; i++)
    {
        // 向栈中添加元素
        s.push(i);
    }

    cout << "栈中元素个数:" << s.size() << endl;
    cout << "栈顶元素:" << s.top() << endl;

    // 弹出栈顶元素
    if (!s.empty())
    {
        s.pop();
    }
  
    cout << "栈中元素个数:" << s.size() << endl;
    cout << "栈顶元素:" << s.top() << endl;

    return 0;
}

2.5、quene容器

  quene 容器是一种 先进先出 的数据结构,它有两个出口。队列容器允许一端新增元素,从另一端移除元素。队列中只有队头和队尾才可以被外界使用,因此队列不允许有遍历行为。队列中进数据过程称为 入队,出数据过程称为 出队

  我们可以使用 quene 容器的构造方法来创建 quene 容器,它的原型如下:

quene<T> q;                  // 默认构造函数
quene(const quene &que);     // 拷贝构造函数

  创建 quene 容器后,我们需要对它进行赋值。

quene & operator=(const quene& que);       // 重载等号操作符

  对 quene 容器进行赋值后,C++ 提供了如下方法用来获取容器的大小。

empty();                    // 判断容器是否为空
size();                     // 容器中元素的个数

  有时,我们需要对 quene 容器的数据进行存取,C++ 中提供如下方式:

push(element);                                        // 向队列添加元素
pop();                                                // 从队列移除第一个元素
front();                                              // 返回队列中第一个元素
back();                                               // 返回队列中最后一个元素
#include <iostream>
#include <queue>

using namespace std;

int main(void)
{
    queue<int> q;

    for (int i = 0; i < 10; i++)
    {
        // 向队列中添加元素
        q.push(i);
    }

    cout << "队列中元素个数:" << q.size() << endl;
    cout << "队列中第一个元素:" << q.front() << endl;
    cout << "队列中最后一个元素:" << q.back() << endl;

    // 出队
    if (!q.empty())
    {
        q.pop();
    }
  
    cout << "队列中元素个数:" << q.size() << endl;
    cout << "队列中第一个元素:" << q.front() << endl;
    cout << "队列中最后一个元素:" << q.back() << endl;

    return 0;
}

2.6、list容器

  list 容器将数据进行链式存储。链表是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针链接实现的。链表由一系列 结点 组成。结点 一般由两部分组成,一部分是存储数据元素的 数据域,另一个区域是存储下一个结点地址的 指针域。STL 中的链表是一个 双向循环列表。由于链表的存储方式并不是连续的内存地址,因此链表中的迭代器只支持前移和后移,属于 双向迭代器

  链表采用动态存储分配,因此不会造成内存浪费和溢出。相对于数组来说,链表执行插入和删除操作十分方便,只需要修改指针即可,不需要移动大量元素,因此链表可以对任意位置进行快速插入或删除元素。链表虽然灵活,但空间(额外需要指针域)和时间(遍历操作)耗费加大。

list容器

list 容器有个重要特性:插入操作和删除操作都不会造成原有 list 迭代器的失效。

  我们可以使用 list 容器的构造方法来创建 list 容器,它的原型如下:

list<T> l;                    // 采用模板方式实现类实现,默认构造函数
list(begin, end);             // 将[begin(), end())区间中的元素拷贝给本身
list(n, element);             // 构造函数将n个element拷贝给本身
list(const list & vec);       // 拷贝构造函数

  创建 list 容器后,我们需要对它进行赋值。

list & operator=(const list & vec);     // 重载等号操作符
assign(begin, end);                     // 将[begin, end)区间中的数据拷贝到当前vector中
assign(n, element);                     // 将n个element拷贝到当前vector中

  对 list 容器进行赋值后,C++ 提供了如下方法用来获取容器的大小。

empty();                    // 判断容器是否为空
size();                     // 容器中元素的个数
resize(num);                // 重新指定容器的长度
resize(num, element)        // 重新指定容器的长度,若指定长度大于原来,则以element元素填充新位置

  有时,我们需要对 list 容器进行插入和删除操作,C++ 中提供如下方式:

push_front(element);                                        // 头部插入元素
pop_front();                                                // 删除第一个元素
push_back(element);                                         // 尾部插入元素
pop_back();                                                 // 删除最后一个元素

insert(const_iterator pos, element);                        // 迭代器指向位置pos插入元素element
insert(const_iterator pos, int count, element);             // 迭代器指向位置pos插入count元素element
erase(const_iterator pos);                                  // 删除迭代器指向的元素
erase(const_iterator start, const_iterator end);            // 删除迭代器从start到end之间的元素
remove(element);                                            // 删除容器中所有与element值匹配的元素
clear();                                                    // 清空vector容器中的元素

  有时,我们还需要存取 list 容器中的数据,C++ 中提供了如下操作:

front();                        // 获取第一个元素
back();                         // 获取最后一个元素

  有时,我们需要对 list 容器进行操作,C++ 中提供了如下方法。

reverse();                     // 反转链表
sort();                        // 排序链表

  有时,我们需要交换两个 list 容器中的元素,可以通过 C++ 提供的 swap() 函数实现。

swap(l);     // 将l与本身元素互换
#include <iostream>
#include <list>

using namespace std;

void printList(const list<int> &l);

int main(void)
{
    list<int> l;

    for (int i = 0; i < 10; i++)
    {
        // 将元素放入到list容器中
        l.push_back(i);
    }
  
    cout << "empty: " << l.empty() << endl;
    cout << "size: " << l.size() << endl;
    cout << "front: " << l.front() << endl;
    cout << "back: " << l.back() << endl;
    printList(l);

    // 调增容器大小,默认以100填充
    l.resize(15, 100);
    printList(l);

    // 删除头部元素
    l.pop_front();
    printList(l);

    // 删除尾部元素
    l.pop_back();
    printList(l);

    // 删除元素
    l.erase(++l.begin(), l.end());
    printList(l);

    // 向头部插入元素
    l.push_front(10);
    printList(l);

    // 向尾部插入元素
    l.push_back(20);
    printList(l);

    // 插入元素
    l.insert(++l.begin(), 3, 30);
    printList(l);

    // 移除元素
    l.remove(30);
    printList(l);

    // 反转容器的元素
    l.reverse();
    printList(l);

    // 排序容器的元素
    l.sort();
    printList(l);

    // 清空容器
    l.clear();
    printList(l);

    list<int> l1 = {1 ,2, 3};
    list<int> l2 = {4 ,5, 6};
    // 交换两个容器的元素
    l1.swap(l2);
    printList(l1);
    printList(l2);

    return 0;
}

void printList(const list<int> &v)
{
    for (list<int>::const_iterator it = v.begin(); it != v.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
}

  在调用 sort() 方法时,我们可以自定义规则对自定义类型的数据进行排序。

#include <iostream>
#include <list>

using namespace std;

struct Person
{
    string name;
    int age;
};

bool Compare(Person &p1, Person &p2);
void printList(const list<Person> &l);

int main(void)
{
    list<Person> l;

    Person p1 = {"Sakura", 10};
    Person p2 = {"Mikoto", 14};
    Person p3 = {"Shana", 15};
    Person p4 = {"Sakura", 12};

    // 向容器中插入数据
    l.push_back(p1);
    l.push_back(p2);
    l.push_back(p3);
    l.push_back(p4);

    printList(l);

    cout << endl;

    l.sort(Compare);

    printList(l);

    return 0;
}

bool Compare(Person &p1, Person &p2)
{
    // 先按姓名进行升序,如果相同再按照年龄进行升序
    if (p1.name == p2.name)
    {
        return p1.age < p2.age;
    }
    else
    {
        return p1.name < p2.name;
    }
}

void printList(const list<Person> &l)
{
    for (list<Person>::const_iterator it = l.begin(); it != l.end(); it++)
    {
        cout << "{name: " << it->name << ", age: " << it->age << "}" << endl;
    }
}

2.7、set和multiset容器

  set 和 multiset 容器的所有元素都会在插入时 自动排序。set 和 multiset 容器属于 关联式容器,底层结构是用 二叉树 实现的。set 不允许容器有重复的元素,而 multiset 允许容器中有重复的元素。

  我们可以使用 set 容器的构造方法来创建 set 容器,它的原型如下:

set<T> s;                  // 默认构造函数
set(const quene & s);      // 拷贝构造函数

  创建 set 容器后,我们需要对它进行赋值。

set & operator=(const set & s);       // 重载等号操作符

  对 set 容器进行赋值后,C++ 提供了如下方法用来获取容器的大小。

empty();                    // 判断容器是否为空
size();                     // 容器中元素的个数

  有时,我们需要对 set 容器的数据进行存取,C++ 中提供如下方式:

insert(element);                                            // 在容器中插入元素
erase(const_iterator pos);                                  // 删除迭代器指向的元素
erase(const_iterator start, const_iterator end);            // 删除迭代器从start到end之间的元素
erase(element);                                             // 删除容器中为element的元素
clear();                                                    // 清空容器中的元素

  有时,我们需要查找或统计元素的个数,C++ 中提供如下方法。

find(key);     // 查找key是否存在,如果存在返回元素的迭代器,否则返回end()
count(key);    // 统计key的个数

  有时,我们需要交换两个 set 容器中的元素,可以通过 C++ 提供的 swap() 函数实现。

swap(s);     // 将s与本身元素互换
#include <iostream>
#include <set>

using namespace std;

void printSet(const set<int> &s);

int main(void)
{
    set<int> s;

    for (int i = 0; i < 10; i++)
    {
        // 将元素放入到set容器中
        s.insert(i);
    }

    printSet(s);

    cout << "empty: " << s.empty() << endl;
    cout << "size: " << s.size() << endl;

    // 查找元素
    set<int>::iterator pos = s.find(3);
    if (pos != s.end())
    {
        cout << "find: " << *pos << endl;
    }
    else
    {
        cout << "not find" << endl;
    }

    // 统计元素个数
    int result = s.count(3);
    cout << "count: " << result << endl;

    // 删除元素
    s.erase(s.begin());
    printSet(s);

    s.erase(++s.begin(), --s.end());
    printSet(s);

    s.erase(1);
    printSet(s);

    // 清空容器
    s.clear();
    printSet(s);

    set<int> s1 = {1 ,2, 3};
    set<int> s2 = {4 ,5, 6};
    // 交换两个容器的元素
    s1.swap(s2);
    printSet(s1);
    printSet(s2);
   
    return 0;
}

void printSet(const set<int> &s)
{
    for (set<int>::const_iterator it = s.begin(); it != s.end(); it++)
    {
        cout << *it << " ";
    }

    cout << endl;
}

  在调用 sort() 方法时,我们可以自定义规则对自定义类型的数据进行排序。

#include <iostream>
#include <set>

using namespace std;

struct Person
{
    string name;
    int age;
};

class Compare
{
public:
    bool operator()(const Person &p1, const Person &p2)
    {
        if (p1.name == p2.name)
        {
            return p1.age < p2.age;
        }
        else
        {
            return p1.name < p2.name;
        }
    }
};

void printSet(const set<Person, Compare> &s);

int main(void)
{
    set<Person, Compare> s;

    Person p1 = {"Sakura", 10};
    Person p2 = {"Mikoto", 14};
    Person p3 = {"Shana", 15};
    Person p4 = {"Sakura", 12};

    s.insert(p1);
    s.insert(p2);
    s.insert(p3);
    s.insert(p4);
  
    printSet(s);
   
    return 0;
}

void printSet(const set<Person, Compare> &s)
{
    for (set<Person, Compare>::const_iterator it = s.begin(); it != s.end(); it++)
    {
        cout << "{name: " << it->name << ", age: " << it->age << "}" << endl;
    }

    cout << endl;
}

对于自定义数据类型,set 必须指定排序规则才可以插入数据。

2.8、map和multimap容器

  map 容器的所有元素都是 pair。pair 中第一个元素为 key(键值),起到索引作用,第二个元素为 value(实值)。map 容器中的所有元素会根据键值自动排序。map 和 multimap 容器都属于 关联式容器,底层结构是用 二叉树 实现的。map 不允许容器有重复的 key 值,而 multimap 允许容器中有重复的 key 值。我们可以根据 key 值快速找到 value 值。

  我们可以使用 map 容器的构造方法来创建 map 容器,它的原型如下:

map<K, V> s;                // 默认构造函数
map(const quene & m);       // 拷贝构造函数

  创建 map 容器后,我们需要对它进行赋值。

map & operator=(const map & m);       // 重载等号操作符

  对 map 容器进行赋值后,C++ 提供了如下方法用来获取容器的大小。

empty();                    // 判断容器是否为空
size();                     // 容器中元素的个数

  有时,我们需要对 map 容器的数据进行存取,C++ 中提供如下方式:

insert(element);                                            // 在容器中插入元素
erase(const_iterator pos);                                  // 删除迭代器指向的元素
erase(const_iterator start, const_iterator end);            // 删除迭代器从start到end之间的元素
erase(key);                                                 // 删除容器中指定key的元素
clear();                                                    // 清空容器中的元素

  有时,我们还需要存取 vector 容器中的数据,C++ 中提供了如下操作:

at(int key);                    // 获取指定键的对应的值
opeartor[](void);               // 获取指定键的对应的值,如果没有则插入

  有时,我们需要查找或统计元素的个数,C++ 中提供如下方法。

find(key);     // 查找key是否存在,如果存在返回元素的迭代器,否则返回end()
count(key);    // 统计key的个数

  有时,我们需要交换两个 set 容器中的元素,可以通过 C++ 提供的 swap() 函数实现。

swap(s);     // 将s与本身元素互换
#include <iostream>
#include <map>

using namespace std;

void printMap(const map<int, int> &m);

int main(void)
{
    map<int, int> m;

    for (int i = 0; i < 10; i++)
    {
        // 将元素放入到map容器中
        m.insert(pair<int, int>(i, i * 10));
    }

    printMap(m);

    cout << "empty: " << m.empty() << endl;
    cout << "size: " << m.size() << endl << endl;

    // 查找元素
    map<int, int>::iterator pos = m.find(3);
    if (pos != m.end())
    {
        cout << "find: " <<  (*pos).first << ", value: " << pos->second << endl;
    }
    else
    {
        cout << "not find" << endl;
    }

    // 统计元素个数
    int result = m.count(3);
    cout << "count: " << result << endl << endl;

    // 获取指定key的值
    result = m.at(3);
    cout << "result: " << result << endl << endl;

    // 获取指定key的值,如果没有则插入
    m[15] = 150;
    printMap(m);

    // 删除元素
    m.erase(m.begin());
    printMap(m);

    m.erase(++m.begin(), --m.end());
    printMap(m);

    m.erase(1);
    printMap(m);

    // 清空容器
    m.clear();
    printMap(m);

    map<int, int> m1 = {map<int, int>::value_type(1, 1), pair<int, int>(2, 2), make_pair(3, 3)};
    map<int, int> m2 = {map<int, int>::value_type(4, 4), pair<int, int>(5, 5), make_pair(6, 6)};
    // 交换两个容器的元素
    m1.swap(m2);
    printMap(m1);
    printMap(m2);
   
    return 0;
}

void printMap(const map<int, int> &m)
{
    for (map<int, int>::const_iterator it = m.begin(); it != m.end(); it++)
    {
        cout << "key: " << (*it).first << ", value: " << it->second << endl;
    }
    cout << endl;
}

  在调用 sort() 方法时,我们可以自定义规则对自定义类型的数据进行排序。

#include <iostream>
#include <map>

using namespace std;

class Compare
{
public:
    bool operator()(int k1, int k2)
    {
        return k1 > k2;
    }
};

void printMap(const map<int, int, Compare> &m);

int main(void)
{
    map<int, int, Compare> m;

    for (int i = 0; i < 10; i++)
    {
        // 将元素放入到map容器中
        m.insert(pair<int, int>(i, i * 10));
    }

    printMap(m);
   
    return 0;
}

void printMap(const map<int, int, Compare> &m)
{
    for (map<int, int, Compare>::const_iterator it = m.begin(); it != m.end(); it++)
    {
        cout << "key: " << it->first << ", value: " << it->second << endl;
    }
    cout << endl;
}

对于自定义数据类型,set 必须指定排序规则才可以插入数据。

三、函数对象

3.1、什么是函数对象

  重载了 函数调用操作符 的类,其对象称为 函数对象函数对象 使用重载的 () 运算符时,行为类似于函数调用,因此也叫 仿函数。函数对象(仿函数)是一个类,不是一个函数。函数对象在使用时,可以像普通函数那样调用,可以有参数,也可以有返回值。函数对象可以有自己的状态。函数对象可以作为参数传递。

#include <iostream>

using namespace std;

class Print
{
public:
    int count = 0;

    Print(void)
    {
        this->count = 0;
    }

    void operator()(string text)
    {
        cout << text << endl;
        this->count++;
    }
};

void doPrint(Print &print, string text);

int main(void)
{
    Print print;

    print("Hello World!");
    print("Hello Sakura!");
    print("Hello Mikoto!");
  
    cout << "count: " << print.count  << endl;

    doPrint(print, "Hello Sakura!");
   
    return 0;
}

void doPrint(Print &print, string text)
{
    print(text);
}

3.2、谓词

  返回 bool 类型的仿函数称为 谓词。如果 operator() 接受一个参数,那么叫做 一元谓词。如果 operator() 接受两个参数,那么叫做 二元谓词

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Compare
{
public:
    bool operator()(int value1, int value2)
    {
        return value1 > value2;
    }
};

void printVector(const vector<int> &v);

int main(void)
{
    vector<int> v = {10, 30, 20, 70, 50};
  
    sort(v.begin(), v.end(), Compare());
    printVector(v);
   
    return 0;
}

void printVector(const vector<int> &v)
{
    for (vector<int>::const_iterator it = v.begin(); it != v.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
}

3.3、内建函数对象

  STL 内建了一些函数对象,我们使用它们的时候,需要引入头文件 functional。内建函数对象一般分为三类:算术仿函数关系仿函数逻辑仿函数

  算符仿函数 用于实现四则运算,其中 negate() 是一元函数,其它都是二元函数。

template<class T> T plus(T);            // 加法仿函数
template<class T> T mius(T);            // 减法仿函数
template<class T> T multiplies(T);      // 乘法仿函数
template<class T> T divides(T);         // 除法仿函数
template<class T> T modulus(T);         // 取模仿函数
template<class T> T negate(T);          // 取反仿函数

  关系仿函数 用于实现关系比较。

template<class T> bool equal_to<T>;         // 等于
template<class T> bool not_equal_to<T>;     // 不等于
template<class T> bool less<T>;             // 小于
template<class T> bool less_equal<T>;       // 小于等于
template<class T> bool greater<T>;          // 大于
template<class T> bool greater_equal<T>;    // 大于等于

  逻辑仿函数 用于实现逻辑运算。

template<class T> bool logical_and<T>;      // 逻辑与
template<class T> bool logical_or<T>;       // 逻辑或
template<class T> bool logical_not<T>;      // 逻辑非
#include <iostream>
#include <functional>

using namespace std;

int main(void)
{
    int num1 = 10, num2 = 20;
    bool b = false;

    negate<int> n;
    plus<int> p;
    greater<int> g;
    logical_not<bool> nn;
  
    cout << n(num1) << endl;
    cout << p(num1, num2) << endl;
    cout << g(num1, num2) << endl;
    cout << nn(b) << endl;

    return 0;
}

四、常用算法

  算法主要由头文件 algorithm、functional、numeric 头文件组成。algorithm 是所有 STL 头文件中最大的一个,范围涉及到比较、交换、查找、遍历、复制、修改等等。numeric 体积很小,只包括几个在序列上面进行简单数学运算的模板函数。functional 定义了一些模板类,用以声明函数对象。

4.1、遍历算法

for_each(iterator begin, iterator end, _func);                         // 遍历
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

void print(int value);

int main(void)
{
    vector<int> v = {10, 30, 20, 70, 50};
  
    // 遍历算法
    for_each(v.begin(), v.end(), print);
   
    return 0;
}

void print(int value)
{
    cout << value << " ";
}

4.2、查找算法

find(iterator begin, iterator end, value);                   // 按值查找元素,找到返回指定位置迭代器,否则返回结束迭代器
find_if(iterator begin, iterator end, Predicate pred);       // 按条件查找元素,找到返回指定位置迭代器,否则返回结束迭代器
adjacent_find(iterator begin, iterator end);                 // 查找相邻重复元素,找到返回指定位置迭代器,否则返回结束迭代器
bool binary_search(iterator begin, iterator end, value);     // 二分查找,找到返回true,否则返回false
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Person
{
public:
    string name;
    int age;

    Person(void) {}
    Person(string name, int age) : name(name), age(age) {}

    // 自定义类型需要重载==运算符,让底层的find知道如何对比
    bool operator==(const Person &p)
    {
        return this->name == p.name && this->age == p.age;
    }
};

int main(void)
{
    Person p1("Sakura", 10);
    Person p2("Mikoto", 14);
    Person p3("Shana", 15);
    Person p4("Sakura", 12);

    vector<Person> v = {p1, p2, p3, p4};

    // 查找算法
    vector<Person>::iterator it = find(v.begin(), v.end(), p4);
    ((it != v.end()) ? cout << "find: {name: " << it->name << ", age: " << it->age << "}" << endl : cout << "not find" << endl);

    it = find_if(v.begin(), v.end(), [](Person &p) { return p.name == "Sakura"; });
    ((it != v.end()) ? cout << "find: {name: " << it->name << ", age: " << it->age << "}" << endl : cout << "not find" << endl);

    it = adjacent_find(v.begin(), v.end());
    ((it != v.end()) ? cout << "找到相邻重复元素:{name: " << it->name << ", age: " << it->age << "}" << endl : cout << "未找到相邻重复元素" << endl);
   
    return 0;
}

4.3、统计算法

count(iterator begin, iterator end, value);          // 统计元素出现个数
count_if(iterator begin, iterator end, function);    // 统计满足条件的元素个数
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

class Person
{
public:
    string name;
    int age;

    Person(void) {}
    Person(string name, int age) : name(name), age(age) {}

    // 自定义类型需要重载==运算符,让底层的count知道如何对比
    bool operator==(const Person &p)
    {
        return this->name == p.name && this->age == p.age;
    }
};

int main(void)
{
    Person p1("Sakura", 10);
    Person p2("Mikoto", 14);
    Person p3("Shana", 15);
    Person p4("Sakura", 12);

    vector<Person> v = {p1, p2, p3, p4};

    // 统计算法
    int num = count(v.begin(), v.end(), p1);
    cout << "num: " << num << endl;

    num = count_if(v.begin(), v.end(), [](Person &p) { return p.age > 10; });
    cout << "num: " << num << endl;
   
    return 0;
}

4.4、排序算法

sort(iterator begin, iterator end, _Pred);           // 对元素进行排序
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>

using namespace std;

int main(void)
{
    vector<int> v = {10, 30, 20, 70, 50};
  
    // 排序算法
    sort(v.begin(), v.end(), greater<int>());
    // 遍历算法
    for_each(v.begin(), v.end(), [](int value){cout << value << " "; });
   
    return 0;
}

4.5、拷贝算法

copy(iterator begin, iterator end, iterator dest);                       // 容器内指定范围的元素拷贝到另一个容器
transform(iterator begin1, iterator end1, iterator begin2, _func2);      // 搬运容器到另一个容器
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void)
{
    vector<int> v1 = {10, 30, 20, 70, 50};
    vector<int> v2;
    vector<int> v3;

    v2.resize(v1.size());
    v3.resize(v1.size());

    copy(v1.begin(), v1.end(), v2.begin());
    for_each(v2.begin(), v2.end(), [](int e) { cout << e << " ";});
    cout << endl;

    // 搬运算法
    transform(v1.begin(), v1.end(), v3.begin(), [](int e) { return e * 10; });
    for_each(v3.begin(), v3.end(), [](int e) { cout << e << " ";});
    cout << endl;
   
    return 0;
}

拷贝和搬运的目标容器必须要提前开辟空间,否则无法正常拷贝或搬运。

4.6、替换算法

replace(iterator begin, iterator end, oldValue, newValue);       // 将容器内指定范围的旧元素替换为新元素
replace_if(iterator begin, iterator end, _Pred, newValue);       // 将容器内指定范围满足条件的元素替换为新元素
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void)
{
    vector<int> v = {10, 20, 30, 40, 50, 40, 30, 20, 10};

    // 替换算法
    replace(v.begin(), v.end(), 20, 200);
    for_each(v.begin(), v.end(), [](int e) { cout << e << " ";});
    cout << endl;

    replace_if(v.begin(), v.end(), [](int e) { return e == 10; }, 100);
    for_each(v.begin(), v.end(), [](int e) { cout << e << " ";});
    cout << endl;
   
    return 0;
}

replace() 会替换区间内所有满足条件的元素。

4.7、交换算法

swap(container c1, container c2);            // 交换两个容器的元素
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void)
{
    vector<int> v1 = {10, 20, 30, 40, 50, 60, 70, 80, 90};
    vector<int> v2 = {100, 200, 300, 400, 500};

    // 交换算法
    swap(v1, v2);

    for_each(v1.begin(), v1.end(), [](int e) { cout << e << " ";});
    cout << endl;

    for_each(v2.begin(), v2.end(), [](int e) { cout << e << " ";});
    cout << endl;
   
    return 0;
}

4.8、合并算法

merge(iterator begin1, iterator end1, iterator begin2, iterator end2, iterator dest);    // 两个容器元素合并,并存储到另一个容器中
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void)
{
    vector<int> v1 = {10, 30, 50, 70, 90, 20, 40, 60, 80};
    vector<int> v2 = {100, 300, 500, 200, 400};
    vector<int> v3;

    v3.resize(v1.size() + v2.size());

    merge(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
    for_each(v3.begin(), v3.end(), [](int e) { cout << e << " ";});
    cout << endl;
   
    return 0;
}

合并时,必须目标容器必须要提前开辟空间,否则无法正常合并。

4.9、反转算法

reverse(iterator begin, iterator end);    // 将容器内元素进行反转
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void)
{
    vector<int> v = {10, 20, 30, 40, 50};

    // 反转算法
    reverse(v.begin(), v.end());
    for_each(v.begin(), v.end(), [](int e) { cout << e << " ";});
    cout << endl;
   
    return 0;
}

4.10、随机算法

random_shuffle(iterator begin, iterator end);    // 指定范围内的元素随机调整次序
#include <iostream>
#include <vector>
#include <algorithm>
#include <ctime>

using namespace std;

int main(void)
{
    vector<int> v = {10, 20, 30, 40, 50};

    // 随机数种子
    srand((unsigned int)time(NULL));
    // 随机算法
    random_shuffle(v.begin(), v.end());
    for_each(v.begin(), v.end(), [](int e) { cout << e << " ";});
    cout << endl;
   
    return 0;
}

4.11、集合算法

set_intersection(iterator begin1, iterator end1, iterator begin2, iterator end2, iterator dest);     // 求两个集合的交集
set_union(iterator begin1, iterator end1, iterator begin2, iterator end2, iterator dest);            // 求两个集合的并集
set_difference(iterator begin1, iterator end1, iterator begin2, iterator end2, iterator dest);       // 求两个集合的差集
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void)
{
    vector<int> v1 = {10, 20, 30, 40, 50, 60, 70, 80, 90};
    vector<int> v2 = {10, 30, 50, 70, 90, 100, 300};
    vector<int> v3;
    vector<int> v4;
    vector<int> v5;

    // 交集,最特殊的情况,大容器中包含小容器,开辟空间时,取小容器的size即可
    v3.resize(min(v1.size(), v2.size()));
    // 获取交集
    vector<int>::iterator itEnd = set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(), v3.begin());
    for_each(v3.begin(), itEnd, [](int e) { cout << e << " ";});
    cout << endl;

    // 并集,最特殊的情况,两个容器没有交集,开辟空间时,两个容器的size相加
    v4.resize(v1.size() + v2.size());
    // 获取并集
    itEnd = set_union(v1.begin(), v1.end(), v2.begin(), v2.end(), v4.begin());
    for_each(v4.begin(), itEnd, [](int e) { cout << e << " ";});
    cout << endl;

    // 差集,最特殊的情况,两个容器没有交集,开辟空间时,取大容器的size即可
    v5.resize(max(v1.size(), v2.size()));
    // 获取差集
    itEnd = set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(), v5.begin());
    for_each(v5.begin(), itEnd, [](int e) { cout << e << " ";});
    cout << endl;
   
    return 0;
}

目标容器必须要提前开辟空间。

原容器必须是有序的。

4.12、算术生成算法

accumulate(iterator begin, iterator end, value);         // 计算容器元素累计总和
fill(iterator begin, iterator end, value);               // 像容器中填充指定的元素
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>

using namespace std;

int main(void)
{
    vector<int> v1 = {10, 20, 30, 40, 50, 60, 70, 80, 90};
  
    int sum = accumulate(v1.begin(), v1.end(), 100);
    cout << "sum = " << sum << endl;

    vector<int> v2;
    v2.resize(10);
    fill(v2.begin(), v2.end(), 100);
    for_each(v2.begin(), v2.end(), [](int val){cout << val << " ";});
   
    return 0;
}
posted @ 2023-05-08 22:28  星光樱梦  阅读(39)  评论(0编辑  收藏  举报