4.6 动态数组的创建及引用

在讲解动态数组之前,先来看下面这段代码,其功能为将一幅像素为320×320的灰度图像扩展为480×480,扩展方式为:每隔四行空两行,每隔两列空两列。出于代码篇幅的考虑,在此主要给出算法的核心部分。


include<stdio.h>

include<stdlib.h>

define N 320

define M 480

int main(void)

{

int i,j;

int a[N][N];

int b[M][M];

for(i=0;i<N;i++)

{

for(j=0;j<N;j++)

{

a[i][j]=rand()%256;

printf("%d\t",a[i][j]);

}

printf("\n");

}

for(i=0;i<M;i++)

{

for(j=0;j<M;j++)

{

if(4==(i%6)||5==(i%6)||4==(j%6)||5==(j%6))

{

b[i][j]=0;

continue;

}

b[i][j]=a[i/64+i%6][j/64+j%6];

}

}

return 0;

}


由于运行结果篇幅过大,在此就不再给出运行结果,有兴趣的读者可以按照比例缩小M和N的值,然后查看运行结果会发现,在编译的时候不会有任何错误,而运行程序时会直接崩溃掉,调试一下会发现“stack overflow”错误,这说明栈溢出了。我们发现这个时候使用静态数组没法解决问题,那么该如何解决这个问题呢?这就需要使用接下来要讲解的动态数组,尤其是在进行图像处理的时候,必须用动态数组来对大量数据进行处理。

先来看什么是动态数组。动态数组是相对于静态数组而言的,从“动”字可以看出它的灵活性。静态数组的长度是预先定义好的,在整个程序中,一旦给定数组大小后就无法改变。而动态数组则不然,它可以根据程序需要重新指定数组大小。动态数组的内存空间是由堆动态分配的,通过执行代码为其分配存储空间,只有程序执行到分配语句时,才为其分配存储空间。

对于静态数组,其创建非常方便,使用完也无需释放,引用起来也简单,但是创建后无法改变数组大小是其致命的弱点。对于动态数组,其创建麻烦,使用完必须由程序员自己释放,否则会引起内存泄露,但是其使用非常灵活,能根据程序需要动态分配大小。因此相对于静态数组来说,使用动态数组的自由度更大。

在创建动态数组的过程中要遵循一个原则,那就是从外层向里层逐层创建,从里层向外层逐层释放。如果要创建一个N维的动态数组,那么要从第一维开始创建,直到第N维为止;而释放时与创建时相反,即从第N维开始释放,直到第一维为止。下面结合代码来讲解。1.一维动态数组创建一维动态数组的一般形式为:


类型说明符数组名=(类型说明符)malloc(数组长度*sizeof(类型说明符));


下面通过一段代码来了解一维动态数组的使用方法。


include<stdio.h>

include<stdlib.h>

int main()

{

int n,i;

int*arr;

printf("请输入所要创建的一维动态数组的长度:");

scanf("%d",&n);

if((arr=(int)malloc(sizeof(int)n))==NULL)

{

printf("分配内存空间失败!\n");

return 0;

}

for(i=0;i<n;i++)

{

arr[i]=i+1;

printf("%d\t",arr[i]);

if(0==(i+1)%4)

printf("\n");

}

free(arr);

return 0;

}


运行结果:


请输入所要创建的一维动态数组的长度:12

1 2 3 4

5 6 7 8

9 10 11 12


在以上动态数组的创建过程中,先使用了malloc()函数向系统动态申请分配了sizeof(int)*n个字节的内存空间,然后将申请的内存空间视为一个一维数组进行操作。当然,一维数组没有体现出动态数组的分配原则。

2.二维动态数组

创建二维动态数组的一般形式为:


类型说明符数组名=(类型说明符)malloc(第一维长度sizeof(类型说明符));


例如:


for(i=0;i<第一维长度;i++)

{

数组名[i]=(类型说明符*)malloc(第二维长度sizeof(类型说明符*));

}


接下来通过一段代码来了解二维动态数组的创建方法。


