C语言中多维数组的替代方案

4

我有以下代码:

 #define FIRST_COUNT 100
 #define X_COUNT 250
 #define Y_COUNT 310
 #define z_COUNT 40

struct s_tsp {

     short abc[FIRST_COUNT][X_COUNT][Y_COUNT][Z_COUNT];
};

struct s_tsp xyz;

我需要像这样浏览数据:
for (int i = 0; i < FIRST_COUNT; ++i)
    for (int j = 0; j < X_COUNT; ++j)
          for (int k = 0; k < Y_COUNT; ++k)
                for (int n = 0; n < Z_COUNT; ++n)
                      doSomething(xyz, i, j, k, n);

我已经尝试想到一种更优雅、更不蠢的方法。(我知道这种多维数组在CPU使用方面是低效的,但在这种情况下这是无关紧要的。)您认为是否有更好的方法来重构我这里的结构呢?


1
你想用这些数据做什么?如果不了解你的具体操作,很难给出建议。 - user1118321
2个回答

5

如果您需要一个4D数组,那就是您需要的。但是将其“展平”为单维malloc()ed“数组”是可能的,但这并不是很干净的做法:

abc = malloc(sizeof(short)*FIRST_COUNT*X_COUNT*Y_COUNT*Z_COUNT);

访问也更加困难:

*(abc + FIRST_COUNT*X_COUNT*Y_COUNT*i + FIRST_COUNT*X_COUNT*j + FIRST_COUNT*k + n)

显然这有点麻烦。

但是你有一个优势,如果你需要简单地迭代每个元素,可以执行以下操作:

for (int i = 0; i < FIRST_COUNT*X_COUNT*Y_COUNT*Z_COUNT; i++) {
    doWhateverWith *(abc+i);
}

显然,对于大多数用途来说,这种方法非常丑陋,但对于一种访问类型来说则更加简洁。它还更节省内存,并且只需要一个指针解引用而不是四个。


1
你可以编写一个带有访问函数的小模块,并将类型封装在结构体中,以获得清晰的API。 - pmr
5
没错。如果你是个极坏的人,甚至可以使用宏来完成这件事。 - Dan

3

注意:本文中所使用的示例仅用于解释概念。因此,示例可能不完整,可能缺乏错误处理等。

C 中使用多维数组有两种可能的方法。

压平数组

C 中,数组被实现为一个连续的内存块。这个信息可以用来操作存储在数组中的值,并允许快速访问特定的数组位置。

例如,

int arr[10][10];
int *ptr = (int *)arr ;  

ptr[11] = 10; 
// this is equivalent to arr[1][0] = 10; assign a 2D array 
// and manipulate now as a single dimensional array. 

利用数组的连续性特征技术被称为数组扁平化

不规则数组

现在考虑以下示例。

char **list;
list[0] = "United States of America";
list[1] = "India";
list[2] = "United Kingdom";
for(int i=0; i< 3 ;i++)
    printf(" %d ",strlen(list[i]));
// prints 24 5 14

这种实现方式称为“不规则数组”,在使用变量大小的字符串时非常有用。流行的方法是在每个维度上进行动态内存分配。
注意:命令行参数(`char * argv []`)只作为不规则数组传递。
比较展平和不规则数组:
现在,让我们考虑以下代码片段,它比较了“展平”和“不规则”数组。
/* Note: lacks error handling */
int flattened[30][20][10];
int ***ragged;
int i,j,numElements=0,numPointers=1;
ragged = (int ***) malloc(sizeof(int **) * 30);
numPointers += 30;
for( i=0; i<30; i++) {
    ragged[i] = (int **)malloc(sizeof(int*) * 20);
    numPointers += 20;
    for(j=0; j<20; j++) {
        ragged[i][j]=(int*)malloc(sizeof(int) * 10);
        numElements += 10;
    }
}

printf("Number of elements = %d",numElements);
printf("Number of pointers = %d",numPointers);

// it prints
// Number of elements = 6000
// Number of pointers = 631 

从上面的例子可以看出,ragged数组需要631个指针,换句话说,额外需要631 * sizeof(int *)的内存位置来指向6000个整数。而flattened数组只需要一个基本指针:也就是数组名称足以指向连续的6000个内存位置。
但是相反,ragged数组非常灵活。如果不知道需要多少内存位置,那么就不能有为最坏情况分配内存的奢侈。同样,在某些情况下,只有在运行时才知道所需的确切内存空间数量。在这种情况下,ragged数组非常方便。 数组的行主序和列主序 C采用行主序顺序处理多维数组。将数组展平可以看作是C中该方面影响的一种效果。C中行主序顺序的重要性在于它符合大多数编程访问数据的自然方式。例如,让我们看一个遍历N * M 2D矩阵的示例。
for(i=0; i<N; i++) {
    for(j=0; j<M; j++)
        printf(“%d ”, matrix[i][j]);
    printf("\n");
}

矩阵中的每一行都通过快速变换列来逐个访问,C数组按这种自然方式在内存中排列。相反,考虑以下示例,

for(i=0; i<M; i++) {
    for(j=0; j<N; j++)
        printf(“%d ”, matrix[j][i]);
    printf("\n");
}

这会更改列索引比行索引更频繁。因此,这两个代码片段之间的效率差异很大。是的,第一个比第二个更有效!

因为第一个按C的自然顺序(行优先)访问数组,所以它更快,而第二个需要更长时间才能跳转。随着维度数量和元素大小的增加,性能差异会加大。

因此,在使用C中的多维数组时,考虑上述细节是很好的选择!


你的代码存在一些重要问题,包括使用未初始化的变量和未定义的类型转换。 - dreamlax
@dreamlax 是的,这些代码片段是不完整的。它们旨在解释想法和概念。所以正如我所提到的,它缺少错误处理、释放已分配内存和未初始化内存。 - Sangeeth Saravanaraj

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接