当前位置: 首页 > article >正文

C语言——链表

大神文献:https://blog.csdn.net/weixin_73588765/article/details/128356985

目录

一、链表概念

1. 什么是链表?

1.1 链表的构成

2. 链表和数组的区别

数组的特点:

链表的特点:               

二者对比:

二、链表静态添加和遍历

三、统计链表节点个数、链表查询及修改节点

四、在指定节点插入新的节点

1.在指定节点后插入新的节点

2.在指定节点的前方插入新节点

1.第一个节点之前插入新的节点;

2.在中间的节点插入新的节点;

完整代码:

五、删除指定节点

1.删除第一个节点

2.删除中间的节点

完整代码:

六、动态创建节点 

头插法

尾插法


一、链表概念

1. 什么是链表?

链表是一种数据结构,是一种数据存放的思想;

链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。

1.1 链表的构成

构成:链表由一个个结点组成,每个结点包含两个部分:数据域 和 指针域。

  • 数据域(data field):每个结点中存储的数据。

  • 指针域(pointer field):每个结点中存储下一个结点的地址。

2. 链表和数组的区别

数组的特点:

  • 数组中的每一个元素都属于同一数据类型的;
  • 数组是一组有序数据的集合;
  • 数组是在内存中开辟一段连续的地址空间用来存放一组数据,可以用数组名加下标来访问数组中的元素; 

链表的特点:               

  • 动态地进行存储分配的一种结构;
  • 链表中的各节点在内存中的地址都是不连续的;
  • 链表是由一个个节点组成,像一条链子一样;
  • 链表中的节点一般包括两个部分:(1)用户要用的数据(2)下一个节点的地址;          

二者对比:

一个数组只能存放同一种类型的数据,而链表中就可以存放不同的数据类型;
数组中的元素地址是连续的,想删除或添加一个新的元素,十分的麻烦不灵活,而且用数组存放数据是都要先定义好数组的大小(即元素的个数),如果在定义数组时,定义小了,内存不够用,定义大了,显然会浪费内存;

链表就可以很好的解决这些问题,链表中每一项都是一个结构体,链表中各节点在内存中的地址可以是不连续的,所以你想删除或添加一个新的节点很简单和方便,直接把节点中存放的的地址拿去修改就ok了(具体怎么添加或删除放在后用代码详细讲)。因为链表是一种动态结构,所以链表在建立的时候并不用像数组一样需要提前定义大小和位置(具体怎么创建也放在后面用代码详细讲)。

二、链表静态添加和遍历

思路:

静态创建的链表节点,都是不同内存地址,是不连续的。

所以我们要在每个节点的指针域中,存储下一个节点的地址,如上图:

  • 节点 1 的next(指针域),存储的是节点 2 的地址
  • 节点 2 的next(指针域),存储的是节点 3 的地址
  • 节点 3 的next(指针域),存储的是节点 4 的地址
  • 节点 4 的next(指针域),存储的是节点 5 的地址

通过这样的操作,就可以把这 5 个节点连接在一起。

#include <stdio.h>struct Test
{int data;struct Test *next;
};// 打印链表(遍历链表)
void printfLink(struct Test *p) // 当前 p 存储的是 t1 的地址,也就是链表头
{while ( p != NULL ) // p 现是链表头节点,通过循环移动到下一个节点,直到 NULL {printf("%d ",p->data); // 输出当前节点的 data 值p = p->next; // 使 p 移动至下一个节点}putchar('\n');
}int main()
{// 创建节点struct Test t1 = {1, NULL}; // t1.data赋值为1,t1.next赋值为NULLstruct Test t2 = {2, NULL};struct Test t3 = {3, NULL};struct Test t4 = {4, NULL};struct Test t5 = {5, NULL};// 链接节点t1.next = &t2; // t1.next存储t2的地址,使t1.next指向t2这个结构体变量t2.next = &t3;t3.next = &t4;t4.next = &t5;// 打印链表printfLink(&t1); // 将 t1(链表头)的地址传递给printfLink函数的结构体指针变量 preturn 0;
}

三、统计链表节点个数、链表查询及修改节点

