线性表的顺序表示,指的是用一组地址连续的存储单元依次存储线性表的数据元素。下面直接上代码
----------------------------------------------------------------------------------------------------------------------------------------------------------
头文件 "SeqList.h"
#ifndef __SEQLIST_H
#define __SEQLIST_H
typedef void SeqList;
typedef void SeqListNode;
SeqList *SeqList_Create(int capacity); //创建一个顺序表
void SeqList_Destroy(SeqList *list); //销毁一个顺序表
void SeqList_Clear(SeqList *list); //清空一个顺序表
int SeqList_Length(SeqList *list); //获取顺序表的长度
int SeqList_Capacity(SeqList *list); //获取顺序表的容量
int SeqList_Insert(SeqList *list, SeqListNode *node, int pos); //插入一个元素到顺序表pos位置
SeqListNode *SeqList_Delete(SeqList *list, int pos); //删除顺序表pos位置的元素
SeqListNode *SeqList_Get(SeqList *list, int pos); //返回特定位置(pos)的元素
#endif
----------------------------------------------------------------------------------------------------------------------------------------------------------
相关操作实现函数文件:SeqList.c
#include <stdio.h>
#include <malloc.h>
#include "SeqList.h"
typedef unsigned int TSeqListNode;
typedef struct {
int capacity;
//顺序的容量
int length;
//当前元素个数
TSeqListNode *node;
} TSeqList;
/*
@param capacity: 顺序表容量capacity
*/
SeqList *SeqList_Create(int capacity)
{
TSeqList *ret = NULL;
if (capacity >= 0)
{
//这里一次性申请了堆空间,这段空间存储是一个TSeqList结构体后面紧跟着一个TSeqListNode数组,长度
//为capacity,所以在下面对成员node赋值时只需要将ret指针加1即可拿到数组首地址
ret = (TSeqList*)malloc(sizeof(TSeqList) + sizeof(TSeqListNode)*capacity);
}
if (ret)
{
ret->capacity = capacity;
ret->length = 0;
ret->node = (TSeqListNode*)(ret + 1);
}
return ret;
}
/*
@param list: 待销毁的顺序表指针SeqList
*/
void SeqList_Destroy(SeqList *list)
{
free(list);
}
/*
@param list: 待清空的顺序表指针SeqList
*/
void SeqList_Clear(SeqList *list)
{
if (list)
{
((TSeqList*)list)->length = 0;
}
}
int SeqList_Length(SeqList *list)
{
int length = -1;
if (list)
{
length = ((TSeqList*)list)->length;
}
return length;
}
int SeqList_Capacity(SeqList *list)
{
int capacity = -1;
if (list)
{
capacity = ((TSeqList*)list)->capacity;
}
return capacity;
}
/*
@return 0:插入失败;1:插入成功
*/
int SeqList_Insert(SeqList *list, SeqListNode *node, int pos)
{
TSeqList *sList = (TSeqList*)list;
int ret = (sList!=NULL);//1. 线性表是否合法
int index = 0;
ret = ret && (sList->length + 1 <= sList->capacity);
ret = ret && (pos >= 0);
//2. 插入位置是否合法
if (ret)
{
if (pos >= sList->length)//对要求插入位置超过当前长度时要调整插入位置
{
pos = sList->length;
}
//3. 将最后一个元素到插入位置的元素后移一个位置
for (index = sList->length; index > pos ; index--)
{
sList->node[index] = sList->node[index-1];
}
//4. 插入新元素
sList->node[pos] = (TSeqListNode)node;//刚开始时这段代码着实让我困惑了许久,差点就推翻了一直以来
//对于C语言数组名含义的理解,后来发现,在我们node指针指向//的那个数组里面,保存的并不是元素的值,而是元素的地址//(TSeqListNode是unsigned int,可以表示一个32位的地址)
//5. 长度加1
sList->length++;
}
return ret;
}
SeqListNode *SeqList_Delete(SeqList *list, int pos)
{
TSeqList *sList = (TSeqList*)list;
SeqListNode *ret = NULL;
int i = 0;
//1. 判断线性表是否合法
if (sList)
{
//2. 判断删除位置是否合法
if (pos <= sList->length && pos >= 0)
{
//3. 取出删除元素
ret = (SeqListNode *)sList->node[pos];
//4. 将最后一个元素到删除位置的元素往前移动一位
for (i = pos; i < sList->length; i++)
{
sList->node[i] = sList->node[i+1];
}
//5. 长度减1
sList->length--;
}
}
return ret;
}
SeqListNode *SeqList_Get(SeqList *list, int pos)
{
TSeqList *sList = (TSeqList*)list;
SeqListNode *ret = NULL;
//1. 判断线性表是否合法
//2. 判断获取位置是否合法
if (sList && pos >= 0 && pos < sList->length)
{
//3. 取出元素
ret = (SeqListNode *)sList->node[pos];
}
return ret;
}
--------------------------------------------------------------------------------------------------------------------------------------------------------
main.c:测试功能
#include <stdio.h>
#include <stdlib.h>
#include "SeqList.h"
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char *argv[]) {
SeqList *list = SeqList_Create(5);
int a = 1;
int b = 2;
int c = 3;
int x = 4;
int y = 5;
int z = 6;
int index = 0;
SeqList_Insert(list, &a, 0);
SeqList_Insert(list, &b, 0);
SeqList_Insert(list, &c, 0);
SeqList_Insert(list, &x, 0);
SeqList_Insert(list, &y, 0);
SeqList_Insert(list, &z, 0);
for (index = 0; index < SeqList_Length(list); index++)
{
int *p = (int *)SeqList_Get(list, index);
printf("%d\n",*p);
}
while(SeqList_Length(list) > 0)
{
int *p = (int *)SeqList_Delete(list, 0);
printf("%d\n", *p);
}
SeqList_Destroy(list);
return 0;
}
这里输出是:
5
4
3
2
1
5
4
3
2
1
对于输出,这里解释两点:
1. 为什么插入6个元素,实际上只插入了5个元素?因为我们在创建顺序表时指定的容量为5(5个元素)
2. 为什么输出会是倒序?因为我们在插入时每次都将新插入元素插入到0位置处,这样就造成了后插入的元素在前面(即倒序)的结果
****最最让我眼前一亮的地方*********
就是在SeqList.h头文件中,"typedef void SeqList; typedef void SeqListNode;"这两行代码和
typedef unsigned int TSeqListNode;
typedef struct {
int capacity;
//顺序的容量
int length;
//当前元素个数
TSeqListNode *node;
} TSeqList;
也就是说在头文件中,用void去替代SeqList和SeqListNode,而在具体实现.C文件中才去指定具体的类型。
这里我们总结这两处typedef的奇妙功能:
1. SeqList.h中的typedef:
a. 实现了对数据的保护,防止一些误操作产生
要想解释这一点,首先必须明确一点,就是void*指针不能对其产生一些实际有意义的操作。我们来看我们的main.c文件中的main函数:
SeqList *list = SeqList_Create(5);
创建了一个顺序表,指定容量为5,并得到一个SeqList类型的地址,实际上这个地址就是我们通过malloc申请的堆空间,这段空间可以将其分为两部分:第一部分存储的是TSeqList结构体本身,第二部分是我们的node数组。实际上这里面产生了一个类型转换,即将TSeqList*转换成void*,当然这是在函数返回时实现的。
再来看list指针,这个list指针的类型是什么?SeqList。而这个SeqList实际上是什么类型?void(SeqList.h)。换句话说,我们list就是一个void*指针。到这里,就能够去解释我们这里提到的妙处:如果,我们此刻使用这样一条语句“list->length = 10;”,首先毋庸置疑,这是不允许的。一旦我们把这行代码写上去,一编译,肯定会报错,这样就把这种误操作扼杀在编译阶段(类似于Java中引入的泛型的作用之一:将类型转换错误扼杀在编译阶段)。也就是说,我们要想改变list所指向的顺序表的内容,必须使用其提供的接口(即我们实现的这些操作方法)。这样,我们提到的对数据的保护和防止误操作产生也就说的通了。这一点和面向对象编程中的封装这一特性相似。
b. 可移植性变得更强
二话不说,我们直接改动一些代码,将待插入的元素(即a,b,c,x,y,z变量的)类型有int变为char,当然,我们在进行取出操作时也要将得到的值转换成char,即int *p = (int *)SeqList_Get(list, index);和int *p = (int *)SeqList_Delete(list, 0);这两个地方也要改成char,输出printf()函数中的格式符应该由%d改成%c,就只改这些地方,然后编译执行,发现也通过,也能得到正确的结果。是不是很神奇?为什么呢?实际上很简单,因为我们在实现这些操作函数时,无论是作为参数(SeqList类型指针)传入还是作为结果返回(SeqListNode指针),实际上都是void*指针,我们在使用这些操作函数或者说在使用这个顺序表时,我们不需要考虑它的实现,我们只需要记住我们传入的是什么类型,我们取出时也还是那个类型。
2. SeqList.c中的typedef:
在上面1中b提到的妙处在我们实现中又是怎样保证的呢?我们知道在C语言中不同类型的变量所占的字节不同,char(1个字节),short(2个字节),int(4个字节)(当然这依赖具体的编译器),那我们是否能够找出一个特征是相同的并且能够不引起类型混乱呢?我们发现,无论是什么样的变量,它的地址都是4个字节的,即32位。而unsigned
int类型变量正适合这种表示,换句话说,我们任意一个地址,都能够用unsigned int变量去存储。所以,在我们TSeqList中的node数组中存储的是每个存储其中的元素的地址。这就是里面的typedef unsigned int TSeqListNode这一行代码的价值所在!