map类型

        map 是键 — 值对的集合。map 类型通常可理解为关联数组(associative array):可使用键作为下标来获取一个值,正如内置数组类型一样。
        而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取。

1、map 对象的定义

        要使用 map 对象,则必须包含 map 头文件。
        在定义 map 对象时,必须分别指明键和值的类型(value type)

// 计数每个单词在输入中出现的次数
map<string, int> word_count; // 从 string 到 int 的空映射

        这个语句定义了一个名为 word_count 的 map 对象,由 string 类型的键索引,关联的值则 int 型。

map的构造函数
map<k, v> m; 创建一个名为 m 的空 map 对象,其键和值的类型分别为 k 和 v
map<k, v> m(m2);创建 m2 的副本 m,m 与 m2 必须有相同的键类型和值类型
map<k, v> m(b, e);创建 map 类型的对象 m,存储迭代器 b 和 e 标记的范围内所有元素的副本。
元素的类型必须能转换为 pair<const k, v>

2、键类型的约束 

        在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。
        默认情况下,标准库使用键类型定义的 < 操作符来实现键(key type)的比较。

        所用的比较函数必须在键类型上定义严格弱排序(strict weak ordering)。
        所谓的严格弱排序可理解为键类型数据上的“小于”关系,虽然实际上可以选择将比较函数设计得更复杂。
        但无论这样的比较函数如何定义,当用于一个键与自身的比较时,肯定会导致 false 结果。
        此外,在比较两个键时,不能出现相互“小于”的情况,而且,如果 k1“小于”k2,k2“小于”k3,则 k1 必然“小于”k3。
        对于两个键,如果它们相互之间都不存在“小于”关系,则容器将之视为相同的键。
        用做 map 对象的键时,可使用任意一个键值来访问相应的元素。

        在实际应用中,键类型必须定义 < 操作符,而且该操作符应能“正确地工作”,这一点很重要。

        例如,在书店问题中,可增加一个名为 ISBN 的类型,封装与国际标准图书编号(ISBN)相关的规则。
        在我们的实现中,国际标准图书编号是 string 类型,可做比较运算以确定编号之间的大小关系。
        因此,ISBN 类型可以支持 < 运算。假设我们已经定义了这样的类型,则可定义一个 map 容器对象,以便高效地查找书店中存放的某本书。 
            map<ISBN, Sales_item> bookstore; 
        
        该语句定义了一个名为 bookstore 的 map 对象,以 ISBN 类型的对象为索引,其所有元素都存储了一个关联的 Sales_item 类类型实例。 
        
注意:对于键类型,唯一的约束就是必须支持 < 操作符,至于是否支持其他的关系或相等运算,则不作要求。 

3、map 定义的类型 

        map 对象的元素是键-值对,也即每个元素包含两个部分:键以及由键关联的值。
        map 的 value_type 就反映了这个事实。
        该类型比前面介绍的容器所使用的元素类型要复杂得多:value_type 是存储元素的键以及值的 pair 类型,而且键为 const。
        例如,word_count 数组的 value_type 为 pair<const string, int> 类型。

map类定义的类型
map<K, V>::key_type在 map 容器中,用做索引的键的类型
map<K, V>::mapped_type在 map 容器中,键所关联的值的类型
map<K, V>::value_type一个 pair 类型,它的 first 元素具有 const map<K, V>::key_type 类型,而 second 元素则为 map<K, V>::mapped_type 类型

        在学习 map 的接口时,需谨记 value_type 是 pair 类型,它的值成员可以修改,但键成员不能修改。

4、map 迭代器进行解引用将产生 pair 类型的对象 

        对迭代器进行解引用时,将获得一个引用,指向容器中一个 value_type 类型的值。
        对于 map 容器,其 value_type 是 pair 类型: 

// 获取 word_count 中元素的迭代器 
map<string, int>::iterator map_it = word_count.begin(); 
// *map_it 是对一对 <const string, int> 对象的引用 
cout << map_it->first; // 输出此元素的键
cout << " " << map_it->second; // 输出此元素的值
map_it->first = "new key"; // error: 键是常量(const),一旦定义,就不能修改
++map_it->second; // ok: 我们可以通过迭代器修改值 


        对迭代器进行解引用将获得一个 pair 对象,它的 first 成员存放键,为 const,而 second 成员则存放值,为非 const。 

