语言基础/单向链表的构建和使用(含Linux中SLIST的解析和使用)

news2024/11/27 11:55:03

文章目录

  • 概述
  • 简单的链表
    • 描述链表的术语
    • 简单实现一个单链表
  • Linux之SLIST机理分析
    • 结构定义
    • 单链表初始化
    • 单链表插入元素
    • 单链表遍历元素
    • 单链表删除元素
  • Linux之SLIST使用实践
  • 纯C中typedef重命名带来的问题
  • 预留

概述

本文讲述了数据结构中单链表的基本概念,头指针、头结点、数据域、指针域等链表的描述术语,及单链表操作的简单实现。并在此基础上详细讲讲述 Linux 源码中 SLIST 单链表系列宏的原理和使用方法。

简单的链表

在讲述单链表前,不得不先回顾下线性表的概念。所谓线性表,是零个或多个数据元素的有限序列(序列是指有顺序的排列)。线性表首先是一个序列,也就是说,元素之间是有顺序的,若存在多个元素,则第一个元素没有前驱,最后的元素没有后继,其他的每个元素都是有些只有一个前驱和后继。以学校的小朋友为例,如果大家分散在操场各处,则不能算是线性表。如果一个小朋友去拉两个小朋友的衣服,那就不可以排成一队了;同样,如果一个小朋友后边的衣服,被两个小朋友拉扯,也不算是线性表。另外,线性表强调有限,事实上,在计算机中处理的对象都是有限的,那种无限的数列,只存在于数学的概念中。

描述链表的术语

typedef struct Node {
    ElemType data;
    struct Node *next;
} Node;

通常,我们把Node称作一个节点,每个节点包含两个部分。其中存储数据元素信息的域(字段)称作数据域,把存储直接后继位置的域称为指针域,指针域中存储的信息(即下一个节点的内存地址)称作指针或链。链表总得有个头,我们把链表中的第一个节点的存储位置(即第一个节点对象的内存地址,如果有头结点,则是头结点对象的内存地址)叫做头指针。为了更加方便地对链表进行操作,会在单链表的第一个节点前附设一个节点,称作头节点
头指针是链表的必要元素或者说是固有的,其具有标识作用,所以常用头指针来代表链表本身。无论链表是否为空,头指针均不为空。头指针指向链表的第一个节点的内存,若有头节点,则是指向头结点对象的指针。而,头节点不一定是链表的必要元素。头节点的数据域是不能向其他节点那样存储业务数据的,一般无意义空置,但你也可以在其中存储些自定义的其他数据信息,如存储链表长度。有了头节点,对在链表第一节点前插入节点和删除第一节点这两种操作,就会变得简单,使得其操作与其他节点的操作过程相统一。

下文示例程序中,使用了头结点,
在这里插入图片描述
如上,在使用头节点的情况下,头指针、头节点、普通节点之间的关系如上图。头指针Ph等于头节点(对象)在内存中地址,而头节点数据域不实际存储数据元素,只是存储了第一节点的地址。参照下文示例代码main函数中定义的 LinkList 类型的 L 即链表头指针的,它是一个节点类型的指针,结合InitList源码,可得,头指针的赋值过程为,

 struct Node *L = (Node*)malloc(sizeof(Node));
 //如下初始化过程,本质上操作的是头节点的指针域
 L->next = NULL; 

对于LIST的客户端来说,头指针是 Node* 和 void* 并没有什么本质区别,它就是一个地址值,只要能在LIST内部使用头指针找到头结点或第一节点就行,只是为了代码上的优雅和易读写性,头指针被顺便定义成了节点类型的指针类型。

简单实现一个单链表

这是以前从某书中的源码里扒拉出来的,只是做了简单的调整,前几年在项目里,我甚至偶尔直接在其基础上私有化一个单链表用于项目。这里贴出来,并不是说它好或者不好,只是为了有个参考,以更好的理解后续要讲述的Linux中SLIST宏单链表。

