有序容器的元素在插入容器后会被按照某种规则自动排序,而C++的有序容器的实现通常是树结构(红黑树)。红黑树需要同时满足以下特性:
特性:
1、节点是红色或黑色 2、根是黑色 3、叶子节点(外部节点,空节点)都是黑色,这里的叶子节点指的是最底层的空节点(外部节点) 4、红色节点的子节点都是黑色 4.1、红色节点的父节点都是黑色 4.2、从根节点到叶子节点的所有路径上不能有 2 个连续的红色节点 5、从任一节点到叶子节点的所有路径都包含相同数目的黑色节点 如果你不了解红黑树具体实现,也不影响你使用有序容器。比较C++标准库都已经实现好了,你只需要学会使用。

我们主要介绍set/multiset 和 map/multimap四种有序容器,set是一种集合,map是关联数组,在python也叫字典。在加上mult前缀后表示可以将重复的key插入到容器中。
容器的默认比较是小于。

#include <iostream>
#include <set>
#include <functional>

int main()
{
    std::set<int> s1;
    s1.insert(5);
    s1.insert(5);
    s1.insert(1);
    s1.insert(4);
    s1.insert(4);

    for (auto i : s1)
    {
        std::cout << i << " "; // 1 4 5
    }
    std::cout << std::endl;

    std::set<int, std::greater<int>> s2;
    s2.insert(5);
    s2.insert(5);
    s2.insert(1);
    s2.insert(4);
    s2.insert(4);

    for (auto i : s2)
    {
        std::cout << i << " "; // 5 4 1
    }
    std::cout << std::endl;

    std::multiset<int> s3;
    s3.insert(5);
    s3.insert(5);
    s3.insert(1);
    s3.insert(4);
    s3.insert(4);

    for (auto i : s3)
    {
        std::cout << i << " "; // 1 4 4 5 5
    }
    std::cout << std::endl;
    return 0;
}

对于map其实也是同样的机制:

#include <iostream>
#include <map>
#include <string>
#include <functional>

int main()
{
    std::map<int, std::string> m1;
    m1.insert(std::make_pair(5, "aaa"));
    m1.insert(std::make_pair(5, "bbb"));
    m1.insert(std::make_pair(1, "ccc"));
    m1.insert(std::make_pair(4, "ddd"));
    m1.insert(std::make_pair(4, "eee"));

    for (auto i : m1)
    {
        std::cout << i.first << " " << i.second << std::endl;
    }
    std::cout << std::endl;

    std::map<int, std::string, std::greater<int>> m2;
    m2.insert(std::make_pair(5, "aaa"));
    m2.insert(std::make_pair(5, "bbb"));
    m2.insert(std::make_pair(1, "ccc"));
    m2.insert(std::make_pair(4, "ddd"));
    m2.insert(std::make_pair(4, "eee"));

    for (auto i : m2)
    {
        std::cout << i.first << " " << i.second << std::endl;
    }
    std::cout << std::endl;

    std::multimap<int, std::string> m3;
    m3.insert(std::make_pair(5, "aaa"));
    m3.insert(std::make_pair(5, "bbb"));
    m3.insert(std::make_pair(1, "ccc"));
    m3.insert(std::make_pair(4, "ddd"));
    m3.insert(std::make_pair(4, "eee"));

    for (auto i : m3)
    {
        std::cout << i.first << " " << i.second << std::endl;
    }
    return 0;
}

运行结果如下:

有状态容器中持久卷用 容器的有状态 和无状态_c++

对于所有的有序容器来说,C++的基本类型都可以很完美的插入到容器中,但是对于自己定义的类型,你需要事先重载“<”操作符。

#include <iostream>
#include <set>
#include <string>
#include <algorithm>

class Student
{
public:
    Student(int num, std::string name) : m_num(num)
    {
        std::cout << "构造函数" << std::endl;
        std::swap(m_name, name);
    }

    Student(const Student& stu) : m_num(stu.m_num), m_name(stu.m_name)
    {
        std::cout << "拷贝构造" << std::endl;
    }

    bool operator<(const Student& other) const
    {
        return other.m_num < m_num;
    }

    friend std::ostream &operator<<(std::ostream &os, Student &stu)
    {
        os << stu.m_num << " " << stu.m_name << std::endl;
        return os;
    }

private:
    int m_num;
    std::string m_name;
};

int main()
{
    std::set<Student> s;
    s.emplace(4, "aaa"); //我上一期也聊过,用emplace可以实现就地构造,不需要再调用拷贝构造,不推荐使用insert
    s.emplace(4, "bbb");
    s.emplace(1, "ccc");
    s.emplace(5, "ddd");
    s.emplace(1, "eee");
    std::cout << "********插入数据完成*********" << std::endl;

    for (auto i : s)
    {
        std::cout << i;       
    }
    return 0;
}

运行结果:

有状态容器中持久卷用 容器的有状态 和无状态_c++_02

你也可以自定义模板参数来实现相关功能:

#include <iostream>
#include <set>
#include <string>
#include <algorithm>

class Student
{
public:
    Student(int num, std::string name) : m_num(num)
    {
        std::swap(m_name, name);
    }

    Student(const Student& stu) : m_num(stu.m_num), m_name(stu.m_name)
    {
    }

    int getNUm()
    {
        return m_num;
    }

    friend std::ostream &operator<<(std::ostream &os, Student &stu)
    {
        os << stu.m_num << " " << stu.m_name << std::endl;
        return os;
    }

private:
    int m_num;
    std::string m_name;
};

int main()
{
    auto comp = [](Student a, Student b)
    {   
        return a.getNUm() < b.getNUm();
    }; 
    std::set<Student, decltype(comp)> s(comp);
    s.emplace(4, "aaa");
    s.emplace(4, "bbb");
    s.emplace(1, "ccc");
    s.emplace(5, "ddd");
    s.emplace(1, "eee");

    for (auto i : s)
    {
        std::cout << i;       
    }
    return 0;
}

运行结果:

有状态容器中持久卷用 容器的有状态 和无状态_开发语言_03


最后我想提醒一下:对于有序容器,在每次插入数据的时候都会自动进行排序,所以当数据量很大的时候插入位置的查找,树的旋转都会影响性能。所以如果你对数据没有实时排序的要求,建议你使用vector,在最后做一次数据的排序。如果你对数据需要实时的排序,那只能用有序容器了。