c++ primer是我们常用的一款C++编程软件,在我们进行C++编程的时候肯定会经常用到关联容器的操作,这里我们牛人技术通过详细讲解和代码案例来给大家演示下如何高效的进行关联容器操作。
虽然这部分的内容较多,但是顺序容器那部分掌握了,这里会很快,因为编程这东西就是一旦理解了后续就容易多了。
map的节点是一对数据,set的节点是一个数据。
关联容器迭代器:map的value_type是pair<const key_type, mapped_type>,所以map迭代器只能改变关键字映射的值(mapped_type),不能修改关键字;set的value_type等于key_type,都是const关键字,不能修改。
map使用键值对的方式来储存数据,键不能有重复的,值可以重复,map使用键来存储数据,系统会根据键来自动将数据排序;set键不能有重复,使用键来存储数据,系统会根据该值来自动将数据排序。
C++STL中map,set的底层实现全是用的红黑树,而且关键字都不能重复,而multimap和multiset关键字可以重复。(红黑树:实际上是AVL的一种变形,但是其比AVL(平衡二叉搜索树)具有更高的插入效率,当然查找效率会平衡二叉树稍微低一点点,毕竟平衡二叉树太完美了。但是这种查找效率的损失是非常值得的。它的操作有着良好的最坏情况运行时间,并且在实践中是高效的: 它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。)
另外,比如:map采用红黑树实现,比hash_map(未进入stl)稳定,但是一般没有hash_map快。
数据结构的的稳定与非稳定的是根据排序移动后原来在前面的逻辑位置仍然在前面保持不变的,所以map和set是稳定排序,multimap和multiset是不稳定排序。
接下来就要介绍各种操作了:
关联容器迭代器
map<string, size_t> word_count; //单词计数程序
auto map_it = word_count.begin(); //map_it作为迭代器指向首元素
cout << map_it->first; //打印键
map_it->first = "new"; //错误:关键字是const
//关键字就是键,键的类型就是<>中的某个类型。
set的迭代器是const:
map和set是关联容器,类似于集合,里面的元素不会重复,而且呈现为有序性。
set<int> iset = {0, 1, 2, 3};
set<int>::iterator set_it = iset.begin();
if(set_it != iset.end())
{
*set_it = 24; //错误:键是const
}
遍历关联容器:
auto map_it = word_count.cbegin();
while(map_it != word_count.cend())
{
cout << map_it->first << " " << map_it->second << endl;
++map_it;
}
我们通常不对关联容器使用泛型算法,因为键是const这一特性就限制了很多泛型算法,所以要用算法也只能用那些只读的算法!添加元素
关联容器的inset成员向容器中添加一个范围内的元素或n个元素。由于map和set包含不重复的关键字,因此插入一个已存在的元素对容器没任何影响。而multimap和multiset允许插入已存在的元素。
vector<int> ivec = {0, 1, 0, 1};
set<int> set2;
set2.insert(ivec.cbegin(), ivec.cend()); //第一种添加元素方式set2 = {0, 1}
set2.insert( {1 ,2 ,3} ); //第二种添加元素方式set2={0, 1, 2, 3}
set2.emplace(2); //不能插入2,因为2已存在
补充:
set容器中emplace和insert都往 set 里增加一个元素。
向map添加元素:
对一个map进行insert操作时,必须记住元素类型是pair(pair中包含两个数据值,两个数据的类型可以不同)。
pair是一种模板类型,其中包含两个数据值,两个数据的类型可以不同。
通常,对于想要插入的数据,并没有一个现成的pair对象,也就是说只能在insert的参数列表中创建一个pair。
map<string, size_t> word_count;
//向word_count插入word的四种方法
word_count.insert( {word, 1} );//在参数列表中使用花括号初始化创建了一个pair。
word_count.insert( make_pair(word, 1) );//也可在参数列表中调用make_pair构造pair。
word_count.insert( pair<string, size_t>(word, 1) );//也可显示构造pair。
word_count.insert( map<string, size_t>::value_type(word, 1) );先构造一个pair类型,再构造该类型的一个新对象(对象类型为pair)。
检测insert的返回值:
只有当关键字不存在时才插入,关联容器的insert()和emplace()返回一个pair。pair的first成员是一个迭代器,指向具有给定关键字的元素;second成员是一个bool值,告诉我们插入成功(返回true)还是插入失败(返回false)。
向multiset或multimap添加元素:
一个作者可能有多个作品,所以,我们应该用multimap,关键字不必唯一,调用insert总会插入一个元素:
multimap<string, string> authors;
authors.insert( {"金庸", "飞狐外传"} );
authors.insert( {"金庸", "雪山飞狐"} );
删除元素
元素删除:可以高效的删除,(和插入一样)会自动调整使红黑树平衡。
关联容器erase:若传入迭代器,必须保证指向真实元素;若传入关键字,则返回被删去的所有该关键字的元素的数量。
set<int> s;
s.erase(2);//删除键为2的元素
map的下标操作
map下标操作(只能用于map(有序)和unorders_map(无序)):返回mapped_type(关键字映射的值),解引用返回value_type(即pair<const key_type, mapped_type>);若关键字还不存在,会创建一个对应关键字且值为0的新元素,已存在则返回最近一次赋的值。
只有map有下标,set类型没有是因为人家就一个键,没有值,而multimap没有是因为可能有多个值跟同一个键相关。
举例:
map<string, size_t> word_count;
word_count["Anna"] = 1;
这样等价于把{“Anna”, 1}插入了。 由于下标运算符可能插入新元素,所以我们只能对非const的map使用下标操作。
访问元素
C++提供了很多访问元素的方法:
set<int> iset = {0, 1, 2, 3, 4};
iset.find(1); //返回一个指向1的迭代器
iset.find(-1); //返回一个指向iset.end()的迭代器
iset.count(1); //(返回该元素的数量),此处返回1
iset.count(11); //返回0
iset.lower_bound(2); //返回一个迭代器指向第一个不小于2的元素,就是2
iset.upper_bound(2); //返回一个指向第一个大于2的元素迭代器,3
无序容器无序容器对键类型的要求:
我们知道它是使用==来比较元素的,那怎么比较元素呢?它并不是直接比较的,要使用一个hash类型的对象为键生成一个哈希值,再去判等。标准库 已经为内置类型(包括指针)都实现了hash模板,还有string和以后要介绍的智能指针也定义了hash,这就是说,我们可以用无序容器直接去装这些类型的元素。
但是,如果要装自定义类型的元素,我们得提供自己的hash模板(以后再介绍怎么做到这一点)或者用另一种方法,类似于有序容器重载 关键字类型的默认比较操作。
以前讲了那么多都是有序的关联容器,接下来我们来介绍下无序的关联容器。在关键字类型的元素没有明显的序关系的情况下,无序容器很好用。
例子:
我们来用无序容器unordered_map重写最初的单词计数程序:
unordered_map<string, size_t> word_count;
string word;
while(cin >> word)
{
++word_count[word];
}
对于每个单词,我们还是得到相同的计数结果,但是不太可能按字典序输出了。无序容器是哈希表实现的,无序容器查询的时间复杂度可达到O(常数),内存消耗在于哈希表;而有序容器是红黑树实现的,查询的时间复杂度为log(n),但内存占用通常会少点。
总结
总体来将C++在关联容器操作方面,稍微有点复杂。但是只要通过上面的教程读明白了,相信你也能很快上手。
网友评论 comments