#include <iostream>
#include <stdio.h>

#define OK 1
#define ERROR 0
#define TRUE 1
#define FALSE 0

//Your Data /can be struct
typedef int ElemType;
//线性表链式存储-单链表结构
typedef struct Node {
    ElemType data;
    struct Node *next;
} Node;

//定义LinkList
typedef struct Node *LinkList; 

/* 初始化链式线性表 */
int InitList(LinkList *L) {
    *L = (LinkList)malloc(sizeof(Node)); /* 产生头结点,并使L指向此头结点 */
    if (!(*L))                           /* 存储分配失败 */
        return ERROR;
    (*L)->next = NULL;                   /* 指针域为空 */
    return OK;
}

//若L为空表,则返回TRUE,否则返回FALSE
int ListEmpty(LinkList L) {
    if (L->next)
        return FALSE;
    else
        return TRUE;
}

//将L重置为空表 
int ClearList(LinkList *L) {
    LinkList p, q;
    p = (*L)->next;           /*  p指向第一个结点 */
    while (p) {               /*  没到表尾 */
        q = p->next;
        free(p);
        p = q;
    }
    (*L)->next = NULL;        /* 头结点指针域为空 */
    return OK;
}

//返回L中数据元素个数
int ListLength(LinkList L) {
    int i = 0;
    LinkList p = L->next;   /* p指向第一个结点 */
    while (p) {
        i++;
        p = p->next;
    }
    return i;
}

//用e返回L中第i个数据元素的值 //1≤i≤ListLength(L)
int GetElem(LinkList L, int i, ElemType *e) {
    int j;
    LinkList p;		//声明一结点p 
    p = L->next;    //让p指向链表L的第一个结点 
    j = 1;		    //j为计数器 
    //p不为空或者计数器j还没有等于i时,循环继续
    while (p && j < i) {
        p = p->next;   /* 让p指向下一个结点 */
        ++j;
    }
    if (!p || j > i)
        return ERROR;  /*  第i个元素不存在 */
    *e = p->data;      /*  取第i个元素的数据 */
    return OK;
}

//返回L中第1个与e满足关系的数据元素的位序 /若这样的数据元素不存在则返回0
int LocateElem(LinkList L, ElemType e) {
    int i = 0;
    LinkList p = L->next;
    while (p) {
        i++;
        if (p->data == e) /* 找到这样的数据元素 */
            return i;
        p = p->next;
    }

    return 0;
}

//在L中第i个位置之前插入新的数据元素e,L的长度加1 //1≤i≤ListLength(L)
int ListInsert(LinkList *L, int i, ElemType e) {
    int j;
    LinkList p, s;
    p = *L;
    j = 1;
    while (p && j < i) {             /* 寻找第i个结点 */
        p = p->next;
        ++j;
    }
    if (!p || j > i) return ERROR;   /* 第i个元素不存在 */
    /*  生成新结点(C语言标准函数) */
    s = (LinkList)malloc(sizeof(Node));  
    s->data = e;
    s->next = p->next;    /* 将p的后继结点赋值给s的后继  */
    p->next = s;          /* 将s赋值给p的后继 */
    return OK;
}

//删除L的第i个数据元素,并用e返回其值,L的长度减1 //1≤i≤ListLength(L)
int ListDelete(LinkList *L, int i, ElemType *e) {
    int j;
    LinkList p, q;
    p = *L;
    j = 1;
    while (p->next && j < i) {	/* 遍历寻找第i个元素 */
        p = p->next;
        ++j;
    }
    if (!(p->next) || j > i)
        return ERROR;           /* 第i个元素不存在 */
    q = p->next;
    p->next = q->next;			/* 将q的后继赋值给p的后继 */
    *e = q->data;               /* 将q结点中的数据给e */
    free(q);                    /* 让系统回收此结点,释放内存 */
    return OK;
}

