Chinaunix首页 | 论坛 | 博客
  • 博客访问: 25303
  • 博文数量: 33
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 0
  • 用 户 组: 普通用户
  • 注册时间: 2015-10-06 22:37
文章分类
文章存档

2015年(33)

我的朋友
最近访客

分类: C/C++

2015-10-11 21:28:16

     C++标准库扩展为我们提供了顺序容器和关联容器两种容器类型。顺序容器按照元素位置存储和读取元素,而关联容器则按照键(Key)的值来存储和读取数据,二者共享一些共同的操作,但是由于关联容器是根据Key作为“索引”的,因此直接关系位置的操作都不再适用,比如:front\back\push_front\push_back\pop_front\pop_back等。这一章主要介绍两种基本的关联容器类型:map和set。map类型类似于我们经常使用的字典,元素为的形式,通过key可以访问到value的值;而set类型就像我们常说的集合,具有集合的唯一性,即每个元素都不相同,一般可以用来作为操作行为的排除列表的构建。

1. pair类型
     为了更方便地使用map容器,C++为我们提供了一种二元向量数据结构:pair类型。使用pair类型前必须包含头文件#include pair类型本身的结构可以从定义的构造函数可以看出:
    pair p1;            //创建一个类型分别为T1和T2的pair对象,采用值初始化
    pair p2(v1, v2);      //使用v1和v2分别初始化pair对象的first和second成员
    make_pair;         //以v1和v2创建pair对象,类型分别为v1和v2类型,返回创建的pair类型--nextpair = make_pair("James", 5);
    p1 < p2                         //执行严格弱排序,即仅当p1.firts < p2.first | ((p1.firts == p2.first) && (p1.second < p2.second))为真时成立
    p1.first      p1.second
     作为一种模板类型,具体使用时可以如下:
   pair word_count;
   pair author("James", "Hellen");
      由于pari类型使用起来书写比较麻烦,所以可以使用typedef简化其声明:
   typedef pari Word_Count;
   Word_Count word_count("hello", 5);

点击(此处)折叠或打开

  1. //test pari type
  2. #include <iostream>
  3. #include <utility>
  4. #include <cstdlib>

  5. using namespace std;

  6. int main()
  7. {
  8.     cout << "定义一个统计得分的pari类型: "<<endl;
  9.     pair<string, int> next_author;
  10.     cout << next_author.first<<"..."<< next_author.second<<endl;
  11.     string str("James");
  12.     int num(5);
  13.     next_author = make_pair(str, num);
  14.     //next_author = make_pair("James", 5); //效果同单独定义变量是一致的
  15.     cout << next_author.first<<"..."<< next_author.second<<endl;
  16.     
  17.     system("pause");
  18.     return 0;
  19. }
      注意make_pair可以接收常量值作为参数,也可以接受变量值作为参数,具体可见上面的小例子。
 

2. map类型
2.1 map对象的定义
     使用map对象之前首先要在头文件中包含#include ,三种基本的定义方法如下:
   map m;         //创建一个名为m的空map对象,其键类型为key,其对应值类型为value
   map m1(m);  //使用map对象m来初始化新对象m1
   map m2(iterb, iterc);    //以迭代器iterb和iterc标识的段范围创建map对象,其每个元素都是pair类型。
     基本上C++提供的每个容器类型都会提供最基本的三种定义方式:默认构造函数定义,复制构造函数定义以及使用迭代器的范围构造定义。这里需要特别说明的是对于map类型来说,能够成为其键的类型必须满足“严格弱排序”的条件,由于map根据键值排序,因此当然要求其键类型能够以'<'比较大小确定先后顺序。所谓严格弱排序,即当一个键与自身比较时,应当返回false。对于value类型没有过多要求,map的这种特殊性要求称为“键类型约束”。
     对于一个map对象而言,其实存在三种数据类型,一种是作为整体来看,是一个pari类型:map::value_type;分开来看,pari类型的first成员是map::key_type类型,而second成员则是map::mapped_type类型,在实际编程中,可以根据这三种类型使用相关的类作用域符实现定义。当我们定义了map对象上的迭代器:
   map::iterator map_it = word_count.begin();
     则使用*map_it时得到的是map::value_type类型,即结果指向一个pari类型,这是很自然的。
2.2 使用下标方位map对象
     我们可以使用“下标”来访问map对象,但是与顺序容器中的“下标”有些不同。顺序容器中的下标标识着元素在对象中的位置顺序;而map对象中的下标实际则是Key值。我们可以直接使用Key值像之前使用数组下标那样访问对象元素:
   word_count["James"] = 2;       //为该元素赋值,修改其mapped_type类型的元素部分
      再进一步说明以前,先来看一个小例子:

点击(此处)折叠或打开

  1. //map对象的下标访问
  2. #include <iostream>
  3. #include <map>
  4. #include <cstdlib>

  5. using namespace std;

  6. int main()
  7. {
  8.     cout << "使用下标访问一个空的map对象:"<<endl;
  9.     map<string, int> word_count;
  10.     cout << "word_count size: "<<word_count.size()<<endl;
  11.     word_count["Anna"] = 1;
  12.     cout << "word_count size: "<<word_count.size()<<endl;
  13.     cout <<word_count["Anna"]<<endl;
  14.     
  15.     //test cin_word_count
  16.     string word;
  17.     while (cin >> word)
  18.        ++word_count[word];
  19.     map<string, int>::iterator iter_map = word_count.begin();
  20.     while (iter_map != word_count.end())
  21.     {
  22.        cout << iter_map->first<< "---" << iter_map->second<< endl;
  23.        iter_map++;
  24.        }
  25.     
  26.     system("pause");
  27.     return 0;
  28. }

      也许有人会由疑问,为什么只是建立的了空值的对象却可以使用下标来访问元素呢?其实对于map对象而言,当调用下标访问时执行以下步骤:
