map 是键-值对的集合。map 类型通常可理解为关联数组(associative array):

可使用键作为下标来获取一个值,正如内置数组类型一样。
而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取。

[1. map 对象的定义]

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

// count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
// 表 10.3. 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>

键类型的约束

在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。
默认情况下,标准库使用键类型定义的 < 操作符来实现键(key type)的比较。
后续篇章将会介绍如何重写默认的操作符,并提供自定义的操作符函数。

所用的比较函数必须在键类型上定义严格弱排序strict weak ordering)。
所谓的严格弱排序可理解为键类型数据上的“小于”关系,虽然实际上可以选择将比较函数设计得更复杂。
但无论这样的比较函数如何定义,当用于一个键与自身的比较时,肯定会导致 false 结果。

此外,在比较两个键时,不能出现相互“小于”的情况,
而且,如果 k1“小于”k2,k2“小于”k3,则 k1 必然“小于”k3。
对于两个键,如果它们相互之间都不存在“小于”关系,则容器将之视为相同的键。
用做 map 对象的键时,可使用任意一个键值来访问相应的元素。

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

对于键类型,唯一的约束就是必须支持 < 操作符,至于是否支持其他的关系或相等运算,则不作要求。

[2. map 定义的类型]

map 对象的元素是“键值对”,也即每个元素包含两个部分:键以及由键关联的值。

map 的 value_type 是存储元素的键以及值的 pair 类型,而且键为 const。
例如,word_count 数组的 value_type 为 pair<const string, int> 类型。

//表 10.4. 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 类型

需谨记: value_type 是 pair 类型,它的值成员可以修改,但键成员不能修改。

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

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

// get an iterator to an element in word_count
map<string, int>::iterator map_it = word_count.begin(); // *map_it is a reference to a pair<const string, int> object
cout << map_it->first; // prints the key for this element
cout << " " << map_it->second; // prints the value of the element
map_it->first = "new key"; // error: key is const
++map_it->second; // ok: we can change value through an iterator

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

map 容器额外定义的类型别名(typedef)
map 类额外定义了两种类型:key_type 和 mapped_type,以获得键或值的类型。
如同顺序容器一样,可使用作用域操作符来获取类型成员,如 map<string, int>::key_type。

[3. 给 map 添加元素]

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

[4. 使用下标访问 map 对象]

如下编写程序时:

map <string, int> word_count; // empty map
// insert default initialzed element with key Anna; then assign 1 to its value
word_count["Anna"] = ;

将发生以下事情:

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。

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

cout << word_count["Anna"]; // fetch element indexed by Anna; prints 1
++word_count["Anna"]; // fetch the element and add one to it
cout << word_count["Anna"]; // fetch the element and print it; prints 2

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

下标行为的编程意义
对于 map 容器,如果下标所表示的键在容器中不存在,则添加新元素,这一特性可简化程序:

// count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
string word;
while (cin >> word)
  ++word_count[word];

上述代码中最有趣的是,在输入的单词是第一次出现时,
会在 word_count 中创建并插入一个以该单词为索引的新元素,同时将它的值初始化为 0。
然后其值立即加 1,所以每次在 map 中添加新元素时,所统计的出现次数正好从 1 开始。

[5. map::insert 的使用]

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

// 表 10.5. 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 中具有给定键的元素

5.1 以 insert 代替下标运算
使用下标给 map 容器添加新元素时,元素的值部分将采用值初始化。
通常,我们会立即为其赋值,其实就是对同一个对象进行初始化并赋值。

然而,添加元素还有另一个方法 —— 直接使用 insert 成员,其语法更紧凑。

// if Anna not already in word_count, inserts new element with value 1
word_count.insert(map<string, int>::value_type("Anna", ));

这个 insert 函数版本的实参是一个新创建的 pair 对象,将直接插入到 map 容器中。

谨记 value_type 是 pair<const K, V> 类型的同义词,K 为键类型,而 V 是键所关联的值的类型。

insert 的实参创建了一个适当的 pair 类型新对象,该对象将插入到 map 容器。
在添加新 map 元素时,使用 insert 成员可避免使用下标操作符所带来的副作用:不必要的初始化。
传递给 insert 的实参相当笨拙。可用 2 种方法简化:

