【转】C++ map的基本操作和使用

来源:http://www.youlihuishou.com 作者:杏彩平台注册网址-操作系统 人气:70 发布时间:2020-01-06
摘要:转自:〉 参考地址:   C++太复杂了。我猜想你一定是在计算机屏幕前不住地点头。其实我也在使劲的点头。没错,C++着实复杂,大概是迄今为止最复杂的语言。于是,便产生了一个推

<转自:〉

参考地址:

 

C++太复杂了。我猜想你一定是在计算机屏幕前不住地点头。其实我也在使劲的点头。没错,C++着实复杂,大概是迄今为止最复杂的语言。于是,便产生了一个推论:C++难学难用。
这句话也相当准确,但并非所有时候都这样。C++的学习和使用可以分为若干个层面,这些层面由简到繁,由易到难,分别适合不同层次的学习者和使用者。
先让我们来看看C++究竟有多复杂。我大致列了个清单,包括C++的主要语言特性。基本的一些特性,象变量、条件、选择等等,都掠去了,主要集中在体现C++复杂性的方面:

1、map简介

  map是一类关联式容器。它的特点是增加和删除节点对迭代器的影响较小,除了那个操作节点,对其它的节点都没有什么影响。对于迭代器来说,可以修改实值,而不能修改key。

  1. 指针。包括变量指针、函数指针等。可以计算。
  2. 引用。包括变量的引用、函数的引用等。
  3. 自由函数。
  4. 类。包括具体类和抽象类。
  5. 重载。包括函数重载和操作符重载。
  6. 成员数据。包括static和非static的。
  7. 成员函数。包括static和非static的。
  8. 虚函数。包括对虚函数的override。
  9. 继承。包括多继承、虚继承等。
  10. 多态。包括动多态和静多态。
  11. 类型转换。包括隐式的和显式的,以及臭名昭著的强制转换。
  12. 模板。包括类模板和函数模板。
  13. 模板特化。包括完全特化和部分特化。
  14. 非类型模板参数。
  15. 模板-模板参数。
    我对照了C#的特性清单。C++比C#多的特性主要是:1、2、3、9的多继承、10的静多态、13、14、15。
    而C#比C++多的特性主要有(这里没有考虑C++/CLI,C++/CLI补充了标准C++):
  16. for   each关键字。
  17. as、is关键字。
  18. seal关键字。
  19. abstract关键字。
  20. override/new关键字。
  21. using关键字的一些补充用法。
  22. implicit/explicit关键字。(C++有explicit关键字,但只用于构造函数)
  23. interface关键字。
  24. property。
  25. delegate。
    我们来分析一下。C++比C#所多的特性集中在编程技术方面,特别是编程模式,如由模板带来泛型编程和元编程机能。在OOP方面,C++走得更远,更彻底,主要表现在多继承和操作符重载方面。在传统的编程技术上,C++区分了对象和对象的引用(指针和引用)。而C#所有引用对象所持的都是引用。最后,C++拥有自由函数。
    反过来再看C#,C#所多的都是些关键字。这些关键字都集中在OOP方面,并且都是以使编程技术易于理解为目的的。很多人(包括我在内)都希望C++也拥有C#的这些关键字(至少部分)。但与一般人的理解不同,C++标准委员会实际上是被编译器开发商所把持着,他们对引入关键字尤其神经过敏。没有办法,现实总是有缺憾的。
    从这些方面可以看出,C++在编程机制上远远多于C#(Java的机制甚至更少)。对于新入行的人而言,一口气吞下这些内容,足以把他们撑死。相反,C#增加的关键字有助于初学者理解代码的含义。这些就是C#和Java比C++易于学习(易于理解)的真正原因。
    但是,必须要强调的是,C#和Java中易于学习和理解的是代码,而不是这些代码背后的技术原理和背景。
    我看到过的绝大多数C#代码都充满了重复代码和大量switch操作分派。如果这些程序员充分利用C#和Java的OOP机制,这些严重的代码冗余可以消除一半。(如果C#杏彩平台注册网址,和Java具备C++那样的泛型编程能力,则另一半也可以消除。)这些程序员都是在没有充分理解语言机制和OOP技术的情况下编写软件,事倍功半。
    这种情况在C++也有发生,但相对少些。这大概是因为C++足够复杂,使得学习者产生了“不彻底理解C++就学不会C++,就用不了C++”的想法。这种想法有利有弊,利在于促使学习者充分理解语言和语言背后的技术,而弊在于它吓跑了很多人。实际上,我们一会儿就会看到,C++可以同C#和Java一样,可以在不理解其中原理的情况下,仅仅按照既定规则编程。当然我们不希望这样,这是不好的做法。但鉴于现在业界
    的浮躁心态,我们也就入乡随俗吧。
    注意了!下面这句话是最关键的,最重要的,也是被长期忽略的:C++之所以复杂,是为了使用起来更简单。听不明白!自相矛盾!胡说!别急,且听我慢慢道来。
    (限于篇幅,我这里只给出最后一部分案例代码,完整的案例在我的blog里:)
    有三个容器,c1、c2、c3。容器的类型和元素的类型都未知。要求写一个算法框架,把c1里的元素同c2里的元素进行某种运算,结果放到c3里。
    由于容器类型未知,必须使用所有容器公共的接口。所以,我写下了如下的C#代码:
    public   delegate   void   alg <T1,   T2,   R> (T1   v1,   T2   v2,   R   r);
    public   static   void   Caculate_Contain <C1,   T1,   C2,   T2,   C3,   T3>
    (C1   c1,   C2   c2,   C3   c3,   alg <T1,   T2,   T3>   a   )
    where   C1:   IEnumerable <T1>
    where   C2   :   IEnumerable <T2>
    where   C3   :   IEnumerable <T3>
    {
    IEnumerator <T1>   eai1   =   c1.GetEnumerator();
    IEnumerator <T2>   eai2   =   c2.GetEnumerator();
    IEnumerator <T3>   eai3   =   c3.GetEnumerator();

2、map的功能

 自动建立Key-value的对应。key和value可以是任意你需要的类型。

 根据key值快速查找记录,查找的复杂度基本是log(N),如果有1000个记录,最多查找10次,1,000,000个记录,最多查找20次。

 快速插入Key - Value 记录。

 快速删除记录

 根据Key修改value记录。

 遍历所有记录。

while   (eai1.MoveNext()   &&   eai2.MoveNext()   &&   eai3.MoveNext())
{
a(eai1.Current,   eai2.Current,eai3.Current);
}
}
//使用
public   static   void   CaculThem(int   v1,   int   v2,int   r)   {
        r=v1*v2;
}
Caculate_Contain(ai1,   ai2,   ai3,   new   alg <int,   int,   int> (CaculThem));
public   static   void   CaculThem2(float   v1,   int   v2,double   r)   {
        r=v1*v2;
}
Caculate_Contain(af1,   ai2,   ad3,   new   alg <float,   int,   double> (CaculThem2));
我使用了一个委托,作为传递处理容器元素的算法的载体。使用时,用具体的算法创建委托的实例。但具体的算法CaculThem()必须同相应的容器元素类型一致。
下面轮到C++:
template <typename   C1,   typename   C2,   typename   C3,   typename   Alg>
Caculate_Container(const   C1&   c1,   const   C2&   c2,   C3&   c3,   Alg   a)
{
transform(c1.begin(),   c1.end(),   c2.begin(),   c3.begin(),   a);
}
//使用
template <typename   T1,   typename   T2,   typename   R>
R   mul_them(T1   v,T2   u)   {
return v*u;
}
Caculate_Container(ai1,   ai2,   ai3,   mul_them <int,   int,   int> );
Caculate_Container(af1,   ad2,   ad3,   mul_them <float,   double,   double> );
如果容器元素有所变化,C#代码必须重写算法CaculThem()。但C++不需要,由于mul_them <> ()本身是个函数模板,那么只需将这个函数模板用新的类型实例化一下即可。
C++的代码相对简单些,灵活性也更高些。但这还不是全部,C++还有一个最终极的解法,不需要循环,不需要创建模板算法,不需要写操作函数:
transform(c1.begin(),   c1.end(),   c2.begin(),   c3.begin(),   _1*_2);