#include <stdio.h>struct Test
{int data;struct Test *next;
};// 打印链表
void printfLink(struct Test *p) // 当前 p 存储的是 t1 的地址,也就是链表头
{while ( p != NULL ) // p 现是链表头节点,通过循环移动到下一个节点,直到 NULL {printf("%d ",p->data); // 输出当前节点的 data 值p = p->next; // 使 p 移动至下一个节点}putchar('\n');
}// 统计链表个数
int statisticsNode(struct Test *head) // 当前 head 存储的是 t1 的地址,也就是链表头
{int cnt = 0; // 计数器,统计节点个数// 遍历链表,直到 head == NULLwhile ( head != NULL ){cnt++; // 记录每一个节点head = head->next; // 使 head 移动至下一个节点}return cnt; // 返回节点个数
}// 查询链表
int seekNode(struct Test *head, int data) // 当前 p 存储的是 t1 的地址,也就是链表头。data:我们需要查询的节点
{struct Test *p = head; // 备份头节点地址// 遍历链表,直到 p == NULLwhile ( p != NULL ){// 判断每个节点的数据域(p->data) 是否等于 我们需要查询的节点(data)if( p->data == data ){return 1; // 查询到,返回 1}p = p->next; // 使 p 移动至下一个节点}return -1;// 查不到,返回 -1
}// 修改指定节点
int modifyNode(struct Test *head, int data) // 当前 p 存储的是 t1 的地址,也就是链表头。data:我们需要修改的节点
{struct Test *p = head; // 备份链表头// 遍历链表while ( p != NULL ){// 判断每个节点的数据域(p->data) 是否等于 我们需要修改的节点(data)if( p->data == data ){// 找了,将这个节点的原数据域的数据,修改为100p->data = 100;return 1; // 返回 1,表示修改成功}p = p->next; // 使 p 移动至下一个节点}return -1; // 返回 -1,找不到这个节点
}int main()
{// 创建节点struct Test t1 = {1, NULL}; // t1.data赋值为1,t1.next赋值为NULLstruct Test t2 = {2, NULL};struct Test t3 = {3, NULL};struct Test t4 = {4, NULL};struct Test t5 = {5, NULL};// 链接节点t1.next = &t2; // t1.next存储t2的地址,使t1.next指向t2这个结构体变量t2.next = &t3;t3.next = &t4;t4.next = &t5;// 打印链表printfLink(&t1);// 统计链表个数int ret = statisticsNode(&t1);printf("链表个数:%d\n", ret);// 查询链表int seekNodeData = 3; // 需要查询的节点ret = seekNode(&t1, seekNodeData); // 将 t1 的地址和需要查询的节点,传递至 seekNode 函数中if( ret == 1 ) // 判断返回值是否为1,如 1 表示找到了,非 1 表示找不到{printf("需查询的值:%d,查询结果:%d\n", seekNodeData, ret);}else{printf("需查询的值:%d,查询结果:%d\n", seekNodeData, ret);}// 修改指定节点int modifyNodeData = 5; // 需要修改的节点printf("修改之前的链表:");printfLink(&t1);ret = modifyNode(&t1, modifyNodeData); // 将 t1 的地址和需要修改的节点,传递至 modifyNode 函数中printf("修改之后的链表:");printfLink(&t1);return 0;
}

四、在指定节点插入新的节点

插入一个新节点有两种方法:        

  1.  在指定节点后插入新的节点
  2.  在指定节点前插入新的节点

1.在指定节点后插入新的节点

如上图,在节点 2 的后方插入新的节点:

  1.  通过循环,遍历到指定的节点
  2. 让新节点的下一个节点,连接到节点 3
    new->next = p->next
  3. 使指定节点的下一个节点,连接到新节点
    p->next = new;
