如何使用动态分配的任意维数组?

典型的1-D数组可以在声明中静态或自动分配。

enum { n=100 }; int arr1[n]; 

或者通过指针动态分配和访问。

 int *arr1m=malloc(n*sizeof*arr1m); int *arr1c=calloc(n, sizeof*arr1c); 

这两种样式都使用相同的语法访问元素。

 int i = n/2; arr1[i] = arr1c[i] = arr1m[i] = 42; 

但是当你添加第二个维度时,需要花费一些力气来实现相同的语法。

 int arr2[n][n]; int *arr2c=calloc(n*n,sizeof*arr2c); arr2[5][5] = arr2c[5*n+5] = 23; 

如果您将其构造为Iliffe向量,则只能获得双重括号。

 int **arr2l=calloc(n,sizeof*arr2l); for (int j=0; j<n; j++) arr2l[j]=calloc(n,sizeof**arr2l); arr2[6][6] = arr2l[6][6] = 72; 

但随着尺寸的增加,这变得越来越麻烦。

另一个困难是在访问元素之前检查动态数组的边界(这样您就不会触及未正确分配的内存)。 真实数组可以使用sizeof运算符来确定边界,但这些动态数组中没有一个带有它们的大小。

如何定义具有快速,连续布局的结构,如数组,但具有一致的语法,用于访问具有索引列表的元素,这些索引对于2Darrays和3Darrays的工作方式相同; 并动态地,动态地提供大小,以便它们可以传递给函数并从函数返回?

没有必要重新发明轮子,C从C99开始,它被称为可变长度arrays,VLA。 它只是语法为“普通”d维数组,只是边界可能是变量的,并且它们不允许在文件范围内。

因为这样的对象可能变得相对较大,所以不应该在堆栈上分配它们,而是使用类似malloc东西

 double (*A)[n][m] = malloc(sizeof(double[k][n][m])); 

编译器然后帮助您进行所有索引计算而不会出现问题。 如果你想将这些动物传递给函数,你必须首先要小心地声明边界:

 void func(size_t k, size_t n, size_t m, double A[k][n][m]); 

这使您的意图对人类读者和编译者都清楚。 我比同等forms更喜欢这个

 void func(size_t k, size_t n, size_t m, double (*A)[n][m]); 

如果将a定义为指向n整数数组的指针,编译器将执行索引算法。

 #define N 7 int (*a)[N]; int main() { a = malloc(N*N*sizeof(int)); a[2][3] = 0; } 

添加:

同样,一个三维的例子:

 #include  #include  #define N 7 int (*a)[N][N]; int main() { int i,j,k; a = malloc(N*N*N*sizeof(int)); for(i=0; i 

在J语言(APL的方言)的实现中使用了一种数据结构,其适应动态分配的任意维数组。 它使用struct和动态数组的混合来实现其数据结构,这种技巧通常称为struct hack 。 (有关J实现的更多信息,请点击 此处 。)

要在简单的上下文中看到这个想法,请考虑一维情况:我们需要一个动态的一维数组,它随之携带大小。 所以:

 struct vec { int n; int p[]; }; 

由于p成员是最后一个,并且C没有内置边界检查,因此它可以用于访问struct 末尾的附加内存。 当然,在分配时,我们需要提供这个额外的内存,而不是简单地分配struct的大小。 struct只是数组的标题 。 C90需要一个数字(比如1)作为p []数组的长度,但C99允许省略数字,因此标题的大小更容易计算。

因此,具有更多维度的数组将需要更多值来保存每个维度的大小。 并且对于我们的结构来适应不同维度的数组,这个维度向量也需要是可变长度的。

我们可以做的是实现所有这一切是应用结构黑客两次,递归自己。 这给了我们这样的内存布局,其中R是我们称之为数组排名的维数, D值是每个维的长度, V值是实际的数组数据:

  1 R Product(D) --- -------------------- ----------------------------- RD[0] D[1] ... D[R-1] V[0] V[1] ... V[Product(D)-1] 

并用C来描述

 typedef struct arr { int r; int d[]; } *arr; 

数组a的元素紧跟在dims向量DR元素之后。 所以V元素可以在a->d[r+0], a->d[r+1], ... a->d[r+i] (在将索引向量减少到单个之后)扁平表示的索引)。 这些元素最容易以行主顺序处理。 实际元素的数量是所有维度的乘积,所有维度相乘。 编辑:这里的表达式写得更好: (a->d+a->r)[0], (a->d+a->r)[1], ... (a->d+a->r)[i]

为了分配这些东西中的一个,我们需要一个函数来计算这个产品作为尺寸计算的一部分。

 int productdims(int rank, int *dims){ int z=1; for(int i=0; i 

要初始化,我们只需要填写成员。

 arr makearr(int rank, int *dims){ arr z = calloc( (sizeof(struct arr)/sizeof(int)) + rank + productdims(rank,dims), sizeof(int)); z->r = rank; memmove(z->d,dims,rank*sizeof(int)); return z; } 

记住使用单个索引访问2D数据的公式(比如[m] [n]个元素的数组)(这是问题中的典型动态数组)。 元素[i] [j]在i×n&plus; j 。 对于3Darrays[m] [n] [o],元素[i] [j] [k]在i×(n×o)+ j×o&plus; k

因此,我们可以从索引数组和维数组计算线性布局数据的单个索引。

 int *elem(arr a, ...){ va_list ap; int idx = 0; va_start(ap,a); if (a->r){ idx = va_arg(ap,int); for(int i=1; ir; i++){ idx *= a->d[i]; idx += va_arg(ap,int); } } va_end(ap); return &a->d[a->r + idx]; } 

不要忘记标题:

 #include  #include  #include  #include  

瞧:

 int main() { { int i,n=6; arr m = makearr(1, (int[]){n}); for (i=0;i 

输出:

 0 1 2 3 4 5 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 

进一步详细说明此代码以支持2D矩阵乘法和列切片已发布到此线程中的 comp.lang.c.

更有动力的问题/更强大的数据结构 。