//遍历链表 //依次对L的每个数据元素输出 
int ListTraverse(LinkList L) {
    LinkList p = L->next;
    while (p) {
        printf("%d ", p->data); //dosmoething..
        p = p->next;
    }
    printf("\n"); return OK;
}

int main() {
    LinkList L;
    ElemType e;
    //初始化
    int i = InitList(&L);
    //插入新元素
    for (int j = 1; j <= 5; j++)
        i = ListInsert(&L, 1, j * 10);
    //遍历
    ListTraverse(L);
    //获取第4个数据
    //GetElem(L, 3, &e);
    //删除第3个数据
    //ListDelete(&L, 3, &e); 
    //...不再赘述...
    system("pause");
}

上述代码可以直接在C和C++环境中编译和运行,具体测试代码比较简单,没有过多在此涉及。

Linux之SLIST机理分析

在这里插入图片描述
进入Linux官网,以HTTTP方式进入 Index of /pub/linux/kernel/ 页面,图个吉利,这里选择下载 linux-6.8.6.tar.xz 版本。解压后可以在相应的目录下找到 linux-6.8.6\drivers\scsi\aic7xxx\queue.h 文件。在Everything中搜索时,可以找到好几个queue.h文件,只有目录 drivers/scsi/aic7xxx 包含的 queue.h 是我们想要的那个。该目录Adaptec AIC-7xxx系列(例如AIC-7870、AIC-7895等)的SCSI(Small Computer System Interface)控制器相关的驱动程序,主要负责与硬件交互,控制SCSI设备,以及提供对SCSI设备的访问和管理功能。该文件中主要包含了单向链表、单向有尾链表(Singly-linked Tail queue 可用作队列)、双向无尾链表、双向有尾链表(Tail queue 可用作队列)、循环链表(Circular queue)的数据结构和操作函数,用于在Linux内核中实现队列和链表的功能。本文仅讲解其中最简单的单链表结构。

/** @brief
 * A singly-linked list is headed by a single forward pointer. 
 * The elements are singly linked for minimum space and pointer manipulation overhead at the expense of O(n) removal for arbitrary elements. 
 * New elements can be added to the list after an existing element or at the head of the list.
 * Elements being removed from the head of the list should use the explicit macro for this purpose for optimum efficiency. 
 * A singly-linked list may only be traversed in the forward direction.  
 * Singly-linked lists are ideal for applications with large datasets and few or no removals or for implementing a LIFO queue.
**/

#if defined(QUEUE_MACRO_DEBUG) || (defined(_KERNEL) && defined(DIAGNOSTIC))
#define _Q_INVALIDATE(a) (a) = ((void *)-1)
#else
#define _Q_INVALIDATE(a)
#endif

/*
 * Singly-linked List definitions.
 */
#define SLIST_HEAD(name, type)						\
struct name {								\
    struct type *slh_first;	/* first element */			\
}

#define	SLIST_HEAD_INITIALIZER(head)					\
    { NULL }

//条目/列表元素
#define SLIST_ENTRY(type)						\
struct {								\
    struct type *sle_next;	/* next element */			\
}

/*
 * Singly-linked List access methods.
 */
#define	SLIST_FIRST(head)	((head)->slh_first)
#define	SLIST_END(head)		NULL
#define	SLIST_EMPTY(head)	(SLIST_FIRST(head) == SLIST_END(head))
#define	SLIST_NEXT(elm, field)	((elm)->field.sle_next)

#define	SLIST_FOREACH(var, head, field)					\
    for((var) = SLIST_FIRST(head);					\
        (var) != SLIST_END(head);					\
        (var) = SLIST_NEXT(var, field))

#define	SLIST_FOREACH_SAFE(var, head, field, tvar)			\
    for ((var) = SLIST_FIRST(head);				\
        (var) && ((tvar) = SLIST_NEXT(var, field), 1);		\
        (var) = (tvar))

/*
 * Singly-linked List functions.
 */
