多维数组的顺序实现
一.前言
对于数组,我们大家肯定不陌生。单维数组肯定很熟悉,那多维数组呢?实际上就是多个单维数组所构成的特殊的数组的数组
。这一内容,在C++的文章里也提到过。那么,我们如何通过用线性结构
来模拟一个多维数组呢?这个内容就是本节所着重讨论的地方。
二.数组的结构再探
1.降维
假设我们有一个多维数组,它的大小是2行3列,那么我们数组可以表示为:A = {
{1,2},{3,4},{5,6}}。如果看图的话,可以把它“按行降维”和“按列降维”,把它变成一个一维数组。按行降维可以得到a={1,2,3,4,5,6},按列降维可以得到a={1,3,5,2,4,6}。
2.下标计算
在严佬的这本《数据结构》中,通过一串很长的公式来推导一维数组计算下标的方法,其实也很简单,只是数学公式看着烦。现在可以来推导一下,假设我们的数组的维度是3,每维的长度分别是2,2,3。那么我们那把这12个元素的下标用(1,1,1)(1,1,2)(1,1,3)(1,2,1)(1,2,2)(1,2,3)(2,1,1)(2,1,2)(2,1,3)(2,2,1)(2,2,2)(2,2,3)表示。那么我们第三维的角标+1的时候(其他两个维的角标没变,第三维角标+1),序号增加了1;第二维的元素角标+1的时候,序号增加了3;第一维的元素角标+1的时候,序号增加了6 。
举例(1,1,1)=1、(1,1,2) =2、(1,2,1)=4、(2,1,1)=7。
假设我们这个下标用(a,b,c)表示,那么它的序号的计算公式因为等于(a-1)*6+(b-1)*3+(c-1) + 1
。当然,如果序号从0开始,就可以不用末尾的+1了。
假设我们用.s表示数组维度的长度,序号从0开始,那么我们这个公式就可以表示为:(a-1)*(b.s*c.s)+(b-1)*(c.s)+(c-1)*(1)
。
3.名词解释
有了刚才那些描述,我们就可以很好的解释一下书上的这些名词了。
元素基址
:就是我们(1,1,1)那个元素的地址。数组维度
:也就是我们这个括号里,有几个数,就有几个维度。数组维度基址
:假设我们三个维度的长度是2,2,3,那么我们可以用顺序表的方式,把每个数组维度的长度的存起来。当然存放第一个维度的长度的地址就是数组维度基址了。数组映像函数
:按照2小节的内容,我们把比它高的维度的长度的积,叫做数组映像函数。数组映像函数常数
:把该维度的数组映像函数求值的结果,就是该常数。比如(a,b,c)中,a所在的维度(第一维)的常数是,bc所在维度(二三维度)的长度的积。
有了数组映像函数常数(记为X.c),我们求某个高维数组[x,y,z]元素的一维数组序号就可以变为(x-1)×(x.c)+(y-1)×(y.c)+(z-1)×(z.c)。
好了,这样我们就可以开始对高维数组进行降维打击了。
三.实现原理
1.数组的结构
对于一个数组,我们肯定要包含以下几个部分:
- 元素区,用于存储元素。
- 数组维度,储存一共有多少维度。
- 维度区,存一下每个维度的长度。
- 数组映像函数常数区,存一下每个维度的数组映像函数常数。
2.数组的初始化
毕竟要初始化,我们肯定要有总维度,和每个维度的长度。
然后根据数组维度,去对维度区赋值,此时在做个累乘,就可以把总元素长度求出来,可以动态申请元素区的长度。然后通过维度区的内容,循环去求数组函数常数区。这样,就初始化了数组。
3.数组的取地址
通过判断下标是否合法,然后做出计算,返回数组元素的相对地址。
4.数组的取值与修改
通过取地址,然后取值或者修改
四.代码实现
1.stdarg.h
因为我们维度是不定长的,所以我们需要用这个库去求取不定长的参数。
1.1 va_list
本质上是char*指针,实际上是用于声明可变长度参数序列。
eg va_list args;
1.2 va_start()
两个参数,第一个参数是va_list
申明的“变量”,第二个参数,函数参数列表...
前的一个参数。
eg printfint(int level, int test, ...)
那么这个就是va_start(args, test)
1.3 va_arg()
两个参数,第一个参数是va_list
声明的“变量”,第二个参数,是参数类型。
eg int context = va_arg(args, int);
1.4 va_end()
一个参数,清空va_start
,传递内容是刚才va_start()的va_list
。
1.6 样例代码
#include <stdio.h>
#include <stdarg.h>
void printfint(int level, int arg, ...);
void printfint(int level, int arg, ...)
{
int temp=-1;
va_list args;
va_start(args, arg);
printf("level(%d):", level);
temp = va_arg(args, int);
while(temp != 0)
{
printf(" %d", temp);
temp = va_arg(args, int);
}
printf("\n");
va_end(args);
}
int main()
{
printfint(0,1,2,3,4,5,0);
printfint(6,5,4,3,2,1,0);
return 0;
}
2.主代码
因为实现了初始化和取下标,那么对于修改值和查询值就很容易了,所以我为了偷懒,只写出了前两个函数:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#define OK 1
#define ERROR 0
typedef struct dataType{
int context;
}dataType;
typedef struct Array{
dataType *base;
int dim;
int *bounds;
int *constants;
}Array;
Array *ArrayInit(int dim, ...);
int ArrayDeInit(Array *array);
int ArrayGetAddr(Array *array, ...);
int main()
{
Array *array = ArrayInit(3,5,10,10); // 这个就是初始化函数
printf("addr=%d",ArrayGetAddr(array, 1, 2, 3)); // 这个是取出指定高维数组下标的顺序数组下标
return 0;
}
Array *ArrayInit(int dim, ...)
{
int temp, cont=1;
int i;
va_list args; // 初始化变长参数
Array *array = NULL;
if (dim < 0) exit(1);
array = (Array*)malloc(sizeof(Array));
if (array == NULL)
{
exit(1);
}
// 初始化数组
array->dim = dim;
array->bounds = (int*)malloc(sizeof(int)*dim);
array->constants = (int*)malloc(sizeof(int)*dim);
if (array->constants == NULL || array->bounds == NULL)
{
exit(1);
}
//初始化数组信息
va_start(args, dim);
for (i = 0; i < dim; ++i) // 获取每个维度的长度
{
*(array->bounds+i) = va_arg(args, int); // 保存每个维度的长度
cont *= *(array->bounds+i); // 统计一共有多少个元素
}
array->base = (dataType *)malloc(sizeof(dataType)*cont);
// 动态生成有多少个元素,开辟储存空间
*(array->constants+dim-1) = 1; // 最高维度的数组映像函数常数为1
for (i = dim - 2; i >= 0; --i) // 最高维度已经手动统计啦
{
*(array->constants+i) = *(array->constants+i+1)*(*(array->bounds+i+1));
// 当前维度的数组映像函数常数 = 比当前高一维度的常数*高一维度的长度
}
va_end(args); // 保护动态参数指针
return array; // 数组生成好了
}
int ArrayDeInit(Array *array)
{
if (!array->base) return ERROR;
free(array->base);
if (!array->constants) return ERROR;
free(array->constants);
if (!array->bounds) return ERROR;
free(array->bounds);
return OK;
}
int ArrayGetAddr(Array *array, ...)
{
int i = 0, temp;
int addr = 0;
va_list args;
va_start(args, *array);
for (; i < array->dim; ++i) // 从每一个维度遍历
{
temp = va_arg(args, int);
if (temp < 1 || temp > *(array->bounds+i))
{
exit(2); // 当前维度下标越界
}
addr += (temp-1)*(*(array->constants+i)); // (每个维度下标-1)乘以常数的和,就是下标
}
return addr;
}