数组——这个C语言中的怪物,自从被直接继承到C++中,便让无数有识之士们前赴后继、绞尽脑汁,试图寻找一种可以动态增长的替代数据类型。当然,最著名的,应该就是vector向量。但是,它的数据定义极其复杂,还有迭代的出现,几乎彻底摧毁了它仅存的一点优势。所以,引入MFC之后,微软斩钉截铁地抛弃了标准C++的模板库。
CArray是MFC中非常重要的几个类模板之一,其他的还有CList、CMap等,但它们的定义略微有点晦涩。以我自己的经验,在MFC中使用CArray定义动态数组是非常方便的。在MSDN中,CArray的声明如下:
template< class TYPE, class ARG_TYPE > class CArray : public CObject
参数
TYPE
模板参数,指定存储在数组中对象的类型。TYPE是CArray返回的参数类型。
ARG_TYPE
模板参数,指定用来访问存储在数组中对象的变量类型,通常是TYPE的引用。ARG_TYPE是传递给CArray的参数类型。
备注
CArray类支持与C中相似的数组,但是必要时可以动态收缩和增长。数组索引总是从0开始。你可以决定是固定数组上界还是允许当添加元素超过当前边界时扩展数组。内存被连续地分配到上界,即使一些元素可能为空。和C中数组一样,CArray索引元素的访问时间是不变的,与数组大小无关。
提示 在使用一个数组之前,使用SetSize建立它的大小和为它分配内存。如果不使用SetSize,则为数组添加元素就会引起频繁地重新分配和拷贝。频繁地重新分配和拷贝不但没有效率,而且会导致内存碎片。
如果需要一堆数组中的个别数据,必须设置CDumpContext对象的深度为1或更大。
此类的某些成员函数调用全局帮助函数,它必须为CArray的大多数使用而定制。请参阅宏和全局量章节中的Collection Class Helpers。
当从一个CArray对象中移去元素时,帮助函数DestructElements被调用。当添加元素时,帮助函数ConstructElements被调用。
数组类的派生与列表的派生类似。
有关使用CArray类的更多信息,请参考Visual C++ Programmer's Guide中的论文集。
#include <afxtempl.h>
看到上面这么一大段文字是不是觉得有点头晕?MSDN就是这样的,不然怎么能体现出它的“权威”呢。其实不要觉得它很复杂,使用CArray类构造动态数组非常简单。首先,你需要包含头文件Afxtempl.h,然后就可以定义自己的动态数组了。例如定义一个int型和CPoint型的动态数组:
#include <afxtempl.h>
CArray <int, int> num;
CArray <CPoint, CPoint&> pt; // 也可以这样:CArray <CPoint, CPoint> pt;
现在,我们构造了两个动态数组,按照MSDN的提示,我们要使用SetSize函数建立它的大小和分配内存。(但其实这一步可以省略,而且我自己就是这么做的,虽然这不符合一个规范程序员的风格。)SetSize的函数原型是:
void SetSize( int nNewSize, int nGrowBy = -1 );
这个函数在MSDN中也有详细的说明,我就不再去翻译了。其中第一个参数指定数组大小,即数组中元素个数(必需大于或等于0)。对于第二个参数,MSDN中有这样一句话:如果使用了默认值,MFC以一种在大多数情况下能够避免内存碎片和最高效的方式去分配内存。既然人家MSDN都这么说了,那我们第二个参数就使用它的默认值了。如果要加上这一步的话,可以这么写(我们先不写):
num.SetSize(40); // 其实大小设为多少没有关系,只要是你认为最合适的就行了
pt.SetSize(10); // 一般地,设得大些可以避免内存碎片和提高效率,但所需空间越大
现在我们可以使用Add函数向数组中添加一个元素,也可以用GetAt函数来获得一个元素。它们的函数的原型分别是:
int Add( ARG_TYPE newElement );
TYPE GetAt( int nIndex ) const;
Add函数向数组末尾增加一个元素,必要的话会扩展数组,它返回增加元素的索引值。GetAt函数返回给定索引的元素值。例如,我们可以这样写:
for (int i=0; i<20; i++) num.Add(i); // 数组大小为20,数组中元素依次为0, 1, 2, … , 19
int n = num.GetAt(10); // 由于索引是从0开始,得到第11个元素,也就是10
CPoint point(100,100);
pt.Add(point); // 或者不用定义point,直接pt.Add(CPoint(100,100));
需要特别说明的是,如果使用了SetSize函数,上面介绍的Add函数会在数组原来的大小上扩展数组,数组前面的元素全为0。即这时候数组的大小变成了50,前30个元素为0,后面的元素依次为0, 1, 2, … , 19。为了避免这种情况,这时候就需要使用SetAt函数来向数组中添加元素,SetAt函数设置给定索引的元素值,不允许扩展数组。其函数原型为:
void SetAt( int nIndex, ARG_TYPE newElement );
如果使用SetAt函数,那么前面的代码就要这么写:
for (int i=0; i<20; i++) num.SetAt(i, i);
如果要向数组中间插入或者删除一个或多个元素,可以使用InsertAt和RemoveAt函数。它们的函数原型分别是:
void InsertAt( int nIndex, ARG_TYPE newElement, int nCount = 1 );
void InsertAt( int nStartIndex, CArray* pNewArray );
void RemoveAt( int nIndex, int nCount = 1 );
接着上面的例子,我们可以这样写:
num.InsertAt(3, 100); // 在索引为3的位置插入元素100
// 那么数组中的元素是这样的:0, 1, 2, 100, 3, 4, 5, … , 19
// num.InsertAt(3, 100, 2); // 在索引为3的位置向后插入两个元素,均为100
// 则数组中元素是这样的:0, 1, 2, 100, 100, 3, 4, 5, … , 19
CArray <int, int> numNew;
for (i=0; i<10; i++) numNew.Add(i); // 新建数组的元素为:0, 1, 2, … , 9
num.InsertAt(5, &numNew); // InsertAt的第二个版本,插入一个数组
// 现在数组中的元素是这样的:0, 1, 2, 100, 3, 0, 1, 2, … , 9, 4, 5, 6, … , 19
num.RemoveAt(2); // 移除索引为2的元素
// 数组中的元素变成这样:0, 1, 100, 3, 0, 1, 2, … , 9, 4, 5, 6, … , 19
// num.RemoveAt(2, 3); // 从索引为2的位置开始,移除3个元素
// 数组中的元素变成这样:0, 1, 0, 1, 2, … , 9, 4, 5, 6, … , 19
我们可以使用GetSize函数得到数组当前的大小(元素个数),可以使用GetUpperBound函数得到数组的上界(最大索引值),可以使用RemoveAll函数清空数组。跟C中一样,我们还可以使用操作符[ ]来访问数组元素,例如接着上面的例子:
n = num.GetSize(); // n = 30
n = num.GetUpperBound(); // n = 29
n = num[2]; // n = 100
num.RemoveAll();
n = num.GetSize(); // n = 0
写到这里,对CArray类的介绍已经差不多了,使用上面的函数基本上可以满足动态数组的操作。该类中还有一些其他函数,需要进一步了解的可以查阅MSDN,在这里只是对CArray的常用函数作一个大概的介绍。
CArray是MFC中非常重要的几个类模板之一,其他的还有CList、CMap等,但它们的定义略微有点晦涩。以我自己的经验,在MFC中使用CArray定义动态数组是非常方便的。在MSDN中,CArray的声明如下:
template< class TYPE, class ARG_TYPE > class CArray : public CObject
参数
TYPE
模板参数,指定存储在数组中对象的类型。TYPE是CArray返回的参数类型。
ARG_TYPE
模板参数,指定用来访问存储在数组中对象的变量类型,通常是TYPE的引用。ARG_TYPE是传递给CArray的参数类型。
备注
CArray类支持与C中相似的数组,但是必要时可以动态收缩和增长。数组索引总是从0开始。你可以决定是固定数组上界还是允许当添加元素超过当前边界时扩展数组。内存被连续地分配到上界,即使一些元素可能为空。和C中数组一样,CArray索引元素的访问时间是不变的,与数组大小无关。
提示 在使用一个数组之前,使用SetSize建立它的大小和为它分配内存。如果不使用SetSize,则为数组添加元素就会引起频繁地重新分配和拷贝。频繁地重新分配和拷贝不但没有效率,而且会导致内存碎片。
如果需要一堆数组中的个别数据,必须设置CDumpContext对象的深度为1或更大。
此类的某些成员函数调用全局帮助函数,它必须为CArray的大多数使用而定制。请参阅宏和全局量章节中的Collection Class Helpers。
当从一个CArray对象中移去元素时,帮助函数DestructElements被调用。当添加元素时,帮助函数ConstructElements被调用。
数组类的派生与列表的派生类似。
有关使用CArray类的更多信息,请参考Visual C++ Programmer's Guide中的论文集。
#include <afxtempl.h>
看到上面这么一大段文字是不是觉得有点头晕?MSDN就是这样的,不然怎么能体现出它的“权威”呢。其实不要觉得它很复杂,使用CArray类构造动态数组非常简单。首先,你需要包含头文件Afxtempl.h,然后就可以定义自己的动态数组了。例如定义一个int型和CPoint型的动态数组:
#include <afxtempl.h>
CArray <int, int> num;
CArray <CPoint, CPoint&> pt; // 也可以这样:CArray <CPoint, CPoint> pt;
现在,我们构造了两个动态数组,按照MSDN的提示,我们要使用SetSize函数建立它的大小和分配内存。(但其实这一步可以省略,而且我自己就是这么做的,虽然这不符合一个规范程序员的风格。)SetSize的函数原型是:
void SetSize( int nNewSize, int nGrowBy = -1 );
这个函数在MSDN中也有详细的说明,我就不再去翻译了。其中第一个参数指定数组大小,即数组中元素个数(必需大于或等于0)。对于第二个参数,MSDN中有这样一句话:如果使用了默认值,MFC以一种在大多数情况下能够避免内存碎片和最高效的方式去分配内存。既然人家MSDN都这么说了,那我们第二个参数就使用它的默认值了。如果要加上这一步的话,可以这么写(我们先不写):
num.SetSize(40); // 其实大小设为多少没有关系,只要是你认为最合适的就行了
pt.SetSize(10); // 一般地,设得大些可以避免内存碎片和提高效率,但所需空间越大
现在我们可以使用Add函数向数组中添加一个元素,也可以用GetAt函数来获得一个元素。它们的函数的原型分别是:
int Add( ARG_TYPE newElement );
TYPE GetAt( int nIndex ) const;
Add函数向数组末尾增加一个元素,必要的话会扩展数组,它返回增加元素的索引值。GetAt函数返回给定索引的元素值。例如,我们可以这样写:
for (int i=0; i<20; i++) num.Add(i); // 数组大小为20,数组中元素依次为0, 1, 2, … , 19
int n = num.GetAt(10); // 由于索引是从0开始,得到第11个元素,也就是10
CPoint point(100,100);
pt.Add(point); // 或者不用定义point,直接pt.Add(CPoint(100,100));
需要特别说明的是,如果使用了SetSize函数,上面介绍的Add函数会在数组原来的大小上扩展数组,数组前面的元素全为0。即这时候数组的大小变成了50,前30个元素为0,后面的元素依次为0, 1, 2, … , 19。为了避免这种情况,这时候就需要使用SetAt函数来向数组中添加元素,SetAt函数设置给定索引的元素值,不允许扩展数组。其函数原型为:
void SetAt( int nIndex, ARG_TYPE newElement );
如果使用SetAt函数,那么前面的代码就要这么写:
for (int i=0; i<20; i++) num.SetAt(i, i);
如果要向数组中间插入或者删除一个或多个元素,可以使用InsertAt和RemoveAt函数。它们的函数原型分别是:
void InsertAt( int nIndex, ARG_TYPE newElement, int nCount = 1 );
void InsertAt( int nStartIndex, CArray* pNewArray );
void RemoveAt( int nIndex, int nCount = 1 );
接着上面的例子,我们可以这样写:
num.InsertAt(3, 100); // 在索引为3的位置插入元素100
// 那么数组中的元素是这样的:0, 1, 2, 100, 3, 4, 5, … , 19
// num.InsertAt(3, 100, 2); // 在索引为3的位置向后插入两个元素,均为100
// 则数组中元素是这样的:0, 1, 2, 100, 100, 3, 4, 5, … , 19
CArray <int, int> numNew;
for (i=0; i<10; i++) numNew.Add(i); // 新建数组的元素为:0, 1, 2, … , 9
num.InsertAt(5, &numNew); // InsertAt的第二个版本,插入一个数组
// 现在数组中的元素是这样的:0, 1, 2, 100, 3, 0, 1, 2, … , 9, 4, 5, 6, … , 19
num.RemoveAt(2); // 移除索引为2的元素
// 数组中的元素变成这样:0, 1, 100, 3, 0, 1, 2, … , 9, 4, 5, 6, … , 19
// num.RemoveAt(2, 3); // 从索引为2的位置开始,移除3个元素
// 数组中的元素变成这样:0, 1, 0, 1, 2, … , 9, 4, 5, 6, … , 19
我们可以使用GetSize函数得到数组当前的大小(元素个数),可以使用GetUpperBound函数得到数组的上界(最大索引值),可以使用RemoveAll函数清空数组。跟C中一样,我们还可以使用操作符[ ]来访问数组元素,例如接着上面的例子:
n = num.GetSize(); // n = 30
n = num.GetUpperBound(); // n = 29
n = num[2]; // n = 100
num.RemoveAll();
n = num.GetSize(); // n = 0
写到这里,对CArray类的介绍已经差不多了,使用上面的函数基本上可以满足动态数组的操作。该类中还有一些其他函数,需要进一步了解的可以查阅MSDN,在这里只是对CArray的常用函数作一个大概的介绍。