单链表详解(无哨兵位),实现增删改查

1.顺序表对比单链表的缺点

  1. 中间或头部插入时,需要移动数据再插入,如果数据庞大会导致效率降低
  2. 每次增容就需要申请空间,而且需要拷贝数据,释放旧空间
  3. 增容造成浪费,因为一般都是以2倍增容

2.链表的基础知识

  1. 链表也是线性表的一种。物理结构:不一定线性,逻辑结构:一定是线性的
  2. 链表物理结构也不是线性的。链表由一个一个的节点组成
  3. 节点由数据和指向下一个地方的指针。每一个节点都会存储下一个节点的地址
  4. List 表示链表, S表示single ,Node表示节点

2.1链表基本结构

typedef int SLDataType;//节点类型,S 表示节点,L表示链表
typedef struct SListNode
{SLDataType data;struct SListNode* next;
}SLNode;

3.代码实现

  1. 设置三个文件,SList.h 头文件 SList.c 功能实现文件,test.c测试文件
  2. 声明写在头文件里面 SList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>//用一个就申请一个空间
#include <assert.h>typedef int SLDataType;
typedef struct SListNode
{int data;struct SListNode* next;
}SLNode;//链表打印
void SLPrint(SLNode* phead);

3.1.实现思路

  1. 为两个节点开辟空间,并通过赋值给他们链接起来,通过节点内的地址
  2. 通过传过来的头节点去找尾节点,从而达到遍历链表
  3. 通过改变pcur的指向,相当于可以访问下一块空间了

void SLPrint(SLNode* phead)//phead 表示头节点
{SLNode* pcur = phead;//pcur 临时的节点while (pcur != 0){printf("%d->", pcur->data);pcur = pcur->next;}printf("NULL\n");
}

3.1.1测试方法

  1. 开辟了空间并且把他们都串起来。
void SListTest1()
{SLNode* Node1 = (SLNode*)malloc(sizeof(SLNode));//初始化Node1->data = 1;SLNode* Node2 = (SLNode*)malloc(sizeof(SLNode));Node2->data = 2;SLNode* Node3 = (SLNode*)malloc(sizeof(SLNode));Node3->data = 3;Node1->next = Node2;Node2->next = Node3;Node3=->next = NULL;//最后一个置为NULL//打印链表SLPrint(Node1);
}

3.1.2当while循环条件不一样时

  1. 这里用图说明问题,当pcur 和pcur->next 。pcur不为NULL,还会多走一步。
  2. pucr->next 为空不往下走,就是说,pcur 比pcur->next,多走一步

4.0尾插

  1. 在尾插开始之前,需要判断两种情况
    1. 第一种就是空链表和非空链表,如果为空链表,肯定要开辟空间的
    2. 在这个新开辟的空间里面,顺便一起把需要插入的数据放这里一起插入,也就说涉及到插入会对空间进行增加的就要新空间
    3. 开辟好了空间,判断有效性,然后放入数据,再给个NULL
//新空间
SLNode* SLBuyNode(SLDataType x)
{SLNode* Newnode = (SLNode*)malloc(sizeof(SLNode));if (Newnode == NULL){perror("malloc New");exit(1);//非0表示异常返回,会导致直接跳出整个程序}//开辟完新空间记得放入数据Newnode->data = x;Newnode->next = NULL;return Newnode;//开辟完空间要记得返回
}
  1. 如果不是空链表,就要通过头结点找到尾节点,一定是要找到存放下一个节点的next
  2. 找了尾节点,就可以直接在尾节点放上新开辟好,因为对应数据已经在新空间里弄好了
//链表尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{assert(pphead);//这里不能判断*pphead,因为有可能本来就是空链表//尾插的时候需要看是否为NULL,空链表和非空链表SLNode* Newnode = SLBuyNode(x);//把需要插入的值传过去if (*pphead == NULL){*pphead = Newnode;//如果是NULL就把新开辟的给到头节点}else{SLNode* ptail = *pphead;while (ptail->next != NULL)//查找尾节点{ptail = ptail->next;}ptail->next = Newnode;//新节点里面已经置为空指针}
}

