XSLT存档  

不及格的程序员-八神

 查看分类:  ASP.NET XML/XSLT JavaScripT   我的MSN空间Blog
//指针类型成员的汇编代码    for (; nCount--; pElements++)
004354D0 EB 09                jmp         ConstructElements+6Bh (004354db)
004354D2 8B 55 08             mov         edx,dword ptr [ebp+8]
004354D5 83 C2 04             add         edx,4
004354D8 89 55 08             mov         dword ptr [ebp+8],edx
004354DB 8B 45 0C             mov         eax,dword ptr [ebp+0Ch]
004354DE 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]
004354E1 83 E9 01             sub         ecx,1
004354E4 89 4D 0C             mov         dword ptr [ebp+0Ch],ecx
004354E7 85 C0                test        eax,eax
004354E9 74 13                je          ConstructElements+8Eh (004354fe)
62:           ::new((void*)pElements) TYPE;
004354EB 8B 55 08             mov         edx,dword ptr [ebp+8]
004354EE 52                   push        edx
004354EF 6A 04                push        4
004354F1 E8 AB AF FD FF       call        @ILT+62620(operator new) (004104a1)
004354F6 83 C4 08             add         esp,8
004354F9 89 45 FC             mov         dword ptr [ebp-4],eax
004354FC EB D4                jmp         ConstructElements+62h (004354d2)
0x052046b8

//对象类型的汇编代码
61:       for (; nCount--; pElements++) //nCount非0为真,否则假 终止循环 ;)
00436415 EB 09                jmp         ConstructElements+80h (00436420)
00436417 8B 55 08             mov         edx,dword ptr [ebp+8]
0043641A 83 C2 14             add         edx,14h
0043641D 89 55 08             mov         dword ptr [ebp+8],edx
00436420 8B 45 0C             mov         eax,dword ptr [ebp+0Ch]
00436423 8B 4D 0C             mov         ecx,dword ptr [ebp+0Ch]
00436426 83 E9 01             sub         ecx,1
00436429 89 4D 0C             mov         dword ptr [ebp+0Ch],ecx
0043642C 85 C0                test        eax,eax
0043642E 74 41                je          ConstructElements+0D1h (00436471)
62:           ::new((void*)pElements) TYPE;
00436430 8B 55 08             mov         edx,dword ptr [ebp+8]
00436433 52                   push        edx
00436434 6A 14                push        14h
00436436 E8 66 A0 FD FF       call        @ILT+62620(operator new) (004104a1)
0043643B 83 C4 08             add         esp,8
0043643E 89 45 EC             mov         dword ptr [ebp-14h],eax
00436441 C7 45 FC 00 00 00 00 mov         dword ptr [ebp-4],0
00436448 83 7D EC 00          cmp         dword ptr [ebp-14h],0
0043644C 74 0D                je          ConstructElements+0BBh (0043645b)
0043644E 8B 4D EC             mov         ecx,dword ptr [ebp-14h]
00436451 E8 D5 74 FE FF       call        @ILT+117030(_JM_Identity_Sub::_JM_Identity_Sub) (0041d92b) //调用对象构造函数
00436456 89 45 E8             mov         dword ptr [ebp-18h],eax
00436459 EB 07                jmp         ConstructElements+0C2h (00436462)
0043645B C7 45 E8 00 00 00 00 mov         dword ptr [ebp-18h],0
00436462 8B 45 E8             mov         eax,dword ptr [ebp-18h]
00436465 89 45 F0             mov         dword ptr [ebp-10h],eax
00436468 C7 45 FC FF FF FF FF mov         dword ptr [ebp-4],0FFFFFFFFh
0043646F EB A6                jmp         ConstructElements+77h (00436417)
63:   }
00436471 8B 4D F4             mov         ecx,dword ptr [ebp-0Ch]
00436474 64 89 0D 00 00 00 00 mov         dword ptr fs:[0],ecx
0043647B 5F                   pop         edi
0043647C 5E                   pop         esi
0043647D 5B                   pop         ebx
0043647E 83 C4 58             add         esp,58h
00436481 3B EC                cmp         ebp,esp
00436483 E8 08 BE 93 00       call        CSaleAdvanceWriteOffListForm::InitListHeader+278h (0093be08)
00436488 8B E5                mov         esp,ebp
0043648A 5D                   pop         ebp
0043648B C2 08 00             ret         8
template<class TYPE>
AFX_INLINE void AFXAPI ConstructElements(TYPE* pElements, int nCount)
{
    ASSERT(nCount == 0 ||
        AfxIsValidAddress(pElements, nCount * sizeof(TYPE)));
    // first do bit-wise zero initialization
    memset((void*)pElements, 0, nCount * sizeof(TYPE));
    // then call the constructor(s)
    for (; nCount--; pElements++)
        ::new((void*)pElements) TYPE;
}

