MFC【5】MFC集合类

时间:2023-01-23 05:04:26

MFC集合类现在来看已经很落后了。

5.1数组

5.1.1MFC数组类

CArray类,它实际是一个模板类,利用它可以创建人和数据类型的类型安全数组。在头文件Afxtempl.h中定义了CArray。其次是非模板化的数组类,分别为保存特定类型的数据而设计。这些类在Afxcoll.h中定义。表5-1中列出了非模板化的MFC数组类以及他们所保存的数据类型。

表5-1

类名 数据类型
CByteArray 8位字节(BYTE)
CWordArray 16位字节(WORD)
CDWordArray 32位双子(DWORD)
CUIntArray 无符号整型(UINT)
CStringArray CString
CPtrArray void指针
CObArray CObject指针

他们共享共用的一组成员函数。下列声明了一个办好10个UINT的数组并用数字1到10对它进行了初始化:

CUIntArray array;
array.SetSize(10);
for(int i = 0;i<10;i++)
  array[i]=i+1;

可以采用同样的方法来声明一个CStrings数组并用整数1到10文本表示来初始化它:

CStringArray array;
array.SetSize(10);
for(int i= 0;i<10;i++){
  CString string;
  string.Format(_T("%d"),i);
  array[i]=string;
}

在两个例子中都是用SetSize来指定数组包含10个元素;重载"[]"运算符调用数组的SetAt函数,该函数将值赋值到数组中指定位置处的元素中;如果数组边界非法,程序将执行断言处理。边界检查内置在SetAt代码中:

ASSERT(nIndx>=0&&nIndex<m_nSize);

在MFC源程序文件Afcoll.inl中您可以看到此代码。

可以使用InserAt函数在不覆盖已有数组想的情况下给数组插入元素项。与SetAt不同,它只给以存在的数组元素赋值,InserAt还要给新的元素分配空间,如果把数组中插入点上方的元素向上移动来完成。下列语句用数字1到4和6到10初始化一个数组并在数字4和6之间插入5:

CUIntArray array;
array.SetSize(9);
for(int i=0;i<4;i++)
  array[i]=i+1;
for(int = 4;i<9;i++)
  array[i]=i+2;
array.InsertAt(4,5);//Insert a 5 at index 4

还可以给InsertAt传递第三个参数指定元素项被插入的次数,或是在第二个参数中传递指向另一个数组对象的指针来插入整个数组。在本例中数组的大小为9个元素而不是10个,而在调用InsertAt时却没有执行断言处理。这是因为InsertAt是那些便于使用额函数之一,它们在新的元素项添加到数组中时自动增大数组尺寸。

使用标注你数组寻址语法可以在MFC数组中检索索要的值。下列将读取先前例子中写入CUIntArray中的UINT:

  for(int i=0;i<10;i++)
    UINT nVal = array[i];

  使用此方法,[]运算符将调用数组的GetAt函数,该函数从数组中的指定位置取回一个值.

  要确定数组包含元素的个数,可以调用数组的GetSize函数。还可以调用GetUpperBound返回数组的上界下标,因为下标从0开始,所以其值为数组元素总数减1.

  MFC的数组类为从数组中删除元素提供了两个函数:RemoveAt和RemoveAll。RemoveAt从数组中删除一个以上的元素项并将删除元素项上边的所有元素项向下移动。RemoveAll清空整个数组。两个函数都将调整数组的上界从而反映出被删除的元素项个数,说明如下:

  //Add 10 items.
  CUIntArray array;
  array.SetSize(10);
  for(int i=0;i<10;i++)
     array[i]=i+1;
  //Remove the item at index 0
  array.RemoveAt(0);
  TRACE(_T("Count = %d\n"),array.GetSize());//9 left
  //Remove items 0,1,and 2
  array.RemoveAt(0,3);
  TRACE(_T("Count = % d\n"),array.GetSize());//6 left
  //Empty the array.
  array.RemoveAll();
  TRACE(_T("Count = % d\n"),array.GetSize());//0 left