// 1. 使用 make_pair
word_count.insert(make_pair("Anna", )); // 2. 使用 typedef
typedef map<string,int>::value_type valType;
word_count.insert(valType("Anna", ));

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

 // count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
string word;
while (cin >> word) {
  // inserts element with key equal to word and value 1;
  // if word already in word_count, insert does nothing
  pair<map<string, int>::iterator, bool> ret =
  word_count.insert(make_pair(word, ));
  if (!ret.second) // word already in word_count
    ++ret.first->second; // increment counter
}

对于每个单词,都尝试 insert 它,并将它的值赋 1。
if 语句检测 insert 函数返回值中的 bool 值。
如果该值为 false,则表示没有做插入操作,按 word 索引的元素已在 word_count 中存在。
此时,将该元素所关联的值加 1。

对第 10 行代码添加一些辅助用的括号:

++((ret.first)->second); // equivalent expression

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

[6. 查找并读取 map 中的元素]

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

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

在这个例子中,如果“foobar”不存在,则在 map 中插入具有该键的新元素,其关联的值为 0。
在这种情况下,occurs 获得 0 值。

然而,大多数情况下,我们只想知道某元素是否存在,而当该元素不存在时,并不想做做插入运算。
对于这种应用,则不能使用下标操作符来判断元素是否存在。

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

// 表 10.6. 不修改 map 对象的查询操作
m.count(k) 返回 m 中 k 的出现次数 m.find(k) 如果 m 容器中存在按 k 索引的元素,则返回指向该元素的迭代器。
        如果不存在,则返回超出末端迭代器(第 3.4 节)

使用 count 检查 map 对象中某键是否存在

对于 map 对象,count 成员的返回值只能是 0 或 1。
map 容器只允许一个键对应一个实例,所以 count 可有效地表明一个键是否存在。
而对于 multimaps 容器,count 的返回值将有更多的用途,相关内容将会后续篇章中介绍。
如果返回值非 0,则可以使用下标操作符来获取该键所关联的值,而不必担心这样做会在 map 中插入新元素:

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

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

读取元素而不插入该元素

find 操作返回指向元素的迭代器,如果元素不存在,则返回 end 迭代器:

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

[7. 从 map 对象中删除元素]

从 map 容器中删除元素的 erase 操作有三种变化形式(表 10.7)。

// 表 10.7. 从 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 类型

与顺序容器一样,可向 erase 传递一个或一对迭代器,来删除单个元素或一段范围内的元素。
map 容器的 erase 操作返回 void,而顺序容器的 erase 操作则返回一个迭代器,指向被删除元素后面的元素。
除此之外,map 类型还提供了一种额外的 erase 操作,
其参数是 key_type 类型的值,如果拥有该键的元素存在,则删除该元素。
对于单词统计程序,可使用这个版本的 erase 函数来删除 word_count 中指定的单词,然后输出被删除的单词:

// erase of a key returns number of elements removed
if (word_count.erase(removal_word))
  cout << "ok: " << removal_word << " removed\n";
else
  cout << "oops: " << removal_word << " not found!\n";

erase 函数返回被删除元素的个数。对于 map 容器,该值必然是 0 或 1。

[8. map 对象的迭代遍历]

与其他容器一样,map 同样提供 begin 和 end 运算,以生成用于遍历整个容器的迭代器。
例如,可如下将 map 容器 word_count 的内容输出:

// get iterator positioned on the first element
map<string, int>::const_iterator map_it = word_count.begin();
// for each element in the map
while (map_it != word_count.end()) {
  // print the element key, value pairs
  cout << map_it->first << " occurs "
     << map_it->second << " times" << endl;
  ++map_it; // increment iterator to denote the next element
}

这段程序的循环体要比前面类似的程序更加复杂,原因在于对于 map 的每个元素都必须分别输出它的键和值。
这个单词统计程序依据字典顺序输出单词。
在使用迭代器遍历 map 容器时,迭代器指向的元素按键的升序排列。

[9. “单词转换” map 对象]

下面的程序说明如何创建、查找和迭代遍历一个 map 对象。
这个程序求解的问题是:给出一个 string 对象,把它转换为另一个 string 对象。
本程序的输入是两个文件。
第一个文件包括了若干单词对,每对的第一个单词将出现在输入的字符串中,
而第二个单词则是用于输出。
本质上,这个文件提供的是单词转换的集合——
在遇到第一个单词时,应该将之替换为第二个单词。第二个文件则提供了需要转换的文本。

 如果单词转换文件的内容是:
