前天实训听见几位推免的大佬聊面试中出现了动态数组,而我们所学并没有涉及到动态数组,遂翻起了尘封已久的《C语言程序设计现代方法》以及《C Primer Plus》,果然大神们写书都很全面(厚),后悔当初没有认真拜读。
下面程序用到了变长数组
#include<stdio.h>
int main() { int i,n;
printf(“How many numbers do you want to reverse?”); scanf(“%d”,&n);
int a[n]; //只有C99支持n决定数组范围
printf(“Enter %d numbers: “,n); for(i=0;i<n;i++) { scanf(“%d”,&a[i]); } printf(“In reverse order:”); //倒序输出数组 for(i=n-1;i>=0;i–) printf(” %d”,a[i]); printf(“\n”);
return 0;
}
上面程序中的数组a是一个变长数组(variable-length array,简称VLA)。变长数组的长度是在程序执行时计算的,而不是在程序编译时计算的。变长数组的主要优点是程序员不必在构造数组时随便给定一个长度,程序在执行时可以准确地计算出所需的元素个数。如果让程序员来制定长度,数组可能过(浪费)或过短(导致程序出错)。
变长数组的长度不一定要用变量来指定,任意表达式(可以含运算符)都可以,例如:
int a[3*i+5];
int b[j+k];
现在我们已经知道什么是变长数组了,但是,假如,变长数组作为形式参数,到底应该如何写呢?
现在,假设有一函数
int sum_array( int a[n] , int n )
{
……
}
编译器会在遇到int a[n]时显示出错信息,因为此前它没有见过n。
下面给出正确的函数原型:
int sum_array(int n , int a[n] ); //version 1
int sum_array(int n , int a[*] ); //version 2
一般来说,变长数组形式参数的长度可以是任意表达式。例如,假设我们要编写一个函数来连接两个数组a和b,要求先复制a的元素,再复制b的元素,把结果写入第三个数组c:
int concatenate(int m , int n , int a[m] , int b[n] , int c[m+n] )
{
…
}
数组c的长度是a和b的长度之和。这里用于指定数组c长度的表达式只用到了另外两个参数;但一般来说,该表达式可以使用函数外部的变量,甚至可以调用其他函数。
敲黑板!!!重点来了
变长数组的大小不会变化,变长数组中的“变”并不表示在创建数组后还可以修改它的大小。变长数组的大小在创建后就是保持不变的。“变”的意思是说其维大小可以用变量来指定。
变长数组允许动态分配存储单元,这表示可以在程序运行时指定数组的大小。常规的C数组是静态存储分配的,也就是说在编译时数组的大小就已经确定。
接下来要说的动态数组,才是大小会变化的数组。
现在我们讨论C语言中如何实现动态数组。请系好安全带,加速了加速了。
基本思路就是使用malloc()库函数(内存分配)来得到一个指向一大块内存的指针。然后,像引用数组一样引用这块内存,其机理就是一个数组下标访问可以改写为一个指针加上偏移量。
1.使用malloc函数为数组分配存储空间
假设正在编写的程序需要n个整数构成的数组,这里的n可以在程序执行期间计算出来。首先,声明一个指针变量:
int *a;
一旦n的值已知了,就让程序调用malloc函数为数组分配存储空间:
a=malloc( n * sizeof(int) );
一旦a指向动态分配的内存块,就可以忽略a是指针的事实,可以把它作为数组的名字。这都要感谢C语言中数组和指针的紧密关系。例如,可以使用下列循环对a指向的数组进行初始化:
for ( i=0 ; i<n ; i++ )
a[i]=0;
当然,用指针算数运算取代下标操作来访问数组元素也是可行的。
2.使用calloc函数为数组分配存储空间
calloc函数在<stdlib.h>中具有如下所示的原型:
void * calloc ( size_t nmemb , size_t size );
calloc函数为nmemb个元素的数组分配内存空间,其中每个元素的长度都是size个字节。如果要求的空间无效,那么此函数返回空指针。在分配了内存之后,calloc函数会通过把所有位设置为0的方式进行初始化。例如下列calloc函数调用为n个整数的数组分配存储空间,并且保证所有整数初始均为零:
a=calloc(n,sizeof(int));
3.使用realloc函数调整数组的大小
一旦为数组分配完内存,稍后可能会发现数组过大或过小。realloc函数可以调整数组的大小使它更适合需要。<stdlib.h>中的realloc原型:
void * realloc ( void *ptr, size_t size );
当调用realloc函数时,ptr必须指向先前通过malloc、calloc或realloc的调用获得的内存块。size表示内存块的新尺寸,新尺寸可能会小于或大于原有尺寸。虽然realloc不要求ptr指向正在用作数组的内存,但实际上通常是这样的。
在要求减少内存块大小时,realloc函数应该“在原先的内存块上”直接进行缩减,而不需要移动存储在内存块中的数据。同理,扩大内存块时也不应该对其进行移动。如果无法扩大内存块(因为内存块后面的字节已经用于其他目的),realloc函数会在别处分配新的内存块,然后把旧块中的内容复制到新块中。所以,一旦realloc函数返回,一定要对指向内存块的所有指针进行更新,因为realloc函数可能会使内存块移动到了其他地方。