Chinaunix首页 | 论坛 | 博客
  • 博客访问: 84771
  • 博文数量: 21
  • 博客积分: 371
  • 博客等级: 一等列兵
  • 技术积分: 225
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-15 21:32
文章分类

全部博文(21)

文章存档

2013年(5)

2012年(16)

我的朋友

分类: C/C++

2012-12-14 23:24:11

1.关联容器和顺序容器的本质区别在于:关联容器通过键值(KEY)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素.

2.pair类型提供的主要操作:

1).pairp1                     创建一个空的pair的类型

2).pairp1(v1,v2)         p1.first=v1,p1.second=v2并创建pair对象

3).make_pair(v1,v2)                 创建v1,v2pair对象

3.map对象中,键值类型必须定义<操作符,而且操作符能正确的工作.

map::key_type              在容器中用作索引的键的类型

map::mapped_type       map容器中,键所关联的值的类型

map::value_type           一个pair类型.他的first元素具有const map::key_type类型,second元素则为map::map_type类型

4.使用下标访问map与使用下标访问数组或vector的行为截然不同:用下标访问不存在的元素将导致在map容器中添加一个新的元素,他的键即为该下标值,插入元素时,如果该键已经存在,则只是插入失败.

5.map容器提供的insert操作

1).m.insert(e);             e是一个在m上的value_type类型的值,如果(e.first)不在m,则插入一个值为e.second的新元素;如果该键存在,m保持不变.该函数返回一个pair类型的对象,包含指向键为e.first元素的map迭代器,以及一个bool类型对象,表示是否插入了该元素

2).m.insert(beg,end)   begend是标记元素范围的迭代器

3).m_insert(iter,e)       ealue_type类型的值

其中:alue_type类型那个可以用make_pair(k,v)来生成

6.不修改map对象的查询操作:

m.count(k)                  返回mk的出现次数(0或者1)

m.find(k)                    如果m中存在按k的索引的元素,则返回指向该元素的迭代器,如果不存在,则返回超出末端的迭代器

7.删除对象

m.erase(k)                   删除m中键值为k的元素,返回size_type类型的值.表示删除的元素的个数

m.erase(p)                   m中删除迭代器p所指元素,p必须指向m中确实存在的元素,而且不等于m.end(),返回void类型

m.erase(b,e)                删除b-e段的元素

m.erase(k)                   删除键值为k的元素

8.set类型:

set不支持下标操作符,而且没有定义mapped_type类型,另外set容器存储的键值也必须唯一,且不能改变

8.multimap:不支持下标运算

关联容器和顺序容器的本质差别在于:关联容器通过键(key)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。

 

    关联容器(Associative containers)支持通过键来高效地查找和读取元素。两个基本的关联容器类型是 mapset

    map 的元素以键-值(key-value)对的形式组织:键用作元素在 map 中的索引,而值则表示所存储和读取的数据。set 仅包含一个键,并有效地支持关于某个键是否存在的查询。

 

    关联容器类型

map

关联数组:元素通过键来存储和读取

set

大小可变的集合,支持通过键实现的快速读取

multimap

支持同一个键多次出现的 map 类型

multiset

支持同一个键多次出现的 set 类型

 

    一般来说,如果希望有效地存储不同值的集合,那么使用 set 容器比较合适,而 map 容器则更适用于需要存储(乃至修改)每个键所关联的值的情况。在做某种文本处理时,可使用 set 保存要忽略的单词。而字典则是 map 的一种很好的应用:单词本身是键,而它的解释说明则是值。 

    set 和 map 类型的对象所包含的元素都具有不同的键,不允许为同一个键添加第二个元素。如果一个键必须对应多个实例,则需使用 multimap 或 multi set,这两种类型允许多个元素拥有相同的键。

 

    在开始介绍关联容器之前,必须先了解一种与之相关的简单的标准库类型—— pair,该类型在 utility 头文件中定义。

     pairs 类型提供的操作

pair p1;

创建一个空的 pair 对象,它的两个元素分别是T1 和 T2 类型,采用值初始化

pair p1(v1, v2);

创建一个 pair 对象,它的两个元素分别是 T1 和 T2 ,其中 first 成员初始化为 v1,而 second 成员初始化为 v2

make_pair(v1, v2)

以 v1 和 v2 值创建一个新 pair 对象,其元素类型分别是 v1 和 v2 的类型

p1 < p2

两个 pair 对象之间的小于运算,其定义遵循字典次序:如果 p1.first < p2.first 或者 !(p2.first < p1.first) && p1.second < p2.second,则返回
true

p1 == p2

如果两个 pair 对象的 first 和 second 成员依次相等,则这两个对象相等。该运算使用其元素的 == 操作符

p.first

返回 p 中名为 first 的(公有)数据成员