3、使用map

  使用map得包含map类所在的头文件

#include <map>//注意,STL头文件没有扩展名.h

  map对象是模板类,需要关键字和存储对象两个模板参数:

std::map<int,string> personnel;

  这样就定义了一个用int作为索引,并拥有相关联的指向string的指针。

  为了使用方便,可以对模板类进行一下类型定义,

typedef map<int,CString> UDT_MAP_INT_CSTRING;
UDT_MAP_INT_CSTRING enumMap;

没看明白?我一开始也看不明白。这里用到了boost库的Lambda表达式。_1占位符对应c1的元素,_2的占位符对应c2的元素,_1*_2表示才c1的元素乘上c2的元素,其结果放在c3里。表达式可以写得更复杂,比如(_1*_2+3*_1)/(_1-_2)。Lambda表达式可以用在所有需要操作的算法中,比如我要去掉字符串中的“-”,可以这样写:
remove_if(s.begin(),   s.end(),   _1==’-’);
Lambda表达式基于一种叫做“模板表达式”的技术,通过操作符重载,将一个表达式一层一层地展开,构成一个解析树。然后作为一个函数对象传递给算法,算法在循环内调用函数对象,执行相应的计算。
没有比这更简单的了吧。原理是够复杂的,但我们可以完全不理睬其中复杂的原理,只管用就是了。别看只是一个小小的算法,要知道,再庞大的软件(象JSF的代码有1900万行之多)都是由这些渺小的算法构成的。C++提供的算法和简化算法使用的库几乎对所有的程序算法都有帮助,不仅仅对这种底层算法有效,在更高层次的算法作用更大。
这里我就不再给出C#的代码了,因为C#还不支持Lambda表达式,也无法模拟。如果想要的话,等C#3.0吧。
好了,应该是做小结的时候了。从上面的这些例子可以看出,在最基本的语句上,C#有时比C++简单些,因为C#提供了更多的关键字。但是,随着算法的逐步复杂,C++的抽象能力渐渐发挥作用。一旦需要建立抽象的算法和代码时,C++的泛型编程能力立刻爆发出巨大的能量。最后,我们利用boost::lambda库最大限度简化了算法的使用。更重要的,Lambda表达式的实现极其复杂,但是使用却异常简单。
这便是开头所说的:“C++之所以复杂,是为了使用起来更简单”这句话的含义。C++提供的那些复杂的机制,是为了构建库,以提供语言没有实现的功能,这些功能可以大幅简化开发工作。如标准库里的容器、算法,boost库的Lambda表达式、BGL的命名参数、智能指针等等。
也就是说,一个程序员可以仅仅学习最基本的C++编程技术,便可以利用现成的各种库开发软件。只管用,别问为什么。在这种情况下,学习和使用C++的难度同C#和Java相比没有本质的差别。但由于C++可以提供更灵活高效的库,在不少情况下,反而比C#和Java更好用。
要达到这种程度,程序员所需的训练的确会比C#和Java多一些。所需的训练主要集中在:标准库的使用;区别对象、指针和引用;指针、内存、资源的处理方法,智能指针的使用;类使用的一些特别要点(构造函数、隐式转换等等);多态的正确处理;模板的用法。另外还需要给学习者定下一些“规矩”,避免误用一些敏感的语言机制。这些“规矩”只需遵守,不要问为什么。一旦这些“规矩”成了本能的一部分(强化训练可以达到这种效果),程序员就成熟了。即便回过头使用C#或Java,也能很容易做到趋利避害,扬长避短。(要小心,这时候程序员很可能会骂人的。我是个比较斯文的人,一般不骂人,除了开车的时候和使用C#的时候)。
这些内容只要编排得当,用法标准,学习者不需要花费很长的时间即可掌握,大概两三个月即可,如有半年的时间,便可以纯熟。这样训练出来的程序员基础非常扎实,无论将来学习什么语言或技术,都可以驾轻就熟。如果他还喜欢C++,那么可以进一步学习C++的高级机制,加入库开发者的行列。
对于自学者,也可以进行这样的训练。但必须要正确选择教材。Lippman的《essential   C++》和《C++   Primer》是我所知的最好的教材。认真地遵循书中的线路,反复做好练习,就行了。入门书是很重要的,千万不要选择那些C(++)的书(大半本C,带上那么一点点C++的内容)。
这样,我们实际上是将C++的使用分成了两个层面,一个是应用层面,另一个是基础开发层面。当然,基础开发层面还可以分成应用性的基础开发,比如帐务管理系统专用的基础库;和工具库的开发,象boost之类的库。应用层面的开发人员不必了解(甚至可以不知道)C++所有的古怪特性,以及那些库的内部机制,只需学会使用即可。要达到这种要求,我相信对能够学会高等数学的人而言,是易如反掌的。
对于一个使用C++的企业,无需每个程序员熟悉所有的C++特性。有少量高手专注于企业级的库的设计和开发,而其余的程序员只需达到上面所说的基础C++的程度。这种配置可以获得非常高的开发效率。但前提是程序员接受正规的、扎实的基础C++培训,这在前面已经讲过了。
再强调一遍,“规矩”是至关重要的。因为C#和Java易学,主要得益于去掉了许多危险(但却非常有用和重要)的语言机制。在C++的教学中引入严格的“规矩”,是在保留这些危险但重要的机制的同时,使学习者避免其损害的手段。必须让初学者知道很多机制是不能碰的,因为这些机制不是给他们用的,是给那些充分了解其危害的人用的。
相比之下,在路上开车压死人要比在C++中犯错误容易得多(毕竟C++不会在马路上乱窜)。之所以我们没有天天出事故,是因为我们遵循了规则(交规和驾驶技巧)。C++编程也一样(任何编程都这样)。但有时人们可以在受控的情况下合理违反交规,比如执行任务的警车和救护车可以逆向行驶、闯红灯。显然这是有条件的,驾驶员受过特别训练,鸣笛和亮警示灯。就像在C++中,只有受过特别训练,在有保护的情况下方能使用象placement   new这样的超危险机制一样。
不错,即便如此,C++的学习依然不是一个轻松的过程,所学的内容也比C#和Java的多。但是在付出的同时,还应看到收获。学习C++的收获不仅仅是可以使用Lambda表达式这样优雅的语法,而且能够真正地掌握编程技术中的核心,为将来的发展打好基础。当你能够熟练地使用标准库时,正宗的抽象思维的能力已经悄悄地潜入了你的骨髓,这是成为一名优秀的软件设计师的基本条件,谁不想要呢。
好了,我这里展示了C++易用性的冰山一角,解释了难学难用的原因,也提供了学好C++的方法。“C#和Java比C++容易学习和使用”这句话尽管不算诽谤,但也是被严重地夸大了。我希望大家能象Discovery   Channel的Mythbusters(Discovery的保留节目,专门用实验的方式检验流言的真实性)那样,勇于尝试和实践,用自己的切身体会打破关于C++的流言。