1-在word_count中查找键值为Anna的元素;若找到则对其赋值修改其后的值;
2-若没有找到该元素,则新建一个键值对,键值为const的Anna,value值部分为值初始化,本例中默认为0;
3-将新建的键值对插入到word_count对象中;
4. 读取新插入的元素,并且将其赋值为1
      由此可见,对于map对象来说,下标访问一个不存在的元素时会自动添加该元素,该键值即为下标值。当我们使用下标形式是返回的是该键所关联的值。上面程序的第二部分利用map对象下标的这种性质巧妙地实现统计输入单词的次数,然后分别打印输出。这里在编写的时候需要注意迭代器的使用。
      所有的容器都定义了迭代器,专门用来遍历容器元素,因此begin()和end()函数都是有定义的,而且像iter++/iter--这样的自增减运算对于循环遍历不可获取,因此也是都具备的通用性质。此外,iter可以比较是否相等,但是由于存储空间未必连续,因为不是所有容器都可以使用<等比较运算符,只有vectore和deque可以使用大小关系比较,其余容器只能比较相等与否。
     还有一个有意思的细节,我们输入时最后输入的"eat",最后却存储在了word_count中第二位,可以看出按键索引是按照键值的顺序(字母顺序)进行存储的。

2.3 .map元素的插入与删除
     map对象提供了insert方法来插入元素,同时提供了erase方法来删除元素。
m.insert(e) 若e.first不在m中,则插入一个值为e.second的元素;若该键已经存在,则保持m不变。该函数返回一个指向e.first的map迭代器以及一个bool变量记录是否插入了元素
m.insert(begin, end) 插入迭代器begin和end之间的m中不存在的元素(Key-value),返回void
m.erase(k) 删除m中键为k的元素,返回size_type类型,表示删除的元素个数
m.erase(p) 删除迭代器p指向的元素,该迭代器指向元素必须存在且不能指向end()
m.erase(begin, end) 删除迭代器begin和end之间标识的元素范围
      这里需要注意以下几个问题:
1-insert方法添加元素时如果已经存在则不再添加,所以只是添加不存在的元素;下标访问也可以达到元素添加的效果;但是insert方法返回一个pair类型,除了一个标识新元素的迭代器还有一个bool类型说明是否添加;
2-我们可以使用m.count(k)来检查map对象中某键是否存在,该方法用来返回指定键k出现的次数,对于map来说不是0就是1;
3-我们可以使用m.find(k)来获得指定键值元素的迭代器,若不存在则返回末端迭代器(end());借助find方法我们可以使用返回的迭代器在不添加元素的前提下访问元素;
     使用insert方法同样可以添加新的元素,请看下面的例子,与上面使用下标方法进行对比:

点击(此处)折叠或打开

  1. //map-insert
  2. #include <iostream>
  3. #include <map>
  4. #include <cstdlib>

  5. using namespace std;

  6. int main()
  7. {
  8.     map<string, int> word_count;
  9.     string word;
  10.     while (cin >> word)
  11.     {
  12.           pair<map<string, int>::iterator, bool> ret = word_count.insert(make_pair(word, 1));
  13.           if (!ret.second)
  14.           {
  15.               ++ret.first->second;
  16.                           }
  17.           }
  18.     //print the map obj
  19.     map<string, int>::iterator iter_map = word_count.begin();
  20.     while (iter_map != word_count.end())
  21.     {
  22.        cout << iter_map->first<< "---" << iter_map->second<< endl;
  23.        iter_map++;
  24.        }
  25.     
  26.     system("pause");
  27.     return 0;
  28.            
  29. }


3. set类型
     set类型类似于我们数学中使用的集合概念,仅仅保存map类型中的键值,与map相同,二者的键值一旦定义初始化都是const不允许修改的,我们可以修改的只有其value,但是对于set由于没有value因此一旦初始化完成不能修改,只能添加或删除。
     添加、删除元素的方法同map对象大同小异,这里不再赘述,只是对于set类型而言,find和count方法的使用更加便捷。下面给出一个例子说明set对象中元素的不重复性:

点击(此处)折叠或打开

  1. //set unique
  2. #include <iostream>
  3. #include <cstdlib>
  4. #include <set>
  5. #include <vector>

  6. using namespace std;

  7. int main()
  8. {
  9.     vector<int> ivec;
  10.     for (vector<int>::size_type i = 0; i < 10; i++)
  11.     {
  12.         ivec.push_back(i);
  13.         ivec.push_back(i);
  14.     }
  15.     set<int> iset(ivec.begin(), ivec.end());
  16.     cout << "vector' size: "<<ivec.size()<<endl;
  17.     cout << "set' size: "<<iset.size()<<endl;
  18.     
  19.     system("pause");
  20.     return 0;
  21. }



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