#include <stdio.h>struct Test
{int data;struct Test *next;
};// 打印链表
void printfLink(struct Test *p) // 当前 p 存储的是 t1 的地址,也就是链表头
{while ( p != NULL ) // p 现是链表头节点,通过循环移动到下一个节点,直到 NULL {printf("%d ",p->data); // 输出当前节点的 data 值p = p->next; // 使 p 移动至下一个节点}putchar('\n');
}// 在指定节点后方插入新节点
void afterInsertionNode(struct Test **head, int appointNode, struct Test *new)
{// 备份链表头地址struct Test *p = *head;// 遍历链表while ( p != NULL ){// 判断当前节点是否等于目标节点if( p->data == appointNode ){new->next = p->next; // 让新节点的下一个节点存储,原节点的下一个节点的地址p->next = new; // 让当前节点指向新节点return; // 找到之后直接返回}p = p->next; // 让当前节点移动到下一个节点}printf("没有找到目标节点,插入失败!\n");
}int main()
{// 创建节点struct Test t1 = {1, NULL}; // t1.data赋值为1,t1.next赋值为NULLstruct Test t2 = {2, NULL};struct Test t3 = {3, NULL};struct Test t4 = {4, NULL};struct Test t5 = {5, NULL};// 创建链表头struct Test *head = NULL;// 定义新节点并赋初值struct Test new = {100,NULL};// 链接节点head = &t1; // 头节点head,存储结构体变量 t1 的地址t1.next = &t2; // t1.next存储t2的地址,使t1.next指向t2这个结构体变量t2.next = &t3;t3.next = &t4;t4.next = &t5;// 打印链表printf("输出插入之前的链表:\n");printfLink(head);// 在指定节点后方插入新节点afterInsertionNode(&head, 2,&new);printf("输出插入之后的链表:\n");printfLink(head);return 0;
}

2.在指定节点的前方插入新节点

有两种情况:

  • 1.第一个节点之前插入新的节点;
  • 2.在中间的节点插入新的节点;

1.第一个节点之前插入新的节点;

 

如上图,在指定节点的节点 1,之前插入的新节点:

  1. 遍历,判断节点是否为指定节点
  2. 新节点的下一个,指向节点1的地址
    new->next = p;
  3. 因为此时新节点变成了头节点,所以此时将new的地址赋值给head
    head = new;
void forwardInsertionNode(struct Test **head, int appointNode, struct Test *new)
{struct Test *p = *head; // 备份链表头的地址// 判断第一个节点的data,是否等于目标节点if( p->data == appointNode ){// 将新节点的下一个节点指向,p的地址,此时new节点变成了链表头new->next = p;// 更新链表头的指向,使*head指向new的地址,让*head重新变成链表头*head = new;return;}
}