4、在map中插入元素

  改变map中的条目非常简单,因为map类已经对[]操作符进行了重载

enumMap[1] = "One";
enumMap[2] = "Two";
.....

  这样非常直观,但存在一个性能的问题。插入2时,先在enumMap中查找主键为2的项,没发现,然后将一个新的对象插入enumMap,键是2,值是一个空字符串,插入完成后,将字符串赋为"Two"; 该方法会将每个值都赋为缺省值,然后再赋为显示的值,如果元素是类对象,则开销比较大。我们可以用以下方法来避免开销:

enumMap.insert(map<int, CString> :: value_type(2, "Two"))

5、查找并获取map中的元素

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

CString tmp = enumMap[2];

  但是,只有当map中有这个键的实例时才对,否则会自动插入一个实例,值为初始化值。 

  我们可以使用Find()和Count()方法来发现一个键是否存在。

  查找map中是否包含某个关键字条目用find()方法,传入的参数是要查找的key,在这里需要提到的是begin()和end()两个成员,map.end()指向map的最后一个元素之后的地址,map.begin()指向map的第一个元素map.begin()可能随着map.erase(iter)或是map.add(key, value)操作而发生改变。例如当第一个元素被删除后,map.begin()就发生了改变,指向原来第一个元素之后的那个元素了。);这两个数据的类型是iterator。

