关于CMap的小知识

最近在mfc中用到字典,自己不会在网上查了资料。简单总结一下:
一,CMap是什么?
      映射(Map),又称为字典(Dictionary),是由关键字(Key)及其对应的元素值(Value)所组成的元素单

元(Element)的表单式集合。CMap是一个mfc的模板类,可以建立一个从任意类型的变量到另外一个任意类型的

变量的映射(map),用的是哈希表作存储,因此速度较快。对于要求查找速度快一般用数组,对于增加/删除操作

方便的都用链表,但要是两者综合一下,最好还是用合希表。

 

二,要注意的几个地方:
   1.如何声明CMap
          许多人对Cmap的声明模式CMap<KEY,ARG_KEY,VALUE,ARG_VALUE>感到迷惑,为什么不用CMap<KEY,VALUE> 

   呢?实际上,CMap中的的数据最终会是CPair,而CPair内部是(KEY,VALUE)。因此,CMap其实存储的是KEY

   ,而非ARG_KEY。然而,如果你查看MFC的源代码,几乎CMap所有的内部参数传递都是访问ARG_KEY和ARG_VALUE,因此,使用KEY&来代替ARG_KEY似乎是正确的,除了在这些情况下:
     1 应用简单的数据类型,如int ,char用值传递与参数传递没有什么不同
     2 如果用CString作为KEY,你应该用LPCTSTR做ARG_KEY而非CString&。

  2.有哪些与Map相关的典型操:

  1 向Map中插入具有给定关键字的元素单元。

  2 在Map中查找具有给定关键字的元素单元。

  3 在Map中删除具有给定关键字的元素单元。

  4 枚举(遍历)Map中的所有元素单元。

   
三,简单的例子:
   例子一: 我们来看一个CMap的用法,下面示例代码:

 CMap<int,int&,CPoint,CPoint&> myMap;

 //初始化哈希表,并指定其大小(取奇数)。MyMap.InitHashTable(257);

 //向myMap中添加元素单元。
 for (int i=0;i < 200;i++)
   myMap.SetAt( i, CPoint(i, i) );

  // 删除实际值为偶数的关键字所对应的的元素单元。
 POSITION pos = myMap.GetStartPosition();
 int nKey;
 CPoint pt;
 while (pos != NULL)
 {
  myMap.GetNextAssoc( pos, nKey, pt );

  if ((nKey%2) == 0)
   myMap.RemoveKey( nKey );
 }

 #ifdef _DEBUG
  afxDump.SetDepth( 1 );
  afxDump << "myMap: " << &myMap << "/n";CMap<int,int&,CPoint,CPoint&> myMap;

//初始化哈希表,并指定其大小(取奇数)。MyMap.InitHashTable(257);

//向myMap中添加元素单元。
for (int i=0;i < 200;i++)
 myMap.SetAt( i, CPoint(i, i) );

 // 删除实际值为偶数的关键字所对应的的元素单元。
 POSITION pos = myMap.GetStartPosition();
 int nKey;
 CPoint pt;
 while (pos != NULL)
 {
  myMap.GetNextAssoc( pos, nKey, pt );

  if ((nKey%2) == 0)
   myMap.RemoveKey( nKey );
 }

 #ifdef _DEBUG
  afxDump.SetDepth( 1 );
  afxDump << "myMap: " << &myMap << "/n";


CMap是个很不错的数据结构,尤其在你建立一个字典的时候。比如idcountry的含义是"中国",这就是一个元组

,也就是一个Pair,Key是"idcountry",而Value是"中国"。

例子二:

1、定义一个CMAP,向这个CMAP中增加数据项(键-值对)。
CMap<CString, LPCTSTR, CString, LPCTSTR>m_ItemMap;
CString strKey = _T(""), str = _T("");
int i;
for(i = 0; i < 5; i++)
    {
        strKey.Format("%d", i);              //这个是键
        str.Format("A%d", i);               //键对应的值 
        m_ItemMap.SetAt(strKey, str);
    }
2、遍历正个CMAP的常用方法。
    POSITION pos = m_ItemMap.GetStartPosition();
    while(pos)
    {
        m_ItemMap.GetNextAssoc(pos, strKey, str);
        cout<< strKey<< ":"<< str<< endl;
    }
3、在CMAP中查找相应的数据项。
    CString pReset;
    if(m_ItemMap.Lookup("1", pReset))
    {
        cout<<pReset<<endl;
    }

 

cmap用法,很详细(转)

 

