目录
引言:
C++zhongdeSTL库有六大配件
容器是其中一大重要的组成部分。我们前面学了string,vector,list 以及deque,一会我们还要学剩下的四个,那容器有没有分类呢?
我们只看顺序性容器和关联性容器
两者区别:
- 顺序容器只有实值val。
- 关联容器的一个元素包含两个部分:键值对(key-value) 即<k值(键值)|实值>。
- 顺序容器不涉及排序,关联容器内部自动排序。
- 本质区别:顺序容器通过元素在容器中的位置顺序存储和访问元素,而关联容器则是通过键(key)存储和读取元素的。
标准容器类
特点
顺序性容器
vector
从后面快速的插入与删除,直接访问任何元素
deque
从前面或后面快速的插入与删除,直接访问任何元素
list
双链表,从任何地方快速插入与删除
关联容器
set
快速查找,不允许重复值
multiset
快速查找,允许重复值
map
一对多映射,基于关键字快速查找,不允许重复值
multimap
一对多映射,基于关键字快速查找,允许重复值
1.set
1.1键值对:
用来表示具有一一对应关系的结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
比如:英汉互译字典中必然有英文单词与其对应的中文含义,而且,英文单词与其中文含义一一对应,即通过该英文单词,在词典中就可以找到与其对应的中文含义。
1.2set介绍
1、set是按照一定次序存储元素的容器。
2、在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
3、在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行排序。
4、set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对子集进行直接迭代。
5、set在底层是用二叉搜索树(红黑树)实现的。
注意:
- 1、与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。
- 2、set中插入元素时,只需要插入value即可,不需要构造键值对。
- 3、set中的元素不可以重复(因此可以使用set进行去重)。
- 4、使用set的迭代器遍历set中的元素,可以得到有序序列
- 5、set中的元素默认按照小于来比较
- 6、set中查找某个元素,时间复杂度为:logN
- 7、set中的元素不允许修改,因为set在底层是用二叉搜索树来实现的,若是对二叉搜索树当中某个结点的值进行了修改,那么这棵树将不再是二叉搜索树。
- 8、set中的底层使用二叉搜索树(红黑树)来实现。
1.21set的构造
既然set是容器,那他也可以像前几个一样被构造。头文件#include<set>
- 1.空构造和拷贝构造
set<int> s1; set<int> s2(s1);
- 2.迭代器构造
string s3("I love C++"); set<char> s4(s.begin(), s.end());
- 3.大于比较
set的底层是搜素二叉树,默认是小于排序,也可是调成大于排序。
set<int, greater<int>> s5;
1.22函数接口
在打印数据是时我们要用到遍历打印,所以讲一下迭代器
迭代器接口
Iterators: 迭代器 begin 将迭代器返回到开头(公共成员函数)
end 返回迭代器到结束(公共成员函数) rbegin 返回反向迭代器以反向开始(公共成员函数) rend 返回反向迭代器到反向结束(公共成员函数) cbegin 将const_iterator返回到开头(公共成员函数) cend 返回const_iterator到结束公共成员函数) crbegin 返回const_reverse_iterator反转开始(公共成员函数) crend 返回const_reverse_iterator反转端(公共成员函数) 这里也就是正反向迭代器用的多其他1知道就行。
容量接口:
capacity 容量大小 empty 反回容器是否为空公共成员功能) size 返回容器大小(公共成员功能) max_size 返回最大大小(公共成员函数) 修改接口:
我直接把一些代码放一起了
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<set> using namespace std; void test1() { set<int> s1; s1.insert(1); //插入操作 s1.insert(3); s1.insert(5); s1.insert(7); s1.insert(4); set<int> s2(s1); set<int>::iterator it = s1.begin(); for (auto e : s1) { cout << e << " "; // 1 3 4 5 7,是升序排列,别搞错了 } cout << endl; set<int>::iterator pos1 = find(s1.begin(), s1.end(), 5); set<int>::iterator pos2 = s1.find(7); //set自带的查找函数 if (it != s1.end()) { s1.erase(pos1); //删除5 s1.erase(pos2); s1.insert(10); s1.insert(20); } while (it != s1.end()) { cout << *it << " "; //1 3 4 10 20 *it++; } cout << endl; //删除指定区间 set<int>::iterator ret = s2.find(5); s2.erase(s2.begin(), ret); for (auto e : s2) { cout << e << " "; //5 7 } cout << endl; //寻找是否有val,找到返回1 if (s2.count(5)) // s2中有5就返回1,进入循环 { cout << "s2中有5:" << endl; //s2中有5 } cout << s2.empty() << endl; // 判空 cout << s1.size() << endl; //s1的个数是5 cout << s2.size() << endl; //s2的个数是2 } int main() { test1(); }
我就简单的举几个例子来写一下代码,其他就不在写了,交给各位大神了。
2.multiset
2.1特性
- 1、multiset是按照特定顺序存储元素的容器,其中元素是可以重复的。
- 2、在multiset中,元素的value也会识别它(因为multiset中本身存储的就是<value, value>组成的键值对,因此value本身就是key,key就是value,类型为T). multiset元素的值不能在容器中进行修改(因为元素总是const的),但可以从容器中插入或删除。
- 3、在内部,multiset中的元素总是按照其内部比较规则(类型比较)所指示的特定严格弱排序准则进行排序。
- 4、multiset容器通过key访问单个元素的速度通常比unordered_multiset容器慢,但当使用迭代器遍历时会得到一个有序序列。
- 5、multiset底层结构为二叉搜索树(红黑树)。
注意:
1、multiset中在底层中存储的是<value, value>的键值对
2、mtltiset的插入接口中只需要插入即可
3、与set的区别是,multiset中的元素可以重复,set是中value是唯一的。
4、使用迭代器对multiset中的元素进行遍历,可以得到有序的序列
5、multiset中的元素不能修改
6、在multiset中找某个元素,时间复杂度为O(logN)
7、multiset的作用:可以对元素进行排序也就是说当我们用multiset进行升序排列时,插入的数据可以重复,但是有些接口就会和set有些不一样。
2.2multiset的修改
multiset的count和erase跟set相比有很大去别:
- set.count(val)是如果找到val就返回1;因为multiset的数可以重复,multiset.count(val)找到val后返回multiset中有几个val。
- set.erase(val)是找到要删除的val后,返回1;multiset.erase(val)找到要删除的val后返回val有几个,并且全删val。
void test2() { multiset<int> multiset; multiset.insert(4); multiset.insert(5); multiset.insert(2); multiset.insert(1); multiset.insert(1); multiset.insert(3); multiset.insert(3); multiset.insert(3); for (auto e : multiset) { cout << e << " "; } cout << endl; set<int> set(multiset.begin(), multiset.end()); cout << multiset.count(1) << endl;//2 返回1的个数 cout << set.count(1) << endl;//1 返回一个bool值,存在返回1 }
void test2() { multiset<int> multiset; multiset.insert(4); multiset.insert(5); multiset.insert(2); multiset.insert(1); multiset.insert(1); multiset.insert(3); multiset.insert(3); multiset.insert(3); for (auto e : multiset) { cout << e << " "; } cout << endl; set<int> set(multiset.begin(), multiset.end()); cout << multiset.erase(1) << endl;//2 返回删除的1的个数 for (auto e : multiset) { cout << e << " "; //2 3 3 3 4 5 } cout << endl; cout << set.erase(4) << endl;//1 返回一个bool值,存在删除的值返回1 for (auto e : multiset) { cout << e << " "; //2 3 3 3 4 5 } cout << endl; auto pos1 = multiset.find(3); //返回中序的第一个3的迭代器 while (pos1 != multiset.end()) { cout << *pos1 << " ";//3 3 3 4 5 pos1++; } }
3.map
3.1介绍
- 1、map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
- 2、在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型value_type绑定在一起,为其取别名称为pair:typedef pair<const key, T> value_type;
- 3、在内部,map中的元素总是按照键值key进行比较排序的。
- 4、map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
- 5、map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
- 6、map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。
- 7头文件#include<map>
3.2插入
- 1.创建map对象
#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<map> using namespace std; int main() { map<string, int> m;//创建一个map对象m //向m中插入pair map<string, int> m1(m.begin(), m.end);//用m的区间构造m1 map<string, int> m2(m1);//用m1拷贝构造m2 return 0; }
- 2.插入insert
map中插入的是pair是一个结构体,它的内部实现
- 普通插入:
void test1() { map<string, string> dict; //创造一个词典dict pair<string, string> kv("English", "英语"); dict.insert(kv); }
- 匿名对象插入:
void test1() { map<string, string> dict; //创造一个词典dict dict.insert(pair<string, string>("English", "英语")); dict.insert(pair<string, string>("Math", "数学")); dict.insert(pair<string, string>("Chinese", "语文")); }
make_pair模板
void test1() { map<string, string> dict; //创造一个词典dict dict.insert(make_pair("vector", "顺序表")); dict.insert(make_pair("list", "链表")); }
3.3map的遍历
map的迭代器和上面的set的一毛一样,不再写了,直接上手。
void test2() { map<string, string> dict; dict.insert(make_pair("string", "字符串")); dict.insert(make_pair("vector", "顺序表")); dict.insert(make_pair("list", "链表")); dict.insert(make_pair("stack", "栈")); dict.insert(make_pair("queue", "堆")); //遍历 map<string, string>::iterator it = dict.begin(); //auto mit = dict.begin(); 跟上面的效果一样 while (it != dict.end()) { cout << it->first << ":" << it->second << endl; it++; } cout << endl; //范围for for (const auto& e : dict) cout << e.first << ":" << e.second << endl; }
cout << it->first << ":" << it->second << endl;中的first和second是结构体pair的成员,本应该写成
cout <<(*it).first << ":" << (*it).second << endl;
但是pair不是结构体吗,支持->,但是应该出现->->编译器为了好看就省去第二个了。
map的key不允许被修改。
3.4operate[]
砍看一下这个例子:
void test3() { string str[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" }; map<string, int> dict; for (auto& e : str) { map<string, int>::iterator it = dict.find(e); if (it != dict.end()) { it->second++; } else { dict.insert(make_pair(e, 1)); //当水果第一次出现就赋值为1 } } for (const auto& kv : dict) cout << kv.first << ":" << kv.second << endl; }
为啥it->second而不是first?
fist是第一个模板参数T1,second是第二个模板参数T2。也可以认为T1是key类型在题中是<string>,T2是value类型在题中是<int>。
map可以用[]进行访问(不是下标访问,一定要注意),我们修改一下源代码。
void test3() { string str[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" }; map<string, int> dict; for (auto& e : str) { dict[e]++; } for (const auto& kv : dict) cout << kv.first << ":" << kv.second << endl; }
- 运算符重载
mapped_type& operator[] (const key_type& k);
具体实现代码如下所示
mapped_type& operator[] (const key_type& k) { return (*((this->insert(make_pair(k, mapped_type()))).first)).second; }
这个如果不好理解的话,看一下下面这个
mapped_type& operator[] (const key_type& k) { //1、调用insert返回迭代器区间 pair<iterator, bool> ret = insert(make_pair(k, mapped_type())); //2、利用ret调用value值 return ret.first->second;//return (*(ret.first)).second; }
- 首先调用insert函数插入键值对返回迭代器ret
- 通过返回的迭代器ret调用元素值value。
在这里我们又分两种情况去处理:
- 如果k在map对象中,则插入失败,返回的bool类型为false,返回的迭代器为k所在结点的迭代器,而迭代器解引用*(ret.first)获得的就是pair,最后再通过pair访问到value值,整体可优化成ret.first->second,这里返回引用的好处为查找k对应v,修改k对应v。
- 如果k不在map对象中,则插入成功,返回的bool类型为true,返回的迭代器为新插入的k所在结点的迭代器位置,接着调用ret.first获得pair的迭代器,再通过->second获得value,这里返回引用的好处为插入和修改。
3.5查找(find)和删除(erase)
- 查找find:
iterator find (const key_type& k); const_iterator find (const key_type& k) const;
返回值类型:
根据k值在map中寻找,找到后,返回对应k值位置的迭代器,若未找到,则返回map::end的迭代器。
void test4() { map<string, string> dict; dict.insert(make_pair("left", "左")); dict.insert(make_pair("right", "右")); dict.insert(make_pair("front", "前")); dict.insert(make_pair("back", "后")); string str; cin >> str;//输入left map<string, string>::iterator pos = dict.find(str); if (pos != dict.end()) { cout << pos->first << " : " << pos->second << endl;//left : 左 } else { cout << "没找到" << endl; } }
- 删除erase
size_type erase (const key_type& k);//通过k删除
void test6() { map<int, string> m; m.insert(make_pair(1, "one")); m.insert(make_pair(2, "two")); m.insert(make_pair(3, "three")); m.insert(make_pair(4, "four")); m.insert(make_pair(5, "five")); m.insert(make_pair(6, "six")); m.insert(make_pair(9, "nine")); for (const auto& kv : m) { cout << kv.first << ":" << kv.second << endl; } //指定key值删除 cout << m.erase(4) << endl; //找到4返回1 map<int, string>::iterator pos = m.find(5); //删除迭代器区间 if (pos != m.end()) { m.erase(pos, m.end());//删除5到9的数字 } for (const auto& kv : m) { cout << kv.first << ":" << kv.second << endl; // 1 2 3 } } int main() { test6(); }
4.multimap
multimap基本和map一毛一样,除了multimap中的key是可以重复的。
4.1介绍
- Multimap是关联式容器,它按照特定的顺序,存储由key和value映射成的键值对<key,value>,其中多个键值对之间的key是可以重复的。
- 在multimap中,通常按照key排序和惟一地标识元素,而映射的value存储与key关联的内容。key和value的类型可能不同,通过multimap内部的成员类型value_type组合在一起,value_type是组合key和value的键值对:typedef pair<const Key, T> value_type;
- 在内部,multimap中的元素总是通过其内部比较对象,按照指定的特定严格弱排序标准对key进行排序的。
- multimap通过key访问单个元素的速度通常比unordered_multimap容器慢,但是使用迭代器直接遍历multimap中的元素可以得到关于key有序的序列。
- multimap在底层用二叉搜索树(红黑树)来实现。
multimap和map的区别:
- map中的valuevalue是唯一的,不允许重复。multimap可以。
- multimap中没有[]。因为它的key和value是一对多的关系,比如上面的苹果,那key是苹果,但是苹果出现了好几次,每一个都有一个value,那他到底返回哪一个呢?为啥map能返回呢,因为在map中每一个key(苹果)都对应一个自己的value,所以每次返回一个value.但是multimap是一个key有好几个value.
void test7() { multimap<string, string> mlp1; mlp1.insert(make_pair("one", "一")); mlp1.insert(make_pair("two", "二")); mlp1.insert(make_pair("three", "三")); mlp1.insert(make_pair("four", "四")); mlp1.insert(make_pair("spring", "五")); for (auto e : mlp1) { cout << e.first << ":" << e.second << endl; } } int main() { test7(); }