C的抽象数据类型:链表、队列

1 抽象数据类型

  抽象数据类型ADT abstract data type;

  抽象数据类型由基本数据结构封装而成,链表,队列,二叉树等都属于有基本数据结构封装而成的抽象数据类型;

2 链表

  链表作用:统一管理和存储实时分配的动态内存;而在rtos系统中用链表来管理各类节点的优先级非常优美;

 

  1.1 链表举例

    1.1.1 链表结构存储数据举例

/*链表定义*/
struct
files * phead=NULL; struct files *ppre , *pcurr; int Arrrate[10]= {1,2,3,4}; struct files { int rating ; struct files * pnext ; } /*使用Arrrate[10]来初始化链表;*/
for(int i=0;i<10;i++) { pcurr=(struct files *)malloc(sizeof(files)); if(phead==NULL) phead=pcurr; else ppre->pnext=pcurr; /*首先将当前结构体的地址放入上一个结构体的指针中*/ pcurr->rating= Arrrate[i]; pcurr->pnext=NULL; ppre=pcurr; /*存放当前结构体的地址,供下一个结构体的指针找到位置*/ }

    1.1.2 链表结构取出数据举例

/*链表取出数据只能顺序取出;并且最后一个链表变量的pnext=NULL;*/
pcurr=phead; while(pcurr!=NULL) { printf("%d", pcurr->rating ); pcurr=pcurr->pnext; }

    1.1.3 使用完后销毁链表内存

/*链表想要销毁内存也只能从phead开始顺序销毁;*/
pcurr=phead; while(pcurr != NULL) { phead=pcurr->pnext; free(pcurr); pcurr=phead; }

  1.2 建立链表的抽象数据类型接口

    目的:从用户的使用角度来对ADT数据类型进行封装,使得用户可以像调用普通接口的方式调用ADT数据类型;

    以下例子只是举例如何对定义进行封装,进一步还需要提供封装后如何使用的函数声明以及函数定义;

typedef struct files
{
...
} Node;
typedef Node * List;
...  ...
/*将前面的files结构体重新声明为Node,然后再将指向结构体的指针声明为List;*/

/*这样的话就将具体的代码抽象化了*/
/*然后利用这些抽象过的类型进行代码编写,有利于逻辑开发,也保护了代码的开发权限*/

/*List scores中的scores是地址来着,这样的话就将files结构体封装成了scores,使得用户直接操作scores就可以了*/

 2 队列queue:

  队列是一种简单的链表;称之为队列,是因为工作方式和排队相像;数据要求从队尾加入,队首离开(“先进先出”FIFO);

  队列先进先出的特点是其结构所致,不值得着重强调;以下给出一个队列定义的例子;

  2.1 队列:声明

#define MAXCNT 10

/*
定义队列Queue;队列结构包含首节点,尾节点,节点项数*/ typedef struct queue{ struct Node * front; struct Node * rear; int itemsCnt; }Queue; /*定义节点Node;Item结构存储每个节点的数据,作为接口留给用户自定义;*/ typedef struct node{ Item item; struct node * next; }Node;

  2.2 队列:简单使用函数

/*初始化函数;先建一个queue类型的空壳子;*/
void initQueue(Queue *pq )
{
    pq->front=NULL;
    pq->rear=NULL;
    pq->itemsCnt=0;
}

/*判断队列是否为空;*/
bool queueIsEmpty(const Queue * pq)
{
    return pq->itemsCnt==0;
}

/*判断队列是否为满;*/
bool queueIsFull(const Queue * pq)
{
    return pq->itemsCnt==MAXCNT;
}

/*判断队列当前的项数*/
int countQueue(const Queue * pq)
{
  return pq->itemsCnt ;
}

/*清空队列*/
int emptyQueue(Queue *pq)
{
    while(!queueIsEmpty(pq))
    {
        delQueue(pq);//这是下面小节节点的删除函数;
    }
} 

  2.3 队列:增加尾节点

    正常步骤:新建一个节点,配置节点数据和节点next指针为NULL;

         节点地址放入前一个节点中;把节点添加到队列尾端,并且项数加1;

    注意事项:如果该节点是首项,front和rear是同一项,要配置front;由于队列的性质决定了加入的节点不是首节点就是尾节点;

