数据结构C语言描述3(图文结合)--双链表、循环链表、约瑟夫环问题
前言
- 这个专栏将会用纯C实现常用的数据结构和简单的算法;
- 有C基础即可跟着学习,代码均可运行;
- 准备考研的也可跟着写,个人感觉,如果时间充裕,手写一遍比看书、刷题管用很多,这也是本人采用纯C语言实现的原因之一;
- 欢迎收藏 + 关注,本人将会持续更新。
文章目录
- 双向链表
- 简介
- 双链表实现
- 循环链表
- 循环链表约瑟夫环问题
双向链表
简介
🚡 双向链表,对比单链表来说,顾名思义,就是指向指向有两个指针,指向前后节点。
🌾 结合单链表,单链表有无头和有头之分,双向链表也是一样,这里是无头双链表,采用再封装写法,不是二级指针写法,再封装写法比较写起来比较容易,个人比较偏爱🤠🤠🤠🤠
双链表图示:

🉑 从ADT抽象中来说:
总结起来一句话:增删改查,🤠🤠🤠,假设双向链表是一个结合,这这个结合功能有:
- 增加元素
- 删除元素
- 拿取元素
- 查询元素
- 修改元素
- …………………………
双链表实现
这里采用再封装的方法,实现无头链表,有头和无头再单链表的那一节以及讲过了,这里采用无头实现
💠 节点封装
在双链表中,对于每一个节点来说,都有一个指向前、指向后节点的指针。
typedef int DataType;typedef struct Node {DataType data;struct Node* prev; // 前struct Node* next; // 后
}Node;
⚜️ 链表封装
这一步封装的作用是,可以更好的操作链表的一些操作,这个size的一个再封装写法的核心,无头与有头再实现的过程中,核心就在于第一个头节点的处理,无头如果没有任何节点,则插入的节点则作为第一个节点,但是这样会改变指针的指向,这也是因为需要传递二级指针的原因,而再封装写法则很好的解决了这个问题。
typedef struct List {Node* headNode;Node* tailNode;int size;
}List;
🍨 创建节点
采用calloc创建节点,这样可以自动赋值为0。
Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}
🍤 创建链表
List* create_list()
{List* list = (List*)calloc(1, sizeof(List));assert(list);return list;
}
🤕 插入–头插
- 情况判断:是否为空链表
- 空链表:插件节点,作为头
- 不为空:头插,如图:
void push_front(List* list, DataType data)
{if (list == NULL) {return;}Node* node = create_node(data);if (list->size == 0) { // 这种写法的优势,不用传递二级指针list->tailNode = node;}else {node->next = list->headNode;list->headNode->prev = node;}list->headNode = node;list->size++;
}
🌮 插入–尾插入
- 情况判断:是否为空链表
- 为空:插入,作为头
- 不为空:找到尾节点,插入
void push_back(List* list, DataType data)
{if (list == NULL) {return;}Node* node = create_node(data);if (list->size == 0) {list->headNode = node;}else {list->tailNode->next = node;node->prev = list->tailNode;}list->tailNode = node;list->size++;
}
💠 插入–任意位置
- 规则: 在元素后位置插入
- 情况:3种
- 没有该元素
- 尾插
- 任意插
void insert(List* list, DataType posData, DataType data)
{if (list == NULL || list->size == 0) { // size != 0 保证有头return;}Node* t = list->headNode;while (t->next != NULL && t->data != posData) {t = t->next;}if (t->data != posData) { // 没有该元素return;}else if (t->next == NULL && t->data == posData) { // 尾插Node* node = create_node(data);node->prev = t;t->next = node;list->tailNode = node; // 尾巴移位}else{Node* node = create_node(data);node->next = t->next;t->next->prev = node;node->prev = t;t->next = node;}list->size++;
}
🎧 删除–头删
注意点:释放后指针指向问题:
- 如果说就一个元素,则删除后,在封装头需要指向NULL
- 如果不是,则下一个元素的prev指针需要赋值为NULL
void pop_front(List* list)
{if (list == NULL || list->size == 0) {return;}Node* node = list->headNode;list->headNode = node->next;free(node);(list->headNode) ? (list->headNode->prev = NULL) : (list->tailNode = NULL); // 判断是否只有一个节点的情况node = NULL;
}
🚖 删除–尾删除
注意点:释放后指针指向问题:
- 如果说就一个元素,则删除后,在封装头需要指向NULL
- 如果不是,则上一个元素的next指针需要赋值为NULL
void pop_back(List* list)
{if (list == NULL || list->size == 0) {return;}Node* node = list->tailNode;list->tailNode = list->tailNode->prev;free(node);(list->tailNode) ? (list->tailNode->next = NULL) : (list->headNode = NULL);list->size--;
}
🦅 删除–任意位置删除
四种情况:
- 没有找到
- 找到了
- 头删
- 尾删
- 任意位置删
void erase(List* list, DataType posData)
{if (list == NULL || list->size == 0) {return;}Node* cur = list->headNode;while (cur->next != NULL && cur->data != posData) {cur = cur->next;}// 没有找到if (cur->data != posData) {return;}else if (cur->next == NULL) { // 尾删除pop_back(list);}else if (cur->prev == NULL) { // 头删pop_front(list);}else {Node* t = cur;cur->prev->next = cur->next;cur->next->prev = cur->prev;free(t);t = NULL;list->size--;}
}
🌐 万金油函数
bool empty(List* list)
{if (list == NULL) {return true;}return list->size == 0;
}size_t size(List* list)
{if (list == NULL) {return 0;}return list->size;
}
📤 向前遍历
void travel_front(List* list)
{if (list == NULL) {return;}Node* cur = list->headNode;while (cur) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}
🎉 向后遍历
void travel_back(List* list)
{if (list == NULL) {return;}Node* cur = list->tailNode;while (cur) {printf("%d ", cur->data);cur = cur->prev;}printf("\n");
}
⏰ 总代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>typedef int DataType;typedef struct Node {DataType data;struct Node* prev;struct Node* next;
}Node;typedef struct List {Node* headNode;Node* tailNode;int size;
}List;Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}List* create_list()
{List* list = (List*)calloc(1, sizeof(List));assert(list);return list;
}void push_front(List* list, DataType data)
{if (list == NULL) {return;}Node* node = create_node(data);if (list->size == 0) {list->tailNode = node;}else {node->next = list->headNode;list->headNode->prev = node;}list->headNode = node;list->size++;
}void push_back(List* list, DataType data)
{if (list == NULL) {return;}Node* node = create_node(data);if (list->size == 0) {list->headNode = node;}else {list->tailNode->next = node;node->prev = list->tailNode;}list->tailNode = node;list->size++;
}void insert(List* list, DataType posData, DataType data)
{if (list == NULL || list->size == 0) { // size != 0 保证有头return;}Node* t = list->headNode;while (t->next != NULL && t->data != posData) {t = t->next;}if (t->data != posData) { // 没有该元素return;}else if (t->next == NULL && t->data == posData) { // 尾插Node* node = create_node(data);node->prev = t;t->next = node;list->tailNode = node; // 尾巴移位}else{Node* node = create_node(data);node->next = t->next;t->next->prev = node;node->prev = t;t->next = node;}list->size++;
}void pop_front(List* list)
{if (list == NULL || list->size == 0) {return;}Node* node = list->headNode;list->headNode = node->next;free(node);(list->headNode) ? (list->headNode->prev = NULL) : (list->tailNode = NULL); // 判断是否只有一个节点的情况node = NULL;
}void pop_back(List* list)
{if (list == NULL || list->size == 0) {return;}Node* node = list->tailNode;list->tailNode = list->tailNode->prev;free(node);(list->tailNode) ? (list->tailNode->next = NULL) : (list->headNode = NULL);list->size--;
}void erase(List* list, DataType posData)
{if (list == NULL || list->size == 0) {return;}Node* cur = list->headNode;while (cur->next != NULL && cur->data != posData) {cur = cur->next;}// 没有找到if (cur->data != posData) {return;}else if (cur->next == NULL) { // 尾删除pop_back(list);}else if (cur->prev == NULL) { // 头删pop_front(list);}else {Node* t = cur;cur->prev->next = cur->next;cur->next->prev = cur->prev;free(t);t = NULL;list->size--;}
}bool empty(List* list)
{if (list == NULL) {return true;}return list->size == 0;
}size_t size(List* list)
{if (list == NULL) {return 0;}return list->size;
}void travel_front(List* list)
{if (list == NULL) {return;}Node* cur = list->headNode;while (cur) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}void travel_back(List* list)
{if (list == NULL) {return;}Node* cur = list->tailNode;while (cur) {printf("%d ", cur->data);cur = cur->prev;}printf("\n");
}int main()
{List* list = create_list();for (int i = 1; i < 4; i++) {push_front(list, i);}for (int i = 1; i < 4; i++) {push_back(list, i * 10);}travel_front(list);travel_back(list);insert(list, 3, 33);insert(list, 30, 300);travel_front(list);travel_back(list);pop_front(list);travel_front(list);travel_back(list);pop_back(list);travel_front(list);travel_back(list);erase(list, 33);erase(list, 30);erase(list, 10);travel_front(list);travel_back(list);return 0;
}
循环链表
循环链表分为循环单链表,循环双链表,单链表和双链表又分为有头和无头链表,这里是有头循环双链表。
双向循环链表(Doubly Circular Linked List)是一种数据结构,其中每个节点都包含两个指针,一个指向前一个节点,一个指向后一个节点。与普通链表不同的是,双向循环链表的最后一个节点的下一个指针指向头节点,而头节点的前一个指针指向最后一个节点,形成一个循环。

🤕 节点封装
typedef int DataType;typedef struct Node {DataType data;struct Node* prev;struct Node* next;
}Node;
🍨 创建节点
Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}
⚜️ 创建链表
这里需要构建一个循环节点(链表),如图:

Node* create_list()
{Node* list = (Node*)calloc(1, sizeof(Node));assert(list);list->next = list;list->prev = list;return list;
}
🎧 插入–头插
双向头删就很容易了,如图:

void push_front(Node* list, DataType data)
{assert(list);Node* node = create_node(data);node->next = list->next;list->next->prev = node; // 难点,不用担心 next,prev为空的时候node->prev = list;list->next = node;
}
🌮 插入–尾插
尾巴删除也很容易,因为头和尾巴互相找到,如图:

void push_back(Node* list, DataType data)
{assert(list);Node* node = create_node(data);node->prev = list->prev;list->prev->next = node;node->next = list;list->prev = node;
}
⛹️♀️ 插入–任意位置
任意位置也不难,找到要插入的位置,要注意的是找不到的情况。
// 找到要插入那个节点的位置节点
void insert(Node* list, DataType posData ,DataType data)
{assert(list);Node* cur = list->next;while (cur->next != list && cur->data != posData) {cur = cur->next;}if (cur->data != posData) { // 思考,为什么不能 cur->next != list ?????return;}else {Node* node = create_node(data);node->next = cur->next;cur->next->prev = node;node->prev = cur;cur->next = node;}}
👟 删除–头删
注意: 一个节点的头节点指向不同。
两种情况:
- 如果一个元素,这个时候删除,头节点指向修改和两个元素以上删除不同,这个时候头节点需要指向自己
- 两个元素及上
void pop_front(Node* list)
{assert(list);Node* cur = list->next;if (cur == list) { // 无节点return;}else if(cur->next == list) { // 一个节点list->prev = list;list->next = list;}else {list->next = cur->next; // 两个节点以上cur->next->prev = list;}free(cur);cur = NULL;
}
📑 删除–尾删
这个也是简单的,因为可以通过头节点直接找到尾节点,这个时候就只需要一种情况即可,因为创建双链表有一个很好的特性,
void pop_back(Node* list)
{assert(list);Node* cur = list->prev; // 因为可以获取尾节点if (cur == list) {return;}else {cur->prev->next = list; // 哪怕是一个节点,也和普通情况也是一样list->prev = cur->prev; // 这个也是一样,free(cur);cur = NULL;}}
🚴♀ 删除–任意位置
很简单,因为这个也不用记录前驱节点,也不用找尾节点了,只需要考虑两种情况:
- 没有找到
- 找到了
void erase(Node* list, DataType posData)
{assert(list);Node* cur = list->next;while (cur->next != list && cur->data != posData) {cur = cur->next;}if (cur->data != posData) {return;}else {cur->prev->next = cur->next;cur->next->prev = cur->prev;free(cur);cur = NULL;}
}
🔱 遍历
void travel_front(Node* list)
{assert(list);Node* cur = list->next;while (cur != list) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}void travel_back(Node* list)
{assert(list);Node* cur = list->prev;while (cur != list) {printf("%d ", cur->data);cur = cur->prev;}printf("\n");
}
⚗️ 总代码
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>// 有头链表实现,简单点typedef int DataType;typedef struct Node {DataType data;struct Node* prev;struct Node* next;
}Node;Node* create_node(DataType data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;return node;
}Node* create_list()
{Node* list = (Node*)calloc(1, sizeof(Node));assert(list);list->next = list;list->prev = list;return list;
}void push_front(Node* list, DataType data)
{assert(list);Node* node = create_node(data);node->next = list->next;list->next->prev = node; // 难点,不用担心 next,prev为空的时候node->prev = list;list->next = node;
}void push_back(Node* list, DataType data)
{assert(list);Node* node = create_node(data);node->prev = list->prev;list->prev->next = node;node->next = list;list->prev = node;
}
// 找到要插入那个节点的位置节点
void insert(Node* list, DataType posData ,DataType data)
{assert(list);Node* cur = list->next;while (cur->next != list && cur->data != posData) {cur = cur->next;}if (cur->data != posData) { // 思考,为什么不能 cur->next != list ?????return;}else {Node* node = create_node(data);node->next = cur->next;cur->next->prev = node;node->prev = cur;cur->next = node;}}void pop_front(Node* list)
{assert(list);Node* cur = list->next;if (cur == list) {return;}else if(cur->next == list) {list->prev = list;list->next = list;}else {list->next = cur->next;cur->next->prev = list;}free(cur);cur = NULL;
}void pop_back(Node* list)
{assert(list);Node* cur = list->prev;if (cur == list) {return;}else {cur->prev->next = list;list->prev = cur->prev;free(cur);cur = NULL;}}void erase(Node* list, DataType posData)
{assert(list);Node* cur = list->next;while (cur->next != list && cur->data != posData) {cur = cur->next;}if (cur->data != posData) {return;}else {cur->prev->next = cur->next;cur->next->prev = cur->prev;free(cur);cur = NULL;}
}void travel_front(Node* list)
{assert(list);Node* cur = list->next;while (cur != list) {printf("%d ", cur->data);cur = cur->next;}printf("\n");
}void travel_back(Node* list)
{assert(list);Node* cur = list->prev;while (cur != list) {printf("%d ", cur->data);cur = cur->prev;}printf("\n");
}int main()
{Node* list = create_list();push_front(list, 1);push_front(list, 2);push_front(list, 3);travel_front(list);travel_back(list);push_back(list, 11);push_back(list, 22);push_back(list, 33);travel_front(list);travel_back(list);insert(list, 2, 20);insert(list, 3, 30);insert(list, 33, 330);travel_front(list);travel_back(list);pop_front(list);travel_front(list);travel_back(list);pop_back(list);travel_front(list);travel_back(list);erase(list, 33);travel_front(list);travel_back(list);return 0;
}
循环链表约瑟夫环问题
讲一个比较有意思的故事:约瑟夫是犹太军队的一个将军,在反抗罗马的起义中,他所率领的军队被击溃,只剩下残余的部队40余人,他们都是宁死不屈的人,所以不愿投降做叛徒。一群人表决说要死,所以用一种策略来先后kill所有人。
于是约瑟夫建议:每次由其他两人一起kill一个人,而被kill的人的先后顺序是由抽签决定的,约瑟夫有预谋地抽到了最后一签,在kill了除了他和剩余那个人之外的最后一人,他劝服了另外一个没死的人投降了罗马。
我们这个规则是这么定的:
- 在一间房间总共有n个人(下标0~n-1),只能有最后一个人活命。
- 按照如下规则去排除人:
- 所有人围成一圈
- 顺时针报数,每次报到q的人将被排除掉
- 被排除掉的人将从房间内被移走
- 然后从被kill掉的下一个人重新报数,继续报q,再清除,直到剩余一人
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>/*
* 用上一个链表,也可以。这里采用无头循环双链表实现
* 无头采用再次封装的写法
*/typedef struct Node {int data;struct Node* prev;struct Node* next;
}Node;typedef struct List {Node* headNode;
}List;// 每一个节点创建都是循环
Node* create_node(int data)
{Node* node = (Node*)calloc(1, sizeof(Node));assert(node);node->data = data;node->prev = node;node->next = node;return node;
}void push_back(List* list, int data)
{assert(list);Node* node = create_node(data);if (list->headNode == NULL) {list->headNode = node;}else {Node* cur = list->headNode->prev;node->next = list->headNode;list->headNode->prev = node;cur->next = node;node->prev = cur;}
}void erase(List* list, Node* node)
{assert(list);assert(node);// 一个节点if (node->next == node) {free(node);node = NULL;list->headNode = NULL;}else {node->prev->next = node->next;node->next->prev = node->prev;if (list->headNode == node) { // 防止删除头list->headNode = node->next;}free(node);node = NULL;}
}void travel(List* list)
{Node* cur = list->headNode;while (cur->next != list->headNode) {printf("%d ", cur->data);cur = cur->next;}printf("%d ", cur->data);printf("\n");
}// 只做演示,不考虑内存问题
void game_run(int n, int m)
{if (n < 0 || m < 0) {return;}List list = { NULL };for (int i = 1; i <= n; i++) {push_back(&list, i);}travel(&list);Node* cur = list.headNode;while (n > 1) {// 报数for (int i = 1; i < m; i++) {cur = cur->next;}Node* next = cur->next;erase(&list, cur);// 重置报数cur = next;n--;}printf("天选之子: %d\n", cur->data);
}int main()
{game_run(10, 3);return 0;
}
相关文章:
数据结构C语言描述3(图文结合)--双链表、循环链表、约瑟夫环问题
前言 这个专栏将会用纯C实现常用的数据结构和简单的算法;有C基础即可跟着学习,代码均可运行;准备考研的也可跟着写,个人感觉,如果时间充裕,手写一遍比看书、刷题管用很多,这也是本人采用纯C语言…...
第二十五章 TCP 客户端 服务器通信 - TCP 设备的 READ 命令
文章目录 第二十五章 TCP 客户端 服务器通信 - TCP 设备的 READ 命令TCP 设备的 READ 命令READ 修改 $ZA 和 $ZB$ZA 和 READ 命令 第二十五章 TCP 客户端 服务器通信 - TCP 设备的 READ 命令 TCP 设备的 READ 命令 从服务器或客户端发出 READ 命令以读取客户端或服务器设置的…...
【C++】哈希表的实现详解
哈希表的实现详解 一、哈希常识1.1、哈希概念1.2、哈希冲突1.3、哈希函数(直接定执 除留余数)1.4、哈希冲突解决闭散列(线性探测 二次探测)开散列 二、闭散列哈希表的模拟实现2.1、框架2.2、哈希节点状态的类2.3、哈希表的扩容2…...
高阶C语言之五:(数据)文件
目录 文件名 文件类型 文件指针 文件的打开和关闭 文件打开模式 文件操作函数(顺序) 0、“流” 1、字符输出函数fputc 2、字符输入函数fgetc 3、字符串输出函数fputs 4、 字符串输入函数fgets 5、格式化输入函数fscanf 6、格式化输出函数fpr…...
服务器上部署并启动 Go 语言框架 **GoZero** 的项目
要在服务器上部署并启动 Go 语言框架 **GoZero** 的项目,下面是一步步的操作指南: ### 1. 安装 Go 语言环境 首先,确保你的服务器上已安装 Go 语言。如果还没有安装,可以通过以下步骤进行安装: #### 1.1 安装 Go 语…...
【Java SE 】继承 与 多态 详解
🔥博客主页🔥:【 坊钰_CSDN博客 】 欢迎各位点赞👍评论✍收藏⭐ 目录 1. 继承 1.1 继承的原因 1.2 继承的概念 1.3 继承的语法 2. 子类访问父类 2.1 子类访问父类成员变量 2.1.1 子类与父类不存在同名成员变量 2.1.2 子类…...
【大语言模型】ACL2024论文-16 基于地图制图的罗马尼亚自然语言推理语料库的新型课程学习方法
【大语言模型】ACL2024论文-16 基于地图制图的罗马尼亚自然语言推理语料库的新型课程学习方法 目录 文章目录 【大语言模型】ACL2024论文-16 基于地图制图的罗马尼亚自然语言推理语料库的新型课程学习方法目录摘要:研究背景:问题与挑战:如何解…...
秋招大概到此结束了
1、背景 学院本,软工,秋招只有同程,快手和网易面试,后两家kpi(因为面试就很水),秋招情况:哈啰(实习转正ing),同程测开offer。 2、走测开的原因 很…...
华为OD机试真题---字符串化繁为简
华为OD机试真题中的“字符串化繁为简”题目是一个涉及字符串处理和等效关系传递的问题。以下是对该题目的详细解析: 一、题目描述 给定一个输入字符串,字符串只可能由英文字母(a~z、A~Z)和左右小括号((、)࿰…...
概念解读|K8s/容器云/裸金属/云原生...这些都有什么区别?
随着容器技术的日渐成熟,不少企业用户都对应用系统开展了容器化改造。而在容器基础架构层面,很多运维人员都更熟悉虚拟化环境,对“容器圈”的各种概念容易混淆:容器就是 Kubernetes 吗?容器云又是什么?容器…...
初识Arkts
创建对象: 类: 类声明引入一个新类型,并定义其字段、方法和构造函数。 定义类后,可以使用关键字new创建实例 可以使用对象字面量创建实例 在以下示例中,定义了Person类,该类具有字段name和surname、构造函…...
基本的SELECT语句
1.SQL概述 SQL(Structured Query Language)是一种用于管理和操作关系数据库的编程语言。它是一种标准化的语言,用于执行各种数据库操作,包括创建、查询、插入、更新和删除数据等。 SQL语言具有简单、易学、高效的特点,…...
51c自动驾驶~合集30
我自己的原文哦~ https://blog.51cto.com/whaosoft/12086789 #跨越微小陷阱,行动更加稳健 目前四足机器人的全球市场上,市场份额最大的是哪个国家的企业?A.美国 B.中国 C.其他 波士顿动力四足机器人 云深处 绝影X30 四足机器人 …...
Python Tutor网站调试利器
概述 本文主要是推荐一个网站:Python Tutor. 网站首页写道: Online Compiler, Visual Debugger, and AI Tutor for Python, Java, C, C++, and JavaScript Python Tutor helps you do programming homework assignments in Python, Java, C, C++, and JavaScript. It contai…...
h5小游戏实现获取本机图片
h5小游戏实现获取本机图片 本文使用cocos引擎 1.1 需求 用户通过文件选择框选择图片。将图片内容转换为Cocos Creator的纹理 (cc.Texture2D),将纹理设置到 cc.SpriteFrame 并显示到节点中。 1.2 实现步骤 创建文件输入框用于获取文件 let input document.createElement(&quo…...
前端 javascript a++和++a的区别
前端 javascript a和a的区别 a 是先执行表达式后再自增,执行表达式时使用的是a的原值。a是先自增再执行表达示,执行表达式时使用的是自增后的a。 var a0 console.log(a); // 输出0 console.log(a); // 输出1var a0 console.log(a); // 输出1 console.l…...
OceanBase V4.x应用实践:如何排查表被锁问题
DBA在日常工作中常常会面临以下两种常见情况: 业务人员会提出问题:“表被锁了,导致业务受阻,请帮忙解决。” 业务人员还会反馈:“某个程序通常几秒内就能执行完毕,但现在却运行了好几分钟,不清楚…...
ctfshow-web入门-SSRF(web351-web360)
目录 1、web351 2、web352 3、web353 4、web354 5、web355 6、web356 7、web357 8、web358 9、web359 10、web360 1、web351 看到 curl_exec 函数,很典型的 SSRF 尝试使用 file 协议读文件: urlfile:///etc/passwd 成功读取到 /etc/passwd 同…...
【日常记录-Git】如何为post-checkout脚本传递参数
1. 简介 在Git中,post-checkout 钩子是一个在git checkout 或git switch命令成功执行后自动调用的脚本。该脚本不接受任何来自Git命令的直接参数,因为Git设计该钩子是为了在特定的版本控制操作后执行一些预定义的任务,而不是作为一个通用的脚…...
《机器人控制器设计与编程》考试试卷**********大学2024~2025学年第(1)学期
消除误解,课程资料逐步公开。 复习资料: Arduino-ESP32机器人控制器设计练习题汇总_arduino编程语言 题-CSDN博客 试卷样卷: 开卷考试,时间: 2024年11月16日 001 002 003 004 005 ……………………装………………………...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
