顺序表思想
定义了一个顺序表的结构体,存放了它的长度,容量信息,还有申请的存结点的内存的首地址。
结构体中定义的是一个二级指针,申请的内存是存放传入结点的指针。
内存管理方面:顺序表管理指表内存和指针数组内存。
结点的内存由上层自己负责。
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);
SeqListNode* SeqList_Get(SeqList *list,int pos);
SeqListNode* SeqList_Delete(SeqList* list ,int pos);
#endif // SEQLIST_H
typedef void SeqList;
typedef void SeqListNode;
传给上层的是一个void类型,对于他们来说只是一个句柄,留给上层管理表的首地址以及结点地址。
上层测试中
Teacher* tmp = (Teacher *)SeqList_Get(list, i);
根据自己的业务来解析表传来的结点地址
上层将顺序表地址和结点地址传给底层函数,底层函数知道具体类型是什么,类型转换后进行操作与处理。
所以在seqlist.c中,每个函数中都有两句代码。
TSeqList *tlist = NULL;
tlist = (TSeqList*)list;
seqlist.c
定义表的结构体
typedef struct _tag_SeqList
{
int length;
int capacity;
unsigned int **node;
}TSeqList;
创建表时
1.要申请表的内存,内存清零
2.根据容量申请相应的内存来存放结点指针,node 是二级指针,node[i]是指针。
3.参数置初始状态
SeqList* SeqList_Create(int capacity)
{
int ret =0;
TSeqList *tmp = NULL;
tmp=(TSeqList*)malloc(sizeof(TSeqList));
if(tmp == NULL)
{
ret = -1;
printf("func SeqList_Create() err:create %d\n",ret);
return NULL;
}
memset(tmp,0,sizeof(TSeqList)); //需要包含string.h头文件
tmp->node=(unsigned int **)malloc(sizeof(unsigned int *)*capacity);
//这里顺序表只是存了结点的地址以保证能访问,但是不把节点数据及内存并入顺序表中
//这样顺序表只管理自己申请的这俩内存,而节点的内存还是由上层管理
//这也是destroy中只用释放申请的tlist->node及tlist本身的内存
if(tmp->node == NULL)
{
ret = -2;
printf("func SeqList_Create() err:malloc %d\n",ret);
return NULL;
}
tmp->capacity = capacity;
tmp->length = 0;
return tmp;
}
顺序表销毁时要释放内存
顺序表清空时主要是长度要置零以重新存数据。
这里可加memset? 我觉得是要加,主动清零内存
void SeqList_Destroy(SeqList* list)
{
TSeqList *tlist = NULL;
if(list == NULL)
{
return ;
}
tlist = (TSeqList*)list;
if(tlist->node!=NULL)
{
free(tlist->node);
}
free(tlist);
return;
}
void SeqList_Clear(SeqList* list)
{
TSeqList *tlist = NULL;
if(list == NULL)
{
return ;
}
tlist= (TSeqList*)list;
//memset(tlist->node,0,sizeof(unsigned int *)*tlist->capacity);
tlist->length = 0;
return;
}
获取长度和容量没什么,返回数据就可以
int SeqList_Length(SeqList* list)
{
TSeqList *tlist = NULL;
if(list == NULL)
{
return -1;
}
tlist = (TSeqList*)list;
return tlist->length;
}
int SeqList_Capacity(SeqList* list)
{
TSeqList *tlist = NULL;
if(list == NULL)
{
return -1;
}
tlist = (TSeqList*)list;
return tlist->capacity;
}
获取结点也不涉及移动,直接返回相应位置的节点即可
SeqListNode* SeqList_Get(SeqList *list,int pos)
{
int i =0,ret =0;
TSeqList *tlist = NULL;
SeqListNode *s_ret = NULL;
if(list == NULL || pos <0)
{
ret = -1;
printf("funt SeqList_Get() err %d\n",ret);
return NULL;
}
tlist = (TSeqList*)list;
s_ret =(SeqListNode*)tlist->node[pos];
return s_ret;
}
插入和删除一个是要移动, 二是要注意长度++或 - -,同时要判断位置是否合法,判满等。
int SeqList_Insert(SeqList* list, SeqListNode *node,int pos)
{
int i =0,ret =0;
TSeqList *tlist = NULL;
if(list == NULL || node == NULL || pos <0) //为什么不判pos>length呢? 因为只要表没满,可以处理让他插在最尾
{
ret = -1;
printf("func SeqList_Insert() err %d\n",ret);
return ret;
}
tlist = (TSeqList*)list;
//先判满
if(tlist->length == tlist->capacity)
{
ret = -2;
printf("func SeqList_Insert() (tlist->length == tlist->capacity) err: %d\n",ret);
return ret;
}
//容错修正
if(pos>tlist->length)
{
pos = tlist->length;
}
//先后移元素
for(i=tlist->length;i>pos;i--)
{
tlist->node[i]=tlist->node[i-1];
}
tlist->node[i] = node;
tlist->length++;
return 0;
}
SeqListNode* SeqList_Delete(SeqList* list ,int pos)
{
int i=0;
TSeqList *tlist = NULL;
SeqListNode *ret = NULL;
if(list == NULL || pos < 0) //删除位置大于表长度的情况如何处理,返回的是NULL
//但存在clear后未覆盖删除原有数据的问题吧?所以应优化clear
{
printf("func SeqList_Delete() err");
return NULL;
}
tlist = (SeqList*)list;
ret = (SeqListNode*)tlist->node[pos];
for(i=pos+1;i<tlist->length;i++)
{
tlist->node[i-1]=tlist->node[i];
}
tlist->length--;
return ret;
}
插入操作时要先判满和容错修正,然后移动,再插入
删除操作时先缓存要弹出的结点,再移动。返回结点
测试用例
ret = SeqList_Insert(list, (SeqListNode*)&t5, 0); //头插法
for (int i = 0; i < SeqList_Length(list); i++)
{
Teacher* tmp = (Teacher *)SeqList_Get(list, i);
if (tmp == NULL)
{
return;
}
printf("tmp->age:%d", tmp->age);
}
一个是头插法。 数据可以传入结构体等类型
再一个是解析弹出的结点,根据上层的数据类型进行类型转换后解读。