目录
引入
好久没有更新了
关联式容器
在之前的学习中,我们已经接触到了STL中的部分容器,比如有:vector、list、deque、forward_list等,这些容器统称为序列式容器,因为其底层实现的原理为线性序列的数据结构,也就是存储元素的本身。
而关联式容器也是用来存储数据的,但是他又与序列式容器不同的是,其里面存储的是kv结构,也就是<key, value>的键值对,这种结构在数据检索时比序列式容器的效率更高。
而接下来的map、set、multimap、multiset是关联式容器中的树形结构,都是使用平衡搜索数(红黑树)作为其底层结构,容器中的元素是一个有序的序列。
键值对(pair)
首先先了解什么键值对。
键值对是用来表示具有一一对应的结构,<key, value>,包含着key和value两个成员变量,key代表键值,value表示与key对应的信息。比如:一个英汉互译的字典,那该字典中英文单词与其中文含义是一一对应的关系,也就可以通过该单词,在词典中就可以找到与其对应的中文含义。
而pair就是这样的结构
template<class T1, class T2>
struct pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
pair() : first(T1()), second(T2())
{}
pair(const T1&a, const T2&b):first(a), second(b)
{}
};
这就是pair的结构定义
Set
创建一个Set的对象
// 我们先创建一个set的对象
// 方法一:
set<int> s1;
// 方法二,创建时初始化
set<int> s2 = { 1,2,3 };
// 方法三,同属数组初始化
int a[] = { 1,2,1,5,8,5,3,2,6 };
// 升序
set<int> s3(a, a + sizeof(a) / sizeof(int));
// 降序
set<int,greater<int>> s4(a, a + sizeof(a) / sizeof(int));
而我们看到,set底层的平衡二叉树,我们有学过平衡二叉树将他中序遍历,他会呈现升序排列,但同时我们可以通过调整他的Compare,来将他变为降序,就比如上面的s4所展示的。
Set的基本功能
Set的基本功能和之前学的list,queue,stack等都大部分有相似的地方,这里就不多说了。我们讲下一些特别的功能。
Set的删除方式的不同
当我们要删除Set中的一个元素时,可以使用set的erase功能和通过find找到其中的元素,再erase。但这有什么区别呢?
int a[] = { 1,2,1,5,8,5,3,2,6 };
set<int,greater<int>> s(a, a + sizeof(a) / sizeof(int));
for (auto e : s)
{
cout << e << " ";
}
cout << endl;
set<int> ::iterator pos = s.find(1);
if (pos != s.end())
{
s.erase(pos);
}
如果我们所要找的元素是set中的元素,则删除时没有什么区别,但当我们所要删除的元素不在set中,则erase删除不会改变set的结构,而我们通过find去找这个元素,再erase时,find在set中查找没有找到所要的元素,则他会指向set.end(),这样再erase时就会报错。
lower_bound()与upper_bound()
std::set<int> myset;
std::set<int>::iterator itlow, itup;
for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90
itlow = myset.lower_bound(30); // ^
itup = myset.upper_bound(60); // ^
myset.erase(itlow, itup); // 10 20 70 80 90
std::cout << "myset contains:";
for (std::set<int>::iterator it = myset.begin(); it != myset.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
在这段代码可以很明显的感受到30~60区间中的值都被删除了,
但要注意:
lower_bound():指的是大于或等于这个数的最小值(即 >= val)
upper_bound():指的是大于这个数的最小值 (即 > val)
这是要注意的问题。
multiset
set的底层的平衡二叉树,而平衡二叉树对其中的元素是去重的,但如果要获得元素可以相等的二叉树,则可以用multiset,其基本功能和set相同。
Map
其基本的结构与set非常相似,但要注意的是
这里map中所对应的value_type是pair类型,也就是上面所提到的。也就意味值,他是结构是<key, value>类型的结构
创建一个map对象
既然map是<key, value>的类型的结构。我们可以这样定义map的对象
map<string, string> dict;
/*pair<string, string> kv1("sort", "排序");
dict.insert(kv1);*/
// 匿名对象初始化
dict.insert(pair<string, string>("sort", "排序"));
dict.insert(pair<string, string>("test", "测试"));
dict.insert(pair<string, string>("string", "字符串"));
dict.insert(make_pair("left", "左边"));
其中map因为是pair的结果,其在insert中包含pair结构对象,但我们也可以使用make_pair()这一函数来实现,这样在insert的时候就不同考虑他的key和value的类型。
map的功能
map的功能与大部分stl容器相近
operator[]
我们主要讲下他的“[ ]”的功能,他不像我们的数组中的[]一样,因为数组中的[]所对应的空间是线性的,而map是平衡二叉树的结构,所以他不能用线性的思维去定义。
string arr[] = { "苹果","西瓜","苹果","西瓜",
"苹果","苹果","西瓜","苹果","香蕉","苹果" ,"香蕉" };
// 统计次数
map<string, int> countMap;
for (auto& str : arr)
{
map<string, int>::iterator it = countMap.find(str);
if (it != countMap.end())
{
//(*it).second++;
it->second++;
}
else
{
countMap.insert(make_pair(str, 1));
}
}
比如我们要统计次数,如果按通常的办法来写则像上面写的一样
但是如果我们用[ ]则会更方便
string arr[] = { "苹果","西瓜","苹果","西瓜",
"苹果","苹果","西瓜","苹果","香蕉","苹果" ,"香蕉" };
map<string, int> countMap;
for (auto& str : arr)
{
countMap[str]++;
}
map<string, int>::iterator it = countMap.begin();
while (it != countMap.end())
{
cout << it->first << ":" << it->second << endl;
++it;
}
cout << endl;
其结果是一致的。
我们再看一段代码:
map<string, string> dict;
dict.insert(make_pair("sort", "排序"));
dict["insert"];
dict["insert"] = "插入";
dict["left"] = "左边";
在dict中,我们首先通过insert插入了(“sort”,“排序”)
但执行dict[“insert”]时,
我们注意到dict[“insert”]的second部分是“”
但继续执行代码:
单词“insert”对应的second变为了“插入”
继续执行:
单词"left“和“左边”一同插入进去了。
通过上面逐条指令监视执行可以看出,operator[]的功能包括插入和修改
在文档中有一句话是:
其相当于调用insert函数,但我们来分析insert函数
通过其返回值的定义,我们可以间接退出:
1、当key在map中是,返回pair<key_iterator, false>
2、当key不在map中时,返回pari<new_key_iterator, true>
也就是:
V& operator[]( const K& key )
{
pair<iterator, bool> ret = insert(make_pair(key,v()));
return ret.first->second;
}
multimap
其原理和multiset一样,可以存储相同数据的多个值。