p.second

返回 p 的名为 second 的(公有)数据成员

 

     pair 的创建和初始化

    如果在创建 pair 对象时不提供初始化式,则调用默认构造函数对其成员采用值初始化。

pair<stringstring> anon; // holds two 
strings  pair<stringint> word_count; // holds a string and an int 

pair<string, vector<int> > line; // holds string and vector 

pair<stringstring> author("James""Joyce"); 

 

    pair 类型的使用相当繁琐,因此,如果需要定义多个相同的 pair 类型对象,可考虑利用 typedef 简化其声明:

    typedef pair Author ;
    Author proust("Marcel",  "Proust") ;
    Author joyce("James",  "Joyce") ;

    与其他标准库类型不同,对于 pair 类,可以直接访问其数据成员:其成员都是公有的,分别命名为 first 和 second。

string firstBook;     // access and test the data members of the pair
if (author.first == "James" && author.second == "Joyce")
{
    firstBook = "Stephen Hero";
}

      除了构造函数,标准库还定义了一个 make_pair 函数,由传递给它的两个实参生成一个新的 pair 对象。可如下使用该函数创建新的 pair 对象,并赋给已存在的 pair 对象:

pair<stringstring> next_auth;
string first, last;
while (cin >> first >> last) 
{
   // generate a pair from first and last 
   next_auth = make_pair(first, last);
   // process next_auth...

}

make_pair 函数生成一个新的 pair 对象,此操作等价于下面更复杂的操作:
// use pair constructor to make first and last into a pair
next_auth = pair<stringstring > (first, last);

 

    由于 pair 的数据成员是公有的,因而可如下直接地读取输入:

pair<stringstring> next_auth;

// read directly into the members of next_auth
while (cin >> next_auth.first >> next_auth.second) 
{
   // process next_auth...

}

“关联容器元素根据键的次序排列”这一事实就是一个重要的结论:在迭代遍历关联容器时,我们可确保按键的顺序的访问元素,而与元素在容器中的存放位置完全无关。

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

    map 对象的定义

map m;

创建一个名为 m 的空 map 对象,其键和值的类型分别为 k 和 v

map m(m2);

创建 m2 的副本 m,m 与 m2 必须有相同的键类型和值类型

map m(b, e);

创建 map 类型的对象 m,存储迭代器 b 和 e 标记的范围内所有元素的副本。元素的类型必须能转换为 pair

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

    在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数。 所用的比较函数必须在键类型上定义严格弱排序(strict weak ordering)。所谓的严格弱排序可理解为键类型数据上的“小于”关系。当用于一个键与自身的比较时,肯定会导致 false 结果。如果它们相互之间都不存在“小于”关系,则容器将之视为相同的键。用做 map 对象的键时,可使用任意一个键值来访问相应的元素。

 

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

    map 对象的元素是键-值对,其 value_type 是存储元素的键以及值的 pair 类型,而且键为 const。

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

 

    map 类定义的类型

map::key_type

在 map 容器中,用做索引的键的类型

map::mapped_type

在 map 容器中,键所关联的值的类型

map::value_type

一个 pair 类型,它的 first 元素具有 const map::key_type 类型,而 second 元素则为 map::mapped_type 类型

点击(此处)折叠或打开

  1. // count number of times each word occurs in the input
  2. map<string, int> word_count; // empty map from string to int
  3. // get an iterator to an element in word_count
  4. map<string, int>::iterator map_it = word_count.begin(); // *map_it is a reference to a pair<const string, int> object
  5. cout << map_it->first; // prints the key for this element

  6. cout << " " << map_it->second; // prints the value of the element
  7.  
  8. map_it->first = "new key"; // error: key is const
  9.  
  10. ++map_it->second; // ok: we can change value through an iterator
map 类额外定义了两种类型:key_type mapped_type,以获得键或值的类型。

 

    给 map 添加元素

    可使用 insert 成员实现;或者,先用下标操作符获取元素,然后给获取的元素赋值。在这两种情况下,一个给定的键只能对应于一个元素这一事实影响了这些操作的行为。 如下编写程序时:    

map <stringint> word_count; // empty map
 
// insert default initialzed element with key Anna; then assign 1 to its value

word_count["Anna"] = 1; 

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

 

    下标操作符返回值的使用

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

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

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

 

    对于 map 容器,如果下标所表示的键在容器中不存在,则添加新元素,这一特性可使程序惊人地简练:这段程序创建一个 map 对象,用来记录每个单词出现的次数。

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

 

    容器提供的 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 中具有给定键的元素

 

    以 insert 代替下表运算

    插入元素的另一个方法是:直接使用 insert 成员,其语法更紧凑:

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

 

    传递给 insert 的实参相当笨拙。可用两种方法简化:使用 make_pair:

  word_count.insert(make_pair("Anna", 1))

    或使用 typedef

    typedef map::value_type valType;
    word_count.insert(valType("Anna", 1));

 

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


