三、STL之常用容器

 

一、string容器

1.1 string 与 c 字符串比较

string 和 c 风格字符串对比:
  1. char*是一个指针,string是一个类。string 封装了char*,管理这个字符串,是一个char*型的容器。
  2. string封装了很多实用的成员方法, 查找find,拷贝copy,删除delete 替换replace,插入insert
  3. 不用考虑内存释放和越界。 string管理char*所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。

1.2 string的常用操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<stdexcept>
#include<vector>
using namespace std;

void doWork01(string s)
{

}
void doWork02(const char *s)
{

}

void test()
{
    /* 
        1. string 构造函数
        string();                    // 创建一个空的字符串 例如: string str;      
        string(const string& str);  // 使用一个string对象初始化另一个string对象
        string(const char* s);        // 使用字符串s初始化
        string(int n, char c);      // 使用n个字符c初始化 
    */
    string s1;
    string s2(s1); // 拷贝构造
    string s3("aaa"); // 有参构造
    string s4(10, 'c'); // 两个参数,有参构造
    cout << "s3 = " << s3 << endl;  // s3 = aaa
    cout << "s4 = " << s4 << endl;  // s4 == cccccccccc

    
    /*
        2. string基本赋值操作
        string& operator=(const char* s);                    // char*类型字符串 赋值给当前的字符串
        string& operator=(const string &s);                    // 把字符串s赋给当前的字符串
        string& operator=(char c);                            // 字符赋值给当前的字符串
        string& assign(const char *s);                        // 把字符串s赋给当前的字符串
        string& assign(const char *s, int n);                // 把字符串s的前n个字符赋给当前的字符串
        string& assign(const string &s);                    // 把字符串s赋给当前字符串
        string& assign(int n, char c);                        // 用n个字符c赋给当前字符串
        string& assign(const string &s, int start, int n);  // 将s从start开始n个字符赋值给字符串, start从0开始计算
    */
    string s5 = s4;
    cout << "s5 = " << s5 << endl;  // s5 = ccccccccc
    s5.assign("abcdefg", 3);
    cout << "s5 = " << s5 << endl;  // s5 = abc
    string s6 = "abcdefg";
    string s7;
    s7.assign(s6, 3, 3);
    cout << "s7 = " << s7 << endl;  // s7 = def

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

        st 和 [] 比较, [] 越界会直接挂掉, at访问越界会跑出一个 异常 out_of_range
    */
    string s8 = "hello world";
    for (int i = 0; i < s8.size(); i++)
    {
        cout << s8[i] << " ";        // h e l l o   w o r l d
        //cout << s.at(i) << " ";   // h e l l o   w o r l d
    }
    cout << endl;
    try
    {
        // s[100];  直接挂掉
        s8.at(100);  // 走到异常
    }
    catch (out_of_range &e)
    {
        cout << e.what() << endl;  // invalid string position
    }

    
    /*
        4. string拼接操作
        string& operator+=(const string& str);            // 重载+=操作符
        string& operator+=(const char* str);            // 重载+=操作符
        string& operator+=(const char c);                // 重载+=操作符
        string& append(const char *s);                    // 把字符串s连接到当前字符串结尾
        string& append(const char *s, int n);            // 把字符串s的前n个字符连接到当前字符串结尾
        string& append(const string &s);                // 同operator+=()
        string& append(const string &s, int pos, int n);// 把字符串s中从pos开始的n个字符连接到当前字符串结尾
        string& append(int n, char c);                    // 在当前字符串结尾添加n个字符c
    */
    string s9 = "";
    string s10 = "";
    s9 += s10;
    cout << "s9 = " << s9 << endl;  // s9 = 王勇
    string s11 = "";
    s9.append(s11);
    cout << "s9 = " << s9 << endl;  // s9 = 王勇好

    
    /*
        5. string查找和替换, 找到返回第一个字符的位置,找不到返回 -1
        int find(const string& str, int pos = 0) const;        // 查找str第一次出现位置,从pos开始查找
        int find(const char* s, int pos = 0) const;            // 查找s第一次出现位置,从pos开始查找
        int find(const char* s, int pos, int n) const;        // 从pos位置查找s的前n个字符第一次位置
        int find(const char c, int pos = 0) const;            // 查找字符c第一次出现位置
        int rfind(const string& str, int pos = npos) const;    // 查找str最后一次位置,从pos开始查找
        int rfind(const char* s, int pos = npos) const;        // 查找s最后一次出现位置,从pos开始查找
        int rfind(const char* s, int pos, int n) const;        // 从pos查找s的前n个字符最后一次位置
        int rfind(const char c, int pos = 0) const;            // 查找字符c最后一次出现位置
        string& replace(int pos, int n, const string& str); // 替换从pos开始n个字符为字符串str
        string& replace(int pos, int n, const char* s);        // 替换从pos开始的n个字符为字符串s
    */
    string s12 = "abcdefghdef";
    int pos = s12.find("def");
    cout << "pos = " << pos << endl;  // pos = 3
    pos = s12.find("den");
    cout << "pos = " << pos << endl;  // pos = -1
    pos = s12.rfind("def");
    cout << "pos = " << pos << endl;  // pos = 8
    pos = s12.find("cd", 6);
    cout << "pos = " << pos << endl;  // pos = -1
    s12.replace(1, 3, "111111");
    cout << "s12 = " << s12 << endl;  // a111111efghdef


    
    /*
        6. string比较操作。 compare函数在>时返回 1,<时返回 -1,==时返回 0。
        int compare(const string &s) const;    //与字符串s比较
        int compare(const char *s) const;    //与字符串s比较
    */
    string s13 = "abcdef";
    string s14 = "abcde";
    int ret;
    ret = s13.compare(s14);
    if (ret == 0)
    {
        cout << "s13 == s14" << endl;
    }
    else if (ret == 1)
    {
        cout << "s13 > s14" << endl;
    }
    else
    {
        cout << "s13 < s14" << endl;
    }

    
    /*
        7. string子串
        string substr(int pos = 0, int n = npos) const;    //返回由pos开始的n个字符组成的字符串
    */
    string s15 = "abcde";
    string s16 = s15.substr(1, 3);
    cout << "s16 = " << s16 << endl; // s16 = bcd
    string s17 = "12121212@sina.com";
    string s18;
    pos = s17.find("@");
    s18 = s17.substr(0, pos);
    cout << "s18 = " << s18 << endl;  // 12121212

    // 需求 将 www.baidu.com.cn 每一个单词截取出来存放vector容器中,也就是按照 . 来切分
    string s19 = "www.baidu.com.cn";
    vector<string> v;
    int start = 0;
    while (true)
    {
        pos = s19.find(".", start);
        if (pos == -1) // 截取结束,找不到.了, 需要将最后的数据添加进来
        {
            string tmp = s19.substr(start, s19.size() - start);
            v.push_back(tmp);
            break;
        }
        string tmp = s19.substr(start, pos - start);
        v.push_back(tmp);
        start = pos + 1;
    }
    for (vector<string>::iterator it = v.begin(); it != v.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
    
    
    /*
        8. string插入和删除操作
        string& insert(int pos, const char* s);        // 插入字符串
        string& insert(int pos, const string& str); // 插入字符串
        string& insert(int pos, int n, char c);        // 在指定位置插入n个字符c
        string& erase(int pos, int n = npos);        // 删除从Pos开始的n个字符
    */
    string s20 = "abcdef";
    s20.insert(2, "sdsd");
    cout << "s20 = " << s20 << endl; // s20 = absdsdcdef
    s20.erase(2, 4);
    cout << "s20 = " << s20 << endl; // s20 = abcdef


    /*
        9. string和c-style字符串转换
        str.c_str()        // string 转 char*
        string str(s)    // char* 转 string
    */
    string s21 = "abcdef";
    const char*  cs21 = s21.c_str();  // 需要有const
    char *s22 = "hello";
    string s23(s22);
    cout << "cs21 = " << cs21 << endl;  // cs21 = abcdef
    cout << "s23 = " << s23 << endl;    // s23 = hello

    doWork01(cs21);  // 隐式类型转换  char * -> string
    //doWork02(s21);   // 无法转换, 编译器不会将 string 隐式类型转换为 char *

    
    /*
        10. 字符串内存重新分配出现的异常
        为了修改string字符串的内容,下标操作符[]和at都会返回字符的引用。但当字符串的内存被重新分配之后,可能发生错误.
    */
    string s24 = "abcdef";
    char &a = s24[2];
    char &b = s24[3];
    a = '1';
    b = '2';
    cout << "s24 = " << s24 << endl; // s24 = ab12ef
    cout << "s24 = " << (int *)s24.c_str() << endl;  // s24 = 005FF320
    s24 = "qqqqqqqqqqqqqqqq";
    // a = '1';
    cout << "s24 = " << (int *)s24.c_str() << endl; // s24 = 000C2978 重新分配了内存,地址发生了变化,所以 a = '1'就会找不到本来的地址
    
    
    /*
        11. string 大小写转换
        toupper() // 小写转大写
        tolower() // 大写转小写
    */
    string s25 = "AbCdEfG";
    for (int i = 0; i < s25.size(); i++)
    {
        s25[i] = toupper(s25[i]); // 小写转大写
        // s25[i] = tolower(s25[i]);
    }
    cout << "s25 = " << s25 << endl;  // s25 = ABCDEFG
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

二、vector容器

2.1 vector容器的基本概念

  vector的数据安排以及操作方式,与array非常相似,两者的唯一差别在于空间的运用的灵活性。Array是静态空间,一旦配置了就不能改变,要换大一点或者小一点的空间,可以,一切琐碎得由自己来,首先配置一块新的空间,然后将旧空间的数据搬往新空间,再释放原来的空间。Vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必害怕空间不足而一开始就要求一个大块头的array了。

  vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率,一旦vector旧空间满了,如果客户每新增一个元素,vector内部只是扩充一个元素的空间,实为不智,因为所谓的扩充空间(不论多大),一如刚所说,是 "配置新空间-数据移动-释放旧空间" 的大工程,时间成本很高,应该加入某种未雨绸缪的考虑, 配置新空间的增加会自动根据代码的重复增加空间会主动发生申请空间的变化。

  单通容器,只有一个通道能增加删除,所以删除的删除的是最后一个元素。

2.2 vector迭代器

  Vector维护一个线性空间,所以不论元素的类型如何,普通指针都可以作为vector的迭代器,因为vector迭代器所需要的操作行为,如operaroe*, operator->, operator++, operator--, operator+, operator-, operator+=, operator-=, 普通指针天生具备。Vector支持随机存取,而普通指针正有着这样的能力。所以vector提供的是随机访问迭代器(Random Access Iterators)。
  根据上述描述,如果我们写如下的代码:
    vector<int>::iterator it1;
    vector<Teacher>::iterator it2;
i    t1的型别其实就是int*, it2的型别其实就是Teacher*.

2.3 vector的数据结构

  vector所采用的数据结构非常简单,线性连续空间,它以两个迭代器_Myfirst和_Mylast分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器_Myend指向整块连续内存空间的尾端。

  为了降低空间配置时的速度成本,vector实际配置的大小可能比客户端需求大一些,以备将来可能的扩充,这边是容量的概念。换句话说,一个vector的容量永远大于或等于其大小,一旦容量等于大小,便是满载,下次再有新增元素,整个vector容器就得另觅居所,也就是重新找一块新的空间。

  所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空间),而是一块更大的内存空间,然后将原数据拷贝新空间,并释放原空间。因此,对vector的任何操作,一旦引起空间的重新配置,指向原vector的所有迭代器就都失效了。

2.4 vector的常用操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<vector>
using namespace std;

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

void test()
{
    /*
        1. vector构造函数
        vector<T> v;                // 采用模板实现类实现,默认构造函数
        vector(v.begin(), v.end());    // 将v[begin(), end())区间中的元素拷贝给本身。
        vector(n, elem);            // 构造函数将n个elem拷贝给本身。
        vector(const vector &vec);    // 拷贝构造函数。
    */
    vector<int> v1;
    vector<int> v2(5, 100);
    printVector(v2);  // 100 100 100 100 100 
    vector<int> v3(v2.begin(), v2.end());  // 100 100 100 100 100
    printVector(v3);


    /*
        2. vector常用赋值操作
        assign(begin, end);                        // 将[begin, end)区间中的数据拷贝赋值给本身。
        assign(n, elem);                        // 将n个elem拷贝赋值给本身。
        vector& operator=(const vector  &vec);    // 重载等号操作符
        swap(vec);                                // 将vec与本身的元素互换。
    */
    vector<int> v4;
    v4.assign(v3.begin(), v3.end());  // 也可以直接写 v4 = v3  
    printVector(v4);  // 100 100 100 100 100
    int arr[] = { 2, 3, 4, 5 };
    vector<int> v5(arr, arr + sizeof(arr) / sizeof(arr[0])); // 本质上就是 (begin, end)
    printVector(v5);  // 2 3 4 5 
    v4.swap(v5);
    printVector(v4);  // 2 3 4 5
    printVector(v5);  // 100 100 100 100 100

    
    /*
        3. vector大小操作
        size();                    // 返回容器中元素的个数
        empty();                // 判断容器是否为空
        resize(int num);        // 重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
        resize(int num, elem);    // 重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长>度的元素被删除。
        capacity();                // 容器的容量
        reserve(int len);        // 容器预留len个元素长度,预留位置不初始化,元素不可访问。
    */
    vector<int> v6;
    v6.push_back(10);
    v6.push_back(30);
    v6.push_back(20);
    cout << v6.size() << endl; // 3
    if (v6.empty())
    {
        cout << "v6不为空" << endl;
    }
    // 重新指定容器的长度
    v6.resize(10);
    printVector(v6);  // 10 30 20 0 0 0 0 0 0 0
    v6.resize(2);
    printVector(v6);  // 10 30
    v6.resize(10, 5);
    printVector(v6);  // 10 30 5 5 5 5 5 5 5 5

    vector<int> v7;
    for (int i = 0; i < 10; i++)
    {
        v7.push_back(i);
        cout << v7.capacity() << " ";  //  1 2 3 4 6 6 9 9 9 13, 说明开辟空间是根据操作情况内部自行优化的
    }
    cout << endl;
    printVector(v7);   // 0 1 2 3 4 5 6 7 8 9


    /*
        4. vector数据存取操作
        at(int idx);    // 返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。
        operator[];        // 返回索引idx所指的数据,越界时,运行直接报错
        front();        // 返回容器中第一个数据元素
        back();            // 返回容器中最后一个数据元素
    */
    // at 和 [] 与string一样
    cout << v7.front() << endl;  // 0
    cout << v7.back() << endl;   // 9


    /*
        5. vector插入和删除操作
        insert(const_iterator pos, int count, ele);        // 迭代器指向位置pos插入count个元素ele.
        push_back(ele);                                    // 尾部插入元素ele
        pop_back();                                        // 删除最后一个元素
        erase(const_iterator start, const_iterator end);// 删除迭代器从start到end之间的元素
        erase(const_iterator pos);                        // 删除迭代器指向的元素
        clear();                                        // 删除容器中所有元素
    */
    printVector(v6);  // 10 30 5 5 5 5 5 5 5 5
    // v6.insert(3, 20); 报错,第一个参数是 const_iterator 迭代器类型
    v6.insert(v6.begin(), 20);
    printVector(v6);  // 20 10 30 5 5 5 5 5 5 5 5
    v6.insert(v6.begin(), 2, 20);
    printVector(v6);  // 20 20 20 10 30 5 5 5 5 5 5 5 5
    v6.pop_back();
    printVector(v6);  // 20 20 20 10 30 5 5 5 5 5 5 5
    v6.erase(v6.begin());  // v6.erase(v6.end()) == v6.pop_back() 都是尾删
    printVector(v6);  // 20 20 10 30 5 5 5 5 5 5 5
    v6.erase(v6.begin(), v6.end());   // 等价位 clear()
    printVector(v6);  // 内容为空。


    /*
        6. 巧用swap,收缩内存空间
    */
    vector<int> v8;
    for (int i = 0; i < 10000; i++)
    {
        v8.push_back(i);
    }
    cout << "v8的容量: " << v8.capacity() << endl;  // v8的容量: 12138
    cout << "v8的大小: " << v8.size() << endl;      // v8的大小: 10000
    v8.resize(3);
    cout << "v8的容量: " << v8.capacity() << endl;  // v8的容量: 12138
    cout << "v8的大小: " << v8.size() << endl;      // v8的大小: 3
    // 收缩数据
    vector<int>(v8).swap(v8);  // vector<int>(v8) 利用拷贝构造初始化匿名对象
    cout << "v8的容量: " << v8.capacity() << endl;  // v8的容量: 3
    cout << "v8的大小: " << v8.size() << endl;      // v8的大小: 3


    /*
        7. reserve预留空间
    */
    vector<int>v9;
    int num = 0;
    int *p = NULL;

    for (int i = 0; i < 1000; i++)
    {
        v9.push_back(i);
        // 下面代码代表添加了1000条数据, 开辟了多少次空间。
        if (p != &v9[0])  
        {
            p = &v9[0];
            num++;
        }
    }
    cout << "num = " << num << endl; // num = 18;
    v9.clear(); // 先清空容器
    // 如果一开始就知道有 1000条数据需要添加,可以提前声明, rserve 预留空间
    v9.reserve(1000);
    num = 0;
    p = NULL;
    for (int i = 0; i < 1000; i++)
    {
        v9.push_back(i);
        // 下面代码代表添加了1000条数据, 开辟了多少次空间。
        if (p != &v9[0])
        {
            p = &v9[0];
            num++;
        }
    }
    cout << "num = " << num << endl; // num = 1;

    
    /*
        8. 逆序遍历vector
        reverse_iterator     // 逆序迭代器
        iterator            // 正序迭代器
        rbegin();            // 起始位置
        rend();              // 结束位置
    */
    vector<int>v10;
    for (int i = 0; i < 10; i++)
    {
        v10.push_back(i);
    }
    printVector(v10); // 0 1 2 3 4 5 6 7 8 9
    for (vector<int>::reverse_iterator it = v10.rbegin(); it != v10.rend(); it++)
    {
        cout << *it << " ";   // 9 8 7 6 5 4 3 2 1 0
    }
    cout << endl;
    printVector(v10);  // 0 1 2 3 4 5 6 7 8 9


    /*
        9. 判断容器的迭代器是否是随机访问迭代器
        如果迭代器可以 + 1, 那么就是随机访问迭代器,比如 list就不可以 ls = ls + 1 因为没有重载 + ,可以自己重载 + 就可以
    */
    vector<int>::iterator vBegin = v10.begin();
    vBegin = vBegin + 1;  // 可以
}


int main()
{
    test();
    return EXIT_SUCCESS;
}

三、deque容器

3.1 deque容器的基本概念

vector容器是单向开口的连续内存空间,deque则是一种双向开口的连续线性空间。所谓的双向开口,意思是可以在头尾两端分别做元素的插入和删除操作,当然,vector容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。

 

  deque容器和vector容器最大的差异,一在于deque允许使用常数项时间(固定的步骤)对头端进行元素的插入和删除操作。二在于deque没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像vector那样,”旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque身上是不会发生的。也因此,deque没有必须要提供所谓的空间保留(reserve)功能.
  虽然deque容器也提供了Random Access Iterator,但是它的迭代器并不是普通的指针,其复杂度和vector不是一个量级,这当然影响各个运算的层面。因此,除非有必要,我们应该尽可能的使用vector,而不是deque。对deque进行的排序操作,为了最高效率,可将deque先完整的复制到一个vector中,对vector容器进行排序,再复制回deque.

3.2 deque容器的实现原理

deque容器是连续的空间,至少逻辑上看来如此,连续线性空间总是令我们联想到array和vector,array无法成长,vector虽可成长,却只能向尾端成长,而且其成长其实是一个假象,事实上
  (1) 申请更大空间
  (2)原数据复制新空间
  (3)释放原空间
  三步骤,如果不是vector每次配置新的空间时都留有余裕,其成长假象所带来的代价是非常昂贵的。

  deque是由一段一段的定量的连续空间构成。一旦有必要在deque前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在deque的头端或者尾端。Deque最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口,避开了重新配置空间,复制,释放的轮回,代价就是复杂的迭代器架构。
  既然deque是分段连续内存空间,那么就必须有中央控制,维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。Deque代码的实现远比vector或list都多得多。
  deque采取一块所谓的map(注意,不是STL的map容器)作为主控,这里所谓的map是一小块连续的内存空间,其中每一个元素(此处成为一个结点)都是一个指针,指向另一段连续性内存空间,称作缓冲区。缓冲区才是deque的存储空间的主体。

3.3 deque的常用操作

// deque 的基本操作和 vector 差不多

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<deque>
#include<algorithm>
using namespace std;

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

void test()
{
    /*
        1. deque构造函数
        deque<T> deqT;            // 默认构造形式
        deque(beg, end);        // 构造函数将[beg, end)区间中的元素拷贝给本身。
        deque(n, elem);            // 构造函数将n个elem拷贝给本身。
        deque(const deque &deq);// 拷贝构造函数。
    */
    deque<int> d1;
    d1.push_back(10);
    d1.push_back(30);
    d1.push_back(20);
    d1.push_back(40);
    printDeque(d1);   // 10 30 20 40
    deque<int> d2(5, 10);
    printDeque(d2);   // 10 10 10 10 10

    
    /*
        2. deque赋值操作
        assign(beg, end);                    // 将[beg, end)区间中的数据拷贝赋值给本身。
        assign(n, elem);                    // 将n个elem拷贝赋值给本身。
        deque& operator=(const deque &deq); // 重载等号操作符
        swap(deq);                            // 将deq与本身的元素互换
    */
    d2.swap(d1);
    printDeque(d1);   // 10 10 10 10 10
    printDeque(d2);   // 10 30 20 40


    /*
        3. deque大小操作
        deque.size();             // 返回容器中元素的个数
        deque.empty();             // 判断容器是否为空
        deque.resize(num);         // 重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
        deque.resize(num, elem); // 重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
    */
    if (d2.empty())
    {
        cout << "d2为空" << endl;
    }
    else
    {
        cout << "d2不为空" << endl;
        cout << "d2的大小是: " << d2.size() << endl;  // d2的大小是: 4
    }


    /*
        4. deque双端插入和删除操作
        push_back(elem);    // 在容器尾部添加一个数据
        push_front(elem);    // 在容器头部插入一个数据
        pop_back();            // 删除容器最后一个数据
        pop_front();        // 删除容器第一个数据
    */
    printDeque(d1);   // 10 10 10 10 10
    printDeque(d2);   // 10 30 20 40
    d1.push_front(50);
    d2.pop_front();
    printDeque(d1);   // 50 10 10 10 10 10
    printDeque(d2);   // 30 20 40


    /*
        5. deque数据存取
        at(idx);    // 返回索引idx所指的数据,如果idx越界,抛出out_of_range。
        operator[];    // 返回索引idx所指的数据,如果idx越界,不抛出异常,直接出错。
        front();    // 返回第一个数据。
        back();        // 返回最后一个数据
    */
    cout << d1.front() << endl; // 50
    cout << d2.back() << endl;  // 40


    /*
        6. deque插入操作
        insert(pos,elem);    // 在pos位置插入一个elem元素的拷贝,返回新数据的位置。pos是一个迭代器
        insert(pos,n,elem);    // 在pos位置插入n个elem数据,无返回值。
        insert(pos,beg,end);// 在pos位置插入[beg,end)区间的数据,无返回值。
    */
    printDeque(d1);   // 50 10 10 10 10 10
    printDeque(d2);   // 30 20 40
    d1.insert(++d1.begin(), 40);
    printDeque(d1);   // 50 40 10 10 10 10


    /*
        7. deque删除操作
        clear();        // 移除容器的所有数据
        erase(beg,end);    // 删除[beg,end)区间的数据,返回下一个数据的位置。
        erase(pos);        // 删除pos位置的数据,返回下一个数据的位置。
    */
    d1.erase(++d1.begin(), --d1.end());
    printDeque(d1);  // 50 10

    /*
        8. deque排序操作
        sort()
    */
    deque<int> d3;
    d3.push_back(10);
    d3.push_back(40);
    d3.push_back(20);
    d3.push_back(50);
    d3.push_back(30);
    sort(d3.begin(), d3.end());
    printDeque(d3);  // 10 20 30 40 50
}


int main()
{
    test();
    return EXIT_SUCCESS;
}

3.4 deque小练习

/*
    有5名选手:选手ABCDE,10个评委分别对每一名选手打分,去除最高分,去除评委中最低分,取平均分。
    //1. 创建五名选手,放到vector中
    //2. 遍历vector容器,取出来每一个选手,执行for循环,可以把10个评分打分存到deque容器中
    //3. sort算法对deque容器中分数排序,pop_back pop_front去除最高和最低分
    //4. deque容器遍历一遍,累加分数,累加分数/d.size()
    //5. person.score = 平均分
*/

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<deque>
#include<vector>
#include<algorithm>
#include<ctime>
using namespace std;

class Person
{
public:
    Person(string name, int score)
    {
        this->Name = name; 
        this->Score = score;
    }
    string Name; // 姓名
    int Score;   // 平均分
};

void createPerson(vector<Person> &v)
{
    string nameSeed = "ABCDE";
    for (int i = 0; i < 5; i++)
    {
        string name = "选手";
        name += nameSeed[i];
        int score = 0;
        Person p(name, score);
        v.push_back(p);
    }
}

void setScore(vector<Person> &v)
{
    for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
    {
        // 有十个评委打分
        deque<int> d;
        for (int i = 0; i < 10; i++)
        {
            int score = rand() % 40 + 60; // 60 - 99;
            d.push_back(score);
        }
        cout << "选手: " << it->Name << " 的分数情况: ";
        for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++)
        {
            cout << *dit << " ";
        }
        cout << endl;
        // 排序
        sort(d.begin(), d.end());
        // 去除最高分和最低分
        d.pop_front();// 最低分
        d.pop_back(); // 最高分
        // 获取总分
        int sum = 0;
        for (deque<int>::iterator dit = d.begin(); dit != d.end(); dit++)
        {
            sum += (*dit);
        }
        // 获取平均分
        int avg = sum / d.size();
        it->Score = avg;
    }
}