Remove函数删除元素,但是如果元素是指针是它并不删除指针所指的对象。如果数组是CPtrArray或CObArray类型的,要清空数组并删除被删除指针所指的对象,就不应该写成:

array.RemoveAll();

要写成:

int nSize=array.GetSize();
for(int i=0;i<nSize;i++)
  delete array[i];
array.RemoveAll();

如果对地址保存在指针数组中的对象删除失败,就会导致内存漏损。

5.1.2动态调整数组大小

除了可以便捷检查外,MFC数组类还支持动态调整大小。由于为保存数组元素而分配的内存可以根据元素的添加或删除而增大或减少,所以没必要预见动态调整制度的数组具有多少元素。

一种动态增大MFC数组的方法是调用SetSize。可以在人和需要的时候调用SetSize来分配额外的内存。假设开始是给数组设置了10个元素项,后来却发现需要20个。这是只要第二次调用SetSize给额外的项分配空间即可:

//Add 10 items.
CUIntArray array;
array.SetSize(10);
for(int i=0;i<10;i++)
  array[i]=i+1;
.
.
.
//Add 10 more
array.SetSize(20);
for(i=10;i<20;i++)
  array[i]=i+1;

用此方法调整数组大小事,原来的项依旧保持他们的值。

另一种增大数组的方法是调用SetAtGrow而不是SetAt来增加元素项。例如:下列代码试图用SetAt给UINT数组添加10个元素项:

CUIntArray array;
for(int i=0;i<10;i++)
  array.SetAt(i+1);

程序会在第一次调用SetAt是就执行断言处理。因为数组大小为零时,SetAt不会自动增大数组来容纳新的元素。但是,将SetAt更改为SetAtGrow后,程序将顺利执行:

CUIntArray array;
for(int i=0;i<10;i++)
  array.SetAtGrow(i,i+1);

与SetAt不同,SetAtGrow会在必要时自动增大数组的内存分配空间。Add函数也是这样,它将元素项添加到数组的末尾。下一个例子的功能与上一个相同,只是使用了Add而不是SetAtGrow来给数组添加元素:

CUIntArray array;
for(int i=0;i<10;i++)
array.Add(i+1);

其他可以自动增大数组来容纳新元素项的函数还包括:InsertAt,Append(将一个数组附加给另一个数组)以及Copy,顾名思义,它将一个数组复制到另一个数组中。

MFC增大数组是通过分配新的内存缓冲区并将元素项从就缓冲区中赋值过去来实现的。如果由于内存不足时数组增大操作失败,则MFC会产生异常事件。为了在错误产生式捕获它们,要在try模块中封存扩大数组的调用命令,同时添加一个处理CmemoryExceptions的catch处理程序:

try{
  CUIntArray array;
  array.SetSize(1000);//Might throw a CMemoryException.
.
.
.
}
catch(CMemoryException *e){
AfxMessageBox(_T("Error: Insufficient memory"));
e->Delete();//Delete the exception object.
}

catch处理程序显示一个出错消息,警告用户系统内粗年不足。在实际中,要想成功第跳出这种内存不足的状态还需要更进一步的处理。

由于每当数组尺寸增加是都要分配新的内粗你,所以太频繁第增大数组会对操作产生不好的影响并有可能导致产生内存碎片。考虑一下如下程序片段:

CUIntArray array;
for(int i = 0; i<100000; i++)
  array.Add(i+1);

这些语句看上去非常正确,但它们的效率不高,要申请分配成千上万个独立的内存,这也正是MFC让您在SetSize中可选的第二个参数内指定“增加量”的原因。下列代码更有小帝初始化了一个数组,它高速MFC在需要申请更多的内存时为10000个新的UINT分配空间:

CUIntArray array;
array.SetSize(0,10000);
for(int i=0;i<100000;i++)
  array.Add(i+1);