点击(此处)折叠或打开

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

  12.   // increment
  13.   counter
  14. }
对于每个单词,都尝试 insert 它,并将它的值赋 1。

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

 

    使用下标存在一个很危险的副作用:如果该键不在 map 容器中,那么下标操作会插入一个具有该键的新元素。map 容器提供了两个操作:count 和 find,用于检查某个键是否存在而不会插入该键。

    不修改 map 对象的查询操作

m.count(k)

返回 m 中 k 的出现次数

m.find(k)

如果 m 容器中存在按 k索引的元素,则返回指向该元素的迭代器。如果不存在,则返回超出末端迭代器

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

     而对于 multimaps 容器,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。

    有一点不同:map 容器的 erase 操作返回 void,而顺序容器的erase 操作则返回一个迭代器,指向被删除元素后面的元素。

    从 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 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";

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

 

    map 对象的迭代遍历

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


点击(此处)折叠或打开

  1. // get iterator positioned on the first element
  2. map<string, int>::const_iterator map_it = word_count.begin();

  3. // for each element in the map
  4. while (map_it != word_count.end())
  5. {
  6.   // print the element key, value pairs
  7.   cout << map_it->first << " occurs " << map_it->second << " times" << endl;
  8.   ++map_it; // increment iterator to denote the next element
  9. }
这个单词统计程序依据字典顺序输出单词。在使用迭代器遍历 map 容器时,迭代器指向的元素按键的升序排列。

 

    set 类型

    当只想知道一个值是否存在时,使用 set 容器是最适合的。

    两种例外包括:set 不支持下标操作符,而且没有定义 mapped_type 类型。在 set 容器中,value_type 不是 pair 类型,而是与 key_type 相同的类型。它们指的都是 set 中存储的元素类型。这一差别也体现了 set 存储的元素仅仅是键,而没有所关联的值。与 map 一样,set 容器存储的键也必须唯一,而且不能修改。

 

    set 容器的定义和使用

    在 set 对象中插入一组元素时,对于每个键,事实上都只添加了一个元素:

点击(此处)折叠或打开

  1. // define a vector with 20 elements, holding two copies of each number from 0 to 9
  2. vector<int> ivec;
  3. for (vector<int>::size_type i = 0; i != 10; ++i)
  4. {
  5.   ivec.push_back(i);
  6.  
  7.   ivec.push_back(i); // duplicate copies of each number
  8. }

  9. // iset holds unique elements from ivec
  10. set<int> iset(ivec.begin(), ivec.end());
  11. cout << ivec.size() << endl; // prints 20
  12. cout << iset.size() << endl; // prints 10
可使用 insert 操作在 set 中添加元素:
set<string> set1; // empty set
 
set1.insert("the"); // set1 now has one element

 
set1.insert("and"); // set1 now has two elements

 

  另一种用法是,调用 insert 函数时,提供一对迭代器实参,插入其标记范围内所有的元素。该版本的 insert 函数类似于形参为一对迭代器的构造函数——对于一个键,仅插入一个元素:

set<int> iset2;  // empty set
iset2.insert(ivec.begin(), ivec.end()); // iset2 has 10 elements

    与 map 容器的操作一样,带有一个键参数的 insert 版本返回 pair类型对象,包含一个迭代器和一个 bool 值,迭代器指向拥有该键的元素,而 bool 值表明是否添加了元素。使用迭代器对的insert 版本返回 void 类型。

 

    正如不能修改 map 中元素的键部分一样,set 中的键也为 const。

// set_it refers to the element with key == 1     
set<int>::iterator set_it = iset.find(1);     

*set_it = 11// error: keys in a set are read-only
 
cout << *set_it << endl; // ok: can read the key

 

    删除指定文件中所有的单词(即该文件记录的是排除集)。也即,我们的单词统计程序只对那些不在排除集中的单词进行统计。使用 set 和 map 容器,可以简单而直接地实现该功能:    


点击(此处)折叠或打开

  1. void restricted_wc(ifstream &remove_file, map<string, int> &word_count)
  2. {
  3.    set<string> excluded; // set to hold words we'll ignore
  4.  
  5.    string remove_word;

  6.    while (remove_file >> remove_word)
  7.       excluded.insert(remove_word);
  8.   
  9.   // read input and keep a count for words that aren't in the exclusion set
  10.   string word;

  11.   while (cin >> word)
  12.      // increment counter only if the word is not in excluded
  13.      if (!excluded.count(word))
  14.        ++word_count[word];
  15. }