include<stdio.h>

include<stdlib.h>

int main()

{

int n1,n2;

int**arr,i,j;

printf("请输入所要创建的动态数组的第一维长度:");

scanf("%d",&n1);

printf("请输入所要创建的动态数组的第二维长度:");

scanf("%d",&n2);

if((arr=(int*)malloc(n1sizeof(int*)))==NULL)//第一维的创建

{

printf("分配内存空间失败!\n");

return 0;

}

for(i=0;i<n1;i++)

{

if((arr[i]=(int)malloc(n2sizeof(int)))==NULL)//第二维的创建

{

printf("分配内存空间失败!\n");

return 0;

}

}

for(i=0;i<n1;i++)

{

for(j=0;j<n2;j++)

{

arr[i][j]=i*n2+j+1;

printf("%d\t",arr[i][j]);

}

printf("\n");

}

for(i=0;i<n1;i++)

{

free(arr[i]);//释放第二维

}

free(arr);//释放第一维

return 0;

}


运行结果:


请输入所要创建的动态数组的第一维长度:4

请输入所要创建的动态数组的第二维长度:3

1 2 3

4 5 6

7 8 9

10 11 12


以上动态数组的创建过程直观地体现出创建时所要遵循的基本原则,先通过下面一行代码来创建最外层,即第一维。


array=(int*)malloc(n1sizeof(int*));//第一维的创建


接下来创建第二维,在创建第二维的过程中需要注意,这里使用了一个for循环语句来进行创建,要创建的二维数组为arr[n1][n2],第一维的大小为n1,第二维的大小为n2,利用前面分析静态数组时的方法将其拆分为arr[n1]和int[n2]两部分,定义第一维的大小为n1,其中每个元素arr[i]都指向含有n2个int型数组元素的内存区域,即指向一片内存大小为n2*sizeof(int)字节的空间,所以在创建第二维的时候要采用下面的方法来实现。


for(i=0;i<n1;i++)

{

arr[i]=(int)malloc(n2sizeof(int));//第二维的创建

}


创建好数组元素之后,接下来对其中的数组元素进行赋值操作,并打印输出。创建动态数组之后千万别忘了释放内存,否则会导致内存泄露,释放时要遵循从内向外的原则,即先释放最高维,然后是次高维,直到第一维为止。这里创建的是二维数组,所以先释放的是第二维。与之前讲的创建方法类似,同样采用下面的循环来释放。


for(i=0;i<n1;i++)

{

free(arr[i]);//释放第二维

}


释放完第二维后采用下面的方式释放第一维。


free(arr);//释放第一维


所以,对动态数组的使用要有始有终,要牢记在使用完之后要及时释放申请的内存区域,避免造成内存泄露。经过上面的讲解,相信读者对于动态数组的创建规则有了一定的了解。

3.三维动态数组

创建三维动态数组的一般形式为:

类型说明符数组名=(类型说明符)malloc(第一维长度sizeof(类型说明符*));

例如:


for(i=0;i<第二维长度;i++)

{

数组名[i]=(类型说明符*)malloc(第二维长度sizeof(类型说明符*));

for(j=0;j<第三维长度;j++)

{

数组名[i][j]=(类型说明符)malloc(第三维长度sizeof(类型说明符));

}

}


从上面的三维动态数组可以看出,虽然三维动态数组的创建可以由二维动态数组推导出来,但是与二维数组的创建相比还是要复杂一些,所以在此特地给出三维动态数组创建方法。接下来通过具体的实例来了解三维动态数组的创建,以进一步加深读者对动态数组的理解。


include<stdlib.h>

include<stdio.h>

int main()