当然,要是预先能给100000个元素项分配空间,那么程序的效率会更高一些。但更经常的是不可能预见到数组要保存元素的数量。如果能预计到要给数组增加许多元素却不能确定到底会需要多少空间,那么指定大的增加量是有益的。

  如果您没有指定增加量,MFC会通过基于数组支持得到简单公式为您选择一个值。数组越大,增加量也越大。如果指定数组尺寸为零,并且根本没有调用SetSize,那么默认增加量为4项。在上一段的两个例子中,第一个的for循环对内存的分配和再分配不少于25000次。而将增加量设置为10000就是的内存分配数减少到了10次。

  同样一个用来增大数组的SetSize函数也可以用来减少数组元素。但是,当它减小数组元素时,SetSize并不会自动缩小保存数组数据的缓冲区。在调用数组的FreeExtra函数之前不会释放内存,说明如下:

array.SetSize(50);//Allocate room for 50 elements.
array.SetSize(30);//Shrink the array size to 30 elements.
array.FreeExtra();//Shrink the buffer to fit exactly 30 elements.

5.1.3用CArray创建类型安全数组类

 CUIntArray,CStringArray以及其他MFC数组类都是针对特定数据类型的。如果假设需要一个其他数据类型的数组,例如CPoint对象,由于不存在CPointArray类,所以必须从MFC的CArray类中自己创建了。CArray是一个模板类,用它可以为任意的数据类型创建类型安全数组类。

为了明了起见,下列给出的程序实例中声明了一个CPoint对象的类型安全数组,并对类进行了实例化,然后描述线段的CPoints数组将其初始化:

CArray<CPoint,CPoint&> array;
//Populate the array,growing it as needed
for(int i=0;i<10;i++)
  array.SetAtGrow(i,CPoint(i=10,0));
//Enumerate the items in the array
int nCount =array.GetSize();
for(i=0;i<nCount;i++){
CPoint point =array[i];
   TRACE(_T("x = % d,y = %d\n"),point.x,point.y);
}

CArray模板中的第一个参数制定了保存在数组中的数据类型,第二个参数指定类型在参数列表中的表示方法。可以使用CPoints来取代CPoint引用,但在元素项的尺寸超出指针的尺寸时,使用引用会更有效。

在CArray模板参数中可以使用任何种类的数据。下例中声明了一个代表三维点的类并用10个类实例填充了数组:

class CPoint3D
{
  public:
  CPoint3D()
  {
    x=y=z=0;
  }
  CPoint 3D(int xPos,int yPos, int zPos)
  {
    x=xPos;
    y=yPos;
    z=zPos;
  }
  int x,y,z;
};
CArray<CPoint3D,CPoint3D&>array;
//Populate the array,growing it as needed.
for(int i=0;i<10;i++)
  array.SetAtGrow(i,CPoint3D(i*10,0,0));
//Enumerate the items in the array
int nCount =array.GetSize();
for(i=0;i<nCount;i++){
  CPoint3D point =array[io];
  TRACE(_T("x = %d,y=%d,z=%d\n"),point.x,point.y,point.z);
}

使用CArray和其他基于模板的MFC集合类工作的时候,在创建的类中包含默认的构造函数很重要,因为MFC在类似InsertAt这样的函数被调用时会使用类的默认构造函数来创建新的元素项。

5.2列表

5.2.1MFC列表类

MFC的模板类CList实现了一半的链表,用它可以自定义处理任何数据类型。MFC还提供了表5-2中列出的处理特定数据类型的非模板列表类。这些类主要用于与MFC就版本兼容,在现代MFC应用程序中并不经常使用。

表5-2 特定类型的MFC列表类

类名 数据类型
CObList CObject指针
CPtrList  void指针
CStringList CString

MFC列表是双向链接的,便于前后移动操作。列表中的位置由抽象数值POSITION标识。对于列表,POSITION实际上是指向CNode数据结构的指针,该结构代表了列表中的列表项。CNode包含了三个字段n:yige指向列表中下一个CNode结构的指针,一个指向上一个CNode结构的指针以及一个指向列表项数据的指针。无论是在列表头还是列表为,或者在POSITION指定的任何位置,插入操作都是快速高效的。还可以对列表进行查询操作,但是由于查询设计到顺序遍历列表并逐个检查列表项,所以要是列表很大的话会占用很多时间。