map 和 set 容器中,一个键只能对应一个实例。而 multisetmultimap 类型则允许一个键对应多个实例。 注意到,关联容器 map 和 set 的元素是按顺序存储的。而 multimap 和 multset 也一样。因此,在 multimap 和 multiset 容器中,如果某个键对应多个实例,则这些实例在容器中将相邻存放 迭代遍历 multimap 或 multiset 容器时,可保证依次返回特定键所关联的所有元素。

 

    基于一个事实——在 multimap 中,同一个键所关联的元素必然相邻存放。

    使用 find 和 count 操作

点击(此处)折叠或打开

  1. // author we'll look for
  2. string search_item("Alain de Botton");
  3.  
  4. // how many entries are there for this author
  5. typedef multimap<string, string>::size_type sz_type;
  6. sz_type entries = authors.count(search_item);
  7.  
  8. // get iterator to the first entry for this author
  9. multimap<string,string>::iterator iter = authors.find(search_item);
  10.  
  11. // loop through the number of entries there are for this author
  12.  for (sz_type cnt = 0; cnt != entries; ++cnt, ++iter)
  13.     cout << iter->second << endl; // print each title

返回迭代器的关联容器操作

m.lower_bound(k)

返回一个迭代器,指向键不小于 k 的第一个元素

m.upper_bound(k)

返回一个迭代器,指向键大于 k 的第一个元素

m.equal_range(k)

返回一个迭代器的 pair 对象它的 first 成员等价于 m.lower_bound(k)。而second 成员则等价m.upper_bound(k)

 

使用这些操作,可如下重写程序:

点击(此处)折叠或打开

  1. // definitions of authors and search_item as above
  2.  
  3. // beg and end denote range of elements for this author
  4. typedef multimap<string, string>::iterator authors_it;
  5. authors_it beg = authors.lower_bound(search_item), end = authors.upper_bound(search_item);
  6.  
  7. // loop through the number of entries there are for this author
  8. while (beg != end)
  9. {
  10.   cout << beg->second << endl; // print each title
  11.   ++beg;
  12. }
如果键 search_item 在容器中存在,则使 beg 指向第一个与之匹配的元素。如果容器中没有这样的元素,那么beg 将指向第一个键比 search_item 大的元素。

    若该键没有关联的元素,则 lower_boundupper_bound返回相同的迭代器:都指向同一个元素或同时指向 multimap的超出末端位置。它们都指向在保持容器元素顺序的前提下该键应被插入的位置。

    如果该键所关联的元素存在,那么 beg 将指向满足条件的元素中的第一个。可对 beg做自增运算遍历拥有该键的所有元素。当迭代器累加至 end 标志时,表示已遍历了所有这些元素。当 beg 等于end 时,表示已访问所有与该键关联的元素。

    equal_range 函数返回存储一对迭代器的 pair 对象。如果该值存在,则 pair 对象中的第一个迭代器指向该键关联的第一个实例,第二个迭代器指向该键关联的最后一个实例的下一位置。如果找不到匹配的元素,则 pair 对象中的两个迭代器都将指向此键应该插入的位置。

 

    使用 equal_range 函数再次修改程序:

点击(此处)折叠或打开

  1. // definitions of authors and search_item as above
  2. // pos holds iterators that denote range of elements for this key
  3. pair<authors_it, authors_it> pos = authors.equal_range(search_item);
  4.  
  5. // loop through the number of entries there are for this author
  6. while (pos.first != pos.second)
  7. {
  8.   cout << pos.first->second << endl; // print each title
  9.  
  10.   ++pos.first;
  11. }

本程序的 pos.first 等价于前一方法中的 beg,而 pos.second 等价于 end。

 

   小结

    关联容器的元素按键排序和访问。关联容器支持通过键高效地查找和读取元素。键的使用,使关联容器区别于顺序容器,顺序容器的元素是根据位置访问的。

    map 和 multimap 类型存储的元素是键-值对。它们使用在 utility 头文件中定义的标准库 pair 类,来表示这些键-值对元素。对 map 或 multimap 迭代器进行解引用将获得 pair类型的值。pair 对象的first 成员是一个 const 键,而 second 成员则是该键所关联的值。set 和 multiset 类型则专门用于存储键。在 map 和 set 类型中,一个键只能关联一个元素。而multimap 和 multiset 类型则允许多个元素拥有相同的键。

    关联容器共享了顺序容器的许多操作。除此之外,关联容器还定义一些新操作,并对某些顺序容器同样提供的操作重新定义了其含义或返回类型,这些操作的差别体现了关联容器中键的使用。

 

    关联容器的元素可用迭代器访问。标准库保证迭代器按照键的次序访问元素。begin操作将获得拥有最小键的元素,对此迭代器作自增运算则可以按非降序依次访问各个元素。



阅读(1423) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~