4.1测试方法

  1. 往后的测试方法都不写了,学会了后根据对应参数测试就OK了。
  2. 这里注意一定要传二级指针的地址,对一级指针改变指向就是要传二级指针
  3. 举个例子,看下面一个传址调用,另外一个是传值调用

SListTest2()
{SLNode* node = NULL;//新创建的节点要初始化//测试尾插//SLPushBack(&node, 2);//SLPushBack(&node, 3);//SLPushBack(&node, 4);//SLPrint(node);//测试头插SLPushFront(&node, 4);SLPushFront(&node, 3);SLPushFront(&node, 2);SLPushFront(&node, 1);SLPrint(node);
}

1.可以看上面你要&node的地址才能对node改变,而不是他创建node,而你就传node,这个就和传值调用不就一样了吗?

5.0头插

  1. assert(pphead);//这里不能判断*pphead,因为有可能本来就是空链表
  2. 头插当然就很简单了,直接让新开辟的空间指向头节点
  3. 然后把头结点权限给新空间,让他newnode来当第一个
//链表头插
void SLPushFront(SLNode** pphead, SLDataType x)
{assert(pphead);//这里不能判断*pphead,因为有可能本来就是空链表SLNode* newnode = SLBuyNode(x);newnode->next = *pphead;//把之前的头节点给到新节点*pphead = newnode;//头插成为新节点
}

6.0尾删

  1. 第一种:如果是一个节点就可以直接删除
  2. 第二种:在进行尾删的时候也要找尾,而且还有保留前一个节点的地址
  3. 然后保留的前一个节点的地址,他指向的下一个地址就可以置为NULL
//链表尾删
void SLDelBack(SLNode** pphead)
{//传过来的地址必须有效assert(pphead && *pphead);//首先要考虑2种情况,有链表的情况,一个节点的情况if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLNode* ptail = *pphead;//再创建一个临时的指针更好,可以避免优先级问题,还有能保留源地址SLNode* pcur = *pphead;while (ptail->next){pcur = ptail;//ptail指向前的一个节点ptail = ptail->next;}free(ptail);//直接free ptailptail = NULL;pcur->next = NULL;}
}

6.1尾删最后一步,倒推图

7.头删

  1. 链表不能为空,为NULL删什么啊,当然对应的二级指针也不能为空
  2. -> 符号优先级 比 * 星号高
  3. 两个问题点
    1. 一个节点:这个问题怎么提出的因为链表为空不能删会报错,那么本身方法无意义了
    2. 多个节点:先把指向的节点给到*pphead,也就是我们的头节点,然后再删除之前的头节点
  4. 头删的时候要把这个节点删除,但是我们要能找到下一个节点并成为头节点
  5. 由此可以得出先把next = Node1->next。不能直接给赋值给*pphead,因为后面还要删除头节点,所以我们先保存下一个节点的地址
void SLDelFront(SLNode** pphead)
{assert(pphead && *pphead);//链表为空就没有删除的意义了SLNode* next = (*pphead)->next;//保存下一个节点free(*pphead);*pphead = next;
}

8.链表查找节点

  1. 在查找链表的时候,你可能要想想,指针走到后面的NULL,万一后面还有需要查找的值呢?所以还要备份一个源数据
  2. 遍历向后查找,找到返回这个节点。
  3. 这个查找代码就是向后遍历,如果相等就返回,否则返回NULL
SLNode* SLFind(SLNode* phead, SLDataType x)
{assert(phead);//等价与 phead != NULLSLNode* pcur = phead;//可能需要多次查找while (pcur)//结束调试是最后一个节点的后一个NULL,往下查找无意义{if (pcur->data == x){return pcur;//如果到了当前节点,就找到当前节点下面的值}pcur = pcur->next;//访问到当前节点指向的下一个,没有向下查找条件会死循环}return NULL;
}