我们借助CStringList来说明列表类的使用方法,但要注意这里讲的许多原则对于其他列表类也是适用的。在下例中创建了一个CStringList对象并给他添加了10个字符串:

//Schools of the Southeastern Conference
const TCHAR szSchools[][20] = {
_T("Alabama"),
_T("Arkansas"),
_T("Florida"),
_T("Georgia"),
_T("Kentucky"),
_T("Mississippi"),
_T("Mississippi State"),
_T("South Carolina"),
_T("Tennessee"),
_T("Vanderbilt")};
CStringList list;
for(int i=0;i<10;i++)
  list.AddTail(szSchools[i]);

AddTail函数在列表结尾处添加了一个列表项(或者是另一个链接列表中所有列表项)。要给列表头添加列表项可以使用AddHead函数。在列表头或为删除列表项同样简单,只要调用RemoveHead或RemoveTail即可。RemoveAll函数一下删除所有的列表项。

每次给CStringList添加一个字符串时,MFC都会讲字符串赋值给CString并在相应的CNode结构中保存它。因此,用来初始化列表的字符串超出创建烈表示设定的范围是完全可以接受的。

一单列表创建成功,就可以使用GetNext和GetPrev函数通过迭代在列表中前后移动了,两个函数都接收表示列表中当前位置的POSITION值并返回该位置处的列表项。两者都要更新POSITION值来引用下一个或上一个列表项。可以使用GetHeadPosition或GetTailPosition来检索列表头和为的POSITION。下列语句从头至尾列举了列表中的列表项,并通过MFC的TRACE宏将从列表中获得的字符串输出到调试窗口:

POSITION pos = list.GetHeadPosition();
while(pos != NULL){
  CString string = list.GetNext(pos);
  TRACE(_T("%S \n"),string);
}
//Walking the list backward is equally simple:
POSITION pos = list.GetTailPosition();
while(pos!=NULL){
  CString string = list.GetPrive(pos);
  TRACE(_T("%s\n"),string);
}

如果给标识特定列表项的POSITION值pos,就可以使用列表的At函数来检查、修改或删除它:

CString string = list.GetAt(pos);//Retrieve the item,
list.SetAt(pos._T("Florida State"));//Change it.
list.RemoveAt(pos);//Delete it.

还可以使用InsertBefore或InsertAfter在列表中插入列表项:

list.InsertBefore(pos,_T("Florida State"));//Insert at pos
list.InsertAfter(pos,_T("Florida State"));//Insert after pos

MFC的列表包含两个成员函数,可以用来执行查找操作。FindIndex接受从0开始的索引号并返回列表中相应位置处的列表项POSITION。Find查找与制定输入匹配的列表项并返回它的POSITION。对于字符串列表,Find比较字符串。对于指针列表,它比较指针,但并不寻找和比较指针所指的列表项。要在字符串列表中查找“Tennessee”:

 

POSITION pos = list.Find(_T("Tennessee"));

 

在默认状态下,Find从头至尾查找列表。如果愿意,可以在函数第二个可选的参数中指定查找的起始点。但要注意,如果要找的列表项在起点POSITION的前面,Find就不会找到它了,因为它不会反悔到列表的开始进行查找。GetCount函数了解列表中元素的个数。如果GetCount返回0,说明列表是空的。而检测孔列表的快捷方式是调用IsEmpty。

5.2.2用CList创建类型安全列表类

可以利用MFC的CList类为所选的任何数据类型创建安全列表类。下面给出一个CPoint对象的链接列表:

CList<CPooint,CPoint&>list;
//Populate the list
for(int i =0;i<10;i++)
  list.AddTail(CPoint(i*10,0));
//Enumerate the items in the list
POSITION pos = list.GetHeadPosition();
while(pos!=NULL){
  CPoint point = list.GetNext(pos);
  TRACE(_t(" x = %d , y = %d \n"),point.x,point.y);
}