#define	SLIST_INIT(head) {						\
    SLIST_FIRST(head) = SLIST_END(head);				\
}

#define	SLIST_INSERT_AFTER(slistelm, elm, field) do {			\
    (elm)->field.sle_next = (slistelm)->field.sle_next;		\
    (slistelm)->field.sle_next = (elm);				\
} while (0)

#define	SLIST_INSERT_HEAD(head, elm, field) do {			\
    (elm)->field.sle_next = (head)->slh_first;			\
    (head)->slh_first = (elm);					\
} while (0)

#define	SLIST_REMOVE_AFTER(elm, field) do {				\
    (elm)->field.sle_next = (elm)->field.sle_next->field.sle_next;	\
} while (0)

#define	SLIST_REMOVE_HEAD(head, field) do {				\
    (head)->slh_first = (head)->slh_first->field.sle_next;		\
} while (0)

#define SLIST_REMOVE(head, elm, type, field) do {			\
    if ((head)->slh_first == (elm)) {				\
        SLIST_REMOVE_HEAD((head), field);			\
    } else {							\
        struct type *curelm = (head)->slh_first;		\
                                    \
        while (curelm->field.sle_next != (elm))			\
            curelm = curelm->field.sle_next;		\
        curelm->field.sle_next =				\
            curelm->field.sle_next->field.sle_next;		\
        _Q_INVALIDATE((elm)->field.sle_next);			\
    }								\
} while (0)

如上代码中的注释部分。
在这里插入图片描述

结构定义

SLIST_HEAD 宏定义了一个名称为 name 的结构体,包含一个 type 类型的字段,其含义是指向第一个元素的指针。SLIST_ENTRY宏定义的是单链表中每个元素的结构,其中包含一个指向下一个元素的指针。抛却字段名称不谈,这俩定义是一致的,看起来有点重复,但SLIST_HEAD和SLIST_ENTRY在单链表的表示和用途上是不同的,这样的设计有助于提高代码的清晰性和可维护性。

//你的私有数据结构
typedef struct tagYourData {
    int a;
    int b;
} TYourData; 
//typedef int TYourData; //also

//借助SLIST_ENTRY定义链表结构
struct TLucyItem {
    TYourData data;
    SLIST_ENTRY(TLucyItem) linkNode;
};

//定义链表头变量
SLIST_HEAD(TslistHead, TLucyItem) slistHead;

结合上文,SLIST_ENTRY宏的功能很明确,也很好理解。哈哈,但是钻个小牛角尖,单词 entry 本意是进入、加入、入口,同时也具有条目、账目、记录等含义。在计算机中,有 data entry: [计]数据输入,entry point: [计]入口点,等含义。那么这里的entry怎么翻译呢?

	struct TLucyItem {
	   TYourData data;
	   struct {
	       struct TLucyItem* sle_next;
	   } linkNode;
	}

结合 SLIST_ENTRY 的实际使用,将结构 TLucyItem 定义展开如上。我给 SLIST_ENTRY 对象取名字 linkNode,含义为链表的连接点,链表连接位置的记录。就这样吧!也许 Entry 这个名字是大神凭借个人喜好采用的。如果不考虑字面意思,这里的 linkNode 代表的是链表结构中的指针域。在链表中,我们通常提到的是数据域和指针域。指针域承担着连接节点的作用,通过指针域,我们可以在链表中进行节点的插入、删除、查找等操作,实现链表的灵活性和可操作性。

链表头变量的展开,如下,
在这里插入图片描述
要特别注意的2点是,
SLIST_ENTRY 宏函数、SLIST_HEAD宏函数中的 type 参数,其代表的类型是 TLucyItem,而不是 TYourData 类型,通过代码的展开,很容易理解这一点。即,type不是字节的数据类型,而是包含自己数据类型的链表结构类型。
宏函数SLIST_INSERT_HEAD、SLIST_FOREACH、SLIST_REMOVE_HEAD等函数参数中都传递了head参数,函数内部把head被认做事指针来使用,因此,如果我们使用SLIST_HEAD定义头对象,而不是指针时,相关位置要传递&slistHead才可以。同理,我们在定义链表头时,也可以直接定义头指针,如下,这可能会更利于编码过程,

