学科分类
目录
C语言

动态数组

前面学习的数组,无论是普通数组还是变长数组都是在栈上分配的内存,数组一旦定义就不再由程序员掌控,为其分配内存、回收内存都由系统决定。这些数组需要的内存空间,包括大小与数据类型,在编译时期就已经被确定,在程序执行期间不可改变。但是有时候仅依靠系统帮我们申请的内存空间并不能满足需求,程序在执行过程中可能临时需要一块内存存储数据并完成数据处理,这个时候就需要程序员自己去堆上申请内存空间。

前面章节讲解了堆内存空间操作函数,其中malloc()、calloc()函数、realloc()函数用于向堆内存申请空间,它们申请的内存空间是一段指定数据类型和大小的连续空间,且可以在程序执行时随时使用随时申请,这些函数在程序执行过程中申请的连续空间被称为动态数组。相应的在栈上的数组被称为静态数组。

相比于静态数组,动态数组可以根据用户需要,有效利用存储空间,但其定义方式复杂,且内存申请后需要程序员手动释放,否则会造成内存泄露。

下面通过一个案例演示动态数组的使用,如例1所示。

例1 dynamicarray1.c

 1  #include <stdio.h>
 2  int main()
 3  {
 4    int arr_len = 10;           //指定生成的数组的长度
 5    int * parr;              //定义int类型的指针
 6    // 申请内存空间作为整型数组,空间大小是sizeof(int) * arr_len个字节
 7    parr = (int *)malloc(sizeof(int) * arr_len);
 8    memset(parr, 0, sizeof(int) * arr_len);// 将这块内存空间全部初始化为0
 9    for (int i = 0; i < arr_len; i++)   // 为整型数组的元素赋值
 10   {
 11     parr[i] = i;
 12   }
 13   for (int i = 0; i < arr_len; i++)   // 打印整型数组的元素的值
 14   {
 15     printf("%d ", parr[i]);
 16   }
 17   printf("\n");
 18   free(parr);              // 释放内存空间
 19   return 0;
 20 }

例1运行结果如图1所示。

图1 例1运行结果

在例1中,第7行代码调用malloc()函数向堆空间申请了一个40字节(10个4字节)的空间,malloc()返回一个void类型的指针,将该指针强制转换为int类型后赋值给int*类型指针parr。第8行代码调用memset()函数将动态数组parr全部元素初始化为0。第9~12行代码使用for循环为动态数组parr赋值。第13~16行代码使用for循环打印出数组中的元素。第18行代码调用free()函数释放动态数组空间。由图1可知,程序成功输出了动态数组中的数据。

通过内存申请函数也可以创建动态二维数组,在创建动态二维数组的时候,要牢记一个原则是创建动态二维数组时,从外层到里层,逐层创建;释放的时候,从里层到外层,逐层释放内存空间。假如通过malloc()函数创建动态二维数组,首先调用malloc()函数申请一段堆内存空间,每个数据单元块中都存储一个指针,然后再调用malloc()函数为每个指针申请一块堆内存空间,即让指针指向一块堆内存空间,该动态二维数组逻辑示意图可用图2表示。

图2 动态二维数组arr

在图2中,parr是一个二维数组,在创建动态二维数组parr时,先创建存储指针的一维数组,即为parr[0]~parr[5]元素申请一段连续内存空间;再创建每个指针指向的一维数组,即为每一行申请连续内存空间。释放内存时,先释放指针parr[0]~parr[5]指向的内存空间,再释放指针parr指向的指针数组的内存空间。如果先释放指针parr指向的空间,则parr[0]~parr[5]指向的空间没有了指针标识,就无法释放,造成内存泄露。

动态二维数组的创建与使用如例2所示。

例2 dynamicArray2.c

 1  #include <stdio.h>
 2  Include <stdlib.h>
 3  int main()
 4  {
 5    int n1 = 6, n2 = 4;        //定义两个变量
 6    int** parr;            //定义一个二级指针
 7    //申请一段堆内存空间,存储int*类型的指针
 8    parr = (int**)malloc(sizeof(int*) * n1);  
 9    //使用for循环为每个指针分配一段内存空间
 10   for (int i = 0; i < n1; i++)
 11     parr[i] = (int*)malloc(sizeof(int) * n2);
 12   //使用for循环初始化动态二维数组
 13   for (int i = 0; i < n1; i++)
 14   {
 15     for (int j = 0; j < n2; j++)
 16     {
 17       parr[i][j] = i;      //为动态二维数组赋值
 18       printf("%3d", parr[i][j]); //输出元素
 19     }
 20     printf("\n");
 21   }
 22   //循环释放指针指向的内存空间
 23   for (int i = 0; i < n1; i++)
 24     free(parr[i]);
 25   free(parr);            //释放parr指针的空间
 26   return 0;
 27 }

例2运行结果如图3所示。

图3 例2运行结果

在例2中,第4行代码定义了两个变量n1和n2,分别用于标识动态二维数组的行和列。第5~7行代码定义了一个二级指针parr,调用malloc()函数申请一块堆内存空间,该空间用于存储n1个int*类型的指针数组的首地址。第9~10行代码,通过for循环调用malloc()函数为指针数组的每一个元素申请堆一块堆内存空间,这块堆内存空间是元素个数为n2的一维数组;第12~20行代码使用for循环嵌套为动态二维数组赋值并输出元素值。第22~23行代码释放parr[0]~parr[n1-1]指向的空间,即指向二维数组每行所占的内存空间。第24行代码释放parr指向的空间。由图3可知,程序成功创建了一个动态二维数组,并成功赋值输出。

需要注意的是,动态二维数组与静态二维数组的内存存储方式并不相同,静态二维数组在栈上是线性存储,但动态二维数组在内存中并不是线性存储的,其每一行是连续的,但行与行之间是不连续的。例6-8中动态二维数组内存空间分布可用图4描述。

图4 动态二维数组内存分布

点击此处
隐藏目录