5、map 容器额外定义的类型别名(typedef)

        map 类额外定义了两种类型:key_type 和 mapped_type,以获得键或值的类型。
        对于 word_count,其 key_type 是 string 类型,而 mapped_type 则是 int 型。
        如同顺序容器一样,可使用作用域操作符(scope operator)来获取类型成员,如

map<string, int>::key_type。 //获取改map的键的类型

// 一个测试,了解即可
cout << typeid(std::map<string, int>::key_type).name() << endl;
/*
    key_type 表示 std::map 的键类型,即 string 的类型信息,所以输出结果为下面所述
	输出结果:class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >
	解释:
		输出的结果描述了 std::basic_string 模板的类型,
		std::basic_string 是 C++ STL 中从 std::string 模板继承而来的模板类,表示字符串类型,
		第一个参数 char 表示字符串中字符的类型,
		第二个参数 std::char_traits<char> 表示字符类型相关的特性,
		第三个参数 std::allocator<char> 表示字符串分配器的类型。
*/

cout << typeid(std::map<string, int>::mapped_type).name() << endl; // int

6、给 map 添加元素 

        定义了 map 容器后,下一步工作就是在容器中添加键-值元素对。
        该项工作可使用 insert 成员实现;或者,先用下标操作符获取元素,然后给获取的元素赋值。
        在这两种情况下,一个给定的键只能对应于一个元素这一事实影响了这些操作的行为

7、使用下标访问 map 对象 

        如下编写程序时: 

map <string, int> word_count; // empty map 
// 插入键为 Anna 的默认初始化元素;然后将1赋给它的值
word_count["Anna"] = 1;

        将发生以下事情:
            1. 在 word_count 中查找键为 Anna 的元素,没有找到。 
            2. 将一个新的键-值对插入到 word_count 中。
               它的键是 const string 类型的对象,保存 Anna。而它的值则采用值初始化,这就意味着在本例中值为 0。
            3. 将这个新的键-值对插入到 word_count 中。 
            4. 读取新插入的元素,并将它的值赋为 1。 

        使用下标访问 map 与使用下标访问数组或 vector 的行为截然不同:用下标访问不存在的元素将导致在 map 容器中添加一个新元素,它的键即为该下标值。 

        如同其他下标操作符一样,map 的下标也使用索引(其实就是键)来获取该键所关联的值。
        如果该键已在容器中,则 map 的下标运算与 vector 的下标运算行为相同:返回该键所关联的值。
        只有在所查找的键不存在时,map 容器才为该键创建一个新的元素,并将它插入到此 map 对象中。
        此时,所关联的值采用值初始化:类类型的元素用默认构造函数初始化,而内置类型的元素初始化为 0。

8、下标操作符返回值的使用 

        通常来说,下标操作符返回左值。它返回的左值是特定键所关联的值。
        可如下读或写元素: 

cout << word_count["Anna"]; // 获取由Anna索引的元素; 输出 1 
++word_count["Anna"]; // 获取元素并添加1
cout << word_count["Anna"]; // 获取元素并且输出; 输出 2 


        有别于 vector 或 string 类型,map 下标操作符返回的类型与对 map 迭代器进行解引用获得的类型不相同。 
        显然,map 迭代器返回 value_type 类型的值——包含 const key_type 和 mapped_type 类型成员的 pair 对象;
        下标操作符则返回一个 mapped_type 类型的值。

9、下标行为的编程意义 

        对于 map 容器,如果下标所表示的键在容器中不存在,则添加新元素,这
        一特性可使程序惊人地简练: 

 // 计数每个单词在输入中出现的次数
map<string, int> word_count; // 从 string 到 int 的空映射
string word; 
while (cin >> word) 
    ++word_count[word];

// 稍微完整点的程序
#include <map>
#include <string>
using namespace std;

int main(void)
{
    std::map<string, int> _Msi;
    string _Word;
    cout << "输入单词(Ctrl + Z结束输入):" << endl;
    while (cin >> _Word) {
	    ++_Msi[_Word];
    }
    std::map<string, int>::iterator iter = _Msi.begin();
    while (iter != _Msi.end()) {
	    cout << "单词:" << iter->first << "\t\t一共出现:" << iter->second << endl;
	    ++iter;
    }
    
    return 0;
}


        这段程序创建一个 map 对象,用来记录每个单词出现的次数。
        while 循环每次从标准输入读取一个单词。
        如果这是一个新的单词,则在 word_count 中添加以该单词为索引的新元素。
        如果读入的单词已在 map 对象中,则将它所对应的值加 1。 
        
        其中最有趣的是,在单词第一次出现时,会在 word_count 中创建并插入一个以该单词为索引的新元素,同时将它的值初始化为 0。
        然后其值立即加 1,所以每次在 map 中添加新元素时,所统计的出现次数正好从 1 开始。 
 

