上次我们已经了解了串的顺序定长实现和串的两种模式匹配算法的实现。此次,我们一起来看看数组的顺序存储实现。
还是老规矩:
程序在码云上可以下载。
地址:https://git.oschina.net/601345138/DataStructureCLanguage.git
数组是也是一种特殊的线性表,它的限制比串、栈和队列都要严格——不仅限制了操作的类型(只能做存取和定位操作),而且限制了一个数组中只能存储同种类型的元素。数组的存储空间是固定的,不允许我们在运行过程中动态改变其存储空间的大小。
首先看看数组的ADT定义,了解一下数组可以进行哪些操作:
ADT Array{
数据对象:ji = 0, …, bi-1, i = 1,2,…,n,
D = {aj1j2…jn|n(>0)称为数据的维数, bi是数组第i维的长度,
ji是数组元素的第i维下标,aj1j2…jn ∈ ElemSet}
数据关系:R = {R1, R2, … ,Rn}
Ri = {<aj1…ji…jn, aj1…ji+1…jn >|
0<=jk<=bk-1, 0<=k<=n,且k!=i,
0<=ji<=bi-2,
aj1…ji…jn, aj1…ji+1…jn ∈ D, i = 1,2,…,n }
基本操作:
InitArray(&A, n, bound1, … , boundn)
操作结果:若维数 n 和各维长度合法,则构造相应的数组A,并返回OK。
DestroyArray(&A)
操作结果:销毁数组A;
Value(A, &e, index1, … , indexn)
初始条件:A是n维数组,e为元素变量,随后是n个下标值。
操作结果:若各下标不超界,则e赋值为所指定的A的元素值,并返回OK。
Assign(&A, e, index1, … , indexn)
初始条件:A是n维数组,e为元素变量,随后是n 个下标值。
操作结果:若下标不超界,则将e的值赋给所指定的A的元素,并返回OK。
} ADT Array
在看代码之前我们需要了解几个非常重要的知识点:
1.可变参数
书上P93页增加了一个头文件:stdarg.h,这个头文件引入了一项重要功能——可变参数。书中的程序也大量使用了可变参数作为形参。我们在写程序之前需要了解清楚可变参数是做什么的,怎么用才可顺利完成程序的实现。
首先要搞明白为什么书的作者要使用可变参数的方式实现数组?
作者的想法是在一个线性的存储空间上实现一个n维数组,将n维数组按照一定的映射方式(映射就是对应的意思)存储到一维数组中,使用数组的时候想要找到某个元素或者修改某个元素只需要提供n维数组对应的下标,程序就会按照事先设置的映射规则(书中称之为映像函数)计算出这个元素相对与存储它的一维数组是在哪个位置上,然后再去一维数组里面找。使用者只需要知道n维数组的下标,不需要了解n维数组的实现原理,也不需要知道指定下标的元素在一位数组中怎样映射就可以实现对数组的操控了。
可是要实现这样的数组,我们需要确定数组是几维数组(维数)以及每一维的长度(维界),才可能知道下标传几个数字才是正确的,比如:一维数组只传一个数字就可以唯一确定一个元素,二维数组传两个数字可以唯一确定一个元素,三维数组传三个数字可以唯一确定一个元素。。。以此类推。那也就是说每次在参数列表中传递的数字个数是不确定的,因为我们想要写出的代码可以适应任意维数的数组,调用处维数修改时不需要修改数组的源代码就可以适应任意维数的情况,提高代码的灵活性,但是在不使用可变参数的情况下我们目前掌握的C语言技术还不足以实现在参数列表中添加任意多个参数的功能,因为按照我们的认知:形式参数和实际参数必须保证位置上一一对应,而且参数类型也必须匹配才可以正确的编译和运行。
想要实现形式参数和实际参数的个数不相等的情况下仍然可以按照实际传入的参数对应形参就必须要靠可变参数了。可变参数可以实现我们的设想:一维数组传在实参列表中写1个下标值,二维数组传在实参列表中写2个下标值,三维数组传在实参列表中写3个下标值。。。以此类推。所以我们要使用可变参数实现n维数组。
百度百科如是说:https://baike.baidu.com/item/stdarg.h/10239382?fr=aladdin
stdarg.h是C语言中C标准函数库的头文件,stdarg是由standard(标准) arguments(参数)简化而来,主要目的为让函数能够接收可变参数。C++的cstdarg头文件中也提供这样的功能;虽然与C的头文件是兼容的,但是也有冲突存在。
可变参数函数(Variadic functions)是stdarg.h内容典型的应用,虽然也可以使用在其他由可变参数函数调用的函数(例如,vprintf)。
也就是说想要使用可变参数必须导入stdarg.h这个头文件。
推荐大家看一篇文章,我也是看这篇文章了解了可变参数的使用方法,非常感谢作者的分享:
http://blog.csdn.net/ddppqq/article/details/17332161
从这篇文章中提炼的对我们有用的信息如下:
VA_LIST的用法:
函数原型:
void va_start( va_list arg_ptr, prev_param );
type va_arg( va_list arg_ptr, type );
void va_end( va_list arg_ptr );
va在这里是variable-argument(可变参数)的意思.
使用步骤
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针
(2)然后用VA_START宏初始化变量刚定义的VA_LIST变量,
这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数。
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型。
(4)最后用VA_END宏结束可变参数的获取。然后你就可以在函数里使用第二个参数了。
如果函数有多个可变参数的,依次调用VA_ARG获取各个参数。
使用VA_LIST应该注意的问题:
(1)因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,
可变参数的类型和个数完全在该函数中由程序代码控制,
它并不能智能地识别不同参数的个数和类型. 也就是说,你想实现
智能识别可变参数的话是要通过在自己的程序里作判断来实现的.
(2)另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,
对编程查错不利.不利于我们写出高质量的代码。
在搞清楚作者的意图的可变参数的使用方法之后,我们需要了解一下上溢OVERFLOW和下溢UNDERFLOW:
2.上溢和下溢
对于计算机中的上溢和下溢,有一种简单的解释:
上溢:运算结果超出所能表示的最大正数
下溢:运算结果超出所能表示的最小负数
但是我们说的并不是这个含义,作者在程序中更想要表示的是数组下标越界的含义:
在math.h中定义了一些表示运算异常的常量,其中有几个我们经常见到:
/*
* Types for the _exception structure.
*/
#define _DOMAIN 1 /* domain error in argument */
#define _SING 2 /* singularity */
#define _OVERFLOW 3 /* range overflow */
#define _UNDERFLOW 4 /* range underflow */
#define _TLOSS 5 /* total loss of precision */
#define _