2.在中间的节点插入新的节点;

    如上图,如果指定节点是5,之前插入新的节点:

    思路:

    按照之前的后面插入新节点的方法,当我们遍历到指定节点 5 的时候,如果将new的下一个节点,指向目标节点,是可以连接上的,但是new的节点如果访问到指定节点的上一个节点呢?这个时候很难找到目标节点的上一个节点的地址。
    可以这么做,我们要在目标节点 5 之前插入一个新节点,比如说:现在 p 指向的是节点 4 ,节点 4 的下一个节点是目标节点 5 。那节点 4 ->next,不就是目标节点 5 吗?,节点 4 ->next->data,不就是节点 5 的data?然后将new->next指向目标节点 5 的地址,节点 4->next 指向new的地址,不就连上了。

        // 判断当前节点的下一个节点,是否为NULLwhile ( p->next != NULL ){// 判断当前节点的下一个节点的data,是否等于目标节点if( p->next->data == appointNode ){// 将new的下一个节点,指向原当前节点的下一个节点new->next = p->next;// 将当前节点的下一个节点指向newp->next = new;return;}p = p->next; // 偏移到下一个节点}

    完整代码:

    #include <stdio.h>struct Test
    {int data;struct Test *next;
    };// 打印链表
    void printfLink(struct Test *p) // 当前 p 存储的是 t1 的地址,也就是链表头
    {while ( p != NULL ) // p 现是链表头节点,通过循环移动到下一个节点,直到 NULL {printf("%d ",p->data); // 输出当前节点的 data 值p = p->next; // 使 p 移动至下一个节点}putchar('\n');
    }// 在指定节点前方插入新节点
    void forwardInsertionNode(struct Test **head, int appointNode, struct Test *new)
    {struct Test *p = *head; // 备份链表的地址,// *head是一个二级指针,保存的是main函数t1的地址,是链表的头地址// 除非链表头发生改变,否则不要更改链表头的地址// 判断目标节点是否为链表的第一个节点if( p->data == appointNode ){new->next = p; // 将新节点的下一个节点指向,p的地址,此时new节点变成了链表头*head = new; // 更新链表头的指向,使*head指向new的地址,让*head重新变成链表头return; }// 判断当前节点的下一个节点,是否为NULLwhile ( p->next != NULL ){// 判断当前节点的下一个节点的data,是否等于目标节点if( p->next->data == appointNode ){// 将new的下一个节点,指向原当前节点的下一个节点new->next = p->next;// 将当前节点的下一个节点指向newp->next = new;return;}p = p->next; // 偏移到下一个节点}
    }int main()
    {// 创建节点struct Test t1 = {1, NULL}; // t1.data赋值为1,t1.next赋值为NULLstruct Test t2 = {2, NULL};struct Test t3 = {3, NULL};struct Test t4 = {4, NULL};struct Test t5 = {5, NULL};// 创建链表头struct Test *head = NULL;// 定义新节点并赋初值struct Test new = {100,NULL};// 链接节点head = &t1; // 头节点head,存储结构体变量 t1 的地址t1.next = &t2; // t1.next存储t2的地址,使t1.next指向t2这个结构体变量t2.next = &t3;t3.next = &t4;t4.next = &t5;// 打印链表printf("输出插入之前的链表:\n");printfLink(head);forwardInsertionNode(&head, 5,&new);printf("输出插入之后的链表:\n");printfLink(head);return 0;
    }

    五、删除指定节点

    有两种情况:

    1. 删除第一个节点
    2. 删除中间的节点

    1.删除第一个节点

    思路:

    head指向的是第一个节点,如果我需要删除第一个节点,需要free()释放内存,此时应当将head指向第二个节点。

        struct Test *p = *head; // 备份链表头的地址// 判断链表第一个节点的data,是否与目标节点相等if( p->data == appointNode ){// 将链表头指向第二个节点的地址*head = p->next;return;}

    2.删除中间的节点

    思路:

    如果我们删除的是节点 3,那么节点 2 应该绕过节点 3,使节点 2 连接节点 4

        // 判断当前节点的下一个节点是否为NULLwhile ( p->next != NULL ){// 判断当前节点的下一个节点的data,是否等于目标节点if( p->next->data == appointNode ){// 当前节点的下一个,指向当前节点的下一个节点的下一个节点p->next = p->next->next;return;}p = p->next; // 将当前节点,移动到下一个节点}

    完整代码:

    #include <stdio.h>struct Test
    {int data;struct Test *next;
    };// 打印链表
    void printfLink(struct Test *p) // 当前 p 存储的是 t1 的地址,也就是链表头
    {while ( p != NULL ) // p 现是链表头节点,通过循环移动到下一个节点,直到 NULL {printf("%d ",p->data); // 输出当前节点的 data 值p = p->next; // 使 p 移动至下一个节点}putchar('\n');
    }// 删除节点
    void delectNode(struct Test **head, int appointNode)
    {struct Test *p = *head; // 备份链表头的地址// 判断链表第一个节点的data,是否与目标节点相等if( p->data == appointNode ){// 将链表头指向第二个节点的地址*head = p->next;return;}// 判断当前节点的下一个节点是否为NULLwhile ( p->next != NULL ){// 判断当前节点的下一个节点的data,是否等于目标节点if( p->next->data == appointNode ){// 当前节点的下一个,指向当前节点的下一个节点的下一个节点p->next = p->next->next;return;}p = p->next; // 将当前节点,移动到下一个节点}
    }int main()
    {// 创建节点struct Test t1 = {1, NULL}; // t1.data赋值为1,t1.next赋值为NULLstruct Test t2 = {2, NULL};struct Test t3 = {3, NULL};struct Test t4 = {4, NULL};struct Test t5 = {5, NULL};// 创建链表头struct Test *head = NULL;// 链接节点head = &t1; // 头节点head,存储结构体变量 t1 的地址t1.next = &t2; // t1.next存储t2的地址,使t1.next指向t2这个结构体变量t2.next = &t3;t3.next = &t4;t4.next = &t5;// 打印链表printf("输出删除之前的链表:\n");printfLink(head);delectNode(&head, 4);printf("输出删除之后的链表:\n");printfLink(head);return 0;
    }

    六、动态创建节点 

    头插法

    如果链条为空,创建的第一个节点为链表头,然后每一次创建的新节点插在之前的链表头之前,再让新节点做为新的链表头;

    #include <stdio.h>
    #include <stdlib.h>struct Test 
    {int data;struct Test *next;
    };// 头插法
    struct Test* insertionHead(struct Test *head, struct Test *new)
    {// 如果head(头节点)是NULLif( head == NULL ){// 让head指向newhead = new;}else{// 如果head(头节点)不是NULL,那么新节点指向head,此时new为新的链表头new->next = head;// 让head指向new,让head重新成为链表头head = new;}return head; // 返回链表头的地址
    }// 动态创建链表节点
    void createNode(struct Test **head)
    {struct Test *new = NULL;while(1){// 开辟内存空间new = (struct Test*)malloc( sizeof(struct Test) );// 判断是否开辟成功if( new == NULL ){printf("malloc error\n");exit(-1);}// 将new的下一个节点指向NULLnew->next = NULL;printf("为新节点的数据域赋值,如果输入0,表示退出\n");scanf("%d", &(new->data));// 判断输入的是否为 0if( new->data == 0 ){printf("输入0,quit\n");free(new); // 释放指针new = NULL; // 避免悬空指针return; }// 重新获取链表头的地址*head = insertionHead(*head,new);}
    }// 打印链表
    void printfLink(struct Test *head)
    {struct Test *p = head;while( p != NULL ){printf("%d ", p->data);p = p->next;}putchar('\n');
    }int main()
    {struct Test *head = NULL;createNode(&head);printfLink(head);return 0;
    }

    尾插法

    如果链表为空,创建的第一个节点做为链表头,然后每一次创建的新节点插在链表最后一个节点的指针域(next)中;

    #include <stdio.h>
    #include <stdlib.h>// 定义链表节点结构体
    struct Test
    {int data;           // 数据域struct Test *next;  // 指针域,指向下一个节点
    };// 在链表尾部插入新节点
    struct Test* insertTail(struct Test *head, struct Test *new)
    {struct Test *p = head;if (head == NULL)  // 如果链表为空,新节点即为头节点{head = new;}else{// 遍历链表,找到最后一个节点while (p->next != NULL){p = p->next;}// 将新节点插入到链表尾部p->next = new;}return head;  // 返回链表头节点
    }// 创建链表节点
    void createNode(struct Test **head)
    {struct Test *new = NULL;while (1){// 开辟内存空间,创建一个新节点new = (struct Test*)malloc(sizeof(struct Test));if (new == NULL)  // 检查内存分配是否成功{printf("malloc error\n");exit(-1);  // 内存分配失败,退出程序}new->next = NULL;  // 初始化新节点的指针域为NULL// 为新节点的数据域赋值printf("为新节点的数据域赋值,输入0,退出\n");scanf("%d", &(new->data));if (new->data == 0)  // 如果输入0,则退出循环{free(new);  // 释放内存new = NULL;  // 避免指针悬空return;}// 将新节点插入链表尾部*head = insertTail(*head, new);}
    }// 打印链表
    void printfLink(struct Test *head)
    {while (head != NULL){printf("%d ", head->data);  // 打印当前节点的数据head = head->next;  // 移动到下一个节点}putchar('\n');  // 打印换行符
    }int main()
    {struct Test *head = NULL;  // 初始化链表头节点为NULLcreateNode(&head);  // 创建链表printfLink(head);   // 打印链表return 0;
    }

    相关文章:

    C语言——链表

    大神文献&#xff1a;https://blog.csdn.net/weixin_73588765/article/details/128356985 目录 一、链表概念 1. 什么是链表&#xff1f; 1.1 链表的构成 2. 链表和数组的区别 数组的特点&#xff1a; 链表的特点&#xff1a; 二者对比&#xff1a; 二…...

    使用免费IP数据库离线查询IP归属地

    一、准备工作 1.下载免费IP数据库 首先&#xff0c;访问 MaxMind官网&#xff08;https://www.maxmind.com/en/home&#xff09;如果你还没有MaxMind账号&#xff0c;可以通过此链接地址&#xff08;https://www.maxmind.com/en/geolite2/signup&#xff09;进行账号注册&…...

    MySQL(单表)知识点

    文章目录 1.数据库的概念2.下载并配置MySQL2.1初始化MySQL的数据2.2注册MYSQL服务2.3启动MYSQL服务2.4修改账户默认密码2.5登录MYSQL2.6卸载MYSQL 3.MYSQL数据模型3.1连接数据库 4.SQL简介4.1SQL的通用语法4.2SQL语句的分类4.3DDL语句4.3.1数据库4.3.2表(创建,查询,修改,删除)4…...

    1.15-16-17-18迭代器与生成器,函数,数据结构,模块

    目录 15&#xff0c;Python3 迭代器与生成器15-1 迭代器15-1-1 基础知识15-1-2 迭代器与for循环工作原理 15-2 生成器&#xff08;本质就是迭代器&#xff09;15-2-1 yield 表达式15-2-2 三元表达式15-2-3 列表生成式15-2-4 其他生成器&#xff08;——没有元祖生成式——&…...

    window下的docker内使用gpu

    Windows 上使用 Docker GPU需要进行一系列的配置和步骤。这是因为 Docker 在 Windows 上的运行环境与 Linux 有所不同,需要借助 WSL 2(Windows Subsystem for Linux 2)和 NVIDIA Container Toolkit 来实现 GPU 的支持。以下是详细的流程: 一、环境准备 1.系统要求 Window…...

    CVE-2025-0392:JeeWMS graphReportController.do接口SQL注入漏洞复现

    文章目录 CVE-2025-0392:JeeWMS graphReportController.do接口SQL注入漏洞复现0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.构造POC2.复现CVE-2025-0392:JeeWMS graphReportController.do接口SQL注入漏洞复现 0x01 前言 免责声明:请勿利用文章内的相…...

    DR和BDR的选举规则

    在 OSPF&#xff08;开放最短路径优先&#xff09;协议中&#xff0c;DR&#xff08;Designated Router&#xff0c;指定路由器&#xff09; 和 BDR&#xff08;Backup Designated Router&#xff0c;备份指定路由器&#xff09; 的选举是为了在广播型网络&#xff08;如以太网…...

    Java 大视界 -- Java 大数据在智能家居能源管理与节能优化中的应用(120)

    &#x1f496;亲爱的朋友们&#xff0c;热烈欢迎来到 青云交的博客&#xff01;能与诸位在此相逢&#xff0c;我倍感荣幸。在这飞速更迭的时代&#xff0c;我们都渴望一方心灵净土&#xff0c;而 我的博客 正是这样温暖的所在。这里为你呈上趣味与实用兼具的知识&#xff0c;也…...

    第七课:Python反爬攻防战:Headers/IP代理与验证码

    在爬虫开发过程中&#xff0c;反爬虫机制成为了我们必须面对的挑战。本文将深入探讨Python爬虫中常见的反爬机制&#xff0c;并详细解析如何通过随机User-Agent生成、代理IP池搭建以及验证码识别来应对这些反爬策略。文章将包含完整的示例代码&#xff0c;帮助读者更好地理解和…...

    MySql的安装及数据库的基本操作命令

    1.MySQL的安装 1.1进入MySQL官方网站 1.2点击下载 1.3下拉选择MySQL社区版 1.4选择你需要下载的版本及其安装的系统和下载方式 直接安装以及压缩包 建议选择8.4.4LST LST表明此版本为长期支持版 新手建议选择红框勾选的安装方式 1.5 安装包下载完毕之后点击安装 2.数据库…...

    VsCode导入时选择相对路径

    自动导入时总是以db://开头了&#xff0c;而我们通常需要的是相对路径&#xff0c;对VsCode进行如下设置&#xff1a; 打开 VSCode 设置&#xff1a; 使用快捷键 Ctrl ,&#xff08;Windows/Linux&#xff09;或 Cmd ,&#xff08;Mac&#xff09;。 或者在菜单栏中选择 …...

    计算机视觉|3D卷积网络VoxelNet:点云检测的革新力量

    一、引言 在科技快速发展的背景下&#xff0c;3D 目标检测技术在自动驾驶和机器人领域中具有重要作用。 在自动驾驶领域&#xff0c;车辆需实时、准确感知周围环境中的目标物体&#xff0c;如行人、车辆、交通标志和障碍物等。只有精确检测这些目标的位置、姿态和类别&#x…...

    创新监管,保障生产安全

    在现代工业生产中&#xff0c;电气焊作业是不可或缺的一环&#xff0c;但同时也伴随着一定的安全风险。为了提高焊接作业的安全性&#xff0c;迪格特电子科技有限公司开发了电气焊安全作业管理平台&#xff0c;该平台通过智能化监管系统&#xff0c;实现了对焊机联网的全面监管…...

    深入解析 C# 中的泛型:概念、用法与最佳实践

    C# 中的 泛型&#xff08;Generics&#xff09; 是一种强大的编程特性&#xff0c;允许开发者在不预先指定具体数据类型的情况下编写代码。通过泛型&#xff0c;C# 能够让我们编写更灵活、可重用、类型安全且性能优良的代码。泛型广泛应用于类、方法、接口、委托、集合等多个方…...

    AI数字人源码开发---SaaS化源码部署+PC+小程序一体化

    #数字人#数字人分身#123数字人#数字人分身源码部署搭建 AI数字人源码开发步骤 确定功能需求&#xff1a;首先确定需要实现的功能和特性&#xff0c;包括语音识别、自然语言处理、人脸识别等功能。这些功能将构成AI数字人的核心功能。 开发AI数字人源码&#xff1a;使用合适的…...

    Mysql-经典故障案例(1)-主从同步由于主键问题引发的故障

    故障报错 Could not execute Write_rows event on table test.users; Duplicate entry 3 for key PRIMARY, Error_code: 1062; handler error HA_ERR_FOUND_DUPP_KEY; the events master log mysql-bin.000031, end_log_pos 329 这是由于从库存在与主库相同主键值&#xff0c…...

    ElasticSearch 分词器介绍及测试:Standard(标准分词器)、English(英文分词器)、Chinese(中文分词器)、IK(IK 分词器)

    ElasticSearch 分词器介绍及测试&#xff1a;Standard&#xff08;标准分词器&#xff09;、English&#xff08;英文分词器&#xff09;、Chinese&#xff08;中文分词器&#xff09;、IK&#xff08;IK 分词器&#xff09; ElasticSearch 分词器介绍及测试1. Standard Analyz…...

    ​DeepSeek:如何通过自然语言生成HTML文件与原型图?

    在当今快节奏的开发与设计环境中&#xff0c;快速生成HTML文件或原型图是每个开发者与设计师的迫切需求。虽然DeepSeek无法直接生成图片&#xff0c;但它却能够通过自然语言生成流程图、原型图以及交互式页面&#xff0c;甚至可以直接输出HTML代码。本文将详细介绍如何与DeepSe…...

    【Redis】终极缓存四连杀:缓存预热、缓存击穿、缓存穿透、缓存雪崩,真的懂了吗?

    &#x1f3af; 前言 你有没有遇到过这种情况&#xff1a; 刚上线的新功能&#xff0c;所有用户一窝蜂冲进来&#xff0c;服务器被打爆&#xff1f;&#x1f680;&#xff08;缓存预热&#xff09;某个热点数据突然失效&#xff0c;数据库压力瞬间飙升&#xff0c;仿佛遭遇 DD…...

    Java Spring MVC (2)

    常见的Request Controller 和 Response Controller 的区别 用餐厅点餐来理解 想象你去一家餐厅吃饭&#xff1a; Request Controller&#xff08;接单员&#xff09;&#xff1a;负责处理你的点餐请求&#xff0c;记录你的口味、桌号等信息。Response Controller&#xff08…...

    Linux网络相关内容与端口

    网络相关命令 ping命令测试连接状态 wget命令&#xff1a;非交互式文件下载器&#xff0c;可以在命令行内下载网络文件 使用ctrlc可以中止下载 curl命令&#xff1a;可以发送http网络请求&#xff0c;用于文件下载、获取信息等 其实和浏览器打开网站一样&#xff0c;cu…...

    Spring Boot + MyBatis + MySQL:快速搭建CRUD应用

    一、引言 1. 项目背景与目标 在现代Web开发中&#xff0c;CRUD&#xff08;创建、读取、更新、删除&#xff09;操作是几乎所有应用程序的核心功能。本项目旨在通过Spring Boot、MyBatis和MySQL技术栈&#xff0c;快速搭建一个高效、简洁的CRUD应用。我们将从零开始&#xff…...

    日新F1、瑞研F600P 干线光纤熔接(熔接损耗最大0.03DB)

    Ⅰ. 设备特性对比与实测验证 1. 日新F1&#xff08;两马达&#xff09;极限参数 切割角度&#xff1a;必须≤0.3&#xff08;双边累计误差&#xff1c;0.6&#xff09; ▶ 实测案例&#xff1a;切割0.35时&#xff0c;损耗波动达0.05-0.08dB&#xff08;超干线标准&#xff09…...

    分布式网络

    分布式网络&#xff08;Distributed Network&#xff09;指的是一种计算机网络架构&#xff0c;其中计算资源&#xff08;计算、存储、数据处理等&#xff09;分布在多个物理或逻辑上的节点上&#xff0c;而不是集中在单一的服务器或数据中心中。这种架构的主要目标是提高系统的…...

    【招聘精英】

    我们公司是一个位于石家庄的一个科技型新型技术公司。主要做人力资源、用工、科技等方面。 有意向回石家庄的或者已经在石家庄的技术大咖、软件大牛、产品大佬、UI大神可以来了解一下。 现在招聘 高级前端开发 高级java开发 其他岗位也可以联系。 有意向的朋友可以私信我。 -…...

    ESP8266 NodeMCU 与 Atmega16 微控制器连接以发送电子邮件

    NodeMCU ESP8266 AVR 微控制器 ATmega16 的接口 Atmega16 是一款低成本的 8 位微控制器,比以前版本的微控制器具有更多的 GPIO。它具有所有常用的通信协议,如 UART、USART、SPI 和 I2C。由于其广泛的社区支持和简单性,它在机器人、汽车和自动化行业有广泛的应用。 Atmega1…...

    MongoDB用户管理和复制组

    用户管理 1、建用户时&#xff0c;use到的库就是此用户的验证库 2、登录时必须明确指定验证库才能登录 3、通常管理员用的验证库是admin&#xff0c;普通用户的验证库一般是所管理的库设置为验证库 4、如果直接登录到数据库&#xff0c;不进行use&#xff08;示例&#xff…...

    GoLang的select是什么?在什么时候场景下用

    在 Go 语言中&#xff0c;select 是专门用于处理通道&#xff08;Channel&#xff09;多路复用的关键字&#xff0c;它可以同时监听多个通道的读写操作&#xff0c;并根据就绪的通道执行对应的逻辑。以下是 select 的用法和典型应用场景&#xff1a; 一、基本用法 select 语法…...

    SQLAlchemy系列教程:集成Pydantic增强数据处理能力

    本教程介绍如何将Pydantic用于数据验证&#xff0c;SQLAlchemy用于数据库操作&#xff0c;从而通过强大的数据处理能力增强Python应用程序。 介绍 在现代web开发中&#xff0c;确保数据的有效性和完整性至关重要。Pydantic和SQLAlchemy是两个功能强大的Python库&#xff0c;可…...

    【数据结构初阶】---堆的实现、堆排序以及文件中的TopK问题

    1.树的概念及结构 1.1树的概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有一个特殊的结点&…...