template<class TYPE>
AFX_INLINE void AFXAPI DestructElements(TYPE* pElements, int nCount)
{
    ASSERT(nCount == 0 ||
        AfxIsValidAddress(pElements, nCount * sizeof(TYPE)));
    // call the destructor(s)
    for (; nCount--; pElements++)
        pElements->~TYPE();
}

 

 

 

最近在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;
}

 


VC中的CArray的使用

 
2009-05-20 15:21
关于 CArray类
2007年01月10日 星期三 17:18

我们在使用vc进行比较复杂的编程时,经常需要用到复杂的数组结构,并希望能实现动态管理。由于C++并不支持动态数组,MFC提供了一个CArray类来实现动态数组的功能。有效的使用CArray类,可以提高程序的效率。
MFC提供了一套模板库,来实现一些比较常见的数据结构如Array,List,Map。CArray即为其中的一个,用来实现动态数组的功能。
CArray是从CObject派生,有两个模板参数,第一个参数就是CArray类数组元素的变量类型,后一个是函数调用时的参数类型。
我们有一个类 class Object,我们要定义一个Object的动态数组,那么我们可以用以下两种方法:

CArray<Object,Object>   Var1;
CArray<Object,Object&>   Var2;
Var1与Var2哪一个的效率要高呢? Var2的效率要高。为什么呢?接下来我们对CArray的源代码做一个剖析就清楚了。
先了解一下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成员函数是用来为数组分配空间的,从这里着手,看CArray是如何对数据进行管理的。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;
 }
}
注意上面代码中标注为粗体的代码,它们实现了对象的构造与析构。如果我们只为对象分配内存,却没有调用构造与析构函数,会不会有问题呢?
如果只是使用c++的基本数据类型,如果int,long,那的确不会有什么问题。如果使用的是一个类,比如下面的类:
class Object
{
public:
   Object(){ ID = 0; }
   ~Object();
protected:
   int ID;
};
我们只为Object类分配了空间,也能正常使用。但是,类的成员变量ID的值却是不定的,因为没有初始化。如果是一个更复杂的组合类,在构造函数中做了许多工作,那可能就不能正常运行了。
同样,删除的数组元素时,也一定要调用它的析构函数。
我们来看下面的Preson类
class Preson
{
public: 
 Preson()
 {
  name = new char[10];
 }
 ~Preson()
 {
  delete []name;
 }
 char* name;
 int    age;
}
如果我没调用构造函数,那么对name操作肯定会出错。我们调用了构造函数后,删除元素时,如果不调用析构函数,那么,name所指向的内存不能正确释放,就会造成内存泄漏。

我们来看一下ConstructElements与DestructElements如何实现构造与析构函数的调用。
下面是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成员函数就可以明。Add函数的作用是向数组添加一个元素。下面是它的定义:
int CArray<TYPE, ARG_TYPE>::Add(ARG_TYPE newElement)
Add函数使用的参数是模板参数的二个参数,也就是说,这个参数的类型是我们来决定的,可以使用Object或Object&的方式。熟悉C++的朋友都知道,传引用的效率要高一些。如果是传值的话,会在堆栈中再产生一个新的对象,需要花费更多的时间。
下面来分析一下Add函数的代码:
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函数来完成这个功能的,它的作用是设置指定元素的值。下面是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类的内部实现有了一定的了解,只要看看MSDN的文档,就可以灵活运用了。
posted on 2023-09-20 14:32  不及格的程序员-八神  阅读(189)  评论(0编辑  收藏  举报