C++ STL关联式容器
STL关联式容器
序列式容器的元素由键值对<key,value>,且元素默认是无序的。STL实现时采用红黑树来解决。
容器 | 特点 |
---|---|
map | 键值为一,容器会按照键值大小默认升序排列(有序的) |
set | 容器的键和值完全相同,且不能重复,容器会按照键值默认升序排列(有序的) |
multimap | 相较于map,键值可以重复 |
multiset | 相较于set,键值可以重复 |
STL无序关联式容器(C++11)
容器 | 特点 |
---|---|
unordered_map | 键值为一,容器会按照键值大小默认升序排列 |
unordered_set | 容器的键和值完全相同,且不能重复,容器会按照键值默认升序排列 |
unordered_multimap | 相较于map,键值可以重复 |
unordered_multiset | 相较于set,键值可以重复 |
键值对采用pair类模板存储<first,second>,定义在<utility>中
pair();//初始化空的pair对象
pair.first="first";
pair.second="second";
pair(const fisrt_type& first,const second_type& second);//通过两个值初始化
template<class U,class V> pair(const pair<U,V>& pr);//拷贝初始化
template<class U,class V> pair(const pair<U,V>&& pr);//移动构造函数
template<class U, class V> pair (U&& a, V&& b);//右值引用参数初始化
pair<string,int>{ "first",1 }
//等价于
make_pair("first",1)
有序关联式容器
1.STL map
template < class Key, // 指定键(key)的类型
class T, // 指定值(value)的类型
class Compare = less<Key>, // 指定排序规则
class Alloc = allocator<pair<const Key,T> > // 指定分配器对象的类型
> class map;
map<string,int> m;
map<string,int> m{{'first',1}};
//make_pair传入参数可构造出pair对象,在<utility>中
map<string,int> m{make_pair('first',1),make_pair('second',2)};
map<string,int> m1(++m.begin(),m.end());//{'second',2}
map<string,int,less<string> >myMap{ {"First",10},{"Second",21} ;//按照键升序
map<string,int,greater<string> >myMap{ {"First",10},{"Second",21} ;//按照键升序
成员方法
成员方法 | 功能 |
---|---|
begin() | 返回指向容器中第一个(注意,是已排好序的第一个)键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
end() | 返回指向容器最后一个元素(注意,是已排好序的最后一个)所在位置后一个位置的双向迭代器,通常和 begin() 结合使用。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
rbegin() | 返回指向最后一个(注意,是已排好序的最后一个)元素的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
rend() | 返回指向第一个(注意,是已排好序的第一个)元素所在位置前一个位置的反向双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的反向双向迭代器。 |
cbegin() | 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
cend() | 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
crbegin() | 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
crend() | 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改容器内存储的键值对。 |
find(key) | 在 map 容器中查找键为 key 的键值对,如果成功找到,则返回指向该键值对的双向迭代器;反之,则返回和 end() 方法一样的迭代器。另外,如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
lower_bound(key) | 返回一个指向当前 map 容器中第一个大于或等于 key 的键值对的双向迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
upper_bound(key) | 返回一个指向当前 map 容器中第一个大于 key 的键值对的迭代器。如果 map 容器用 const 限定,则该方法返回的是 const 类型的双向迭代器。 |
equal_range(key) | 该方法返回一个 pair 对象(包含 2 个双向迭代器),其中 pair.first 和 lower_bound() 方法的返回值等价,pair.second 和 upper_bound() 方法的返回值等价。也就是说,该方法将返回一个范围,该范围中包含的键为 key 的键值对(map 容器键值对唯一,因此该范围最多包含一个键值对)。<lower_bound(key),upper_bound(key)> |
empty() | 若容器为空,则返回 true;否则 false。 |
size() | 返回当前 map 容器中存有键值对的个数。 |
max_size() | 返回 map 容器所能容纳键值对的最大个数,不同的操作系统,其返回值亦不相同。 |
operator[] | map容器重载了 [] 运算符,只要知道 map 容器中某个键值对的键的值,就可以向获取数组中元素那样,通过键直接获取对应的值。 |
at(key) | 找到 map 容器中 key 键对应的值,如果找不到,该函数会引发 out_of_range 异常。 |
insert() | 向 map 容器中插入键值对。 |
erase() | 删除 map 容器指定位置、指定键(key)值或者指定区域内的键值对。后续章节还会对该方法做重点讲解。 |
swap() | 交换 2 个 map 容器中存储的键值对,这意味着,操作的 2 个键值对的类型必须相同。 |
clear() | 清空 map 容器中所有的键值对,即使 map 容器的 size() 为 0。 |
emplace() | 在当前 map 容器中的指定位置处构造新键值对。其效果和插入键值对一样,但效率更高。 |
emplace_hint() | 在本质上和 emplace() 在 map 容器中构造新键值对的方式是一样的,不同之处在于,使用者必须为该方法提供一个指示键值对生成位置的迭代器,并作为该方法的第一个参数。 |
count(key) | 在当前 map 容器中,查找键为 key 的键值对的个数并返回。注意,由于 map 容器中各键值对的键的值是唯一的,因此该函数的返回值最大为 1。 |
获取键值
map[key]=value;//如果key不存在,访问元素变为添加元素,key存在变为修改元素的value
int value=map[key];//如果key不存在,访问元素变为添加元素,key存在变为添加元素
map.at(key);//key不存在则抛出异常
map.find(key);//key存在返回该元素迭代器,否则返回map.end()
插入键值
map[key]=value;//key不存在
//val为新插入的键值对
//返回一个键值对,如果插入成功<新插入元素的迭代器,true>
//插入失败,则该键值对p已经存在<p的迭代器,false>
//std::pair<std::map<string, string>::iterator, bool> ret;
pair<iterator,bool> insert (const value_type& val);
//以右值引用的方式传递键值对
template <class P> pair<iterator,bool> insert (P&& val);
map.insert({ "first",1});
//在固定位置插入元素
//成功返回该元素迭代器,失败返回容器中该元素的迭代器
iterator insert (const_iterator position, const value_type& val);
//以右值引用的方式在固定位置传递键值对
template <class P> iterator insert (const_iterator position, P&& val);
void insert ({val1, val2, ...});//val为键值对
即便指定了新键值对的插入位置,map 容器仍会对存储的键值对进行排序。元素的位置取决于key
//返回值,插入成功,<新元素的迭代器,true>
//插入失败,<容器中该元素的迭代器,false>
template <class... Args> pair<iterator,bool> emplace (Args&&... args);
map.emplace("first",1)
//返回值,插入成功,<新元素的迭代器,true>
//插入失败,<容器中该元素的迭代器,false>
template <class... Args> iterator emplace_hint (const_iterator position, Args&&... args);
map.emplace_hint(mymap.begin(),"first",1);
2.multimap
template < class Key, // 指定键(key)的类型
class T, // 指定值(value)的类型
class Compare = less<Key>, // 指定排序规则
class Alloc = allocator<pair<const Key,T> > // 指定分配器对象的类型
> class multimap;
multimap<char, int> m{ {'a',10},{'b',20},{'b',15}, {'c',30} };
multimap<char, int> m{ {'a',10},{'b',20},{'b',15}, {'c',30} };
与map容器相比,multimap可以存在多个相同元素,因此不能使用at,[]来访问元素
3.set
set容器中的键和值是完全相同的
因此*iterator直接直接获取到迭代器的值
template < class T, // 键 key 和值 value 的类型
class Compare = less<T>, // 指定 set 容器内部的排序规则.less,greater
class Alloc = allocator<T> // 指定分配器对象的类型
> class set;
set<string> s;
set<string> s{"first","second"};
set<string> s1(s);//拷贝构造函数
set<string> s1(s.begin(),s.end());//拷贝构造函数
内部函数同map,set 容器类模板中未提供 at() 成员函数,也未对 [] 运算符进行重载,访问set容器元素只能通过迭代器进行
find(val);//找到该元素,返回迭代器,否则返回set.end()
插入元素
//插入元素成功<该元素迭代器,true>
//插入失败<容器中该元素迭代器,false>
pair<iterator,bool> insert (const value_type& val);
set<string> s;
string str="first";
s.insert(str);//普通传值
s.insert("first");//右值引用
//带位置
//插入成功,返回新元素的迭代器
//插入失败,返回set中该元素
iterator insert (const_iterator position, const value_type& val);
iterator insert (const_iterator position, value_type&& val);
//插入其他set的元素,返回值为空
s.insert(s1.begin(),s2.end());
//一次插入多个元素
void insert ( {E1, E2,...,En} );
//emplace执行效率高
//插入元素成功<该元素迭代器,true>
//插入失败<容器中该元素迭代器,false>
s.emplace("first");
//带位置
//插入成功,返回新元素的迭代器
//插入失败,返回set中该元素
s.emplace(s.begin(),"first");
删除元素
//删除 set 容器中值为 val 的元素
size_type erase (const value_type& val);
s.erase("first");
//删除 position 迭代器指向的元素
iterator erase (const_iterator position);
s.erase(s.bengin());
//删除 [first,last) 区间内的所有元素
iterator erase (const_iterator first, const_iterator last);
s.erase(s.begin(),--s.end());
4.multiset
template < class T, // 存储元素的类型
class Compare = less<T>, // 指定容器内部的排序规则
class Alloc = allocator<T> > // 指定分配器对象的类型
> class multiset;
用法同set一致,但multiset元素允许重复
//删除nultiset中所有该值的元素
s.erase("first");
//降序
multiset<string, greater<string> > s{"first","second"}
5.关联式容器自定义排序
排序规则 | 功能 |
---|---|
std::less |
底层采用 < 运算符实现升序排序,各关联式容器默认采用的排序规则。 |
std::greater |
底层采用 > 运算符实现降序排序,同样适用于各个关联式容器。 |
std::less_equal |
底层采用 <= 运算符实现升序排序,多用于 multimap 和 multiset 容器。 |
std::greater_equal |
底层采用 >= 运算符实现降序排序,多用于 multimap 和 multiset 容器。 |
//仿函数
struct cmp {
//重载 () 运算符
bool operator ()(const string &a, const string &b) {
//按照字符串的长度,做升序排序(即存储的字符串从短到长)
return (a.length() < b.length());
}
};
set<string, cmp>myset{"first","second","third"};
当关联式容器中存储的数据类型为自定义的结构体变量或者类对象时,不能使用上述方法排序,通过对现有排序规则中所用的关系运算符进行重载,可实现自定义排序规则的目的。
bool operator <(const myString &stra, const myString & strb) {
//以字符串的长度为标准比较大小
return stra.getStr().length() < strb.getStr().length();
}
//升序就用greater
bool operator >(const myString &stra, const myString & strb) {
//以字符串的长度为标准比较大小
return stra.getStr().length() > strb.getStr().length();
}
//创建空 set 容器,仍使用默认的 less<T> 排序规则
set<myString>myset;
set<myString,greater<myString> >myset;
6.修改map,set的key
set<Book>::iterator iter = mymap.begin();
(*iter).setname("aaa");
//无法修改
//*iter 会调用 operator*,其返回的是一个 const T& 类型元素
set<student>::iterator iter = mymap.begin();
const_cast<student&>(*iter).setname("aaa");//此方法修改
虽然使用 const_cast 能直接修改 set 或者 multiset 容器中的元素,但一定不要修改元素的键!如果要修改,只能采用“先删除,再添加”的方式。map 和 multimap 容器中元素的键是无法直接修改的,但借助 const_cast,我们可以直接修改 set 和 multiset 容器中元素的非键部分。
set,multiset的存储类型为T
map,multimap的存储类型为const T
无序关联式容器
无序关联式容器也采用pair存储元素
选择:
-
涉及大量遍历操作,选择关联式容器
-
设计通过键取值的操作,选择非关联式容器
1.STL unordered_map
template < class Key, //键值对中键的类型
class T, //键值对中值的类型
class Hash = hash<Key>, //容器内部存储键值对所用的哈希函数
class Pred = equal_to<Key>, //判断各个键值对键相同的规则
class Alloc = allocator< pair<const Key,T> > // 指定分配器对象的类型
> class unordered_map;
unordered_map<string,int> um;
unordered_map<string,int> um{{"first",1},{"second",2}};
unordered_map<string,int> um2(um);//拷贝构造函数
unordered_map<string,int> um2(++um.begin(),um.end());//拷贝构造函数
相较于map,unordered_map元素是无序的,因此当类型为自定义类型时,需要指定容器存储各键值的哈希函数和判断相等的函数
函数 | 功能 |
---|---|
bucket_count() | 返回当前容器底层存储键值对时,使用桶(一个线性链表代表一个桶)的数量。 |
max_bucket_count() | 返回当前系统中,unordered_map 容器底层最多可以使用多少桶。 |
bucket_size(n) | 返回第 n 个桶中存储键值对的数量。 |
bucket(key) | 返回以 key 为键的键值对所在桶的编号。 |
load_factor() | 返回 unordered_map 容器中当前的负载因子。负载因子,指的是的当前容器中存储键值对的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。 |
max_load_factor() | 返回或者设置当前 unordered_map 容器的负载因子。 |
rehash(n) | 将当前容器底层使用桶的数量设置为 n。 |
reserve() | 将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。 |
hash_function() | 返回当前容器使用的哈希函数对象。 |
所有的无序容器采用链地址法存储元素,使用无序容器时,程序申请一块内存空间,不存储元素,存储各链表的头指针,键值对存储在链表节点中。其中每个链表成为桶(bucket)
存储元素
- 调用哈希函数得到哈希值
- H%n得到桶的编号
- 建立新节点,将该节点链接到相应的桶上
负载因子:(总元素数/桶数)键值对的空满程度,负载因子越大,查找效率越低
当负载因子大于1时,容器会自动扩容进行rehash,会导致之前的迭代器失效。rehash会导致元素顺序发生变化,但仍然可以进行遍历。
访问元素:
um[key];//实现了对operator[]的重载
map.at(key);//提供了键值检查
//查找成功,返回该元素迭代器
//查找失败,返回um.end()
find(key);
插入元素
//以普通方式传递参数
//插入成功,返回<新元素迭代器,true>
//插入失败,返回<容器中该元素迭代器,false>
pair<iterator,bool> insert ( const value_type& val );
pair<string,int> p("first",1);
um.insert(p);
//以右值引用的方式传递参数
template <class P> pair<iterator,bool> insert ( P&& val );
um.insert(make_pair("first",1));
//在固定位置插入
//以普通方式传递 val 参数
iterator insert ( const_iterator hint, const value_type& val );
//以右值引用方法传递 val 参数
template <class P> iterator insert ( const_iterator hint, P&& val );
//插入位置取决于键值,而不是pos参数
//插入其他容器中的元素
um.insert(um1.begin(),um1.end());
//插入多个元素
um.insert({{"first",1},{"second",2}})
um.emplace("first",1);//直接在容器内生成该元素
um.emplace(um.begin()+5,"first",1);//直接在容器内指定位置生成该元素
删除元素
um.erase(um.begin());//通过迭代器删除元素,并返回该元素之后位置的迭代器
um.erase(key);//通过key删除元素,返回成功删除元素的数量
um.erase(um.begin(),um.end());//删除指定范围的元素,返回指定范围最后一个元素后一个位置的迭代器
2.STL unordered_multimap
容器模板同unorder_map一致,与其相比,该容器元素可以重复,用法同unordered_map一致。
unorder_multimap<string,int> ump{{"first",1},{"second",2}}
unorder_multimap<string,int> ump(ump1);//拷贝构造函数
unorder_multimap<string,int> ump(ump1.begin(),ump1.end());//拷贝构造函数
3.STL unordered_set
无序set容器,不会自动对容器内元素排序
相较于set,不再以pair存储元素,而直接存储值,容器内的元素唯一且不能修改。底层采用哈希表结构存储数据。
template < class Key, //容器中存储元素的类型
class Hash = hash<Key>, //确定元素存储位置所用的哈希函数
class Pred = equal_to<Key>, //判断各个元素是否相等所用的函数
class Alloc = allocator<Key> //指定分配器对象的类型
> class unordered_set;
unordered_set us{"first"};
unordered_set us(us1);//拷贝构造函数
unordered_set us(us1.begin(),us2.end());
函数 | 功能 |
---|---|
bucket_count() | 返回当前容器底层存储元素时,使用桶(一个线性链表代表一个桶)的数量。 |
max_bucket_count() | 返回当前系统中,unordered_map 容器底层最多可以使用多少桶。 |
bucket_size(n) | 返回第 n 个桶中存储元素的数量。 |
bucket(key) | 返回值为 key 的元素所在桶的编号。 |
load_factor() | 返回 unordered_map 容器中当前的负载因子。负载因子,指的是的当前容器中存储元素的数量(size())和使用桶数(bucket_count())的比值,即 load_factor() = size() / bucket_count()。 |
max_load_factor() | 返回或者设置当前 unordered_map 容器的负载因子。 |
rehash(n) | 将当前容器底层使用桶的数量设置为 n。 |
reserve() | 将存储桶的数量(也就是 bucket_count() 方法的返回值)设置为至少容纳count个元(不超过最大负载因子)所需的数量,并重新整理容器。 |
hash_function() | 返回当前容器使用的哈希函数对象。 |
该容器不能通过[]
和at
取值,且不能修改元素,因此只能获取到元素迭代器
4.STL unordered_multiset
底层直接以值存储,且用链地址法存储,值可以重复,不能被修改,相同元素会存储在同一个桶上。
5.自定义无序函数比较规则
自定义哈希函数:本质返回一个整数
class Book{
public:
string name;
int page;
Book(string name,int page):name(name),page(page){};
}
//重载(),参数必须为const,函数也必须为const
class hash_new {
public:
int operator()(const Book &b) const {
return b.page;
}
};
unordered_set<Book,hash_new> us;
自定义比较函数
//在 Book 类中重载 == 运算符
class Book{
public:
string name;
int page;
Book(string name,int page):name(name),page(page){};
bool operator==(const Book &b1, const Book &b2) {
return b1.name == b2.name;
}
}
//仿函数,以函数对象类的方式自定义比较规则
class cmp {
public:
bool operator()(const Book &b1, const Book &b2) const {
return b1.name == b2.name;
}
};
unordered_set<Book, hash_new, cmp> us;