在C++中怎样获得数组的大小?
版权:the1(博客名) (博客地址)http://blogs.msdn.com/b/the1/
翻译:magictong(童磊) 2011年4月
原文地址:http://blogs.msdn.com/b/the1/archive/2004/05/07/128242.aspx
P.S. 之前在一篇文章(http://blog.csdn.net/magictong/archive/2011/04/14/6322084.aspx)中说过VS2005下面_countof的实现,最后提到过为什么使用模板而抛弃之前的实现,后来在网上看到了这篇文章,感觉讲得比较清楚,就翻译了一下,还算简单清楚。
问题非常简单,:给定一个数组(例如:int x[10]就是定义了一个名为x的整型数组),你怎样获得这个数组里面元素的个数?
最简单最明显的方法是定义如下的一个宏(定义 1):
#define _countof(_Array) (sizeof(_Array) / sizeof(_Array[0]))
我们不能说这种定义是不正确的,因为当你传一个数组给这个宏的时候,它确实能给你返回正确的结果。然而,当你把一个不是数组的的变量传递给上面这个宏的时候,它得出了一个错误的结果,譬如,如果你有这样一个指针声明:
int* p;
那么在那种整型指针和整型变量具有相同的大小的机器上_countof(p)计算出来的结果将总是1(例如:win32平台就是这样)。
而且,这个宏还会错误的接受那种重载了方括号运算符(operator[])的类的对象作为参数。举个例子,假如你写了这样一个类:
class IntArray
{
private:
int * p;
size_t size;
public:
int & operator [] ( size_t i );
} x;
那么sizeof(x)的值将是x对象的大小,而不是x.p这个指针指向的缓冲区的大小。因此,通过_countof(x)你无法得到正确的结果。
从上面的讨论,我们可以得出结论定义 1并不是太好,因为编译器无法阻止你错误的使用它。它也能保证只能传递一个数组给它。
我们该怎么办?还有更好的选择吗?
答案是肯定的。
如果我们希望编译器来验证传递给_countof的参数始终是一个数组,那么我们必须首先找到只允许传递一个数组参数这样一个语境。在这种语境下任何非数组的表达式都会予以拒绝(译注:编译失败)。
初学者可能会试着这样尝试来定义(定义 2):
template <typename T, size_t N>
size_t countof( T array[N] )
{
return N;
}
他们希望这个模版函数接受一个有N个元素的数组作为参数,并且返回N。
然而不幸的是,这种写法无法通过编译,因为在C++里面把一个数组作为参数跟把一个指针作为参数是等同的,也就是说上面的定义等价于:
template <typename T, size_t N>
size_t countof( T * array )
{
return N;
}
现在就很明显了,函数体根本无法知道N到底是个啥。
但是,如果一个函数需要接受一个数组的引用来作为参数,那么编译器就将确保实际数组参数的大小和声明的是匹配的。这意味着我们只需要把定义 2稍微修改一下就可以了(定义 3):
template <typename T, size_t N>
size_t countof( T (&array)[N] )
{
return N;
}
现在_countof将会很正常的工作,而且会阻止你错误的给它传递一个指针。但是,现在它是一个函数,而不是一个宏了。这意味着你不能在编译时就计算出它的大小。特别是,你现在不能写下这样的代码了:
int x[10];
int y[ 2*countof(x) ]; // twice as big as x
现在该怎么办?
有人(我不知道他是谁 - 我是从一个不出名的作者那看到的这样一个代码片段)想出了一个聪明的点子:把N从函数体移到返回类型里面去(例如:使函数返回一个拥有N个元素的数组),那么我们可以得到这个N的值,甚至不需要去实际调用这个函数。
准确地说,我们必须使这个函数返回一个数组引用,因为C++不允许返回一个具体的数组。具体写法如下:
template <typename T, size_t N>
char (&_ArraySizeHelper(T (&array)[N]))[N];
#define _countof(array) (sizeof(_ArraySizeHelper(array)))
不可否认的是这个语法看起来实在的是有点恶心而可怕了(译注:我第一次看到的时候也懵了),因此对它进行一些解释是必要的。
首先,我们看一下最外层的东西:
char ( &_ArraySizeHelper( ... ))[N];
这个表达式应该这样理解:_ArraySizeHelper是一个函数,并且它的返回值是一个具有N个元素的char数组的引用(注意&符号)。
接下来,我们看这个函数的参数:
T (&array)[N]
它是一个具有N个元素的的T类型数组的引用。
最后,_countof被定义为计算函数_ArraySizeHelper返回值结果的大小。请注意,我们甚至不需要去实现_ArraySizeHelper()——一个声明就足够了。
使用新的定义,我们又可以写这样的代码了,现在它的结果也符合我们的预期了:
int x[10];
int y[ 2*countof(x) ]; // twice as big as x
现在可以高兴了么?呃,我想这个定义应该比我们已经见过的一些定义要好,但是它仍然不是我想要的。主要是,它无法和一个定义在函数里面的类型很好的工作。这是因为模版函数_ArraySizeHelper期望的类型是全局范围都可以访问的。(译注:这个地方有点奇怪,函数中定义的类型本身就是只有那个函数中可见的,在那个定义类型的函数中应该是可以使用这个宏的,但是函数外面因为对那个类型是不可见的,即使不可以使用也没有问题啊,可能是我对这句话理解不够,我觉得作者的意思应该是说编译会通不过。不过我是使用的VS2005测试的,可能是后来模板的实现变化,因为在新标准里面是允许的,而作者的这篇文章写于2004年)
我没有更好的解决方法了,如果你有的话,请告诉我吧o(∩_∩)o 。
顺便说一句,同样的问题在c#中怎么解决呢?其实你只需要调用x.length()就可以了,就是这么简单(我忘记了具体的语法,但是你应该明白的吧)。
但是有一点需要指出的是,在c#中一个数组的元素个数不会隐含在它的类型中,而是在运行时当创建数组时由您来决定它的元素个数。因此,就不要去尝试在编译时去获取这个信息了。