int nFindKey = 2; //要查找的Key
//定义一个条目变量(实际是指针)
UDT_MAP_INT_CSTRING::iterator it= enumMap.find(nFindKey);
if(it == enumMap.end()) {
//没找到
}
else {
//找到
}

  通过map对象的方法获取的iterator数据类型是一个std::pair对象,包括两个数据 iterator->first 和 iterator->second 分别代表关键字和存储的数据。

6、从map中删除元素

  移除某个map中某个条目用erase()

  该成员方法的定义如下

iterator erase(iterator it); //通过一个条目对象删除 
iterator erase(iterator first, iterator last); //删除一个范围 
size_type erase(const Key& key); //通过关键字删除

  clear()就相当于 enumMap.erase(enumMap.begin(), enumMap.end());

7、map的基本操作函数:

  C++ Map是一种关联式容器,包含“关键字/值”对,常用的操作函数如下:

      begin()          //返回指向map头部的迭代器
      clear()        // 删除所有元素
      count()          //返回指定元素出现的次数
      empty()          //如果map为空则返回true
      end()            //返回指向map末尾的迭代器
      equal_range()   // 返回特殊条目的迭代器对
      erase()          //删除一个元素
      find()           //查找一个元素
      get_allocator() // 返回map的配置器
      insert()         //插入元素
      key_comp()       //返回比较元素key的函数
      lower_bound()    //返回键值>=给定元素的第一个位置
      max_size()       //返回可以容纳的最大元素个数
      rbegin()         //返回一个指向map尾部的逆向迭代器
      rend()           //返回一个指向map头部的逆向迭代器
      size()           //返回map中元素的个数
      swap()            //交换两个map
      upper_bound()     //返回键值>给定元素的第一个位置
      value_comp()      //返回比较元素value的函数

例子:

//遍历:
map<string,CAgent>::iterator iter;
 for(iter = m_AgentClients.begin(); iter != m_AgentClients.end(); ++iter)
 {
   if(iter->first=="8001"  {
     this->SendMsg(iter->second.pSocket,strMsg);
   }
 }
//查找:
map<string,CAgent>::iterator iter=m_AgentClients.find(strAgentName);
 if(iter!=m_AgentClients.end())//有找到  {
 }
 else //没有{
 }
//元素的个数
if (m_AgentClients.size()==0)
//删除
map<string,CAgent>::iterator iter=m_AgentClients.find(pSocket->GetName());
 if(iter!=m_AgentClients.end())
 {
     m_AgentClients.erase(iter);//列表移除
 }

 

转自:

本文由杏彩彩票app发布于杏彩平台注册网址-操作系统,转载请注明出处:【转】C++ map的基本操作和使用

关键词:

最火资讯