10、map::insert 的使用 

        map 容器的 insert 成员与顺序容器的类似,但有一点要注意:必须考虑键的作用。
        键影响了实参的类型:插入单个元素的 insert 版本使用键-值 pair 类型的参数。
        类似地,对于参数为一对迭代器的版本,迭代器必须指向键-值 pair 类型的元素。
        另一个差别则是:map 容器的接受单个值的 insert 版本的返回类型。

map 容器提供的 insert 操作 
m.insert(e)e 是一个用在 m 上的 value_type 类型的值。
如果键(e.first)不在 m 中,则插入一个值为 e.second 的新元素;如果该键在 m 中已存在,则保持 m 不变。
该函数返回一个 pair 类型对象,包含指向键为 e.first 的元素的 map 迭代器,以及一个 bool 类型的对象,表示是否插入了该元素 
m.insert(beg, end)beg 和 end 是标记元素范围的迭代器,其中的元素必须为 m.value_type 类型的键-值对。
对于该范围内的所有元素,如果它的键在 m 中不存在,则将该键及其关联的值插入到 m。
返回 void 类型 
m.insert(iter, e)e 是一个用在 m 上的 value_type 类型的值。
如果键(e.first)不在 m 中,则创建新元素,并以迭代器 iter 为起点搜索新元素存储的位置。
返回一个迭代器,指向 m 中具有给定键的元素

11、检测 insert 的返回值 

        map 对象中一个给定键只对应一个元素。
        如果试图插入的元素所对应的键已在容器中,则 insert 将不做任何操作。
        含有一个或一对迭代器形参的 insert 函数版本并不说明是否有或有多少个元素插入到容器中。
        
        但是,带有一个键-值 pair 形参的 insert 版本将返回一个值:包含一个迭代器和一个 bool 值的 pair 对象,其中迭代器指向 map 中具有相应键的元素,而 bool 值则表示是否插入了该元素。
        如果该键已在容器中,则其关联的值保持不变,返回的 bool 值为 true。
        在这两种情况下,迭代器都将指向具有给定键的元素。下面是使用 insert 重写的单词统计程序:

#include <iostream>
#include <map>
#include <string>
using namespace std;

void mapCalcWordInsert()
{
	// 计数每个单词在输入中出现的次数 
	std::map<string, int> _Word_count; // 从 string 到 int 的空映射
	string word;
	cout << "输入单词(Ctrl + Z结束输入):" << endl;
	while (cin >> word) {
		// 插入键等于单词和值为1的元素;
		// 如果 word 已经在 _Word_count 中,则 insert 不执行任何操作
		pair<std::map<string, int>::iterator, bool> ret = _Word_count.insert(make_pair(word, 1));
		if (!ret.second) // 单词已经在单词计数中
			++ret.first->second; // 递增计数 
	}
	std::map<string, int>::const_iterator it = _Word_count.begin();
	while (it != _Word_count.end()) {
		cout << "单词:" << it->first << "\t\t一共出现:" << it->second << endl;
		++it;
	}
    /*
		对于每个单词,都尝试 insert 它,并将它的值赋 1。
		if 语句检测 insert 函数返回值中的 bool 值。
        如果该值为 false,则表示没有做插入操作,按 word 索引的元素已在 word_count 中存在。
		此时,将该元素所关联的值加 1。
	*/
}

int main(void)
{
    mapCalcWordInsert();
    return 0;
}

ret 的定义和自增运算可能比较难解释: 

pair<map<string, int>::iterator, bool> ret = word_count.insert(make_pair(word, 1));

            首先,应该很容易看出我们定义的是一个 pair 对象,它的 second 成员为 bool 类型。
            而它的 first 成员则比较难理解,这是 map<string, int> 容器所定义的迭代器类型。 
            
            根据操作符的优先级次序,可如下从添加圆括号开始理解自增操作: 
                ++((ret.first)->second); // 等效表达式
            
            下面对这个表达式一步步地展开解释: 
            ① ret 存储 insert 函数返回的 pair 对象。该 pair 的 first 成员是一个 map 迭代器,指向插入的键。 
            ② ret.first 从 insert 返回的 pair 对象中获取 map 迭代器。 
            ③ ret.first->second 对该迭代器进行解引用,获得一个 value_type 类型的对象。
                这个对象同样是 pair 类型的,它的 second 成员即为我们所添加的元素的值部分。 
            ④ ++ret.first->second 实现该值的自增运算。 

            归结起来,这个自增语句获取指向按 word 索引的元素的迭代器,并将该元素的值加 1。 
 

