顺序表操作实现

本文介绍线性表的顺序存储实现方式,包括创建、销毁、清空等基本操作及插入、删除、获取元素的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线性表的顺序表示,指的是用一组地址连续的存储单元依次存储线性表的数据元素。下面直接上代码

----------------------------------------------------------------------------------------------------------------------------------------------------------

头文件 "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这一行代码的价值所在!

































评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值