分配的数组
使用分配的数组很容易理解。
声明你的指针数组。此数组中的每个元素都指向一个struct Test
:
struct Test *array[50];
然后根据需要分配和分配指向结构的指针。使用循环很简单:
array[n] = malloc(sizeof(struct Test));
然后声明一个指向这个数组的指针:
// an explicit pointer to an array
struct Test *(*p)[] = &array; // of pointers to structs
这允许您使用(*p)[n]->data
; 引用第 n 个成员。
如果这些东西令人困惑,请不要担心。这可能是 C 语言中最困难的方面。
动态线性阵列
如果您只想分配一个结构块(实际上是一个结构数组,而不是指向结构的指针),并且有一个指向该块的指针,您可以更轻松地做到这一点:
struct Test *p = malloc(100 * sizeof(struct Test)); // allocates 100 linear
// structs
然后你可以指向这个指针:
struct Test **pp = &p
您不再有指向结构的指针数组,但它大大简化了整个事情。
动态分配结构的动态数组
最灵活,但并不经常需要。它与第一个示例非常相似,但需要额外分配。我编写了一个完整的程序来演示这个应该可以正常编译。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
struct Test {
int data;
};
int main(int argc, char **argv)
{
srand(time(NULL));
// allocate 100 pointers, effectively an array
struct Test **t_array = malloc(100 * sizeof(struct Test *));
// allocate 100 structs and have the array point to them
for (int i = 0; i < 100; i++) {
t_array[i] = malloc(sizeof(struct Test));
}
// lets fill each Test.data with a random number!
for (int i = 0; i < 100; i++) {
t_array[i]->data = rand() % 100;
}
// now define a pointer to the array
struct Test ***p = &t_array;
printf("p points to an array of pointers.\n"
"The third element of the array points to a structure,\n"
"and the data member of that structure is: %d\n", (*p)[2]->data);
return 0;
}
输出:
> p points to an array of pointers.
> The third element of the array points to a structure,
> and the data member of that structure is: 49
或整套:
for (int i = 0; i < 100; i++) {
if (i % 10 == 0)
printf("\n");
printf("%3d ", (*p)[i]->data);
}
35 66 40 24 32 27 39 64 65 26
32 30 72 84 85 95 14 25 11 40
30 16 47 21 80 57 25 34 47 19
56 82 38 96 6 22 76 97 87 93
75 19 24 47 55 9 43 69 86 6
61 17 23 8 38 55 65 16 90 12
87 46 46 25 42 4 48 70 53 35
64 29 6 40 76 13 1 71 82 88
78 44 57 53 4 47 8 70 63 98
34 51 44 33 28 39 37 76 9 91
单动态分配结构的动态指针数组
最后一个例子是相当具体的。正如我们在前面的示例中看到的,它是一个动态的指针数组,但与这些不同的是,元素都在一次分配中分配。这有其用途,最值得注意的是在不同配置中对数据进行排序,同时保持原始分配不受干扰。
我们首先分配一个元素块,就像我们在最基本的单块分配中所做的那样:
struct Test *arr = malloc(N*sizeof(*arr));
现在我们分配一个单独的指针块:
struct Test **ptrs = malloc(N*sizeof(*ptrs));
然后,我们用原始数组之一的地址填充指针列表中的每个槽。由于指针算法允许我们从一个元素移动到另一个元素地址,这很简单:
for (int i=0;i<N;++i)
ptrs[i] = arr+i;
此时以下都指的是同一个元素字段
arr[1].data = 1;
ptrs[1]->data = 1;
经过以上审查,我希望清楚原因。
当我们完成指针数组和原始块数组时,它们被释放为:
free(ptrs);
free(arr);
注意:我们不会单独释放ptrs[]
数组中的每个项目。这不是他们的分配方式。它们被分配为单个块(由 指向arr
),这就是它们应该被释放的方式。
那么为什么有人要这样做呢?几个原因。
首先,它从根本上减少了内存分配调用的数量。而不是N+1
(一个用于指针数组,N 用于单个结构)您现在只有两个:一个用于数组块,一个用于指针数组。内存分配是程序可以请求的最昂贵的操作之一,并且在可能的情况下,最好将它们最小化(注意:文件 IO 是另一个,仅供参考)。
另一个原因:同一基本数据数组的多种表示形式。假设您想对数据进行升序和降序排序,并同时提供两种排序表示。您可以复制数据数组,但这需要大量复制并占用大量内存。相反,只需分配一个额外的指针数组并用基数组中的地址填充它,然后对该指针数组进行排序。当被排序的数据很大(每个项目可能是千字节,甚至更大)时,这具有特别显着的好处。原始项目保留在基本数组中的原始位置,但是现在您有了一个非常有效的机制,您可以在其中对它们进行排序无需实际移动他们。您对指向项目的指针数组进行排序;这些物品根本不会移动。
我意识到这是一个非常重要的内容,但是指针的使用对于理解 C 语言可以做的许多强大的事情至关重要,所以请阅读书籍并不断刷新你的记忆。它会回来的。