12、查找并读取 map 中的元素 

        下标操作符给出了读取一个值的最简单方法: 

map<string,int> word_count; 
int occurs = word_count["foobar"];

        但是,使用下标存在一个很危险的副作用:如果该键不在 map 容器中,那么下标操作会插入一个具有该键的新元素。

        这样的行为是否正确取决于程序员的意愿。
        在这个例子中,如果“foobar”不存在,则在 map 中插入具有该键的新元素,其关联的值为 0。
        在这种情况下,occurs 获得 0 值。 

        我们的单词统计程序的确是要通过下标引用一个不存在的元素来实现新元素的插入,并将其关联的值初始化为 0。
        然而,大多数情况下,我们只想知道某元素是否存在,而当该元素不存在时,并不想做做插入运算。
        对于这种应用,则不能使用下标操作符来判断元素是否存在。

        map 容器提供了两个操作:count 和 find,用于检查某个键是否存在而不会插入该键。

不修改map对象的查询操作
m.count(k)返回 map 容器中给定的键 K 的出现次数,且返回值只能是0或者1,
因为 map 只允许一个键对应一个实例
m.find(k)如果 m 容器中存在按 k 索引的元素,则返回指向该元素的迭代器。
如果不存在,则返回超出末端迭代器。
例如:map<string,int>::iterator it = word_count.find(k);

      使用 count 检查 map 对象中某键是否存在 
              对于 map 对象,count 成员的返回值只能是 0 或 1。
              map 容器只允许一个键对应一个实例,所以 count 可有效地表明一个键是否存在。

              如果返回值非 0,则可以使用下标操作符来获取该键所关联的值,而不必担心这样做会在 map 中插入新元素: 

int occurs = 0; 
if (word_count.count("foobar")) 
occurs = word_count["foobar"]; 

        当然,在执行 count 后再使用下标操作符,实际上是对元素作了两次查找。
        如果希望当元素存在时就使用它,则应该用 find 操作。

    读取元素而不插入该元素 
        find 操作返回指向元素的迭代器,如果元素不存在,则返回 end 迭代器: 

int occurs = 0; 
map<string,int>::iterator it = word_count.find("foobar");  
if (it != word_count.end()) 
    occurs = it->second;

         如果希望当具有指定键的元素存在时,就获取该元素的引用,否则就不在容器中创建新元素,那么应该使用 find。 


13、从 map 对象中删除元素 

        从 map 容器中删除元素的 erase 操作有三种变化形式(下表)。
        与顺序容器一样,可向 erase 传递一个或一对迭代器,来删除单个元素或一段范围内的元素。
        其删除功能类似于顺序容器,但有一点不同:map 容器的 erase 操作返回 void,而顺序容器的 erase 操作则返回一个迭代器,指向被删除元素后面的元素。 

        除此之外,map 类型还提供了一种额外的 erase 操作,其参数是 key_type 类型的值,如果拥有该键的元素存在,则删除该元素。
        对于单词统计程序,可使用这个版本的 erase 函数来删除 word_count 中指定的单词,然后输出被删除的单词: 

// 擦除键返回删除的元素数 
if (word_count.erase(removal_word)) {
    cout << "ok: " << removal_word << " removed\n"; 
}
else {
    cout << "oops: " << removal_word << " not found!\n";
}

        erase 函数返回被删除元素的个数。
        对于 map 容器,该值必然是 0 或 1。
        如果返回 0,则表示欲删除的元素在 map 不存在。

从 map 对象中删除元素
m.erase(k)删除 m 中键为 k 的元素。
返回 size_type 类型的值,表示删除的元素个数
m.erase(p)从 m 中删除迭代器 p 所指向的元素。
p 必须指向 m 中确实存在的元素,而且不能等于 m.end()。
返回 void 类型
m.erase(b, e)从 m 中删除一段范围内的元素,该范围由迭代器对 b 和 e 标记。
b 和 e 必须标记 m 中的一段有效范围:即 b 和 e 都必须指向 m 中的元素或最后一个元素的下一个位置。
而且,b 和 e 要么相等(此时删除的范围为空),要么 b 所指向的元素必须出现在 e 所指向的元素之前。
返回 void 类型

                 
    

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值