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

文章目录

  • 概述
  • 简单的链表
    • 描述链表的术语
    • 简单实现一个单链表
  • 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;elsereturn 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(){//TESTSLIST_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.rhkb.cn/news/408980.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go语言操作文件上传和下载应用教程

Go语言操作文件上传和下载应用教程 我们在使用Go的日常开发中&#xff0c;经常会遇到对文件的处理&#xff0c;例如&#xff1a;上传、下载、读写等&#xff08;详情见Go 文件操作基本方法大全&#xff09;&#xff0c;且我们在实际应用中&#xff0c;基本都是使用框架自带的文…

电商项目DevOps一体化运维实战

主要讲了git和jkins的使用&#xff0c;其中maven的一个插件还挺好用的&#xff0c;主要可以用来查看哪些类没有使用&#xff0c;哪些导入的包是多余的等。这里展示一下用法。至于git和jkins的搭建后续再操作。 maven插件的使用&#xff1a; 编译后就可以在target下面看到这个h…

[ACL 2024] Revisiting Knowledge Distillation for Autoregressive Language Models

Contents IntroductionMethodRethinking Knowledge Distillation for Autoregressive LMsImproving Knowledge Distillation with Adaptive Teaching Modes ExperimentsReferences Introduction 作者提出 Autoregressive KD with Adaptive Teaching Modes (ATKD)&#xff0c;通…

5000套精美PPT免费分享

目录 部分展示目录 几乎包含各种应用场景的PPT模板 这里只展示部分目录 部分展示目录 ##PPT下载 链接&#xff1a;https://pan.baidu.com/s/1ckvN9xeMR82hL30lHXfJ0g 提取码&#xff1a;ZYNB 点击下载&#xff0c;记得点个赞哦

3 pytest Fixture

目录 3.1 通过 conftest.py 共享 fixture3.2 使用 fixture 执行配置及销毁逻辑3.3 使用 --setup-show 回溯 fixture 的执行过程3.4 使用 fixture 传递测试数据3.5 使用多个 fixture3.6 指定 fixture 作用范围3.7 使用 usefixtures 指定 fixture3.8 为常用 fixture 添加 autouse…

MySQL从入门到精通(第9-10章)

文章目录 9 子查询9.1 需求分析与问题解决9.1.1 实际问题9.1.2 子查询的使用9.1.3 子查询的分类 9.2 单行子查询9.2.1 单行比较操作符9.2.2 代码示例9.2.3 HAVING中的子查询9.2.4 CASE中的子查询9.2.5 子查询中的空值问题9.2.6 非法使用子查询 9.3 多行子查询9.3.1 多行比较操作…

linux系统使用 docker 来部署web环境 nginx+php7.4 并配置称 docker-compose-mysql.yml 文件

Docker是一个开源的容器化平台&#xff0c;旨在简化应用程序的创建、部署和管理。它基于OS-level虚拟化技术&#xff0c;通过将应用程序和其依赖项打包到一个称为容器的标准化单元中&#xff0c;使得应用程序可以在任何环境中快速、可靠地运行。 Docker的优势有以下几个方面&a…

如何使用ssm实现基于java斗车交易系统设计与实现+vue

TOC ssm082基于java斗车交易系统设计与实现vue 系统概述 1.1 概述 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们的生活水平不断提高&#xff0c;日常生活中人们对斗车交易方面的要求也在不断提高&#xff0c;需要咨询的人数更是不断增加&#xff0c;使得…

【第69课】Java安全JWT攻防Swagger自动化算法签名密匙Druid未授权

免责声明 本文发布的工具和脚本&#xff0c;仅用作测试和学习研究&#xff0c;禁止用于商业用途&#xff0c;不能保证其合法性&#xff0c;准确性&#xff0c;完整性和有效性&#xff0c;请根据情况自行判断。 如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利&#xff0…

RM遥控键鼠控制总结

硬件&通信介绍 RM比赛中各个参赛队伍使用的都是大疆官方提供的遥控器套装&#xff0c;包括遥控器和接收机&#xff0c;接收机上共三个引脚&#xff1a;VCC&#xff0c;GND&#xff0c;DBUS&#xff08;数据通道&#xff09;&#xff0c;首次使用需要进行遥控器和接收机配对…

C++类和对象(下):初始化列表、explicit关键字、友元函数、友元类

文章目录 C类和对象9、初始化列表9.1构造函数体赋值9.2初始化列表9.3 explicit&#xff08;显示&#xff09;关键字 10、友元10.1友元函数10.2友元类 C类和对象 9、初始化列表 一个类的构造函数要初始化成员变量有两种方式&#xff0c;一种是构造函数体赋值&#xff0c;另一种…

8.23-docker基础命令学习

docker 1.docker容器 [rootdocker ~]# systemctl start docker[rootdocker ~]# docker imagesREPOSITORY TAG IMAGE ID CREATED SIZEcentos latest 5d0da3dc9764 2 years ago 231MB​# 容器执行完就退出了​[rootdocker ~]# docker run -it …

spring框架简介

文章目录 1.Spring的简介2.Spring的起源与发展3.Spring的核心体系介绍4.Spring框架的特点总结5.xml定义bean的相关属性1、class属性、id属性、name属性2、作用域属性3、初始化方法和销毁方法 1.Spring的简介 Spring的英文翻译为春天&#xff0c;可以说是给Java程序员带来了春天…

python爬虫——入门

一、概念 万维网之所以叫做网&#xff0c;是因为通过点击超链接或者进入URL&#xff0c;我们可以访问任何网络资源&#xff0c;从一个网页跳转到另一个网页&#xff0c;所有的相关资源连接在一起&#xff0c;就形成了一个网。 而爬虫呢&#xff0c;听名字就让人想起来一个黏糊…

设计模式篇(DesignPattern - 创建型模式)

目录 模式一&#xff1a;单例模式 一、简介 二、种类 1. 饿汉式(静态常量&#xff09; 1.1. 代码 1.2. 优缺点 2. 饿汉式&#xff08;静态代码块&#xff09; 2.1. 代码 2.2. 优缺点 3. 懒汉式(线程不安全) 3.1. 代码 3.2. 优缺点 4. 懒汉式(线程安全&#xff0c;…

Vulkan入门系列16 - 生成多级纹理贴图( Mipmaps)

一:概述 我们的程序现在可以加载和渲染 3D 模型了。在本章中,我们将再添加一项功能-- Mipmaps 生成。Mipmaps 广泛应用于游戏和渲染软件中,Vulkan 让我们可以完全控制 Mpmaps 的生成方式。 Mipmaps 是预先计算的、缩放的图像。每个新图像的宽度和高度都是前一个图像的一半。…

ssrf漏洞之——漏洞复现

漏洞介绍 SSRF漏洞&#xff1a;SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由恶意访问者构造url&#xff0c;由服务端对此url发起请求的一个安全漏洞。 漏洞原理 SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能&#xff0c;并且没有对目…

(QT-UI)十四、在时间轴上绘制一段段时间片

本系列预计实现 ①刻度上方文字显示&#xff0c; ②时间轴拖动效果&#xff0c; ③时间轴刻度缩放&#xff0c; ④时间轴和其他控件联动显示&#xff0c; ⑤鼠标放置到时间轴&#xff0c;显示具体时间。 ⑥通过定时器&#xff0c;实时更新时间轴 ⑦时间轴上绘制时间片 完…

用excel内容批量建立文件夹

建文件夹是电脑操作过程中比较常见的&#xff0c;但是用EXCEL内容批量建文件夹&#xff0c;这似乎不相关的两个操作&#xff0c;那么怎么实现这样的一个功能&#xff0c;我们需要用到专门的软件进行关联&#xff0c;推荐&#xff1a;可易文件夹批量生成器&#xff0c;这个软件有…

数据结构基础详解(C语言): 栈与队列的详解附完整代码

数据结构 栈 栈的核心重点&#xff1a; 栈是只能从表尾插入和删除的数据结构。 栈的顺序存储结构由两部分组成&#xff0c;top指针和数组。 链栈其实本质就是单链表头插法 文章目录 数据结构 栈1.栈的基本概念1.1 栈的常用操作 2.栈的存储结构2.1 栈的顺序存储结构2.1.1 栈的定…