9.指定位置之前插入

  1. insert 插入 after 在什么之后 prev 在什么之前,对数据增加和删除,就需要二级指针
  2. 链表的二级指针不能为空,而且链表也不能为空 因为要pos要通过链表找打指定位置,当然pos也不能为NULL,在NULL的位置插入吗
  3. prev需要找到pos之前的位置while(prev->next ! = 0),没有找到就继续往下走
  4. prev 和 pos都找到了,那么要在两者之前插入
  5. 注意:关于pos参数,是Find查找到前的,因为find是查找函数,找到指定位置然后返回
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{assert(pphead && *pphead);assert(pos);//不能为空,为你还找啥呢?if (pos == *pphead){SLPushFront(pphead, x);}else{SLNode* prev = *pphead;//prev 表示 pos的前一个节点SLNode* newnode = SLBuyNode(x);while (prev->next != pos)//如过指向的下一个节点不是pos,则继续往下找{prev = prev->next;}newnode->next = pos;prev->next = newnode;}
}

10.在指定位置之后插入数据

  1. 从图中可以看出正确的方式是的二种,第一种导致找不到pos->next的节点
  2. 在指定位置之后不需要第一个有效节点,用不到第一个节点,通过pos就可以找到后面一个节点
//在指定位置之后插入
void SLInsetAfter(SLNode* pos, SLDataType x)
{assert(pos);//指定为空,还怎么删除SLNode* newnode = SLBuyNode(x);newnode->next = pos->next;pos->next = newnode;//pos->next 原先指向的数据已经给到newnode->next
}

11.pos位置删除

  1. 首先就是要断言,传过来的二级指针里面装着的链表,当然都不能为空,pos节点为空了就说明指定位置没有该数据
  2. 情况1:pos不是第一个有效节点的位置
  3. 情况2:pos是第一个有效节点的位置
//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{assert(pphead && *pphead);assert(pos);//两种情况:1.pos节点没有指向第一个有效节点,反之就是指向了if (pos == *pphead){//相当于头删了SLDelFront(pphead);}else{SLNode* prev = *pphead;while (prev->next != pos)//他指向的节点,等于了pos就相当于找到了前一个节点{prev = prev->next;//通过当前的找到下一个,直到找到pos}//prev -> pos -> pos->nextprev->next = pos->next;free(pos);pos = NULL;}
}

12.pos之后的节点删除

  1. pos和pos->next,的节点都不能为NULL,下一个节点为NULL你删除什么?
  2. 在删除pos之后的节点之前,我们要先保存pos->next这个节点,防止内存泄漏,创建一个临时的del 变量来保存
