数据结构——栈、队列
内核链表:双向循环链表,不再将数据存储在链表结点中,而是将结点嵌入到存储的数据中
offset_of:获取内核链表中链表结点到结构体起始位置的偏移量
container_of:通过偏移量,获取结构体首地址(结点首地址-偏移量)
栈
系统栈
linux下内存空间有所划分,分为内核空间、堆区、栈区、数据区、文本区,其中栈区存局部变量、函数的形参、函数的返回值、函数的调用关系,用来保护现场和恢复现场
栈的特点是先进后出:FILO,此特点可以用来判断是否为栈
栈常应用于撤销重做等操作中
构造栈
栈不仅包括系统栈,自己也可以构造栈,再数据结构上称为栈结构,只允许从一端进行数据的插入和删除的线性存储结构,称之为栈结构,而栈结构的操作有入栈(压栈)、出栈(弹栈),进行插入和删除的一端称为栈顶,另一端称为栈底
两种实现栈结构的方法
(1)顺序表(数组)属于顺序栈
满栈、空栈:栈顶存在数据就是满栈,没有就是空栈。满栈先移动栈顶再入栈,而空栈是先入栈再移动栈顶,反之,满栈先出栈再移动栈顶,而空栈是先移动栈顶再出栈
增栈、减栈:入栈时栈顶向内存高地址移动的是增栈,相反是减栈
(2)链式结构(链表)链式栈
链式栈操作
定义栈
创建栈
头插入栈
第一个函数为判断是否为空栈。
先调用堆区空间,然后判断给的指针是否为空,接着给新的结点初始化,判断是否为空栈,如果是空栈,则栈顶和栈底指向同一个结点,如果不是,则需要将旧栈底结点指向新结点,并将栈底指向新结点,上图将二者结合,由于空栈栈底本身就是空指针,所以和新结点后继指向空不矛盾,如果不是空栈则正好被覆盖
需要注意的是,没有尾插法,因为栈是先进后出,不能尾插
遍历
循环遍历,直到为空指针
出栈(头删)
先判断是否为空结点,如果是则无法出栈。将头节点指向后其后续结点,将旧头节点数据赋给所需赋值的变量,并释放旧头节点的空间,如果只有一个结点,那么栈底也应该指向NULL,结点数clen减一
提取栈底元素
判断是否为空,赋值
与出栈不同,这里只是提取元素,并没有出栈,所以不用释放栈底空间
销毁链式栈
值得注意的是,函数的形参为二级指针,因为不仅要释放栈的空间,也要将指向该栈的指针滞空,所以就不能和上述其他操作一样是一级指针,因为函数是值传递,所以想要改变一个变量的值,就要传递这个变量的指针,而这里要改变指针的值,就要传递指针的指针,所以是**
队列
队列:允许从一端进行数据的插入,另一端进行数据删除的线性存储结构,称为队列结构
插入操作,即入队操作,插入的这端称为队列的队尾
删除操作,即出队操作,删除的这端称为队列的队头
特点:先进先出FIFO
应用:数据缓存,例如缓冲区(高速设备和低速设备进行数据交互时,为了提高效率)
两种方式:
(1):顺序表,顺序队列
(2):链式表,链式队列
链式队列
定义
创建
尾插入队
申请堆区空间,判断链表对象是否有误,判断是否为空队列,如果是,则需要将队头和队尾都指向入队的结点,如果不是,则不需要改变队头,旧队尾的后继和队尾都需要指向入队的结点,同时结点数加一
由于队列为先进先出,所以不存在头插
遍历
出队、获取队头元素、销毁
顺序队列
顺序队列存在假溢出问题,即在队尾指出队列空间时,队列仍有空间,造成这种现象的原因是在入队同时可以出队,而出队就会造成队列留有空间,所以为了防止这种现象,采用循环队列,即在队尾指指向最后一个结点之后,再指向队列的第一个结点
判断循环队列为满还是空:队尾和队头指向的位置
需要说明的是,满队列并不是队列所有空间都被使用,会少存储一个数据,即牺牲了一处存储空间
空队列:队头和队尾指向同一位置
满队列:队尾+1跟上队头
定义
其中的SEQQUE_MAX_LEN是定义的宏,为队列的长度
创建
pbase为队列结点个数,对应申请堆区空间
尾插入队
由于顺序队列采用的是数组形式,所以是pbase[],将尾结点赋值所要入队的数据,而倒数第三行是将尾结点指针向前指,为了防止假溢出而常见的循环队列,需要做到从最后一个结点指向第一个结点
遍历
出队、获取队头元素、销毁队列
由于是数组形式,该队列和其余链式结构大有不同,显而易见的是用到了[],只是由于顺序结构在内存中的空间是连续的,而链式结构在内存中是不连续的,所以顺序结构只需要将基类型加一即可到下一个结点