典型的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-vector,则只能获得双重括号。
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
运算符来确定边界,但这些动态数组都没有随附它们的大小。
如何定义具有快速,连续布局的结构,如数组,但具有一致的语法,用于访问具有索引列表的元素,这些索引对于2D阵列和3D阵列的工作方式相同;和动态,动态,大小可用,以便它们可以传递给函数并从函数返回?
答案 0 :(得分:8)
没有必要重新发明轮子,C从C99开始,它被称为可变长度阵列,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]);
这使得您的意图对人类读者和编译者都清楚。我比同等形式更喜欢这个
void func(size_t k, size_t n, size_t m, double (*A)[n][m]);
答案 1 :(得分:2)
在J语言(APL的一种方言)的实现中使用了一种数据结构,它适应动态分配的任意维数组。它使用struct
和动态数组的混合来实现其数据结构,这种技巧通常称为 struct hack 。 (有关J实施的更多信息here和here。)
要在简单的上下文中看到这个想法,请考虑一维情况:我们需要一个动态的一维数组,它随之携带大小。所以:
struct vec { int n; int p[]; };
由于p
成员是最后一个,并且C没有内置边界检查,因此可以使用它来访问struct
的最后的附加内存。当然,在分配时,我们需要提供额外的内存而不是简单地分配struct
的大小。 struct
只是数组的标题。 C90需要一个数字(比如1)作为p []数组的长度,但是C99允许省略数字,因此标题的大小更容易计算。
因此,具有更多维度的数组将需要更多值来保存每个维度的大小。对于我们的结构来容纳不同维度的数组,这个维度向量也需要是可变长度的。
我们可以做的就是将 struct hack 两次应用于递归自身。这给了我们这样的内存布局,其中R
是我们称之为数组的 rank 的维数,D
值是每个的长度维度,V
值是实际的数组数据:
1 R Product(D)
--- -------------------- -----------------------------
R D[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向量R
的{{1}}元素。因此,D
元素可以在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<rank; i++)
z *= dims[i];
return z;
}
记住使用单个索引访问2D数据(例如[m] [n]个元素的数组)的公式(它是问题中的典型动态数组)。元素[i] [j]位于 i×n + j 。对于3D阵列[m] [n] [o],元素[i] [j] [k]在 i×(n×o)+ j×o + k 。
因此,我们可以从索引数组和维数组计算线性布局数据的单个索引。
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;
}
不要忘记标题:
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; i<a->r; i++){
idx *= a->d[i];
idx += va_arg(ap,int);
}
}
va_end(ap);
return &a->d[a->r + idx];
}
瞧瞧:
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
输出:
int main() {
{
int i,n=6;
arr m = makearr(1, (int[]){n});
for (i=0;i<n;i++)
*elem(m,i) = i;
for (i=0;i<n;i++,printf(" "))
printf("%d",*elem(m,i));
}
puts("\n");
{
int i,j,n=4;
arr m = makearr(2, (int[]){n,n});
for (i=0;i<n;i++)
for (j=0;j<n;j++)
*elem(m,i,j) = i*n+j;
for (i=0;i<n;i++,printf("\n"))
for (j=0;j<n;j++,printf(" "))
printf("%d",*elem(m,i,j));
}
puts("\n");
{
int i,j,k,n=3;
arr m = makearr(3, (int[]){n,n,n});
for (i=0;i<n;i++)
for (j=0;j<n;j++)
for (k=0;k<n;k++)
*elem(m,i,j,k) = (i*n+j)*n+k;
for (i=0;i<n;i++,printf("\n"))
for (j=0;j<n;j++,printf("\n"))
for (k=0;k<n;k++,printf(" "))
printf("%d",*elem(m,i,j,k));
}
return 0;
}
此代码的进一步详细说明已支持二维矩阵乘法和列切片,已发布到comp.lang.c中的this thread。
答案 2 :(得分:2)
如果将a
定义为指向n
整数数组的指针,编译器将执行索引算法。
#define N 7
int (*a)[N];
int main() {
a = malloc(N*N*sizeof(int));
a[2][3] = 0;
}
增加:
同样,一个三维的例子:
#include <stdio.h>
#include <stdlib.h>
#define N 7
int (*a)[N][N];
int main() {
int i,j,k;
a = malloc(N*N*N*sizeof(int));
for(i=0; i<N; i++) {
for(j=0;j<N;j++) {
for(k=0;k<N;k++) {
a[i][j][k] = (i*10+j)*10+k;
}
}
}
for(i=0; i<N; i++) {
for(j=0;j<N;j++) {
for(k=0;k<N;k++) {
printf("%2d ", a[i][j][k]);
}
printf("\n");
}
printf("\n");
}
}