//删除pos节点之后的
void SLEraseAfter(SLNode* pos)
{assert(pos && pos->next);//都不能为空SLNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}

13.销毁节点

  1. 销毁节点需要一个一个的销毁
  2. 大致的思路是
    1. 先保存下一个节点的地址,然后再释放这个节点
    2. 释放完后,把下一个节点的地址给pcur,从而构成循环
    3. 直到为NULL为止
//销毁链表
void SLDesTroy(SLNode** pphead)
{SLNode* pcur = *pphead;while (pcur){SLNode* del = pcur->next;//在销毁之前保存下一个节点的地址free(pcur);pcur = del;}pcur = NULL;
}

4.最后的完整代码

4.1头文件部分

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int SLDataType;
typedef struct SListNode
{SLDataType val;struct SListNode* next;
}SLNode;//链表打印
void SLPrint(SLNode* phead);//链表尾插和头插
void SLPushBack(SLNode** pphead, SLDataType x);
void SLPushFront(SLNode** pphead, SLDataType x);//链表头删和尾删
void SLDelBack(SLNode** pphead);
void SLDelFront(SLNode** pphead);//链表查找
SLNode* SLFind(SLNode* phead, SLDataType x);//链表pos之前插入
void SLInset(SLNode** pphead, SLNode* pos, SLDataType x);//链表pos之后插入
void SLInsetAfter(SLNode* pos, SLDataType x);//链表pos节点删除
void SLErase(SLNode** pphead,SLNode* pos);//链表pos之后删除
void SLEraseAfter(SLNode* pos);//链表的销毁
void SLDesTroy(SLNode* pphead);

4.2方法部分

#include "SList.h"
void SLPrint(SLNode* phead)
{SLNode* pcur = phead;while (pcur)//如果是pcur->next为结束条件会导致,提前结束不进入循环{printf("%d->", pcur->val);pcur = pcur->next;}printf("NULL\n");
}
//新节点空间
SLNode* SLBuyNode(SLDataType x)
{SLNode* Newnode = (SLNode*)malloc(sizeof(SLNode));if (Newnode == NULL){perror("malloc new");exit(1);//异常返回退出整个工程}Newnode->val = x;Newnode->next = NULL;return Newnode;
}
//尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{assert(pphead);SLNode* newnode = SLBuyNode(x);//空链表和非空链表if (*pphead == NULL){*pphead = newnode;}else{SLNode* ptail = *pphead;while (ptail->next != NULL){ptail = ptail->next;}ptail->next = newnode;}
}
//头插
void SLPushFront(SLNode** pphead, SLDataType x)
{assert(pphead);SLNode* newnode = SLBuyNode(x);newnode->next = *pphead;*pphead = newnode;//直接把第一个节点的位置给newnode,成为新的头结点
}//尾删
void SLDelBack(SLNode** pphead)
{assert(pphead && *pphead);//一个节点的情况if ((*pphead)->next == NULL){free(*pphead);*pphead = NULL;}else{SLNode* prev = *pphead;//保留的前一个节点SLNode* ptail = *pphead;while (ptail->next != NULL)//跳出循环就表示找到前一个节点了{prev = ptail;//保留了结束前的前一个地址ptail = ptail->next;}free(ptail);ptail = NULL;prev->next = NULL;//指向的下一个节点为NU}
}//头删
void SLDelFront(SLNode** pphead)
{assert(pphead && *pphead);SLNode* pcur = (*pphead)->next;free(*pphead);*pphead = pcur;
}//查找
SLNode* SLFind(SLNode* phead, SLDataType x)
{assert(phead);SLNode* pcur = phead;while (pcur){if (pcur->val == x)return pcur;pcur = pcur->next;}return NULL;
}//pos位置之前插入
void SLInset(SLNode** pphead, SLNode* pos, SLDataType x)
{assert(pphead && *pphead);assert(pos);SLNode* newnode = SLBuyNode(x);if (*pphead == pos)//因为下面一种会找不到{SLPushFront(pphead, x);//这里要传二级指针因为,要对链表进行改变}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}//perv posnewnode->next = pos;prev->next = newnode;}}
//pos之后插入
void SLInsetAfter(SLNode* pos, SLDataType x)
{assert(pos);SLNode* newnode = SLBuyNode(x);newnode->next = pos->next;pos->next = newnode;
}//pos位置处删除
void SLErase(SLNode** pphead, SLNode* pos)
{assert(pos);if (*pphead == pos)//调用头删{SLDelFront(pphead);}else{SLNode* prev = *pphead;while (prev->next != pos){prev = prev->next;}prev->next = pos->next;free(pos);pos = NULL;}
}//pos之后删除
void SLEraseAfter(SLNode* pos)
{assert(pos && pos->next);//pos后面的一个节点也不能为NULLSLNode* del = pos->next;pos->next = del->next;free(del);del = NULL;
}//销毁链表
void SLDesTroy(SLNode** pphead)
{SLNode* pcur = *pphead;while (pcur){SLNode* del = pcur->next;//在销毁之前保存下一个节点的地址free(pcur);pcur = del;}pcur = NULL;
}

4.3测试部分

#include "SList.h"
SListTest1()
{SLNode* node = NULL;//新创建的节点要初始化//测试尾插//SLPushBack(&node, 2);//SLPushBack(&node, 3);//SLPushBack(&node, 4);//SLPrint(node);//测试头插SLPushFront(&node, 4);SLPushFront(&node, 3);SLPushFront(&node, 2);SLPushFront(&node, 1);SLPrint(node);测试尾删//SLDelBack(&node);//SLDelBack(&node);//SLDelBack(&node);//SLDelBack(&node);//SLPrint(node);测试头删//SLDelFront(&node);//SLDelFront(&node);//SLDelFront(&node);//SLDelFront(&node);//SLDelFront(&node);//SLPrint(node);//测试查找SLNode* find = SLFind(node,3);if (find)//非0值,NULL其实原本意思也是0printf("找到了\n");elseprintf("没找到\n");//pos前插入//SLInset(&node, find, 11);//SLPrint(node);//pos后插入//SLInsetAfter(find, 22);//SLPrint(node);//删除pos位置//SLErase(&node, find);//SLPrint(node);//删除pos位置之后的SLEraseAfter(find);SLPrint(node);SLDesTroy(&node);
}
int main()
{SListTest1();return 0;
}

总结:

  1. 做代码题的时候,把可能发生情况按照步骤写清楚,分为几点等等
  2. 最好把每个情况里的推导步骤也要写清楚

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

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

相关文章

【随笔】Git 高级篇 -- 快速定位分支 ^|~(二十三)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

[面向对象] 单例模式与工厂模式

单例模式 是一种创建模式&#xff0c;保证一个类只有一个实例&#xff0c;且提供访问实例的全局节点。 工厂模式 面向对象其中的三大原则&#xff1a; 单一职责&#xff1a;一个类只有一个职责&#xff08;Game类负责什么时候创建英雄机&#xff0c;而不需要知道创建英雄机要…

SQL 注入之 Windows/Docker 环境 SQLi-labs 靶场搭建!

在安全测试领域&#xff0c;SQL注入是一种常见的攻击方式&#xff0c;通过应用程序的输入执行恶意SQL查询&#xff0c;从而绕过认证和授权&#xff0c;可以窃取、篡改或破坏数据库中的数据。作为安全测试学习者&#xff0c;如果你要练习SQL注入&#xff0c;在未授权情况下直接去…

DedeCMS 未授权远程命令执行漏洞分析

dedecms介绍 DedeCMS是国内专业的PHP网站内容管理系统-织梦内容管理系统&#xff0c;采用XML名字空间风格核心模板&#xff1a;模板全部使用文件形式保存&#xff0c;对用户设计模板、网站升级转移均提供很大的便利&#xff0c;健壮的模板标签为站长DIY自己的网站提供了强有力…

【AIGC】本地部署通义千问 1.5 (PyTorch)

今天想分享一下 Qwen 1.5 官方用例的二次封装&#xff08; huggingface 说明页也有提供源码&#xff09;&#xff0c;其实没有太多的技术含量。主要是想记录一下如何从零开始在不使用第三方工具的前提下&#xff0c;以纯代码的方式本地部署一套大模型&#xff0c;相信这对于技术…

Springboot+Vue项目-基于Java+MySQL的旅游网站系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

JDX图片识别工具:扫描PDF文件批量改名的应用场景

业务场景 案例1&#xff1a;人事部人员扫描了几百份简历&#xff0c;保存为PDF格式&#xff0c;但是名字是一串数字&#xff0c;不好分辨&#xff0c;有没有能识别里面姓名内容并自动重命名文件的方法 案例2&#xff1a;财务人员有的批量导出很多PDF电子发票&#xff0c;导出…

MATLAB 构建协方差矩阵,解算特征值和特征向量(63)

MATLAB 局部点云构建协方差矩阵,解算特征值和特征向量(63) 一、算法介绍二、算法实现1.代码2.结果一、算法介绍 对于某片有待分析的点云,我们希望构建协方差矩阵,计算特征值和特征向量,这是很多算法必要的分析方法,这里提供完整的计算代码(验证正确) !!! 特别需要注意…

swiftui macOS实现加载本地html文件

import SwiftUI import WebKitstruct ContentView: View {var body: some View {VStack {Text("测试")HTMLView(htmlFileName: "localfile") // 假设你的本地 HTML 文件名为 index.html.frame(minWidth: 100, minHeight: 100) // 设置 HTMLView 的最小尺寸…

分享 GoLand 2024.1 激活的方案,支持JetBrains全家桶

大家好&#xff0c;欢迎来到金榜探云手&#xff01; GoLand 公司简介 JetBrains 是一家专注于开发工具的软件公司&#xff0c;总部位于捷克。他们以提供强大的集成开发环境&#xff08;IDE&#xff09;而闻名&#xff0c;如 IntelliJ IDEA、PyCharm、和 GoLand等。这些工具被广…

华为海思校园招聘-芯片-数字 IC 方向 题目分享——第二套

华为海思校园招聘-芯片-数字 IC 方向 题目分享(共9套&#xff0c;有答案和解析&#xff0c;答案非官方&#xff0c;未仔细校正&#xff0c;仅供参考&#xff09;——第二套&#xff08;共九套&#xff0c;每套四十个选择题&#xff09; 部分题目分享&#xff0c;完整版获取&am…

SQL注入sqli_libs靶场第一题

第一题 联合查询 1&#xff09;思路&#xff1a; 有回显值 1.判断有无注入点 2.猜解列名数量 3.判断回显点 4.利用注入点进行信息收集 爆用户权限&#xff0c;爆库&#xff0c;爆版本号 爆表&#xff0c;爆列&#xff0c;爆账号密码 2&#xff09;解题过程&#xff1…

Qt发布可执行exe

文章目录 方法步骤1&#xff1a;在release下编译程序。步骤2&#xff1a;windous R 输入CMD步骤3&#xff1a;切换到你的release目录下。步骤4&#xff1a;打开cmd运行windeployqt xx.exe Qt错误&#xff1a;windeployqt 不是内部或外部命令&#xff0c;也不是可运行的程序 或批…

购物车实现

目录 1.购物车常见的实现方式 2.购物车数据结构介绍 3.实例分析 1.controller层 2.service层 1.购物车常见的实现方式 方式一&#xff1a;存储到数据库 性能存在瓶颈方式二&#xff1a;前端本地存储 localstorage在浏览器中存储 key/value 对&#xff0c;没有过期时间。s…

自动化测试-web

一、自动化测试理论&#xff1a; UI: User Interface &#xff08;用户接口-用户界面&#xff09;&#xff0c;主要包括&#xff1a;app 和webUI自动化测试&#xff1a;使用工具或代码执行用例的过程什么样的项目适合做自动化&#xff1a; 需要回归测试项目&#xff08;甲方自…

CH254X 8051芯片手册介绍

1 8051CPU 8051是一种8位元的单芯片微控制器&#xff0c;属于MCS-51单芯片的一种&#xff0c;由英特尔(Intel)公司于1981年制造。Intel公司将MCS51的核心技术授权给了很多其它公司&#xff0c;所以有很多公司在做以8051为核心的单片机&#xff0c;如Atmel、飞利浦、深联华等公…

34-5 CSRF漏洞 - CSRF分类

环境准备:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客 1)GET 类型 传参: 参数连接在URL后面 POC构造及执行流程: 构造URL,诱导受害者访问点击利用利用标签进行攻击: 构造虚假URL,在链接上添加payload抓包获取数据包,通过CSRF POC…

JavaEE初阶Day 7:多线程(5)

目录 Day 7&#xff1a;多线程&#xff08;5&#xff09;1. 死锁2. 死锁场景3. 场景二&#xff1a;两个线程&#xff0c;两把锁4. 场景三&#xff1a;N个线程&#xff0c;M把锁5. 避免死锁问题6. 内存可见性问题 Day 7&#xff1a;多线程&#xff08;5&#xff09; 回顾synchr…

grpc-教程(golang版)

目录 一、介绍 二、环境准备 三、Golang中使用grpc 1.编写protobuf文件 2.服务端 3.客户端 四、proto文件详解 1.proto语法 2.数据类型 基本数据类型 数组类型 map类型 嵌套类型 编写风格 3.多服务 4.多个proto文件 五、流式传输 1.普通rpc 2.服务器流式 …

03-JAVA设计模式-责任链模式

责任链模式 什么是责任链模式 责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为设计模式&#xff0c;允许你将请求沿着处理者链进行传递。每个处理者均对请求进行某些处理&#xff0c;并可决定是否将请求沿着链传递下去。这种模式给予请求的处理…