{

int n1,n2,n3;

int*arr;

int i,j,k;

printf("请输入所要创建的动态数组的第一维长度:");

scanf("%d",&n1);

printf("请输入所要创建的动态数组的第二维长度:");

scanf("%d",&n2);

printf("请输入所要创建的动态数组的第三维长度:");

scanf("%d",&n3);

if((arr=(int)malloc(n1sizeof(int)))==NULL)//第一维的创建

{

printf("分配内存空间失败!\n");

return 0;

}

for(i=0;i<n1;i++)

{

if((arr[i]=(int*)malloc(n2sizeof(int*)))==NULL)//第二维的创建

{

printf("分配内存空间失败!\n");

return 0;

}

for(j=0;j<n2;j++)

{

if((arr[i][j]=(int)malloc(n3sizeof(int)))==NULL)//第三维的创建

{

printf("分配内存空间失败!\n");

return 0;

}

}

}

for(i=0;i<n1;i++)

{

for(j=0;j<n2;j++)

{

for(k=0;k<n3;k++)

{

arr[i][j][k]=i+j+k+1;

printf("%d\t",arr[i][j][k]);

}

printf("\n");

}

printf("\n");

}

for(i=0;i<n1;i++)

{

for(j=0;j<n2;j++)

{

free(arr[i][j]);//释放第三维

}

}

for(i=0;i<n1;i++)

{

free(arr[i]);//释放第二维

}

free(arr);//释放第一维

return 0;

}


运行结果:


请输入所要创建的动态数组的第一维长度:3

请输入所要创建的动态数组的第二维长度:3

请输入所要创建的动态数组的第三维长度:3

1 2 3

2 3 4

3 4 5

2 3 4

3 4 5

4 5 6

3 4 5

4 5 6

5 6 7


从上面的代码可以看出,三维动态数组的创建方法与前面讲解的二维动态数组的创建方法类似,在此就不再展开分析了。

4.可扩展动态数组

细心的读者可能发现了一个问题,那就是前面所讲的动态数组都是一次性创建好的,如果在接下来使用数组时需要扩展或删减一些不再使用的数组元素,该怎么办呢?这个时候就要想办法扩展动态数组,这就是接下来要讲解的——可扩展动态数组。在讲解可扩展动态数组之前,先看一段关于动态数组扩展的代码,在此以一维动态数组的扩展为例,其他类型的数组以此类推。


include<stdio.h>

include<stdlib.h>

int main()

{

intn,p;

int i,n1,n2;

printf("请输入所要创建的动态数组的长度:");

scanf("%d",&n1);

if((n=(int)malloc(n1sizeof(int)))==NULL)

{

printf("分配内存空间失败!\n");

return 0;

}

for(i=0;i<n1;i++)

{

n[i]=n1-i;

printf("%d\t",n[i]);

if(0==(i+1)%4)

printf("\n");

}

printf("\n请输入所要扩展的动态数组的长度:");

scanf("%d",&n2);

if((p=(int)realloc(n,(n2)sizeof(int)))==NULL)//动态扩充数组

{

printf("分配内存空间失败!\n");

return 0;

}

for(i=0;i<n2;i++)

{

p[i]=n2-i;

printf("%d\t",p[i]);

if(0==(i+1)%4)

printf("\n");

}

free(p);

return 0;

}


运行结果:


请输入所要创建的动态数组的长度:8

8 7 6 5

4 3 2 1

请输入所要扩展的动态数组的长度:12

12 11 10 9

8 7 6 5

4 3 2 1


在上面的代码中,一开始分配的数组长度是n1,接下来把数组长度扩展为n2。这时要注意,有的读者可能会采用如下方式来释放定义的动态数组:


free(p);

free(n);


因为在代码中定义了两个整型指针n和p,所以释放的时候需要分别对n和p进行内存分配,所以有的读者认为应该采用这种释放方式。事实上,这是不正确的,至于具体的错误原因在本书10.7节关于malloc()、calloc()、realloc()三者区别的讲解部分有详细分析,不明白的读者一看便知其中缘由了。

上面讲解了动态数组的扩展,而动态数组的缩减该如何操作呢?只要在运行上面的代码时输入的n2小于n1,就可以实现对动态数组的缩减。在此就不再给出代码了,读者可以在运行程序时通过控制n1和n2的值来实现对动态数组的扩展和缩减。