与CArray相同,第一个模板参数制定了数据类型(CPoint对象),第二个参数之处参数列表中列表项的传送方式。如果CList中使用了类而不是原始数据类型并且调用列表的Find函数,除非下列条件之一成立否则程序不会的到编译:

  类具有重载了的==运算符,执行与相似对象的比较。

  用特殊类型的版本覆盖了模板函数CompareElements,执行对两个类实例的比较。

  第一种方法更常用,在MFC类如CPoint和CString中已经为您实现了。如果自己亲自编写一个雷,就必须进行运算符重载。下面给出一种修改后的CPoint3D实现,为与CList::Find兼容而重载了比较运算符:

class CPoint3D
{
  public:
    CPoint3D()
    {
      x = y = z = 0; 
    }
    CPoint3D(int xPos,int yPos, int zPos)
    {
      x = xPos;
      y = yPos;
      z = zPos;
    }
    operator==(CPoint3D point) const
    {
      return(x==point.x&&y==point.y&&z==point.z);
    }
    int x,y,z;
};

除了重载比较运算符外,还可以覆盖全局CompareElements函数,如下:

class CPoint3D
{
  public:
    CPoint3D()
    {
      x = y = z = 0;
    }
    CPoint3D(int xPos,int yPos,int zPos)
    {
      x = xPos;
      y = yPos;
      z = zPos;
    }
    //Note:No operator ==
    int x,y,z;
}

BOOL AFXAPI CompareElements(const CPoint3D*p1,const CPoint3D*p2)
{
  return (p1->x==p2->x&&p1->y==p2->y&&p1->z==p2->z);
}

覆盖CompareElements消除了对重载运算符的需要,这是因为默认的CompareElements实现在被CList::Find调用时,将使用比较运算符来比较列表项。

5.3映射表

映射表也称字典,其中一种项目是另一种项目的关键字。如果查找操作是首要工作,那么映射表就是大量数据的理想容器。

5.3.1MFC映射表类

除了基于模板的映射表类CMap以外,它可以被用来处理特殊的数据类型,MFC还提供了表5-3中列出的特定类型(不是基于模板的)映射表类。每个类都包含如下成员函数:添加和删除项目,拖过关键字检索项目以及美剧映射表中的所有项目。

表5-3 特定类型的MFC映射表类

类名 说明
CMapWordToPtr 保存void指针,关键字为WORD 
CMapPtrToWord 保存WORD,关键字为void指针 
CMapPtrToPtr  保存void指针,关键字为void指针 
CMapWordToOb  保存CObject指针,关键字为WORD 
CMapStringToOb 保存CObject指针,关键字为字符串 
CMapStringToPtr  保存void指针,关键字为字符串 
CMapStringToString 保存字符串,关键字为字符串 

 为了说明使用映射表的语法规则,下面我们使用CMapStringToString创建一个简单的包含一周各天名称的英法词典。下列语句生成一个映射表:

CMapStringToString map;
map[_T("Sunday")] =_T("Dimanche");
map[_T("Monday")] =_T("Lundi");
map[_T("Tuesday")] =_T("Mardi");
map[_T("Wednesday")] =_T("Mercredi");
map[_T("Thursday")] =_T("Jeudi");
map[_T("Friday")] =_T("Vendredi");
map[_T("Saturday")] =_T("Samedi");

在本例中,保存在映射表中的项目是法语一周各天的名称。二每个项目肚饿关键字对应的是英语名称的字符串。因为CMapStringToString在CString对象中保存关键字和项目,所以插入项目就是讲项目文本和关键字文本复制给CStrings.

对于这样初始化了的映射表,现在要检索星期四的法语名称。您可以通过调用映射表的Lookup函数并制定关键字来进行查找:

CString string;
if(map.Lookup(_T("Thursday"),string)
  TRACE(_T("Thursday in English =%s in Frensh\n"),string);

从Lookup返回非零值说明成功检索到项目了。返回为零表示该项目不存在,也就是说,没有项目的额关键字Lookup的第一个参数指定的关键字。

可以使用RemoveKey和RemoveAll从映射表中删除项目。GetCount返回映射表中项目的数量,IsEmpty检查映射表是否包含任何项目。使用GetStartPosition和GetNextAssoc可以逐项美剧映射表中的内容:

POSITION pos = map.GetStartPosition();
while(pos!=NULL){
CString strKey,strItem;
map.GetNextAssoc(pos,strKey,strItem);
TRACE(_T("Key = %s, Item =$ \n"),strKey,strItem);
}

运行以上给出的CMapStringToString对象,程序会产生以下输出:

Key = Tuesday,Item = Mardi
Key = Saturday,Item = Samedi
Key = Wednesday,Item = Mercredi
Key = Thursday,Item = Jeudi
Key = Friday, Item =Vendredi
Key = Monday, Item =Lundi
Key = Sunday, Item = Dimanche

如上所示,项目并没有必要按加入时的顺序保存。

如果您在给映射表插入项目时,所插入的项目具有与以前插入项目相同的关键字,那么新的项目将会取代旧的。MFC映射表不可能包含由同一个关键字标识的两个以上的项目。

5.3.2映射表工作方式

在映射表生成后不久(通常是在添加第一个项目是后之前),会为一个散列表分配内存空间,该表实际上是一个纸箱CAssoc结构指针的数组。MFC使用CAssoc结构来代表给映射表加入的项目(和关键字)。对于CMapStringToString 如下定义CAssoc:

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

无论何时项目被加入映射表,就会创建一个新的CAssoc结构,根据项目的关键字计算散列值,纸箱CAssoc结构的指针被复制到索引号为i的散列表中,其中i由以下公式计算得到:

i=nHashValue%nHashTableSize

nHashVaue是从关键字计算来的散列值:nHashTableSize是散列表中的元素个数。默认散列表大小为17个输入项。如果偶尔索引号i出的元素已经包含了一个CAssoc指针,MFC就会创建一个CAssoc结构的单项链接列表。列表中第一个CAssoc结构的地址保存在散列表中。第二个CAssoc结构的地址保存在第一个CAssoc结构的pNext字段,如此等等。

当调用映射表的Lookup函数是,MFC根据输入关键字计算散列值,使用上面给出的公式将散列值转换为散列表中的的索引号,并从散列表的相应位置检索得到CAssoc指针。在理想情况下,在要找的位置处只有一个CAssoc指针,而不是CAssoc指针链接列表。在这种情况下,只要在映射表中进行一次查找就可以找到项目,它的值可以有CAssoc对象得到。如果从散列表中得到的CAssoc指针是链接列表表头,MFC就会查找该列表知道找到要找的关键字。一个适当创建的映射表在他的CAssoc结构列表中不会具有超过两个或三个项目元素,这就意味着查找过程不会要求检查多于两个或三个项目。

5.3.3提高查找效率

执行查找的效率取决于两个因素:

  散列表的大小。

  散列表算法根据任意输入的关键字生成唯一散列表值得能力。

Microsoft建议将散列表的尺寸设置为避映射表所包含项目的总数大百分之十到二十。这样可以合理地平衡内存消耗和查找效率。要指定散列表尺寸,可以调用映射表的InitHashTable函数:

//Assume the map will hold about 1,000 items
map.InitHashTable(1200);//1200 = 1000 +20 percent

从统计上将,对散列表尺寸使用指数会更好地减少冲突。所以对于1000个项目,初始化一个散列表最好按如下方式调用InitHashTable:

map.InitHashTable(1201);

应该在给映射表添加任何项目之前调用InitHashTable.在映射表已经包含了一个以上项目是试图调整散列表的大小会导致断言错误。

虽然MFC使用的用来生成散列值的算法在大多数情况下已经够用, 但您还是可以用自己的算法替换他。要转换一个输入关键字,MFC会调用名为HashKey的全局模板函数。对于大多数数据类型,HashKey的实现如下:

AFX_INLINE UINT AFX API HashKey(ARG_KEY key)
{
  //default identity hash-works for most primitive values
  return((UINT)(void*)(DWORD)key)>>4;
}

对于字符串,它的实现如下:

UINT AFXAPI HashKey(LPCWSTR key)//Unicode strings
{
  UINT nHash = 0;
  while(*key)
    nHash =(nHash<<5)+nHash+*key++;
  return nHash;
}

要想对特殊的数据类型实现自己的算法,只要编写特定类型HashKey函数即可。您可以将上面给出的字符串HashKey作为模型使用。

5.3.4用CMap创建类型安全映射表类

可以使用MFC的CMap模板类来为那些特定类型映射表类不支持的数据类型创建叶舌表。下例创建了一个CPoint对象的集合,关键字为CString并执行查找操作:

CMap<CString,CString&,CPoint,CPoint&> map;
map[CString(_T("Vertex1"))] = CPoint(0,0);
map[CString(_T("Vertex2"))] = CPoint(100,0);
map[CString(_T("Vertex3"))] = CPoint(100,100);
map[CString(_T("Vertex4"))] = CPoint(0,100);
CPoint point;
if(map.Lookup(CString(_T("Vertex3")),point))
  TRACE(_T("Vertex 3 =(%d,%d)\n)",point.x,point.y);

因为使用CString作为关键字,所以除非讲HashKey覆盖实现吧CString转换成散列值功能,否则次程序不会得到编译。下列是可能的一种实现:

UINT AFXAPI HashKey(CString&string)
{
  LPCTSTR key = (LPCTSTR) string;
  UINT nHash = 0;
  while(*key)
    nHash=(nHash<<5)+nHash+*key++;
  return nHash;
}

在把CString引用转换为常规字符串指针之后,次程序就可以像MFC的LPCSTR/LPCWSTR HashKey函数那样讲字符串转换为散列值了。

如同CList类的Find函数一样,CMap::Lookup使用CompareElements模板函数来比较元素。由于CompareElements使用了==运算符执行比较,所以对于原始数据类型和重载了==运算符的类其默认实现就是很好用。但是,如果使用自己设计的类作为映射表中的关键字,就必须在这些类中重载==运算符或对单独的数据类型覆盖CompareElements。

5.4类型指针类

名字中带有Ptr和Ob的MFC集合类可以方便地实现保存一般(void)指针的容器和保存指向MFC对象(由Object派生类创建的对象)指针的容器。

MFC的“类型指针类”是一组三个模板类,用来以安全的方式处理指针集合,它为保存指针二不危害类型安全问题提供了一种简便的解决方法。在表5-4中列出了类型安全指针类。

表5-4指针类的合集

类名 说明
CTypedPtrArray 管理指针数组
CTypedPtrList 管理指针链接列表
CTypedPtrMap 管理使用指针作为项目或关键字的映射表

假设您编写了一个绘图程序,并且创建了一个名为CLine的类来代表屏幕上绘制的线段。每次用户绘制一条线就创建一个新的CLine对象。您需要一个地方来保存CLine指针,而且由于希望能够在集合的任何位置添加和删除指针都不会造成操作冲突,所以您决定使用链接列表。因为是从CObject派生的CLine,所以CObList好像是个自燃的选择。

CObList可以完成任务,但是每次从列表中检索到一个CLine指针,都必须将它强制转换为CLine*,因为CObList返回的是CObject指针。CTypedPtrList是一个很好的选择,它不需要类型强制转换。下列程序代码说明了着一点:

CTypedPtrList<CObList,CLine*> list;
//Populate the list
for(int i=0;i<10;i++){
  int x= i*10;
  CLine * pLine =new CLine(x,0,x100);
  list.AddTal(pLine);
}
//Enumerate the items in the list
POSITION pos = list.GetHeadPosition();
while(pos!= NULL)
  CLine*pLine=list.GetNext(pos);//No casting!

档您使用GetNext检索一个CLine指针是,得到的就是一个CLine指针而不需要强制转化。这就是类型安全。

  CTypedPtrList和其它类型安全指针类要从第一个模板参数制定的类中实现派生。在派生类内部,类型安全成员桉树封装了基类中相应的成员函数。可以在基类或派生类中调用任意个函数,但是吐过他们重叠,通常就要使用函数的类型安全实现版本。一般情况下,对于其保存的指针是从指向CObject拍摄功能来的对象集合,要使用Ob类作为基类;而对于其保存的指针是只想起他类型的对象的集合,就要使用Ptr类作为基类。

注:如果您不删除CLines,没有人会为您删除。