SLIST_HEAD(TslistHead, TLucyItem) *pslistHead;

单链表初始化

    //链表初始化
    SLIST_INIT(&slistHead);

单链表插入元素

宏函数参数中的,field 不光有田地、场地,处理、应付等含义,它还具有字段的意思,在编程领域其可代表结构体字段。

    //第一个元素
    item = (struct TLucyItem*)malloc(sizeof(struct TLucyItem));
    item->data.a = 1;
    item->data.b = 10;
    SLIST_INSERT_HEAD(&slistHead, item, linkNode);
    //展开 _INSERT_HEAD
    //item->linkNode.sle_next = (&slistHead)->slh_first;
    //(&slistHead)->slh_first = item;

这里要特别注意的是,SLIST_INSERT_HEAD(head, elm, field) 宏函数的 head 参数,要传递的是 &slistHead,即slistHead的地址,而不是直接传递slistHead本身。

宏函数 SLIST_INSERT_AFTER(slistelm, elm, field)
函数参数中 slistelm 是列表中的某已知的节点,elm 是新要插入的节点,本函数的功能是,将结点elm插入到结点slistelm后面。

单链表遍历元素

    //遍历单链表
    SLIST_FOREACH(item, &slistHead, linkNode) {
        printf("%d, %d \r\n ", item->data.a, item->data.b);
    }

在这里插入图片描述
在SLIST实际使用中,可能要在其基础上进行一些功能扩展,如,保持单链表中元素的唯一性,此时也会使用到遍历操作。

单链表删除元素

SLIST 提供了3个删除元素的函数,具体参见上一节的原码。

//删除elm指定的后一个节点
SLIST_REMOVE_AFTER(elm, field)
//删除头节点指定的节点
SLIST_REMOVE_HEAD(head, field)
//删除elm指定的节点
SLIST_REMOVE(head, elm, type, field)

链表清空方案1,

    //链表清空操作
    while (!SLIST_EMPTY(&slistHead)) {
        item = SLIST_FIRST(&slistHead); //
        printf("remove %d, %d \n", item->data.a, item->data.b);
        SLIST_REMOVE(&slistHead, item, TLucyItem, linkNode);
        free(item); //同步释放item堆内存
    }

链表清空方案2,

    //链表清空操作 //需要在另外的过程中释放item堆内存
    while (!SLIST_EMPTY(&slistHead)) {
        SLIST_REMOVE_HEAD(&slistHead, linkNode);
    }

上述列表清空操作的代码可以展开为,
在这里插入图片描述
需要注意的是,清空方案P2过程中并没有释放链表元素对应的堆内存,不小心地化会造成内存泄漏哈。

Linux之SLIST使用实践

https://www.cnblogs.com/imlgc/archive/2012/05/02/2479654.html

//你的私有数据结构
typedef struct tagYourData {
    int a;
    int b;
} TYourData; 

//借助SLIST_ENTRY定义链表结构/注意没有使用typedef定义结构别名
struct TLucyItem {
    TYourData data;
    SLIST_ENTRY(TLucyItem) linkNode;
};