一、 Map的基本知识

  MFC中的提供了基于模板的CMap类。利用CMap模板类,可以处理特定的数据类型,例如用户自定义的类或结构体等。同时,MFC也提供了基于指定 数据类型的非模板类,其中包括: 
 

类名 关键字类型 元素值类型
CMapWordToPtr WORDS Void pointers
CMapPtrToWord Void pointers WORDS
CMapPtrToPtr Void pointers Void pointers
CMapWordToOb WORDS Objects
CMapStringToOb Strings Objects
CMapStringToPtr Strings Void pointers
CMapStringToString Strings String

二、 Map的工作原理 

在MFC的CMap及其相关的Map类中,只要对Map进行正确设置,Lookup函数通常能够一次到位的查找到任意元素,而很少需要进行两次或者三 次以上的查找比对。 

struct CAssoc
{
CAssoc* pNext;
UINT nHashValue;
CString key;
CString value;
};

 

nHashTableSize是哈希表中元素的数目(默认情况下,哈希表的大 小为17)。

如果在哈希表中的索引值为i的位置已经容纳了一个CAssoc指针,那么MFC将建立一个单独的CAssoc结构体的链表(List),链表中的第一 个CAssoc结构体的地址被存储到哈希表中,而将第二个CAssoc结构体的地址存储到前一个CAssoc结构体的pNext域,以此类推。

  但是,正如我们先前所讨论的那样,只要正确设置Map,链表中的元素一般就不会超过三个,这就意味着,查找通常可 以在三次元素比对操作之内完成。

三、 优化查找效率

  微软推荐将哈希表的大小设置为Map中所存储元素数目的 110% ~120%,以使得Map的应用性能在内存消耗和查找效率之间取得相对平衡。

在MFC中,指定哈希表大小,可调用InitHashTable()函数:

map.InitHashTable(1200);

从统计学上考虑,用奇数作为哈希表的大小也将有助于减少冲突的发生。因此,初始化一个存储1000个元素的哈希表的InitHashTable() 函数可以如下形式使用:

map.InitHashTable(1201);

同时,在InitHashTable()函数的调用时机上,应该注意的是,该函数应当在map包含有任何元素之前使。如果map中已经包含了一个或者 更多的元素,那么,重新改变map的大小,将会引发断言(Assertion)错误。

尽管MFC中所使用的哈希算法能够适应于大多数场合,但如果您真的有所需要,或者,只要你愿意,用户也可以使用自己的算法来取代原有的算法。对于一个 输入的关键字的值,要计算出它的哈希值,MFC通常调用一个全局模板函数HashKey(),对于大多数数据类型而言,HashKey()函数是以下面的 方式实现的:
 


AFX_INLINE UINT AFXAPI HashKey(ARG_KEY key)
{
//一般情况的默认算法。
return ((UINT)(void*)(DWORD)key) >> 4;
}

但对于字符串而言,其具体的实现方式如下:

UINT AFXAPI HashKey(LPCWSTR key) // Unicode 编码字符串
{
UINT nHash = 0;
while (*key)
nHash = (nHash<<5) + nHash + *key++;
return nHash;
}

UINT AFXAPI HashKey(LPCSTR key) // ANSI编码字符串
{
UINT nHash = 0;
while (*key)
nHash = (nHash<<5) + nHash + *key++;
return nHash;
}


要实现对应于特定数据类型的用户自定义哈希算法,您可以使用上述的字符串版本的HashKey()函数作为参考,写一个类似的特定类型的 HashKey()函数。

 

四、 使用MFC中的CMap类

  构造函数:

CMap 构造一个关键字和元素值映射的集合类。

操作: 

Lookup 通过给定的关键字查找相应的元素值。
SetAt 向Map中插入一个元素单元;若存在匹配键字,则替代之。
operator [] 向Map中插入一个元素 -SetAt的子操作
RemoveKey 移除由关键字标示的元素单元
RemoveAll 移除Map中的所有元素单元
GetStartPosition 返回第一个元素单元的位置
GetNextAssoc 读取下一个元素单元
GetHashTableSize 返回哈希表的大小(元素单元的数目)
InitHashTable 初始化哈希表,并指定它的大小

状态: 

GetCount 返回Map中元素的数目
IsEmpty 检查Map是否为空(无元素单元)

应用实例如下: 


CMap myMap;
//初始化哈希表,并指定其大小(取奇数)。

MyMap.InitHashTable(257);

//向myMap中添加元素单元。
for (int i=0;i < 200;i++)
myMap.SetAt( i, CPoint(i, i) );

