目录
目录
前言
由于二维数组在日常程序中使用过程中使用频繁,由于普通的二维数组定义需要至少一个阶数,行阶或者列阶。例:int arr[M][N]提前明确二维数组的行阶和列阶, 而int (*p)[N]则仅仅需要一个列阶,之后通过p = arr,p可以直接对arr二维数组中的元素进行访问。
于是我们可以编写一个“通用二维数组工具”!这个工具让用户在使用时,不必关心其行阶与列阶的明确声明,可以直接使用。要求用户不必明确确定N!也不用明确确定M!但是需要告知工具行阶和列阶。即可生成使用一个二维数组。
一、前驱知识
1.数组的概念及原则
数组是一种线性逻辑结构,也就是一对一结构;这里讲的数组与C语言语法中的数组不是同一个概念,应该说,现在讲的数组,是“概念”上的数组,或者说,是所有程序设计语言的数组语法的共同特性的讨论与核心技术的研究。
以某种程序设计语言中数组定义为例: DIM arr(3 .. 15) AS INTEGER
上面语句的意思是:定义一个名称为arr的数组,下标从3到15,且每一个元素的数据类型为整型。对于数组这个概念,无论什么样的程序设计语言,都有如下基本原则:
1、数组是由多个变量元素构成;
2、这些变量(空间)所占用的内容空间是连续存储空间;
3、数组所占用的空间的首地址可以用Loc(arr)表示,且也是数组第一个元素的首地址;
4、数组中的每一个元素的数据类型都是相同的;或者说,数组的每一个元素其空间长度相同;
5、根据第2点、第3点和第4点可知,数组中的每一个元素的首地址,可以通过直接计算得到,并可以根据计算出的首地址对相关元素进行访问,这种访问方法称为:随机访问。
(补充:与随机访问相对应的是:顺序访问;对于链表中的节点,就是用顺序访问方法。)
2.二维数组一维化
在进行二维数组一维化时需要提到一个知识点——地址映射。这是一个在某种程序设计语言中数组的定义:DIM arr(s .. e) AS TYPE,且sizeof TYPE为len,Loc(arr)是数组首地址,则,假设index在[s, e],要求计算:Loc(arr(index))。
上述计算要求被称为:逻辑地址向物理地址的转换/映射,简称:地址映射。
地址映射是计算机原理(包括操作系统等)中非常基础和重要的技术。软件调入内存后,在内存中所占用的空间的首地址通常称为:段地址;软件中某一条指令,或某一个数据(变量或常量)在软件中的首地址,通常称为:偏移量。
物理地址 = 段地址 + 偏移量 (这是操作系统级别的概念)
上面的计算物理地址的式子,被称为:地址映射。
在《数组》这一章中所讨论的,还应该是计算偏移量的过程。即,Loc(arr)是逻辑首地址,也就是相对于软件首地址而言的偏移量。
Loc(arr(index)) = Loc(arr) + (index - s) * len
上述式子中,最关键的是:index - s,因此,对于后续要讲述的多维数组元素定位,主要讨论的
也是这类公式。
在简单了解了地址转换之后,便可以进行二维数组一维化的过程了。在二维数组用一维数组表示时最主要是下标的变化,即使用二维数组的行与列表示出一维数组的下标。而对于二维数组的地址转化我们很容易可以想到新的下标T = X * Ycount + Y;至此,二维数组用一维数组表示的最重要的一点就已经解决了。
二、模块实现
1.数组的建立与销毁
建立
首先我们需要定义一个结构体来存放数组的行阶、列阶以及指向存放数据的数组的指针。
代码如下:
typedef int USER_TYPE;
typedef struct MATRIX {
USER_TYPE *matrix;
int rowCount;
int colCount;
}MATRIX;
这个结构体里由于数据类型可能多变,需要我们定义一个不定的类型,在使用时需要在头文件中定义数据类型,即typedef int USER_TYPE。
至于对数组的建立与销毁与上一篇所描述的队列的实现过程相似,类比可得,不太清楚代码含义的可以移步上一篇拙作。
代码如下:
boolean matrixInit(MATRIX **head, int rowCount, int colCount) {
MATRIX *h = NULL;
if (NULL == head || *head != NULL || rowCount <= 0 || colCount <= 0) {
return FALSE;
}
h = (MATRIX *) calloc(sizeof(MATRIX), 1);
if (NULL == h) {
return FALSE;
}
h->matrix = (USER_TYPE*) calloc(sizeof(USER_TYPE), rowCount * colCount);
h->rowCount = rowCount;
h->colCount = colCount;
if (NULL == h->matrix) {
free(h);
return FALSE;
}
*head = h;
return TRUE;
}
销毁
代码如下:
void matrixDestory(MATRIX **head) {
if (NULL == head || NULL == *head) {
return;
}
free((*head)->matrix);
free(*head);
*head = NULL;
}
2.数据的存储
数据存储的实现最主要就是用二维数组下标转化为一维数组下标来存储。判断条件是因为如果所输入的行列值大于阶数则返回存储错误。
t = row * head -> colCount + col !!!
代码如下:
boolean setValueAt(MATRIX *head, int row, int col, USER_TYPE data) {
if (NULL == head || row >= head->rowCount || col >= head->colCount) {
return FALSE;
}
head->matrix[row * head->colCount + col] = data;
return TRUE;
}
3.数据的输出
对于数据的输出我们可以类比数据的输入,即将二维数组的横纵坐标转化为一维数组的下标,定位到数据所在位置。
但是在取出数据有一个很重要的地方,在选择函数返回值的时候,不可以为了方便直接返回一个数据指针,直接返回一个*data 的话,在进行数组销毁时无法销毁这个指针,这个指针将指向一个空间,造成内存泄漏。虽然在短期看影响不大,但是在进行一些比较大的项目实现中如果发生内存泄漏后果不可想象。所以我们可以传递一个 *head 形参,来存储取出来的数据。
代码如下:
boolean getValueAt(MATRIX *head, int row, int col, USER_TYPE *data) {
int tmp;
if (NULL == head || row >= head->rowCount || col >= head->colCount) {
return FALSE;
}
*data = head->matrix[row * head->colCount + col];
return TRUE;
}
总结
本文主要讲了一个任意阶数二维数组的实现以及简单应用 。其中使用的思想核心就是二维数组的一维化,本质即下标的转换。而在具体功能的实现中数组,队列以及堆栈的主体框架都大致相同,本文想强调对于编程思想与手工过程比盲目编写代码更为重要。