/*item为需要增加的节点数据,pq为队列指针*/
int addQueue(Item item , Queue * pq)
{
    Node * pnode;
    if(queueIsFull(pq)) return 0;
    pnode=(Node*)malloc(sizeof(Node));
    if(pnote==NULL) return 0;
    else
        pnode->item=item;          /*尾节点的数据*/
        pnode->next = NULL;        /*尾节点的next指针为NULL*/
    
    if(queueIsEmpty(pq))           
        pq->front=pnode;       /*如果是首项,front和rear是同一项;需要配置front的地址*/
    else
        pq->rear->next=pnode;    /*否则是尾项,需要将当前节点的地址放入前一个节点的指针中;*/
        
        pq->rear=pnode;            /*设置struct queue的rear*/
        pq->itemsCnt++;            /*设置struct queue的itemsCnt*/
     return 1; }

  2.4 队列:删除首节点

    步骤:将首节点中存储的第二个节点的地址拿出来先,然后free首节点;重新配置首节点;

/*pq为指向队列的指针; */
int delQueue(Queue *pq)
{
    if(queueIsEmpty(pq)) return 0;
    Node * pSecNode;
    pSecNode=pq->front->next;  //将首节点中存储的后面一个节点的地址取出先;
    free(pq->front);           //删除首节点
    pq->front=pSecNode;        //重新配置首节点
    pq->itemsCnt--;         //项数减1
   if(pq->itemsCnt==0)
    pq->rear=NULL;
  return 1;
}

1&2 小结:队列是一种简单的链表,所以相似的地方比较多;但是队列结构更规范些;

  链表的phead,搬运数据的pcurr和ppre同普通变量定义相似,较为零散;而队列将这些指针放在结构体里,定义成了队列;

  链表没有对数据的节点个数进行统计,队列结构体对数据的节点个数单独做了统计;

  链表的变量是一些指针加一个数据结构体,而队列的变量是两个结构体;队列更加规范,符合逻辑性;

5 节点函数

  当插入的list_node前后都有node时,插入list_node的代码顺序应该如图,如果是after则从后往前写代码,如果是before则从前往后写代码,完美;

  

  5.1 第一次插入node

    list_head和list_node在初始化的时候,都初始化成了自身地址;如果只有一个list_head,那么在插入第一个list_node的时候节点代码又是如何处理的呢?

    以insert_after函数举例,步骤1的l->next->prev = l->prev = n;即list_head->prev = list_rear;  list首可寻址尾;

                步骤3的n->next = l->next = l;           即list_rear->next = list_head;list尾可寻址首;

    这个首尾寻址十分巧妙嘞,既不用多余的代码处理第一次插入,又使得链表可以时间片轮询,一举两得;

#ifndef __RT_SERVICE_H__
#define __RT_SERVICE_H__
//rtservice.h


//已知结构体节点,反推结构体首地址;
#define rt_container_of(ptr, type, member)    ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member)))			
#define rt_list_entry(node, type, member)    rt_container_of(node, type, member)

//简单整理了一下上面的宏变成下面这样方便理解;只有最外层和node的括号可以去,其他括号都有用;
//#define  rt_list_entry(nodeAddr, struct, member)   (struct *)(   (char *)nodeAddr - (unsigned long)( &((struct *)0)->member )   )
		

//将list_head初始化成自身,这样第二个node插入之后,会让两个节点首位相连十分地妙;
rt_inline void rt_list_init(rt_list_t *l)
{
    l->next = l->prev = l;
}

//将n_node插入l_node之后,代码顺序如图,如果先修改l节点则存储在l节点中的数据就会丢失;
rt_inline void rt_list_insert_after(rt_list_t *l, rt_list_t *n)
{
    l->next->prev = n;
    n->prev = l;
    n->next = l->next;
    l->next = n;   
}

//将n_node插入l_node之前,代码顺序如图;
rt_inline void rt_list_insert_before(rt_list_t *l, rt_list_t *n)
{
    l->prev->next = n;
    n->next = l;
    n->prev = l->prev;
    l->prev = n;
}

//将前后节点连起来后,再初始化n_node为自身地址;
rt_inline void rt_list_remove(rt_list_t *n)
{
    n->prev->next = n->next;
    n->next->prev = n->prev;  
    n->next = n->prev = n;
}

//空节点的next和prev都被初始化成了自身,判断一个即可;
rt_inline int rt_list_isempty(const rt_list_t *l)
{
    return l->next == l;
}

#endif /* __RT_SERVICE_H__ */

 

posted @ 2020-06-04 18:09  caesura_k  阅读(866)  评论(0编辑  收藏  举报