int main() {
    //定义链表头变量 //更建议直接定义成指针
    SLIST_HEAD(TslistHead, TLucyItem) slistHead;

    //链表初始化
    SLIST_INIT(&slistHead);

    //链表元素项//要动态创建
    struct TLucyItem* item = NULL;
    //第一个元素
    item = (struct TLucyItem*)malloc(sizeof(struct TLucyItem));
    item->data.a = 1;
    item->data.b = 10;
    SLIST_INSERT_HEAD(&slistHead, item, linkNode);
    //第二个元素
    item = (struct TLucyItem*)malloc(sizeof(struct TLucyItem));
    item->data.a = 2;
    item->data.b = 20;
    SLIST_INSERT_HEAD(&slistHead, item, linkNode);

    //遍历单链表
    SLIST_FOREACH(item, &slistHead, linkNode) {
        printf("iterator %d, %d \r\n", item->data.a, item->data.b);
    }

    //链表删除操作
    while (!SLIST_EMPTY(&slistHead)) {
        item = SLIST_FIRST(&slistHead);
        printf("remove %d, %d \n", item->data.a, item->data.b);
        SLIST_REMOVE(&slistHead, item, TLucyItem, linkNode);
        free(item);
    }
    
    system("pause");
}

上述代码运行结果如下,
在这里插入图片描述

纯C中typedef重命名带来的问题

在Keil5集成开发环境(STMF429+FreeRTOS)下,标准C99,使用SLIST时,遇到了一些问题。主要代码如下,

//
typedef struct tagLucyItem {
    TYourData data;
    SLIST_ENTRY(tagLucyItem) linkNode;
} TLucyItem;
//直接定义成指针会方便些
SLIST_HEAD(TSListHead4Luck, TLucyItem) *s_pListHead; 

//主要功能代码
int do_something(){
	//TEST
	SLIST_INIT(s_pListHead);
	//开辟堆内存
	TLucyItem *ptNode = pvPortMalloc(sizeof(TLucyItem));
	ptNode->data.a = 100; ptNode->data.b = 100;
	//执行插入操作
	SLIST_INSERT_HEAD(s_pListHead, ptNode, linkNode);
	...
}

在编译时,存在如下编译错误,
在这里插入图片描述
先谈谈C语言中,为什么喜欢将结构定义typedef为一个别名。
在纯C环境下,我们通常要定义结构体的别名,如上使用typedef定义的TLucyItem类型。如果不这么做,那么任何出现TLucyItem类型名称的地方,都要使用 struct TLucyItem 样式,如前一章节中SLIST的实践代码那样。在C++中,对于结构体类型的定义和使用,可以不用去typedef别名,而是直接使用结构类型名称即可。正是因为这样,出现了上述编译错误。我们宏展开报错的代码,

do {			
    (ptNode)->linkNode.sle_next = (s_pListHead)->slh_first;			
    (s_pListHead)->slh_first = (ptNode);					
   } while (0)

一共两行代码,每行对应一个错误告警。第一个错误显示,右边 slh_first 是 struct TLucyItem* 类型,左边 sle_next 是 struct tagLucyItem*类型,类型不兼容。好吧,这也能报错,在C++中tagLucyItem都可以做构造函数名称啦。一点点改呗,

typedef struct tagLucyItem {
    TYourData data;
    SLIST_ENTRY(/*tagLucyItem*/TLucyItem) linkNode;
} TLucyItem;

如上修改 TLucyItem 定义后,果然只剩下第2个错误告警了。我们继续来看看这个错误。右边 TLucyItem* 类型和左边 struct TLucyItem* 类型不兼容,好吧,这也太死板啦,就不能变通一点点。slh_first 是struct TLucyItem*类型,其中关键字struct是在通过SLIST_HEAD宏定义头结构时被宏定义函数体添加的。分析到这里,问题的原因算是确定了,struct Taa 和 Taa 在C编译过程中不兼容。有两种解决方案,
P1、这是不建议的方案。修改SLIST宏实现,将宏实现中 type 参数前的 struct 全部干掉。
P2、去掉上述TLucyItem的别名定义,直接定义它。当在程序内部使用到该结构类型时,统一的加上struct关键字使用它。好在在SLIST使用的过程中节点类型TLucyItem并不会多次使用,这种方案是可行的。不改动引用的源码,所以推荐。

预留

好了,该睡觉了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2060678.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

监控状态流图中的测试点