// 删除实际值为偶数的关键字所对应的的元素单元。
POSITION pos = myMap.GetStartPosition();
int nKey;
CPoint pt;
while (pos != NULL)
{
myMap.GetNextAssoc( pos, nKey, pt );

if ((nKey%2) == 0)
myMap.RemoveKey( nKey );
}

#ifdef _DEBUG
afxDump.SetDepth( 1 );
afxDump << "myMap: " << &myMap << "/n";
#endif


2、选择适当大小的奇数-- 或者,有可能的话,使用素数的效果会更好一些--来作为哈希表的初始值。 

3、然后,向myMap中添加元素单元。 

4、使用myMap进行数据映射、查找、遍历等操作。 

5、调用myMap.RemoveAll()函数移除所有元素,释放myMap占用的内存空间。 

CMap对应IMPLEMENT_SERIAL,从而支持用户对其元素进行串行化(Serialization)以及倾注(Dumping)操作。在 对CMap的独立元素进行倾注操作时,应该注意的是,你必须将倾注环境(Dump Context)的深度设置为1或者更大的数字。 


CArray

 播报编辑讨论上传视频
计算机科学领域术语
本词条缺少概述图,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧!
CArray类支持与C arrays相似的数组,但是必要时可以动态压缩并扩展。数组索引从0开始。可以决定是固定数组上界还是允许当添加元素时扩展当前的边界。内存对上界是连续地分配空间,甚至一些元素可为空。
和C arrays一样,CArray索引元素的访问时间是不变的,与数组大小无关。
 
 
外文名
CArray
属    性
计算机科学领域术语
CArray
需要包含的头文件 <afxtempl.h>
提示:
在使用一个数组之前,使用SetSize建立它的大小和为它分配内存。如果不使用SetSize,则为数组添加元素就会引起频繁地重新分配和拷贝。频繁地重新分配和拷贝不但没有效率,而且导致内存碎片
如果需要一堆数组中的个别数据,必须设置CDumpContext对象的深度为1或更大。
此类的某成员函数调用全局帮助函数,它必须为CArray的大多数使用而定制。请参阅宏和全局量部分中的“类收集帮助器”。
当从一个CArray对象中移去元素时,帮助函数DestructElements被调用。
当添加元素时,帮助函数ConstructElements被调用。
数组类的派生与列表的派生相似。
MFC提供了一套模板库,来实现一些比较常见的数据结构如Array,List,Map。CArray即为其中的一个,用来实现动态数组的功能。CArray是从CObject派生,有两个模板参数,第一个参数就是CArray类数组元素的变量类型,后一个是函数调用时的参数类型。有一个类 class Object,要定义一个Object的动态数组,那么可以用以下两种方法:
CArray<Object,Object> Var1;
CArray<Object,Object&> Var2;
Var2的效率要高。
先了解一下CArray中的成员变量及作用。TYPE* m_pData; // 数据保存地址的指针
int m_nSize; // 用户当前定义的数组的大小
int m_nMaxSize; // 当前实际分配的数组的大小
int m_nGrowBy; // 分配内存时增长的元素个数
构造函数,对成员变量进行了初始化。
CArray<TYPE, ARG_TYPE>::CArray()
{
m_pData = NULL;
m_nSize = m_nMaxSize = m_nGrowBy = 0;
}
SetSize成员函数是用来为数组分配空间的。SetSize的函数定义如下:
void SetSize( int nNewSize, int nGrowBy = -1 );
nNewSize 指定数组的大小
nGrowBy 如果需要增加数组大小时增加的元素的个数。
对SetSize的代码,进行分析。
void CArray<TYPE, ARG_TYPE>::SetSize(int nNewSize, int nGrowBy)
{
if (nNewSize == 0)
{
// 第一种情况
// 当nNewSize为0时,需要将数组置为空,
// 如果数组本身即为空,则不需做任何处理
// 如果数组本身已含有数据,则需要清除数组元素
if (m_pData != NULL)
{
//DestructElements 函数实现了对数组元素析构函数的调用
//不能使用delete m_pData 因为我们必须要调用数组元素的析构函数
DestructElements<TYPE>(m_pData, m_nSize);
//释放内存
delete[] (BYTE*)m_pData;
m_pData = NULL;
}
m_nSize = m_nMaxSize = 0;
}
else if (m_pData == NULL)
{
// 第二种情况
// 当m_pData==NULL时还没有为数组分配内存
//首先我们要为数组分配内存,sizeof(TYPE)可以得到数组元素所需的字节数
//使用new 数组分配了内存。注意,没有调用构造函数
m_pData = (TYPE*) new BYTE[nNewSize * sizeof(TYPE)];
//下面的函数调用数组元素的构造函数
ConstructElements<TYPE>(m_pData, nNewSize);
//记录下当前数组元素的个数
m_nSize = m_nMaxSize = nNewSize;
}
else if (nNewSize <= m_nMaxSize)
{
// 第三种情况
// 这种情况需要分配的元素个数比已经实际已经分配的元素个数要少
if (nNewSize > m_nSize)
{
// 需要增加元素的情况
// 与第二种情况的处理过程,既然元素空间已经分配,
// 只要调用新增元素的构造函数就Ok
ConstructElements<TYPE>(&m_pData[m_nSize], nNewSize-m_nSize);
}
else if (m_nSize > nNewSize)
{
//元素减少的情况下,我们是否要重新分配内存呢?
// No,这种做法不好,后面来讨论。
// 下面代码释放多余的元素,不是释放内存,只是调用析构函数
DestructElements<TYPE>(&m_pData[nNewSize], m_nSize-nNewSize);
}
m_nSize = nNewSize;
}
else
{
//这是最糟糕的情况,因为需要的元素大于m_nMaxSize,
// 意味着需要重新分配内存才能解决问题
// 计算需要分配的数组元素的个数
int nNewMax;
if (nNewSize < m_nMaxSize + nGrowBy)
nNewMax = m_nMaxSize + nGrowBy;
else
nNewMax = nNewSize;
// 重新分配一块内存
TYPE* pNewData = (TYPE*) new BYTE[nNewMax * sizeof(TYPE)];
//实现将已有的数据复制到新的的内存空间
memcpy(pNewData, m_pData, m_nSize * sizeof(TYPE));
// 对新增的元素调用构造函数
ConstructElements<TYPE>(&pNewData[m_nSize], nNewSize-m_nSize);
//释放内存
delete[] (BYTE*)m_pData;
//将数据保存
m_pData = pNewData;
m_nSize = nNewSize;
m_nMaxSize = nNewMax;
}
}
下面是ConstructElements函数的实现代码template<class TYPE>
AFX_INLINE void AFXAPI ConstructElements(TYPE* pElements, int nCount)
{
// first do bit-wise zero initialization
memset((void*)pElements, 0, nCount * sizeof(TYPE));
for (; nCount--; pElements++)
::new((void*)pElements) TYPE;
}
ConstructElements是一个模板函数。对构造函数的调用是通过标为黑体的代码实现的。可能很多人不熟悉new 的这种用法,它可以实现指定的内存空间中构造类的实例,不会再分配新的内存空间。类的实例产生在已经分配的内存中,并且new操作会调用对象的构造函数。因为vc中没有办法直接调用构造函数,而通过这种方法,巧妙的实现对构造函数的调用。
再来看DestructElements 函数的代码template<class TYPE>
AFX_INLINE void AFXAPI DestructElements(TYPE* pElements, int nCount)
{
for (; nCount--; pElements++)
pElements->~TYPE();
}
DestructElements函数同样是一个模板函数,实现很简单,直接调用类的析构函数即可。
如果定义一个CArray对象 CArray<Object,Object&> myObject ,对myObject就可象数组一样,通过下标来访问指定的数组元素。
CArray[]有两种实现,区别在于返回值不同。
template<class TYPE, class ARG_TYPE>
AFX_INLINE TYPE CArray<TYPE, ARG_TYPE>::operator[](int nIndex) const
{ return GetAt(nIndex); }
template<class TYPE, class ARG_TYPE>
AFX_INLINE TYPE& CArray<TYPE, ARG_TYPE>::operator[](int nIndex)
{ return ElementAt(nIndex); }
前一种情况是返回的对象的实例,后一种情况是返回对象的引用。分别调用不同的成员函数来实现。
TYPE GetAt(int nIndex) const
{ ASSERT(nIndex >= 0 && nIndex < m_nSize);
return m_pData[nIndex]; }
TYPE& ElementAt(int nIndex)
{ ASSERT(nIndex >= 0 && nIndex < m_nSize);
return m_pData[nIndex]; }
除了返回值不同,其它都一样.
CArray<int,int&> arrInt;
arrInt.SetSize(10);
int n = arrInt.GetAt(0);
int& l = arrInt.ElementAt(0);
cout << arrInt[0] <<endl;
n = 10;
cout << arrInt[0] <<endl;
l = 20;
count << arrInt[0] << endl;
结果会发现,n的变化不会影响到数组,而l的变化会改变数组元素的值。实际即是对C++中引用运算符的运用。
CArray下标访问是非安全的,它并没有超标预警功能。虽然使用ASSERT提示,但下标超范围时没有进行处理,会引起非法内存访问的错误。
Add函数的作用是向数组添加一个元素。下面是它的定义: int CArray<TYPE, ARG_TYPE>::Add(ARG_TYPE newElement).Add函数使用的参数是模板参数的二个参数,也就是说,这个参数的类型是我们来决定的,可以使用Object或Object&的方式。熟悉C++的朋友都知道,传引用的效率要高一些。如果是传值的话,会在堆栈中再产生一个新的对象,需要花费更多的时间。
template<class TYPE, class ARG_TYPE>
AFX_INLINE int CArray<TYPE, ARG_TYPE>::Add(ARG_TYPE newElement)
{
int nIndex = m_nSize;
SetAtGrow(nIndex, newElement);
return nIndex;
}
它实际是通过SetAtGrow函数来完成这个功能的,它的作用是设置指定元素的值。
template<class TYPE, class ARG_TYPE>
void CArray<TYPE, ARG_TYPE>::SetAtGrow(int nIndex, ARG_TYPE newElement)
{
if (nIndex >= m_nSize)
SetSize(nIndex+1, -1);
m_pData[nIndex] = newElement;
}
SetAtGrow的实现也很简单,如果指定的元素已经存在,就把改变指定元素的值。如果指定的元素不存在,也就是 nIndex>=m_nSize的情况,就调用SetSize来调整数组的大小
首先定义
CArray<char *> arryPChar;
这里以定义char*的为例子。
接下来我们来熟悉CArray这个类里的函数。
INT_PTR GetCount() const;
获得当前这个数组有多少个元素。
void SetSize(INT_PTR nNewSize, INT_PTR nGrowBy = -1);
设置数组的大小。
TYPE& GetAt(INT_PTR nIndex);
void SetAt(INT_PTR nIndex, ARG_TYPE newElement);
获得/设置序列的元素
INT_PTR Add(ARG_TYPE newElement);
在数组的末尾添加一个元素,数组的长度加1。如果之前使用SetSize是nGrowBy大于1,则内存按照nGrowBy增加。函数返回newElement的数组元素索引
void RemoveAt(INT_PTR nIndex, INT_PTR nCount = 1);
从指定的nIndex位置开始,删除nCount个数组元素,所有元素自动下移,并且减少数组的上限,但是不释放内存。这里我们自己手动的申请的就必须自己释放。new对应delete相信大家都知道的。
void RemoveAll();
从数组中移除素有的元素,如果数组为空,该行数也起作用。
INT_PTR Append(const CArray& src);
将同个类型的一个数组A附加到本数组的尾部,返回A第一数组元素在本数组的索引
void InsertAt(INT_PTR nIndex, ARG_TYPE newElement, INT_PTR nCount = 1);
void InsertAt(INT_PTR nStartIndex, CArray* pNewArray);
在指定的nIndex或者nStartIndex位置插入nCount个newElement数组元素或者pNewArray数组
下面是我应用的实例:
view plaincopy to clipboardprint?
CArray <char*>arrPChar;
//初始化元素
arrPChar.SetSize(10);
for (int i=0;i<10;i++)
{
char *aChar=new char[10];
strcpy_s(aChar,10,"hello arr");
arrPChar.SetAt(i,aChar);
}
//在数组的末尾插入一个元素
char *bChar = new char[10];
strcpy_s(bChar,10,"asdfefdsd");
arrPChar.Add(bChar);
//在索引2的位置插入一个元素,即在第三位插入一个元素
char *cChar=new char[5];
strcpy_s(cChar,5,"aidy");
arrPChar.InsertAt(2,cChar);
for (int j=0;j<arrPChar.GetCount();j++)
{
TRACE("%d,%s\n",j,arrPChar.GetAt(j));
}
//删除数组里的所有元素,要释放内存,如果单单Remove的话则内存不会被释放
//这里因为使用RemoveAll的话内存无法被释放,所以没有给实例。
int count = arrPChar.GetCount();
for (int k=0; k<count; k++)
{
char *dChar=arrPChar.GetAt(0);
arrPChar.RemoveAt(0);
delete dChar;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章