'em them
cuz because
gratz grateful
i I
nah no
pos supposed
sez said
tanx thanks
wuz was 而要转换的文本是:
nah i sez tanx cuz i wuz pos to
not cuz i wuz gratz 则程序将产生如下输出结果:
no I said thanks because I was supposed to
not because I was grateful

单词转换程序

下面给出的解决方案是将单词转换文件的内容存储在一个 map 容器中,
将被替换的单词作为键,而用作替换的单词则作为其相应的值。
接着读取输入,查找输入的每个单词是否对应有转换。
若有,则实现转换,然后输出其转换后的单词,否则,直接输出原词。

该程序的 main 函数需要两个实参:

单词转换文件的名字以及需要转换的文件名。程序执行时,首先检查实参的个数。
第一个实参 argv[0] 是命令名,而执行该程序所需要的两个文件名参数则分别存储在 argv[1] 及 argv[2] 中。
如果 argv[1] 的值合法,则调用 open_file打开单词转换文件。
假设 open 操作成功,则读入“单词转换对”。
以“转换对”中的第一个单词为键,第二个为值,调用 insert 函数在容器中插入新元素。
while 循环结束后,trans_map 容器对象包含了转换输入文本所需的数据。
而如果该实参有问题,则抛出异常并结束程序的运行。
接下来,调用 open_file 打开要转换的文件。
第二个 while 循环使用 getline 函数逐行读入文件。
因为程序每次读入一行,从而可在输出文件的相同位置进行换行。
然后在内嵌的 while 循环中使用 istringstream 将每一行中的单词提取出来。

内层的 while 循环检查每个单词,判断它是否在转换的 map 中出现。
如果在,则从该 map 对象中取出对应的值替代此单词。
最后,无论是否做了转换,都输出该单词。
同时,程序使用 bool 值 firstword 判断是否需要输出空格。
如果当前处理的是这一行的第一个单词,则无须输出空格。

 /*
* A program to transform words.
* Takes two arguments: The first is name of the word transformation file
* The second is name of the input to transform
*/
int main(int argc, char **argv)
{
  // map to hold the word transformation pairs:
  // key is the word to look for in the input; value is word to use in the output
  map<string, string> trans_map;
  string key, value;
  if (argc != )
    throw runtime_error("wrong number of arguments");
  // open transformation file and check that open succeeded
  ifstream map_file;
  if (!open_file(map_file, argv[]))
    throw runtime_error("no transformation file");
  // read the transformation map and build the map
  while (map_file >> key >> value)
    trans_map.insert(make_pair(key, value));
  // ok, now we're ready to do the transformations
  // open the input file and check that the open succeeded
  ifstream input;
  if (!open_file(input, argv[]))
    throw runtime_error("no input file");
  string line; // hold each line from the input
  // read the text to transform it a line at a time
  while (getline(input, line)) {
    istringstream stream(line); // read the line a word at a time
    string word;
    bool firstword = true; // controls whether a space is printed
    while (stream >> word) {
      // ok: the actual mapwork, this part is the heart of the program
      map<string, string>::const_iterator map_it =
      trans_map.find(word);
      // if this word is in the transformation map
      if (map_it != trans_map.end())
      // replace it by the transformation value in the map
        word = map_it->second;
      if (firstword)
        firstword = false;
      else
        cout << " "; // print space between words
      cout << word;
    }
    cout << endl; // done with this line of input
  }
  return ;
}

