14.4 无法将数组传递给函数
你期望程序清单14.2中的代码打印出什么结果?
程序清单14.2
- void process_array(int ar[10])
- {
- printf("[");
- for(size_t i = 0; i < dimensionof(ar); ++i)
- {
- printf("%d ", ar[i]);
- }
- printf("]\n");
- }
-
- int main()
- {
- int ar1[10] =
- {
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
- };
- process_array(ar1);
- return 0;
- }
如果你说"[ 0 1 2 3 4 5 6 7 8 9 ]",那么你也许会惊讶地发现实际结果并非如此。事实上,该程序输出为"[0]"。C/C++(www.cppentry.com)程序员第一次遇到这种情况时可能会非常惊讶。我们声明了一个拥有10个整数的数组,赋给其一组值,并将其传递给一个函数,该函数接受一个含有10个整数的数组作为参数。看起来一切都井井有条,问题究竟出在哪里呢?
呃,是这样的,在C/C++(www.cppentry.com)中你无法将数组传给函数!但是,从你开始使用C/C++(www.cppentry.com)编程(www.cppentry.com)起,你就清楚地记得的确是把数组传给函数的,不是吗?可惜这只不过是个假象而已!在C里面,数组被传递给函数时总是被转换成指针,非常干脆地阻止了你获取数组大小的企图。而C++(www.cppentry.com)基于兼容性的考虑,亦是如此。在上面的示例中,我们可以将process_array声明为以下任意一种形式,其行为都是一样的:
- void process_array(int ar[20]);
- void process_array(int ar[]);
- void process_array(int *ar);
现在,随着我把这个问题提上了台面,你也许会回忆起一个类似的现象:main()的参数既可以被声明为char **argv,又可以被声明为char *argv[ ]。我并不打算深入讨论导致这种情形的历史原因,Peter van der Linden在他的Deep C Secrets[Lind1994]的第9章中极其深刻地描述了这个问题。我想指出的是:
Imperfection:C/C++(www.cppentry.com)数组在被传递给函数时会退化成指针。
这种灵活性在许多场合下都是非常有用的,但它也会成为一些问题的来源。C++(www.cppentry.com)(从这一点来说,也包括C)提供了一个严格的类型系统:我们不能将一个指向double的指针传递给某个期望得到指向float指针的地方,数组的情形亦然。然而我们却可以将一个任意长度的数组传递给一个期望接受(指针形式的)数组的函数。那么,我们究竟该怎么办呢?
问题的实质在于数组的大小丢失了,因此,如果我们可以找到一种将数组大小随之传递给函数的机制,问题就会迎刃而解。意料之中,这可以通过模板来实现,我将此类模板称为array_proxy,如程序清单14.3所示:
程序清单14.3
- template <typename T>
- class array_proxy
- {
- // 构造函数
- public:
- template <size_t N>
- explicit array_proxy(T (&t)[N])
- : m_base(t)
- , m_size(N)
- {}
- explicit array_proxy(T *base, size_t size)
- : m_base(t)
- , m_size(size)
- {}
- // 状态
- public:
- T *base()
- {
- return m_base;
- }
- size_t size() const
- {
- return m_size;
- }
- // 数据成员
- private:
- T *const m_base;
- size_t const m_size;
- // 声明但不予实现
- private:
- array_proxy &operator =(array_proxy const &);
- };
这里还定义了一组转发函数(我们在14.6.5小节将会看到它们的运用),这些函数使我们能够在无需指明类型 的情况下创建array_proxy实例。- template <typename T, size_t N>
- inline array_proxy<T> make_array_proxy(T (&t)[N])
- {
- return array_proxy<T>(t);
- }
- template <typename T >
- inline array_proxy<T> make_array_proxy(T *base, size_t size)
- {
- return array_proxy<T>(base, size);
- }
该模板是本章的一个重点内容,是一个特色,后面我们会对其不断精化,以便使其能够对其他数组/指针问题提供帮助,所以暂且别担心它当前较为粗陋的形式。
现在我们总算可以将数组真正当成数组来传递了:
- void process_array(array_proxy<int> const &ar)
- {
- printf("[");
- for(size_t i = 0; i < ar.size(); ++i)
- {
- printf("%d ", i[ar.base()]); // 新手往往难以理解这行代码!
- }
- printf(")\n");
- }
现在这个函数可以打印出我们想要的结果了。显然,我们不想每次在索引时都调用base(),有没有更优雅的使用接口呢?别忘了,array_proxy会不断进化的。