此示例展示了如何将数据或状态指定为测试点&#xff0c;你可以在仿真过程中使用浮动范围绘制这些测试点或将其记录到MATLAB基本工作区。 关于状态流图中的测试点 Stateflow测试点是您可以在模拟过程中观察到的信号&#xff0c;例如&#xff0c;通过使用浮动范围块。您可以使用…

进阶SpringBoot之 SpringSecurity(1)环境搭建

Spring Security 中文文档 Spring Security 是一个 Java 框架&#xff0c;用于保护应用程序的安全性 它提供认证&#xff08;authentication&#xff09;、授权&#xff08;authorization&#xff09;和保护&#xff0c;以抵御常见的攻击 Spring Security 基于过滤器链的概念…

Linux虚拟机磁盘管理-创建新磁盘分区

1.查看新加的硬盘情况 b英文为block表示块 查看磁盘信息方法一&#xff1a;ll /dev/sd* 查看磁盘信息方法二&#xff1a;lsblk 2.创建分区 1&#xff09;创建磁盘分区 以sdb这块磁盘进行分区为例 一个磁盘最多分4个分区 输入w进行确认创建一个房间&#xff0c;这个房间就能…

Nginx: 配置项之main段核心参数用法梳理

概述 我们了解下配置文件中的一个全局段&#xff0c;有哪些配置参数&#xff0c;包括后面的 events 字段&#xff0c;有哪些配置参数这里面也有一些核心参数, 对于我们Nginx运行的性能也是有很重要的帮助我们现在首先关注整个 main 段的一个核心参数用法所谓 main 段&#xff…

前后端分离开发:用 Apifox 高效管理 API

目录 1.前后台分离开发介绍 2.API 2.1 APIfox介绍 2.2 接口文档管理 1.前后台分离开发介绍 前端开发有2种方式&#xff1a;「前后台混合开发」和「前后台分离开发」。 前后台混合开发&#xff0c;顾名思义就是前台后台代码混在一起开发&#xff0c;如下图所示&#xff1a…

5. 数据结构—栈的实际案例