map 类型的更多相关文章

  1. Flex ActionScript版本的Map类型

    ActionScript中没有Map类型,因为Object就相当于Map了.Object的属性相当于key,值相当于value. 也就是说,没有必要有Map类型.但是,这样做,也会带来一些问题,造成不 ...

  2. zk框架中利用map类型传值来创建window,并且传值

    @Command @NotifyChange("accList") public void clear(@BindingParam("id") String a ...

  3. C++map类型

    map是键-值对的集合,可以理解为关联数组,可以使用键作为下标来获取一个值 本文地址:http://www.cnblogs.com/archimedes/p/cpp-map.html,转载请注明源地址 ...

  4. Hibernate执行原生SQL返回List<Map>类型结果集

    我是学java出身的,web是我主要一块: 在做项目的时候最让人别扭的就是hibernate查询大都是查询出List<T>(T指代对应实体类)类型 如果这时候我用的联合查询,那么返回都就是 ...

  5. javabean实体类对象转为Map类型对象的方法(转发)

    //将javabean实体类转为map类型,然后返回一个map类型的值 public static Map<String, Object> beanToMap(Object obj) { ...

  6. Play Framework常用标签list,set,如何遍历list、map类型数据

    最近一段时间的项目都是在Play这个框架上进行开发的,挺强大的,但不足之处也挺多的.今天分享下play中强大的标签,遍历list,map类型的数据的用法. 遍历单纯的list数据,例如:List< ...

  7. struts2学习笔记(2)---Action中訪问ServletAPI获取Map类型的Servlet元素

    源码: strust.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts ...

  8. C++map类型 之 简单介绍

    一:map的前世今生 (1)从关联容器与顺序容器说起.关联容器通过键(key)存储和读取元素.而顺序容器则通过元素在容器中的位置顺序存储和訪问元素(vector,queue,stack,list等). ...

  9. 使用MyBatis时接收值和返回值选择Map类型或者实体类型

    MyBatis作为现近JavaEE企业级项目开发中常用的持久层框架之一,以其简洁高效的ORM映射和高度的SQL的自由性被广大开发人员认可.Mybatis在接收系统传来的参数和返回的参数时主要可以有Ma ...

随机推荐

  1. 《MonkeyRunner原理剖析》第九章-MonkeyImage实现原理 - 第一节 - 关键类作用及关系

    MonkeyRunner框架暴露了几个类的大量的API出去给用户编写脚本时候使用,其中最主要的三个就是: MonkeyDevice目标设备操作类,HierarchyViewer窗口界面对象操作类以及M ...

  2. PL/SQL个人学习笔记(二)

    IF条件 declare cursor s is            select version from city_server t;   s_ city_server.version%type ...

  3. Bootstrap的栅格系统

    Bootstrap的栅格系统 上一节:ASP.NET MVC5 + EF6 入门教程 (6) View中的Razor使用 源码下载:点我下载 要做一个完整的系统,除了需要MVC这样的B/S框架及EF这 ...

  4. VMWare Workstation:局域网PC连接虚拟机里的远程桌面或端口

    很简单.做一个理解: 1.NAT 2.VM的网卡,相当于路由器 环境: 物理路由器:192.168.0.1 PC1(win):192.168.0.2 PC2(win):192.168.0.3 PC2里 ...

  5. 记录一下Fedora21下安装Foundation5遇到的问题[尚有遗留问题]

    写在前面:之前安装过了gem,所以下面的步骤没有这一过程,再有就是忘记哪一步需要ruby中的一个.h文件.可以使用如下命令解决 sudo yum install ruby-devel ------ S ...

  6. PHP 3:从Login界面谈PHP标记

    原文:PHP 3:从Login界面谈PHP标记 前一篇文章简要介绍了此实例.OK,我们就从第一个页面Login页面入手吧.还是看看界面怎么样,是不是很想指导它到底是如何实现的呢?好的,看看其代码吧: ...

  7. 项目笔记---CSharp图片处理

    原文:项目笔记---CSharp图片处理 项目笔记---CSharp图片处理 最近由于项目上需要对图片进行二值化处理,就学习了相关的图片处理上的知识,从开始的二值化的意义到动态阀值检测二值化等等,并用 ...

  8. Lyx输入中文与代码高亮

    如果您看了我的这个随笔:<OpenSUSE 13.2安装Texlive2014+Texmaker+Lyx> (一)LyX中文 打开Lyx直接新建开始使用,那么输入的中文会是编译失败的,疑? ...

  9. 如何把va_list可变参数传送到下一级函数中(如传送到printf)

    最近我在一个LCD上想实现打印格式化字符串的功能,实现这样的功能可有两种方式: 一 最直接的就是自己去解析类似于printf功能的一个函数: 二 比较简单的方法是使用已有的sprintf功能,把格式化 ...

  10. 分析Java因为语言慢

    Java在早期的(例JDK1.2一旦)这是很慢.也许是因为有很多的优化,以提高装置的版本号,Java这是越来越快,所以这是现在非常的Java和C/C++什么什么慢速争议. 我想我自己的理解.谈论的影响 ...