void showScore(vector<Person> &v)
{
    for (vector<Person>::iterator it = v.begin(); it != v.end(); it++)
    {
        cout << "姓名: " << it->Name << "  平均分: " << it->Score << endl;
    }
}

int main()
{
    // 设置随机数种子
    srand((unsigned int)time(NULL));
    // 1. 存放选手的文件
    vector<Person> v;
    // 2. 创建五名选手
    createPerson(v);
    // 3. 打分
    setScore(v);
    // 4. 显示得分
    showScore(v);
    
    return EXIT_SUCCESS;
}

四、stack容器

4.1 stack容器的基本概念

  stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口,形式如图所示。stack容器允许新增元素,移除元素,取得栈顶元素,但是除了最顶端外,没有任何其他方法可以存取stack的其他元素。换言之,stack不允许有遍历行为。
  有元素推入栈的操作称为:push,将元素推出stack的操作称为pop.

4.2 stack没有迭代器

stack所有元素的进出都必须符合”先进后出”的条件,只有stack顶端的元素,才有机会被外界取用。Stack不提供遍历功能,也不提供迭代器。

4.3 stack的常用操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<stack>
using namespace std;

void test()
{
    /*
        1. stack构造函数
        stack<T> stkT;            // stack采用模板类实现, stack对象的默认构造形式:
        stack(const stack &stk);// 拷贝构造函数
    */
    stack<int> s1;


    /*
        2. stack数据存取、赋值、大小操作
        push(elem);    // 向栈顶添加元素
        pop();        // 从栈顶移除第一个元素
        top();        // 返回栈顶元素
        stack& operator=(const stack &stk); // 重载等号操作符
        empty(); // 判断堆栈是否为空
        size();  // 返回堆栈的大小
    */
    s1.push(1);
    s1.push(2);
    s1.push(3);
    stack<int> s2 = s1;
    cout << "s2的大小: " << s2.size() << endl;
    while (!s1.empty())
    {
        // 输出栈顶元素
        cout << "栈顶元素: " << s1.top() << endl;
        // 出栈
        s1.pop();
    }
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

五、queue容器

5.1 queue容器的基本概念

queue是一种先进先出(First In First Out,FIFO)的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素。

5.2 queue没有迭代器

queue所有元素的进出都必须符合”先进先出”的条件,只有queue的顶端元素,才有机会被外界取用。queue不提供遍历功能,也不提供迭代器。

5.3 queue的常用操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<queue>
using namespace std;

class Person
{
public:
    Person(string name, int age) :Name(name), Age(age)
    {

    }
    string Name;
    int Age;
};

void test()
{
    /*
        1. queue构造函数
        queue<T> queT;            // queue采用模板类实现,queue对象的默认构造形式:
        queue(const queue &que);// 拷贝构造函数
    */
    queue<Person> q1;
    Person p1("aaa", 10);
    Person p2("bbb", 20);
    Person p3("ccc", 30);
    Person p4("ddd", 40);

    /*
        2. queue存取、插入和删除, 赋值, 大小操作操作
        push(elem);    // 往队尾添加元素
        pop();        // 从队头移除第一个元素
        back();        // 返回最后一个元素
        front();    // 返回第一个元素
        queue& operator=(const queue &que);// queue赋值操作 重载等号操作符
        empty(); // 判断队列是否为空
        size();  // 返回队列的大小
    */
    q1.push(p1);
    q1.push(p2);
    q1.push(p3);
    q1.push(p4);
    queue<Person> q2 = q1;
    cout << "q2的大小: " << q2.size() << endl;
    while (!q1.empty())
    {
        // 获取队头元素
        Person p = q1.front();
        cout << "队头元素: 姓名: " << p.Name << "  年龄: " << p.Age << endl;
        // 出队
        q1.pop();
    }
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

六、list容器

6.1 list容器基本概念

  链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。

  相较于vector的连续线性空间,list就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或者释放一个元素的空间。因此,list对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素的移除,list永远是常数时间。

  list和vector是两个最常被使用的容器。

  list容器是一个循环双向链表。

    1. 采用动态存储分配,不会造成内存浪费和溢出
    2. 链表执行插入和删除操作十分方便,修改指针即可,不需要移动大量元素
    3. 链表灵活,但是空间和时间额外耗费较大

6.2 list容器的迭代器

  list容器不能像vector一样以普通指针作为迭代器,因为其节点不能保证在同一块连续的内存空间上。list迭代器必须有能力指向list的节点,并有能力进行正确的递增、递减、取值、成员存取操作。所谓 "list正确的递增,递减、取值、成员取用" 是指,递增时指向下一个节点,递减时指向上一个节点,取值时取的是节点的数据值,成员取用时取的是节点的成员。

  由于list是一个双向链表,迭代器必须能够具备前移、后移的能力,所以list容器提供的是Bidirectional Iterators(双向迭代器).

  list有一个重要的性质,插入操作和删除操作都不会造成原有list迭代器的失效。这在vector是不成立的,因为vector的插入操作可能造成既有体重新配置,导致原有的迭代器全部失效,甚至llist元素的删除,也只有被删除的那个元素的迭代器失效,其他迭代器不受任何影响。

6.3 验证list是循环双向链表

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<list>
using namespace std;

void test02()
{
    list<int> myList;
    for (int i = 0; i < 5; i++)
    {
        myList.push_back(i);
    }
    // _Myhead visual 2013 暴露出的接口, 2015就没有了
    list<int>::_Nodeptr node = myList._Myhead->_Next;   // 获取头指针指向的下一个节点,也就是首节点
    for (int i = 0; i < myList._Mysize * 2; i++)
    {
        cout <<  node->_Myval << " ";  // 获取节点的值
        node = node->_Next;
        if (node == myList._Myhead)
        {
            node = node->_Next;
        }
    }
}

int main()
{
    test02();
    return EXIT_SUCCESS;
}

6.3 list的常用操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<list>
#include<algorithm>
using namespace std;

// 正序打印
void printList(list<int> &L)
{
    for (list<int>::iterator it = L.begin(); it != L.end(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
}

// 逆序打印
void printListReverse(list<int> &L)
{
    for (list<int>::reverse_iterator it = L.rbegin(); it != L.rend(); it++)
    {
        cout << *it << " ";
    }
    cout << endl;
}


bool myCompare(int v1, int v2)
{
    return v1 > v2;
}

class Person
{
public:
    Person(string name, int age, int high)
    {
        this->Name = name;
        this->Age = age;
        this->High = high;
    }
    string Name;
    int Age;
    int High;
};

bool MyPersonCompare(Person &p1, Person &p2)
{
    // 按照年龄升序,年龄相同,按照身高降序
    if (p1.Age == p2.Age)
    {
        return p1.High > p2.High;
    }
    return p1.Age < p2.Age;
}

void test()
{
    /*
        1. list构造函数
        list<T> lstT;            // list采用采用模板类实现,对象的默认构造形式:
        list(beg,end);            // 构造函数将[beg, end)区间中的元素拷贝给本身。
        list(n,elem);            // 构造函数将n个elem拷贝给本身。
        list(const list &lst);    // 拷贝构造函数。
    */
    list<int> l1;
    list<int> l2(5, 5);
    printList(l2);    // 5 5 5 5 5
    list<int> l3(l2);
    printList(l3);    // 5 5 5 5 5

    /*
        2. list数据元素插入和删除操作
        push_back(elem);    // 在容器尾部加入一个元素
        pop_back();            // 删除容器中最后一个元素
        push_front(elem);    // 在容器开头插入一个元素
        pop_front();        // 从容器开头移除第一个元素
        insert(pos,elem);    // 在pos位置插elem元素的拷贝,返回新数据的位置。pos 是迭代器
        insert(pos,n,elem);    // 在pos位置插入n个elem数据,无返回值。
        insert(pos,beg,end);// 在pos位置插入[beg,end)区间的数据,无返回值。
        clear();            // 移除容器的所有数据
        erase(beg,end);        // 删除[beg,end)区间的数据,返回下一个数据的位置。
        erase(pos);            // 删除pos位置的数据,返回下一个数据的位置。
        remove(elem);        // 删除容器中所有与elem值匹配的元素。
    */
    list<int> l4;
    l4.push_back(10);
    l4.push_back(20);
    l4.push_front(30);
    l4.push_front(40);
    printList(l4);  // 40 30 10 20
    printListReverse(l4);  // 20 10 30 40
    l4.insert(++l4.begin(), 100);
    printList(l4);  // 40 100 30 10 20
    l4.erase(++l4.begin());
    printList(l4);  // 40 30 10 20
    l4.push_back(10);
    printList(l4);  // 40 30 10 20 10
    l4.remove(10);
    printList(l4);  // 40 30 20


    /*
        3. list大小操作
        size();        //返回容器中元素的个数
        empty();    //判断容器是否为空
        resize(num);//重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
        resize(num, elem);//重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
    */
    cout << l4.size() << endl;  // 3
    l4.resize(5);
    printList(l4); // 40 30 30 0 0


    /*
        4. list赋值操作
        assign(beg, end);                    // 将[beg, end)区间中的数据拷贝赋值给本身。
        assign(n, elem);                    // 将n个elem拷贝赋值给本身。
        list& operator=(const list &lst);    // 重载等号操作符
        swap(lst);                            // 将lst与本身的元素互换。
    */
    list<int> l5;
    l5.assign(l4.begin(), l4.end());
    printList(l5);  // 40 30 30 0 0
    l5.assign(10, 5);
    printList(l5);  // 5 5 5 5 5 5 5 5 5 5
    l5.swap(l4);
    printList(l4);  // 5 5 5 5 5 5 5 5 5 5
    printList(l5);    // 40 30 30 0 0


    /*
        5. list数据的存取
        front();// 返回第一个元素。
        back(); // 返回最后一个元素。
    */
    cout << l5.front() << endl;  // 40
    cout << l4.back() << endl;   // 5


    /*
        6. list反转排序
        reverse();    // 反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。
        sort();        // list排序
    */
    // 反转
    l5.reverse();
    printList(l5);  // 0 0 20 30 40
    l5.push_back(10);

    // 排序
    // sort(l5.begin(), l5.end()); 这样是不可以的,因为是不支持随机访问迭代器
    // 所有系统提供的标准算法,使用的容器提供的迭代器必须是支持随机访问的
    // 不支持随机访问的迭代器的容器,内部会对应提供想用的算法的接口。
    
    // 正序排列
    l5.sort();  
    printList(l5);  // 0 0 10 20 30 40
    // 逆序排序
    l5.sort(myCompare);
    printList(l5);  // 40 30 20 10 0 0
    // 自定义类型排序
    list<Person> L;
    Person p1("大娃", 30, 180);
    Person p2("二娃", 28, 170);
    Person p3("三娃", 26, 165);
    Person p4("四娃", 24, 168);
    Person p5("五娃", 24, 173);
    Person p6("蛇精", 999, 666);
    Person p7("爷爷", 100, 168);
    L.push_back(p1);
    L.push_back(p2);
    L.push_back(p3);
    L.push_back(p4);
    L.push_back(p5);
    L.push_back(p6);
    L.push_back(p7);
    for (list<Person>::iterator it = L.begin(); it != L.end(); it++)
    {
        cout << "姓名: " << it->Name << "  年龄: " << it->Age << "  身高: " << it->High << endl;
    }
    // L.sort(); 直接这样排序是不行
    // 自定义的数据类型需要制定排序规则
    L.sort(MyPersonCompare);
    for (list<Person>::iterator it = L.begin(); it != L.end(); it++)
    {
        cout << "年龄排序后的结果是: " << "姓名: " << it->Name << "  年龄: " << it->Age << "  身高: " << it->High << endl;
    }
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

七、set/multiset容器

7.1 set/multiset容器的基本概念

7.1.1 set容器的基本概念

  set的特性是。所有元素都会根据元素的键值自动被排序,是关联式容器。set的元素不像map那样可以同时拥有实值和键值,set的元素即是键值又是实值。set不允许两个元素有相同的键值。

  我们可以通过set的迭代器改变set元素的值吗?不行,因为set元素值就是其键值,关系到set元素的排序规则。如果任意改变set元素值,会严重破坏set组织。换句话说,set的iterator是一种const_iterator。

  set拥有和list某些相同的性质,当对容器中的元素进行插入操作或者删除操作的时候,操作之前所有的迭代器,在操作完成之后依然有效,被删除的那个元素的迭代器必然是一个例外。

7.1.2 multiset容器的基本概念

multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。set和multiset的底层实现是红黑树,红黑树为平衡二叉树的一种。

https://www.cnblogs.com/wangyong123/articles/16123087.html#_label2

7.2 set的常用操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<set>
#include<algorithm>
using namespace std;

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

// 利用仿函数指定set容器的排序规则
class MyCompare
{
public:
    bool operator()(int v1, int v2)
    {
        return v1 > v2;
    }
};

void printSetByMyCompare(set<int, MyCompare> &s)
{
    for (set<int>::iterator it = s.begin(); it != s.end(); it++)
    {
        cout << *it << " ";  //  此时 *it 值得是 int类型的数据
    }
    cout << endl;
}

class Person
{
public:
    Person(string name, int age)
    {
        this->Name = name;
        this->Age = age;
    }
    string Name;
    int Age;
};

class PersonCompare
{
public:
    bool operator() (const Person &p1, const Person &p2)
    {
        // 年龄升序
        return p1.Age < p2.Age;
    }
};

void test()
{
    /*
        1. set构造函数
        set<T> st;            // set默认构造函数:
        mulitset<T> mst;    // multiset默认构造函数:
        set(const set &st);    // 拷贝构造函数
    */
    set<int> s1;

    /*
        2. set赋值、插入、删除、大小操作
        set& operator=(const set &st);    // 重载等号操作符
        swap(st);                        // 交换两个集合容器
        insert(elem);                    // 在容器中插入元素。
        clear();                        // 清除所有元素
        erase(pos);                        // 删除pos迭代器所指的元素,返回下一个元素的迭代器。
        erase(beg, end);                // 删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
        erase(elem);                    // 删除容器中值为elem的元素。
        size();                            // 返回容器中元素的数目
        empty();                        // 判断容器是否为空
        
    */
    s1.insert(10);
    s1.insert(20);
    s1.insert(50);
    s1.insert(40);
    s1.insert(30);
    printSet(s1);  // 10 20 30 40 50
    s1.erase(20);
    printSet(s1);  // 10 30 40 50


    /*
        3. set查找操作
        find(key);                // 查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
        count(key);                // 查找键key的元素个数, 要么是0,要么是1
        lower_bound(keyElem);    // 返回第一个key >= keyElem 元素的迭代器。
        upper_bound(keyElem);    // 返回第一个key > keyElem元素的迭代器。
        equal_range(keyElem);    // 返回容器中key与keyElem相等的上下限的两个迭代器。
    */
    set<int>::iterator pos = s1.find(10);
    if (pos != s1.end())
    {
        cout << "找到了元素: " << *pos << endl;
    }

    cout << "10的个数: " << s1.count(10) << endl;   // 10的个数: 1
    set<int> s2;
    s2.insert(10);
    s2.insert(20);
    s2.insert(50);
    s2.insert(40);
    s2.insert(30);
    printSet(s2);   // 10 20 30 40 50
    pos = s2.lower_bound(30);  // 返回第一个 >= 30的元素的 迭代器
    if (pos != s2.end())
    {
        cout << "找到了元素: " << *pos << endl;  // 找到了元素: 30
    }
    pos = s2.upper_bound(30);  // 返回第一个 > 30的元素的 迭代器
    if (pos != s2.end())
    {
        cout << "找到了元素: " << *pos << endl;  // 找到了元素: 40
    }
    pair<set<int>::iterator, set<int>::iterator> it = s2.equal_range(30);  // 返回 key = 30的元素的 上下两个迭代器, 也就是lower_bound与upper_bound的结合
    if (it.first != s2.end())
    {
        cout << "找到了equal_range中的lower_bound的值: " << *(it.first) << endl;  // 找到了equal_range中的lower_bound的值:: 30
    }
    else
    {
        cout << "未找到" << endl;
    }
    if (it.second != s2.end())
    {
        cout << "找到了equal_range中的upper_bound的值: " << *(it.second) << endl;  // 找到了equal_range中的lower_bound的值: 40
    }
    else
    {
        cout << "未找到" << endl;
    }

    /*
        4. 无法插入重复值验证 与 multiset 插入重复值
    */
    set<int> s3;
    pair<set<int>::iterator, bool> ret = s3.insert(10);
    if (ret.second)
    {
        cout << "第一次插入成功" << endl;   // 第一次插入成功
    }
    else
    {
        cout << "第一次插入失败" << endl;
    }
    ret = s3.insert(10);
    if (ret.second)
    {
        cout << "第二次插入成功" << endl;
    }
    else
    {
        cout << "第二次插入失败" << endl;  //  第二次插入失败
    }
    cout << "set 同一个元素只能插入一次" << endl;
    multiset<int> ms;
    ms.insert(10);
    ms.insert(10);
    
    for (multiset<int>::iterator it = ms.begin(); it != ms.end(); it++)
    {
        cout << *it << " "; //10 10
    }
    cout << endl;

    /*
        5. 插入之前指定从大到小排序, 默认从小到大
    */ 
    set<int, MyCompare> s4;
    s4.insert(10);
    s4.insert(30);
    s4.insert(20);
    s4.insert(50);
    s4.insert(40);
    // 重新书写打印输出规则
    printSetByMyCompare(s4); // 50 40 30 20 10


    /*
        6. 自定义数据类型排序
    */
    set<Person, PersonCompare> s5;
    Person p1("aaa", 10);
    Person p2("bbb", 30);
    Person p3("ccc", 50);
    Person p4("ddd", 20);
    Person p5("eee", 40);
    // 对自定义类型直接 insert会报错,所以需要先声明排序规则
    s5.insert(p1);
    s5.insert(p2);
    s5.insert(p3);
    s5.insert(p4);
    s5.insert(p5);
    for (set<Person, PersonCompare>::iterator it = s5.begin(); it != s5.end(); it++)
    {
        cout << "姓名: "<< (*it).Name << "  年龄: " << (*it).Age  << endl;
    }
}


int main()
{
    test();
    return EXIT_SUCCESS;
}

7.3 对组(pair)

7.3.1 什么是对组

对组(pair)将一对值组合成一个值,这一对值可以具有不同的数据类型,两个值可以分别用pair的两个公有属性first和second访问。
类模板:template <class T1, class T2> struct pair.

7.3.2 创建对组

//第一种方法创建一个对组
pair<string, int> pair1(string("name"), 20);
cout << pair1.first << endl; //访问pair第一个值
cout << pair1.second << endl;//访问pair第二个值

//第二种
pair<string, int> pair2 = make_pair("name", 30);
cout << pair2.first << endl;
cout << pair2.second << endl;

//pair=赋值
pair<string, int> pair3 = pair2;
cout << pair3.first << endl;
cout << pair3.second << endl;

八、map/multimap容器

8.1 map/multimap容器的基本概念

map的特性是,所有元素都会根据元素的键值自动排序。map所有的元素都是pair,同时拥有实值和键值,pair的第一元素被视为键值,第二元素被视为实值,map不允许两个元素有相同的键值。

我们可以通过map的迭代器改变map的键值吗?答案是不行,因为map的键值关系到map元素的排列规则,任意改变map键值将会严重破坏map组织。如果想要修改元素的实值,那么是可以的。

map和list拥有相同的某些性质,当对它的容器元素进行新增操作或者删除操作时,操作之前的所有迭代器,在操作完成之后依然有效,当然被删除的那个元素的迭代器必然是个例外。

multimap和map的操作类似,唯一区别multimap键值可重复。
map和multimap都是以红黑树为底层实现机制。

8.2 map的常用操作

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<map>
#include<algorithm>
using namespace std;

class MyCompare
{
public:
    bool operator()(int v1, int v2)
    {
        return v1 > v2;
    }
};

void test()
{
    /*
        1. map构造函数
        map<T1, T2> mapTT;    // map默认构造函数:
        map(const map &mp);    // 拷贝构造函数
    */
    map<int, int> m1;


    /*
        2. map赋值、插入数据元素操作
        map.insert(...);                                        // 往容器插入元素,返回pair<iterator,bool>
        map<int, string> mapStu;                                
        mapStu.insert(pair<int, string>(3, "小张"));            // 第一种 通过pair的方式插入对象
        mapStu.inset(make_pair(-1, "校长"));                    // 第二种 通过pair的方式插入对象
        mapStu.insert(map<int, string>::value_type(1, "小李"));    // 第三种 通过value_type的方式插入对象
        mapStu[3] = "小刘";                                        // 第四种 通过数组的方式插入值
        mapStu[5] = "小王";
        map& operator=(const map &mp);    // 重载等号操作符
        swap(mp);                        // 交换两个集合容器
    */
    m1.insert(pair<int, int>(1, 10));
    m1.insert(make_pair(2, 20));
    m1.insert(map<int, int>::value_type(3, 30));
    m1[4] = 40;
    for (map<int, int>::iterator it = m1.begin(); it != m1.end(); it++)
    {
        cout << "key = " << it->first << "  value = " << it->second << endl;
    }

    /*
        3. map大小操作
        size();        // 返回容器中元素的数目
        empty();    // 判断容器是否为空
    */
    cout << m1.size() << endl;


    /*
        4. map删除操作
        clear();        // 删除所有元素
        erase(pos);        // 删除pos迭代器所指的元素,返回下一个元素的迭代器。
        erase(beg,end);    // 删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
        erase(keyElem);    // 删除容器中key为keyElem的对组。
    */
    m1.erase(m1.begin());
    m1.erase(3);  // 按照 key 来删除
    for (map<int, int>::iterator it = m1.begin(); it != m1.end(); it++)
    {
        cout << "key = " << it->first << "  value = " << it->second << endl;
    }


    /*
        5. map查找操作
        find(key);                // 查找键key是否存在,若存在,返回该键的元素的迭代器;/若不存在,返回map.end();
        count(keyElem);            // 返回容器中key为keyElem的对组个数。对map,要么是0,要么是1。对multimap来说,值可能大于1。
        lower_bound(keyElem);    // 返回第一个key>=keyElem元素的迭代器。
        upper_bound(keyElem);    // 返回第一个key>keyElem元素的迭代器。
        equal_range(keyElem);    // 返回容器中key与keyElem相等的上下限的两个迭代器。
    */
    map<int, int>::iterator ret = m1.find(2);
    if (ret != m1.end())
    {
        cout << "找到了" << (*ret).first << endl;
    }
    else
    {
        cout << "没找到" << endl;
    }

    int num = m1.count(4);
    cout << "key = 4 的个数为: " << num << endl;

    // 再插入删除的数据
    m1.insert(make_pair(1, 10));
    m1.insert(make_pair(3, 30));
    ret = m1.lower_bound(3);
    if (ret != m1.end())
    {
        cout << "找到了 lower_bound 的 key = " << ret->first << "   value = " << ret->second << endl;
    }
    ret = m1.upper_bound(3);
    if (ret != m1.end())
    {
        cout << "找到了 upper_bound 的 key = " << ret->first << "   value = " << ret->second << endl;
    }
    pair<map<int, int>::iterator, map<int, int>::iterator> pit = m1.equal_range(3);
    if (pit.first != m1.end())
    {
        cout << "找到了 equal_range 的 key = " << pit.first->first << "   value = " << pit.first->second << endl;
    }
    if (pit.second != m1.end())
    {
        cout << "找到了 equal_range 的 key = " << pit.second->first << "   value = " << pit.second->second << endl;
    }


    /*
        6. 修改默认排序规则,默认为升序排列
    */
    map<int, int, MyCompare> m2;
    m2.insert(pair<int, int>(1, 10));
    m2.insert(make_pair(2, 20));
    m2.insert(map<int, int>::value_type(3, 30));
    m2[4] = 40;
    for (map<int, int, MyCompare>::iterator it = m2.begin(); it != m2.end(); it++)
    {
        cout << "key = " << it->first << "   value = " << it->second << endl;
    }
}

int main()
{
    test();
    return EXIT_SUCCESS;
}

8.3 multimap案例

/*
    公司今天招聘了5个员工,5名员工进入公司之后,需要指派员工在那个部门工作
    人员信息有: 姓名 工资等组成
    通过Multimap进行信息的插入 保存 显示
    分部门显示员工信息 显示全部员工信息
*/

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<ctime>
using namespace std;

class Worker
{
public:
    string Name;
    int Money;
};

enum 
{
    CAIWU,
    RENLI,
    MEISHU
};

// 创建员工
void createWorker(vector<Worker> &v)
{
    string nameSeed = "ABCDE";
    for (int i = 0; i < 5; i++)
    {
        Worker worker;
        string name = "员工";
        worker.Name = name + nameSeed[i];
        worker.Money = rand() % 10000 + 10000; // 10000 - 19999
        v.push_back(worker);
    }
}


// 员工分组
void setGroup(vector<Worker> &v, multimap<int, Worker> &m)
{
    for (vector<Worker>::iterator it = v.begin(); it != v.end(); it++)
    {
        // 随机产生部门编号 0 1 2
        int id = rand() % 3;
        // 将员工插入到分组的容器中
        m.insert(make_pair(id, *it));
    }
}

// 显示部门员工
void showWorker(multimap<int, Worker> &m)
{
    cout << "财务部门人员如下: " << endl;
    multimap<int, Worker>::iterator pos = m.find(CAIWU);
    int index = 0;
    int count = m.count(CAIWU);
    for (; pos != m.end(), index<count; pos++, index++)
    {
        cout << "姓名: " << pos->second.Name << "  工资: "<< pos->second.Money << endl;
    }
    cout << "人力部门人员如下: " << endl;
    pos = m.find(RENLI);
    index = 0;
    count = m.count(RENLI);
    for (; pos != m.end(), index<count; pos++, index++)
    {
        cout << "姓名: " << pos->second.Name << "  工资: " << pos->second.Money << endl;
    }
    cout << "美术部门人员如下: " << endl;
    pos = m.find(MEISHU);
    index = 0;
    count = m.count(MEISHU);
    for (; pos != m.end(), index<count; pos++, index++)
    {
        cout << "姓名: " << pos->second.Name << "  工资: " << pos->second.Money << endl;
    }

}

int main()
{
    srand((unsigned int) time(NULL));
    // 创建员工
    vector<Worker> v;  // 存放员工的容器
    createWorker(v);

    for (vector<Worker>::iterator it = v.begin(); it != v.end(); it++)
    {
        cout << "姓名: " << it->Name << "  工资:" << it->Money << endl;
    }

    // 员工分组
    multimap<int, Worker> m;
    setGroup(v, m);

    // 分部门显示员工
    showWorker(m);
    return EXIT_SUCCESS;
}

九、STL容器使用时机

 

 

1. vector的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。

2. deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。

  vector与deque的比较:
    一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却是不固定的。
    二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。
    三:deque支持头部的快速插入与快速移除,这是deque的优点。

3. list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。

4. set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。

5. map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。

 

posted on 2022-04-13 18:19  软饭攻城狮  阅读(33)  评论(0)    收藏  举报

导航