1. 10进制转8进制 void conversion(int n){LinkStack S;InitStack(S);while(n){Push(S,n%8);nn/8;}while(!StackEmpty(S)){int x;Pop(S,x);printf("%d",x);} } 2. 括号匹配 bool Matching(){LinkStack S;char ch,x;InitStack(S);while(cin>>ch){if(ch#)bre…

tomcat的session会话保持

1.Memcached简介 Memcached 只支持能序列化的数据类型&#xff0c;不支持持久化&#xff0c;基于 Key-Value 的内存缓存系统。 memcached虽然没有像redis 所具备的数据持久化功能&#xff0c;比如 RDB 和 AOF 都没有&#xff0c;但是可以通过做集群同步的方式&#xff0c; 让各…

netty websocket使用

一.maven依赖 <!-- springboot的依赖&#xff0c;如果系统不使用web相关的功能&#xff0c;也可以不使用 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>…

Matlab基本知识

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” %% Matlab基本的小常识 % (1)在每一行的语句后面加上分号&#xff08;一定要是英文的) a3; a5; % (2)多行注释&#xff1a;选中要注释的若干语句&#xff0c;快捷键CtrlR % a3; %…

【Vue3】集成 Element Plus

【Vue3】集成 Element Plus 背景简介开发环境开发步骤及源码总结 背景 随着年龄的增长&#xff0c;很多曾经烂熟于心的技术原理已被岁月摩擦得愈发模糊起来&#xff0c;技术出身的人总是很难放下一些执念&#xff0c;遂将这些知识整理成文&#xff0c;以纪念曾经努力学习奋斗的…

openai whisper使用

whisper使用 介绍 Whisper是一种通用的语音识别模型。它是在大量不同音频数据集上训练的&#xff0c;也是一个多任务模型&#xff0c;可以执行多语言语音识别、语音翻译和语言识别。 GitHub&#xff1a;https://github.com/openai/whisper 论文链接&#xff1a;https://arx…

AI-Talk开发板更新CH32固件

一、说明 CSK6011A有33个GPIO&#xff0c;但把WIFI、LCD、TP、CAMERA这些外设全部加上后&#xff0c;CSK6011A的IO不够用&#xff0c;还差6个&#xff0c;所以增加了一颗IO扩展MCU。CSK6-MIX开发板使用的IO扩展MCU为CH32V003F4P6&#xff0c;并且SDK也包含了此MCU的固件。AI-Ta…

机械学习—零基础学习日志(如何理解概率论3)

随机变量的函数分布 一维随机变量分布&#xff0c;可以看到下图&#xff0c;X为不同情况的概率。而x如果是大于等于X&#xff0c;那么当x在40以内时&#xff0c;没有概率&#xff0c;为0。 当x变大&#xff0c;在40-80之间&#xff0c;那么x大于X的概率为&#xff0c;0.7&…

性能测试 —— 系统架构性能优化思路!

这篇文章重点还是谈已经上线的业务系统后续出现性能问题后的问题诊断和优化重点。 系统性能问题分析流程 我们首先来分析下如果一个业务系统上线前没有性能问题&#xff0c;而在上线后出现了比较严重的性能问题&#xff0c;那么实际上潜在的场景主要来自于以下几个方面。 业务出…

回归预测 | Matlab实现BES-ESN秃鹰搜索算法优化回声状态网络多输入单输出回归预测

回归预测 | Matlab实现BES-ESN秃鹰搜索算法优化回声状态网络多输入单输出回归预测 目录 回归预测 | Matlab实现BES-ESN秃鹰搜索算法优化回声状态网络多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现BES-ESN秃鹰搜索算法优化回声状态网络…

宿舍管理系统_o4dvi

TOC springboot574宿舍管理系统_o4dvi--论文 第一章 概述 1.1 研究背景 近些年&#xff0c;随着中国经济发展&#xff0c;人民的生活质量逐渐提高&#xff0c;对网络的依赖性越来越高&#xff0c;通过网络处理的事务越来越多。随着宿舍管理的常态化&#xff0c;如果依然采用…

JVM 运行时内存结构简介

JVM 运行时内存结构简介 一、前言二、JVM 运行时内存结构2.1 线程隔离数据区&#xff1a;2.2 线程共享数据区&#xff1a; 三、JVM 内存区域划分1. 程序计数器&#xff08;PC&#xff09;2. 虚拟机栈3. 本地方法栈4. Java 堆5. 方法区6. 运行时常量池 附录 一、前言 JVM&#…

LLM RAG检索生成的深度解析:理解其工作原理与应用

前言 2024年随着大模型进一步增强升级&#xff0c;越来越多的大模型应用落地&#xff0c;经过初期的探索和研究&#xff0c;目前业界逐渐收敛聚聚于两个主要的应用方向&#xff1a;RAG和Agents。今天我们就来先聊聊这个RAG&#xff5e; 一.RAG基本介绍 RAG&#xff1a;全称R…

Linux下enable bbr

最近开通一台VPS&#xff0c;操作系统选择了Ubuntu 22.04&#xff0c;需要启用bbr功能。 BBR 是 Bottleneck Bandwidth&#xff08;瓶颈带宽&#xff09;的缩写&#xff0c;而 RTT 是一种拥塞控制系统。您可以在 Linux 桌面上启用 TCP BBR&#xff0c;以改善整体网上冲浪体验。…

IO模型-----聊天室

运行1个服务器和2个客户端 实现效果&#xff1a; 服务器和2个客户端互相聊天&#xff0c;服务器和客户端都需要使用select模型去实现 服务器要监视2个客户端是否连接&#xff0c;2个客户端是否发来消息以及服务器自己的标准输入流 客户端要监视服务器是否发来消息以及客户端自己…