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

【数据结构初阶】三、 线性表里的链表(无头+单向+非循环链表)

=========================================================================

相关代码gitee自取

C语言学习日记: 加油努力 (gitee.com)

 =========================================================================

接上期

【数据结构初阶】二、 线性表里的顺序表_高高的胖子的博客-CSDN博客

 =========================================================================

                   

引言

通过上期对顺序表介绍使用

我们可以知道顺序表有以下优点缺点:

            

顺序表优点

                  

  • 尾插尾删 操作足够快
                   
  • 能够使用下标随机访问修改,这是很方便

            

-----------------------------------------------------------------------------------------------

            

顺序表缺点

                  

  • 头部/中间插入删除操作较慢时间复杂度为O(N)
                              
  • 增容需要申请新空间拷贝数据释放旧空间。会有不小的消耗
                         
  • 增容一般呈2倍的增长势必会有一定的空间浪费
    例如:
    当前容量100,满了以后增容到 200,我们再继续插入了5个数据
    后面没有数据插入了,那么就浪费了95个数据空间

            

            

下面将要介绍和实现的链表不会出现这些问题

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

1 . 链表

(1). 链表的概念及结构:

           

链表的概念

链表是一种物理存储结构非连续非顺序存储结构

数据元素的逻辑顺序通过链表中的指针链接次序实现的,
单链表一般不会单独使用
只有头插头删实现简单效率高

              

链表的结构

  • 链表属于线性表线性表物理存储时,
    通常数组(顺序表)链式结构(链表)形式存储
    链式结构逻辑上连续的,但在物理上不一定连续
                     
  • 现实中的结点一般是从堆上申请出来的
                  
  • 从堆上申请的空间,是按照一定的策略来分配的,
    两次申请的空间可能连续,也可能不连续
图示:

                     


                    

(2). 链表的分类:

            

实际链表的结构非常多样
以下情况组合起来就有8种链表结构

           

单向 或 双向 链表

单向链表图示

                

双向链表图示

            

            

---------------------------------------------------------------------------------------------

            

带头(哨兵位) 或 不带头(哨兵位) 链表

带头链表图示

               

不带头链表图示

            

            

---------------------------------------------------------------------------------------------

            

循环 或 非循环 链表

循环链表图示

                  

非循环链表图示

            

            

虽然有这么多的链表的结构,
但是我们实际中最常用还是两种结构

无头单向非循环链表 和 带头双向循环链表

                     


                    

(3). 两种常用链表结构:

            

无头单向非循环链表

简介:

结构简单一般不会单独用来存数据

实际中更多是作为其他数据结构的子结构

哈希桶图的邻接表等等

另外这种结构在笔试面试中出现很多

          

图示:

            

            

---------------------------------------------------------------------------------------------

            

带头双向循环链表

简介:

结构最复杂一般用在单独存储数据

实际中使用的链表数据结构,都是带头双向循环链表

另外这个结构虽然结构复杂

但是使用代码实现以后会发现结构会带来很多优势实现反而简单

          

图示:

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

2 . 链表的实现
(无头+单向+非循环链表)

(详细解释在图片的注释中,代码分文件放最后)

                  

实现具体功能前的准备工作

  • 创建单链表数据类型 -- 链表结点中数据域里存储的数据的类型
                    
  • 创建单链表结点结构体(类型) -- 结点中应有 数据域 指针域
图示

            

            

---------------------------------------------------------------------------------------------

            

PrintSList函数 -- 遍历打印链表

  • 需要使用链表头指针phead为了后续使用不能改变头指针,所以要代替头指针
                     
  • 因为链表到最后结点里指针域的NULL(0x00000000)才结束
    所以可以使用while循环进行遍历并打印
                  
  • 最后提示已指向NULL指针链表遍历结束
图示

            

            

---------------------------------------------------------------------------------------------

            

SLTNode函数 -- 生成链表的结点

  • 开辟结点所需的动态空间,要注意一个结点大小看数据域和指针域的大小
               
  • 检查空间是否开辟成功
               
  • 将要放入结点数据域的数据放入
               
  • 设置新创建结点的指针域
               
  • 返回创建的新结点的指针(地址)
图示

测试 -- PrintSList、SLTNode函数

            

            

---------------------------------------------------------------------------------------------

            

SLTPushBack函数(难) -- 向链表尾部插入一个结点(尾插)

  • 因为要插入一个结点,所以先使用BuySListNode函数创建一个结点
               
  • 尾插时要判断链表是否已经有结点
    如果没有结点
    将新创建结点的地址赋给头指针
    因为是对指针本身进行操作,所以二级指针对头指针进行操作
               
  • 如果已经有了结点
    先创建一个指针替代头指针
    (这里是对头指针直线的结点结构体进行操作,所以用指针就够了不需要二级指针
    使用while循环获得最后一个结点的地址
    最后将尾插的结点连接起来
图示

(可以结合单链表物理图来理解

↓↓↓↓

测试 -- SLTPushBack函数

            

            

---------------------------------------------------------------------------------------------

            

SLTPushFront函数 -- 向链表头部插入一个结点(头插)

  • 因为要插入一个结点,所以先使用BuySListNode函数创建一个结点
               
  • 使用plist把原本头结点地址赋给新插入头结点指针域
               
  • 再把头指针改为指向新插入头结点
图示

测试 -- SLTPushFront函数

            

            

---------------------------------------------------------------------------------------------

            

SLTPopBack函数 -- 删除链表尾部结点(尾删)

  • 链表尾删考虑三种情况
    第一种情况链表为空,没有任何结点
    这种情况直接assert断言即可
               
  • 第二种情况链表只有一个结点
    这种情况要释放掉唯一的结点
               
  • 第三种情况链表有一个以上结点
    这种情况需要两个指针来进行操作
图示

测试 -- SLTPopBack函数

            

            

---------------------------------------------------------------------------------------------

            

SLTPopFront函数 -- 删除链表头部结点(头删)

  • 头删分两种情况就够了:空链表 非空链表
               
  • 空链表头删
    直接assert断言pass掉
    (莫名戳中笑点哈哈哈哈哈哈哈哈)
               
  • 非空链表头删
    通过plist头指针获得第二个结点地址,方便头删后让原本的第二个结点做新的头结点
    free释放掉头指针
    最后让plist头指针指向原本的第二个结点做新的头结点
图示

测试 -- SLTPopFront函数

            

            

---------------------------------------------------------------------------------------------

            

SLTFind函数 -- 查找指定值(x)所在结点的地址

  • 创建一个变量代替头指针
               
  • 使用while循环在链表中进行遍历查找指定值(x)
图示

测试 -- SLTFind函数

            

            

---------------------------------------------------------------------------------------------

            

SLTInsert函数 -- 在链表指定位置(pos)之前插入一个值(x)

  • 分三种情况讨论
    链表为空(空链表)在第一个结点前插入(头插)非头插
               
  • 链表为空
    直接assert断言pass掉
               
  • 在第一个结点前插入(头插)
    复用头插SLTPushFront函数进行插入
               
  • 非头插
    使用链表头指针找到pos结点的前一个结点地址prev
  • 创建一个新结点newnode
    将新结点newnode插入pos结点之前prev结点之后
图示

测试 -- SLTInsert函数

            

            

---------------------------------------------------------------------------------------------

            

SLTInsertAfter函数 -- 在链表指定位置(pos)之后插入一个值(x)

  • 如果接收的pos结点地址为空无法继续操作
    用assert断言继续处理
               
  • 因为要在链表中插入一个值
    所以要用BuySListNode函数新增一个结点
               
  • 让newnode结点的指针域next指向后一个结点
               
  • 让pos结点指向newnode结点
    将链表连接起来
图示

测试 -- SLTInsertAfter函数

            

            

---------------------------------------------------------------------------------------------

            

SLTErase函数 -- 在链表删除指定位置(pos)结点

  • 防止要删的pos结点指针为空指针
    使用assert断言
               
  • 分两种情况
    头删非头删
               
  • 头删
    直接复用头删SLTPopFront函数
               
  • 非头删
    找到pos结点的前一个结点prev
    prev结点 pos结点后一个结点 连接起来
               
  • 最后释放(删除)pos结点完成删除操作
图示

测试 -- SLTErase函数

            

            

---------------------------------------------------------------------------------------------

            

SLTEraseAfter函数 -- 在链表删除指定位置(pos)之后一个结点

  • 使用assert断言分别排除pos结点指针为空pos结点为尾结点的情况,
    我们是删除pos结点的后一个结点
    如果pos为尾结点,就会无法操作
               
  • 将要删除的pos结点下个结点posNext地址保存起来
               
  • pos结点下下个结点连接起来
               
  • 最后释放(删除)posNext结点
图示

测试 -- SLTEraseAfter函数

            

            

---------------------------------------------------------------------------------------------

            

SLTDestroy函数 -- 销毁链表释放开辟的动态空间

  • 因为链表释放后,还要把头指针plist给置为空
    要操作到plist,所以要用到二级指针
               
  • 进行assert断言
    pphead二级指针不为空
    以上函数中参数带二级指针pphead的都需要进行此操作
               
  • 创建一个指针进行遍历释放空间
               
  • 最后将链表头指针置为空
图示

         

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

             

3 . 对应代码

SList.h -- 单链表头文件

//链表头文件:
#pragma once//先包含之后要用到的头文件:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>//重命名一个单链表数据类型
typedef int SLTDataType;
//SLT -- single link table//创建一个单链表结点结构体 
//这里数据域是int类型4字节,指针域指针也是4个字节,
//所以一个结点就是8个字节
typedef struct SListNode
{//结点数据域:SLTDataType data;//结点指针域://因为是指向单链表结点结构体的指针,//所以是单链表结点结构体类型的指针struct SListNode* next;//第一个结点要找到第二个结点,物理空间不连续//通过上个结点指向下个结点,实现逻辑连续}SLTNode; //将struct SListNode重命名为SLTNode//创建遍历打印单链表的函数 -- 接收单链表头指针
void PrintSList(SLTNode* phead);//创建生成结点的函数 -- 接收要存入结点数据域的数据;返回该结点的指针
SLTNode* BuySListNode(SLTDataType x);//创建尾插函数 -- 
//使用二级指针接收单链表头指针的地址 和 接收要插入的值
void SLTPushBack(SLTNode** pphead, SLTDataType x);//创建头插函数 -- 
//使用二级指针接收单链表头指针的地址 和 接收要插入的值
void SLTPushFront(SLTNode** pphead, SLTDataType x);//创建尾删函数 --
//使用二级指针接收单链表头指针的地址
void SLTPopBack(SLTNode** pphead);//创建头删函数 --
//使用二级指针接收单链表头指针的地址
void SLTPopFront(SLTNode** pphead);//创建查找函数 --
//查找指定值(x)所在结点的地址
//接收 链表头指针phead、查找值x
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);//创建指定位置插入函数1 --
//在链表指定位置(pos)之前插入一个值(x)
//接收 链表头指针地址pphead、指定结点指针pos、插入值x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);//创建指定位置插入函数2 --
//在链表指定位置(pos)之后插入一个值(x)
//接收 指定结点指针pos、插入值x
void SLTInsertAfter(SLTNode* pos, SLTDataType x);//创建删除指定结点函数1 --
//在链表删除指定位置(pos)结点
///接收 链表头指针地址pphead、指定结点指针pos
void SLTErase(SLTNode** pphead, SLTNode* pos);//创建删除指定结点函数2 --
//在链表删除指定位置(pos)之后一个结点
//接收 指定结点指针pos
void SLTEraseAfter(SLTNode* pos);//创建销毁链表函数:
void SLTDestroy(SLTNode** pphead);

            

            

---------------------------------------------------------------------------------------------

SList.c -- 单链表函数实现文件

//链表实现文件:
#define _CRT_SECURE_NO_WARNINGS 1//包含链表头文件:
#include "SList.h"//创建遍历单链表的函数:接收单链表结点的头指针
void PrintSList(SLTNode* phead)
{//phead头指针指向第一个结点,//之后还要多次使用phead头指针,//所以不要改变phead头指针,//所以可以创建一个和phead头指针一样类型的指针cur代替phead头指针SLTNode* cur = phead;//因为链表到最后结点的指针域为NULL才结束//所以只要cur不为NULL就继续遍历结点进行打印:while (cur != NULL){//通过cur当前指向的结点打印cur里数据域的内容:printf("%d->", cur->data);//通过结点里指针域指向下一个结点,方便打印下个结点的数据域内容cur = cur->next;}//最后提示已指向NULL指针链表,遍历结束:printf("NULL\n");
}//创建生成结点的函数 -- 接收要存入结点数据域的数据;返回该结点的指针
SLTNode* BuySListNode(SLTDataType x)
{//给结点开辟动态空间(注意开辟空间的大小是SLTNode结点的大小--8个字节)SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode)); //返回该结点地址//检查是否开辟成功:if (newnode == NULL) //返回空指针,说明开辟失败{//打印错误信息:perror("malloc fail");//开辟失败直接退出程序:exit(-1);}//将接收的值x赋给该结点的数据域:newnode->data = x;//设置新创建结点的指针域://因为是最新的结点,即最尾部的结点,//所以指针域的指针应是NULL(链表末尾结束)//之后通过指针域连接各个结点的操作要看情况操作,先都初始化为NULLnewnode->next = NULL;//返回该新结点的指针(地址)return newnode;
}//创建尾插函数
//使用二级指针接收单链表头指针的地址 和 接收要插入的值
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{//改变结构体,要用结构体指针//改变结构体指针,要用结构体指针的指针(二级指针)//因为pphead二级指针存储的是plist指针,//plist指针指向的值可能为空,但指针的地址不能为空//pphead是plist的地址,正常情况下一定不为空assert(pphead);//先使用newnode函数为要尾插的值创建一个结点//并返回该结点地址SLTNode* newnode = BuySListNode(x);//判断phead是否是NULL,如果是说明链表还没开辟过一个结点if (*pphead == NULL){//如果为空,则将上面开辟的newnode结点地址赋给phead*pphead = newnode;//要改变结构体的指针,就要用二级指针//这里pphead是二级指针,存放链表头指针plist的地址//因为如果用一级指针SLTNode* phead的话,//phead形参只是plist实参的临时拷贝,两者空间相互独立//改变phead的话不会改变plist,plist还会是空指针//所以要用二级指针pphead存放plist指针的地址,//*pphead解引用就能得到一级指针plist,//即			*pphead = plist//所以实际上上面的代码就相当于:plist = newnode//这样就让本来指向空指针的头指针指向了新创建的结点指针//链表就能够连接起来}else//不为空则往尾部插入新结点:{//创建另一个指针存放单链表头指针SLTNode* tail = *pphead; //*pphead == plist//使用while循环获得最后一个结点的地址while (tail->next != NULL)//如果下个结点的next指针域不为NULL,//则继续往后找,直到tail等于最后结点的地址{//把当前结点指针域里下个结点的地址给到tail,//进行while循环下次判断:tail = tail->next;}//tail找到最后结点地址后,//把尾插的新结点地址给到tail的next指针域,//连接起链表:tail->next = newnode;//要改变结构体,用结构体的指针即可//因为newnode、tail、pphead都是临时(局部)变量,//所以函数运行结束后都会销毁,//但malloc函数开辟的空间(结点)都在堆上不会销毁//通过解引用二级指针pphead改变plist也没有问题}
}//创建头插函数 -- 
//使用二级指针接收单链表头指针的地址 和 接收要插入的值
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{//因为pphead二级指针存储的是plist指针,//plist指针指向的值可能为空,但指针的地址不能为空//pphead是plist的地址,正常情况下一定不为空assert(pphead);//先使用newnode函数为要头插的值创建一个结点//并返回该结点地址SLTNode* newnode = BuySListNode(x);//因为也要用到链表头指针本身,所以也要使用二级指针//因为plist指向原本头结点地址,//所以可以使用plist把原本头结点地址赋给新插入头结点指针域:newnode->next = *pphead;//再把头指针改为指向新插入头结点:*pphead = newnode;
}//创建尾删函数 --
//使用二级指针接收单链表头指针的地址
void SLTPopBack(SLTNode** pphead)
{//尾删需要考虑三种情况://注:*pphead 就是 plist//因为pphead二级指针存储的是plist指针,//plist指针指向的值可能为空,但指针的地址不能为空//pphead是plist的地址,正常情况下一定不为空assert(pphead);// 第一种情况:链表为空 -- 没有任何结点//没有结点那就删不了了,使用assert接收到空指针就报警告assert(*pphead);// 第二种情况:链表只有一个结点if ((*pphead)->next == NULL)// -> 也是解引用(结构体的),优先级比 * 高//所以 *pphead 要用() 括起来{//只有一个结点,又要尾删,那就直接把这唯一的结点删掉:free(*pphead); //直接free释放掉plist指向的结点//再把释放掉的plist置为空指针,防止成为野指针:*pphead = NULL;}else// 第三种情况:链表有一个以上结点{//这种情况额外两个指针,//一个tail指针 -- 用来找到最后一个结点地址并将其释放,//还有一个tailPrev指针 -- 指向tail指针的前一个结点地址,//用来改变该结点的指针域,//防止原本指向tail结点的指针域变成野指针//先替代头指针plist:SLTNode* tail = *pphead;//创建tailPrev指针,//之后操作会指向tail结点的前一个结点,//即倒数第二个结点SLTNode* tailPrev = NULL;//再使用while循环找到尾结点://和尾插的操作类似while (tail->next != NULL){//tail查找之前先把当前指向结点地址给tailPrev//这样最后tail会指向尾结点,//tailPrev会指向倒数第二个结点tailPrev = tail;tail = tail->next;}//结束while循环后tail指向尾结点地址,//因为是尾删,所以free释放掉tail就可以“删掉”尾结点free(tail);//因为tail是局部(临时)变量,出了函数就销毁,//所以不置为指针也可以,不用担心成为野指针//删除原尾结点后,倒数第二个结点成为尾结点,//要把其指针域next置为空指针,成为链表新结尾tailPrev->next = NULL;}
}//创建头删函数 --
//使用二级指针接收单链表头指针的地址
void SLTPopFront(SLTNode** pphead)
{//因为pphead二级指针存储的是plist指针,//plist指针指向的值可能为空,但指针的地址不能为空//pphead是plist的地址,正常情况下一定不为空assert(pphead);//分两种情况://第一种情况:链表里没有结点(空链表)//没有结点可以删,直接assert断言pass掉:assert(*pphead);//第二种情况:链表有一个和多个结点(非空链表)//因为是删掉头结点,所以不用考虑找尾结点//所以不用细分一个或多个结点的情况://这种情况要先通过plist拿到第二个结点的地址:SLTNode* newhead = (*pphead)->next;//使用newhead存储第二个结点地址//拿到第二个结点地址后,再释放掉头结点,//实现头删效果:free(*pphead);//这时要让第二个结点成为新的头结点:*pphead = newhead; //头指针指向原本的第二个结点
}//创建查找函数 --
//查找指定值(x)所在结点的地址
//接收 链表头指针phead、查找值x
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{//像SLTFind和PrintSList函数只用头指针遍历//不改变指针本身就不需要用到二级指针//创建个变量代替头指针:SLTNode* cur = phead;//使用while循环对链表进行遍历查找:while (cur != NULL)//只要cur指针指向地址不为空就继续循环
//while遍历时,(cur->next != NULL) 和 (cur != NULL) 的区别:
//(cur->next != NULL):这个条件最后cur会是最后结点的地址
//(cur != NULL):这个条件最后cur会是NULL
//(cur->next != NULL) 会比 (cur != NULL) 少一次循环{//遍历过程中依次查找各结点数据域数据是否与x相同:if (cur->data == x){//找到了一个结点数据域数据是x,返回该结点地址return cur;}//这里如果while循环的条件是(cur->next != NULL)//最后一个结点进不来,不能判断最后结点数据域数据是不是xcur = cur->next; //改变循环条件,指向下个结点}//如果能指向到这说明没找到,返回NULL:return  NULL;
}//创建指定位置插入函数1 -- 
//在链表指定位置(pos)之前插入一个值(x)
//接收 链表头指针地址pphead、指定结点指针pos、插入值x
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{//因为pphead二级指针存储的是plist指针,//plist指针指向的值可能为空,但指针的地址不能为空//pphead是plist的地址,正常情况下一定不为空assert(pphead);//第一种情况:空指针//先排除空指针的情况:assert(pos);//第二种情况:头插if (pos == *pphead)// *pphead == plist//如果pos是pphead即第一个指针,//则在第一个指针前插入一个值,相当于头插{//直接复用头插函数SLTPustFront:SLTPushFront(pphead, x);//直接传pphead二级指针过去}else//第三种情况:非头插{//从头开始找pos结点的前一个结点://先获得头指针SLTNode* prev = *pphead;//当前结点的指针域不是指向pos结点则继续循环while (prev->next != pos){prev = prev->next;}//此时prev已指向pos结点的前一个结点//为要插入的结点创建一个结点newnode://插入位置是pos结点之前,prev结点之后SLTNode* newnode = BuySListNode(x);//让prev结点指针域指向新插入结点地址:prev->next = newnode;//newnode结点指针域指向pos结点:newnode->next = pos;//此时newnode新结点就插入完成了}
}//创建指定位置插入函数2 --
//在链表指定位置(pos)之后插入一个值(x)
//接收 指定结点指针pos、插入值x
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{//因为是在pos结点后插入一个值(结点)//所以不可能会有头插的情况,那就不需要头指针plist//pos指针存储结点地址,可能会接收到空指针//使用assert断言pass掉assert(pos);//先为插入值创建一个新结点newnode:SLTNode* newnode = BuySListNode(x);//先让newnode的指针域next指向后一个结点://这里后一个结点就是pos结点指针域里的地址//因为未插入前pos就是指向后一个地址newnode->next = pos->next;//再让pos的指针域next指向newnode:pos->next = newnode;
}//创建删除指定结点函数1 --
//在链表删除指定位置(pos)结点
///接收 链表头指针地址pphead、指定结点指针pos
void SLTErase(SLTNode** pphead, SLTNode* pos)
{//因为pphead二级指针存储的是plist指针,//plist指针指向的值可能为空,但指针的地址不能为空//pphead是plist的地址,正常情况下一定不为空assert(pphead);//防止要删的pos结点指针为空指针//使用assert断言:assert(pos);//分两种情况:// 1.头删:if (pos == *pphead)//pos结点 == 头结点 --> 头删{//直接复用SLTPopFront头删函数:SLTPopFront(pphead);}else// 2.非头删://尾删不用单独分出一种情况,因为还得判断是不是尾结点//直接用通用逻辑删除也可以处理尾删的情况{//从头开始找pos结点的前一个结点://先获得头指针SLTNode* prev = *pphead;//当前结点的指针域不是指向pos结点则继续循环while (prev->next != pos){prev = prev->next;}//此时prev已指向pos结点的前一个结点//因为要删除pos结点,//所以要让pos前一个和后一个结点连接起来:prev->next = pos->next;//连接成功后再把pos结点释放,实现删除效果:free(pos);}
}//创建删除指定结点函数2 --
//在链表删除指定位置(pos)之后一个结点
//接收 指定结点指针pos
void SLTEraseAfter(SLTNode* pos)
{//删除pos结点后的一个结点,//那么就不可能出现头删的情况,//pos结点是尾结点也没用,//因为尾结点后就没有结点可以删了//使用assert断言处理:assert(pos); //防止接收“空结点”assert(pos->next); //防止接收尾结点//将要删的pos结点的下个结点posNext先保存起来:SLTNode* posNext = pos->next;//再把pos结点和下下个结点连接起来:pos->next = posNext->next;//posNext的指针域next就是pos结点的下下个结点地址//最后释放(删除)posNext结点:free(posNext);
}//创建销毁链表函数:
void SLTDestroy(SLTNode** pphead)
{//因为链表释放后,还要把头指针plist给置为空,//要操作到plist,所以要用到二级指针://plist指向空链表,pphead也不能为空:assert(pphead);//创建一个指针进行遍历:SLTNode* cur = *pphead;//*pphead == plist//进行遍历释放:while (cur != NULL){//创建一个指针保存下个结点地址:SLTNode* next = cur->next;//释放当前结点:free(cur);//cur指针移向下一个结点:cur = next;}//将链表头指针置为空:*pphead = NULL;
}

            

            

---------------------------------------------------------------------------------------------

test.c -- 单链表测试文件

//链表测试文件:
#define _CRT_SECURE_NO_WARNINGS 1/* 链表学习引入小测试:#include <stdio.h>//重命名一个单链表数据类型
typedef int SLTDataType;
//SLT -- single link table//创建一个单链表结点结构体
typedef struct SListNode
{//结点数据域:SLTDataType data; //结点指针域://因为是指向单链表结点结构体的指针,//所以是单链表结点结构体类型的指针struct SListNode* next; }SLTNode; //将struct SListNode重命名为SLTNode//创建遍历单链表的函数:接收单链表结点的头指针
void PrintSList(SLTNode* phead)
{//phead头指针指向第一个结点,//之后还要多次使用phead头指针,//所以不要改变phead头指针,//所以可以创建一个和phead头指针一样类型的指针cur代替phead头指针SLTNode* cur = phead;//因为链表到最后结点的指针域为NULL才结束//所以只要cur不为NULL就继续遍历结点进行打印:while (cur != NULL){//通过cur当前指向的结点打印cur里数据域的内容:printf("%d->", cur->data);//通过结点里指针域指向下一个结点,方便打印下个结点的数据域内容cur = cur->next;}//最后提示已指向NULL指针链表,遍历结束:printf("NULL\n");
}int main()
{//创建单链表结点:SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));n1->data = 10; //在该结点数据域存放数据SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));n2->data = 20; //在该结点数据域存放数据SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));n3->data = 30; //在该结点数据域存放数据n1->next = n2; //n1的指针域指向结点n2n2->next = n3; //n2的指针域指向结点n3n3->next = NULL; //n3的指针域指向NULL(结束)PrintSList(n1); //调用函数打印单链表return 0;
}*///包含链表头文件:
#include "SList.h"//测试函数1:测试--PrintSList、BuySListNode函数
void TestList1()
{int n; //存放链表长度//打印提示信息:printf("请输入链表的长度:>");//接收链表长度scanf("%d", &n); //打印提示信息:printf("\n请依次输入每个结点的值:>");SLTNode* plist = NULL; //链表头指针,一开始链表没数据所以为NULL//使用for循环,循环创建结点并赋值,形成链表:for (int i = 0; i < n; i++){int val; //存放结点数据域数据scanf("%d", &val); //接收结点数据域数据//使用BuySListNode函数创建结点并给数据域赋值:SLTNode* newnode = BuySListNode(val);//头插: 使用头插把链表连接起来//把链表头指针plist指向的头结点地址赋给新创建结点的指针域next//这样新结点的指针域next就能指向原来的头结点地址newnode->next = plist;//再把新创建结点的地址赋给头指针,//这样头指针就指向了新创建结点地址,让其成为新的头结点plist = newnode;}//使用PrintSList函数打印链表PrintSList(plist); //接收头指针后打印//使用SLTPushBack函数手动向链表尾部插入数据(尾插):SLTPushBack(plist, 10000);//再使用PrintSList函数打印插入后的链表PrintSList(plist);//plist和phead都是单链表头指针,//但 plist是实参  phead是形参//phead 是 plist 的一份临时拷贝
}//测试函数2:测试--SLTPushBack、SLTPushFront函数
void TestList2()
{//创建单链表头指针:SLTNode* plist = NULL;//使用尾插随机插入几个值: //(此时头指针指向NULL还没有开辟空间创建结点)SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);SLTPushBack(&plist, 4);SLTPushBack(&plist, 5);//使用头插随机插入几个值:SLTPushFront(&plist, 10);SLTPushFront(&plist, 20);SLTPushFront(&plist, 30);SLTPushFront(&plist, 40);//使用SLTPrintf函数打印该链表:PrintSList(plist);
}//测试函数3:测试--SLTPopBack(尾删)函数
void TestList3()
{//创建单链表头指针:SLTNode* plist = NULL;//使用尾插随机插入几个值: //(此时头指针指向NULL还没有开辟空间创建结点)SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//使用SLTPrintf函数打印该链表:printf("尾删前链表:\n");PrintSList(plist);//使用尾删函数:SLTPopBack(&plist);//使用SLTPrintf函数打印该链表:printf("尾删后链表:\n");PrintSList(plist);SLTPopBack(&plist);//使用SLTPrintf函数打印该链表:printf("尾删后链表:\n");PrintSList(plist);SLTPopBack(&plist);//使用SLTPrintf函数打印该链表:printf("尾删后链表:\n");PrintSList(plist);SLTPopBack(&plist);//使用SLTPrintf函数打印该链表:printf("尾删后链表:\n");PrintSList(plist);//使用SLTPrintf函数打印该链表:printf("尾删后链表:\n");PrintSList(plist);
}//测试函数4:测试--SLTPopFront(头删)函数
void TestList4()
{//创建单链表头指针:SLTNode* plist = NULL;//使用尾插随机插入几个值: //(此时头指针指向NULL还没有开辟空间创建结点)SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//使用SLTPrintf函数打印该链表:printf("头删前链表:\n");PrintSList(plist);SLTPopFront(&plist);//使用SLTPrintf函数打印该链表:printf("头删后链表:\n");PrintSList(plist);SLTPopFront(&plist);//使用SLTPrintf函数打印该链表:printf("头删后链表:\n");PrintSList(plist);SLTPopFront(&plist);//使用SLTPrintf函数打印该链表:printf("头删后链表:\n");PrintSList(plist);SLTPopFront(&plist);//使用SLTPrintf函数打印该链表:printf("头删后链表:\n");PrintSList(plist);
}//测试函数5:测试 -- SLTFind函数
void TestList5()
{//创建单链表头指针:SLTNode* plist = NULL;//使用尾插随机插入几个值: //(此时头指针指向NULL还没有开辟空间创建结点)SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//使用SLTPrintf函数打印该链表:printf("操作前链表:\n");PrintSList(plist);//用SLTFind函数查找链表中数据域为3的结点的地址SLTNode* pos = SLTFind(plist, 1);if (pos != NULL){	//找到了可以对该结点进行修改pos->data *= 10;//所以SLTFind查找函数可以负责查找和修改的操作}printf("操作后链表:\n");PrintSList(plist);
}//测试函数6:测试 -- SLTInsert函数
void TestList6()
{//创建单链表头指针:SLTNode* plist = NULL;//使用尾插随机插入几个值: //(此时头指针指向NULL还没有开辟空间创建结点)SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//使用SLTPrintf函数打印该链表:printf("操作前链表:\n");PrintSList(plist);//用SLTFind函数查找链表中数据域为3的结点的地址SLTNode* pos = SLTFind(plist, 2);if (pos != NULL){int x; //接收要插入的值scanf("%d", &x); //输入要插入的值SLTInsert(&plist, pos, x); //在2前面插入x }printf("操作后链表:\n");PrintSList(plist);
}//测试函数7:测试 -- SLTInsertAfter函数
void TestList7()
{//创建单链表头指针:SLTNode* plist = NULL;//使用尾插随机插入几个值: //(此时头指针指向NULL还没有开辟空间创建结点)SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//使用SLTPrintf函数打印该链表:printf("操作前链表:\n");PrintSList(plist);//用SLTFind函数查找链表中数据域为3的结点d的地址SLTNode* pos = SLTFind(plist, 2);if (pos != NULL){int x; //接收要插入的值scanf("%d", &x); //输入要插入的值SLTInsertAfter(pos, x); //在2后面插入x }printf("操作后链表:\n");PrintSList(plist);
}//测试函数8:测试 -- SLTErase函数
void TestList8()
{//创建单链表头指针:SLTNode* plist = NULL;//使用尾插随机插入几个值: //(此时头指针指向NULL还没有开辟空间创建结点)SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//使用SLTPrintf函数打印该链表:printf("操作前链表:\n");PrintSList(plist);//用SLTFind函数查找链表中数据域为3的结点d的地址SLTNode* pos = SLTFind(plist, 2);if (pos != NULL){int x; //接收要插入的值scanf("%d", &x); //输入要插入的值SLTErase(&plist, pos); //删除pos所在结点//pos结点指针删除(释放)后,要将其置为空:pos = NULL;}printf("操作后链表:\n");PrintSList(plist);
}//测试函数9:测试 -- SLTEraseAfter函数
void TestList9()
{//创建单链表头指针:SLTNode* plist = NULL;//使用尾插随机插入几个值: //(此时头指针指向NULL还没有开辟空间创建结点)SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//使用SLTPrintf函数打印该链表:printf("操作前链表:\n");PrintSList(plist);//用SLTFind函数查找链表中数据域为3的结点d的地址SLTNode* pos = SLTFind(plist, 2);if (pos != NULL){int x; //接收要插入的值scanf("%d", &x); //输入要插入的值SLTEraseAfter(pos); //删除pos结点的下个结点//pos结点指针删除(释放)后,要将其置为空:pos = NULL;}printf("操作后链表:\n");PrintSList(plist);
}//测试函数10:测试 -- SLTDestroy函数
void TestList10()
{//创建单链表头指针:SLTNode* plist = NULL;//使用尾插随机插入几个值: //(此时头指针指向NULL还没有开辟空间创建结点)SLTPushBack(&plist, 1);SLTPushBack(&plist, 2);SLTPushBack(&plist, 3);//使用SLTPrintf函数打印该链表:printf("操作前链表:\n");PrintSList(plist);SLTDestroy(&plist);
}//主函数:
int main()
{//TestList1();//TestList2();//TestList3();//TestList4();//TestList5();//TestList6();//TestList7();//TestList8();//TestList9();TestList10();return 0;
}

相关文章:

【数据结构初阶】三、 线性表里的链表(无头+单向+非循环链表)

相关代码gitee自取&#xff1a; C语言学习日记: 加油努力 (gitee.com) 接上期&#xff1a; 【数据结构初阶】二、 线性表里的顺序表_高高的胖子的博客-CSDN博客 引言 通过上期对顺序表的介绍和使用 我们可以知道顺序表有以下优点和缺点&#xff1a; 顺序表优点 尾插 和 尾…...

Mybatis 映射器与XML配置职责分离

之前我们介绍了使用XML配置方式完成对数据的增删改查操作&#xff0c;使用此方式在实际调用时需要使用【命名空间.标签编号】的方式执行&#xff0c;此方式在编写SQL语句时很方便&#xff0c;而在执行SQL语句环节就显得不太优雅&#xff1b;另外我们也介绍了使用映射器完成对数…...

微服务引擎

微服务引擎&#xff0c;MSE_微服务引擎 MSE-阿里云帮助中心 一、什么是微服务引擎MSE 微服务引擎MSE&#xff08;Microservices Engine&#xff09;是一个面向业界主流开源微服务生态的一站式微服务平台&#xff0c;提供注册配置中心&#xff08;原生支持Nacos/ZooKeeper/Eur…...

前端JavaScript入门到精通,javascript核心进阶ES6语法、API、js高级等基础知识和实战 —— JS基础(三)

允许一切发生&#xff0c;生活不过是见招拆招。 思维导图 一、循环-for 1.1 for 循环-基本使用 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEe…...

搭建部署属于自己的基于gpt3.5的大语言模型(基于flask+html+css+js+mysql实现)

一、简介 本项目是一个基于GPT-3.5模型的聊天机器人网站&#xff0c;旨在为用户提供一个简便、直接的方式来体验和利用GPT-3.5模型的强大功能。项目以Flask为基础&#xff0c;构建了一个完整的Web应用程序&#xff0c;其中包含了多个前端页面和后端API接口&#xff0c;能够处理…...

AI创作专家,免费的AI创作专家工具

AI创作专家是一种崭新的工具&#xff0c;它们利用先进的人工智能技术&#xff0c;帮助创作者和写手更轻松地应对创作挑战。这些工具不仅可以生成文字&#xff0c;还可以提供灵感、帮助构思和组织思路&#xff0c;使创作过程更加高效。 147GPT批量文章生成工具​www.147seo.com/…...

Nginx之gzip模块解读

目录 gzip基本介绍 gzip工作原理 Nginx中的gzip 不建议开启Nginx中的gzip场景 gzip基本介绍 gzip是GNUzip的缩写&#xff0c;最早用于UNIX系统的文件压缩。HTTP协议上的gzip编码是一种用来改进web应用程序性能的技术&#xff0c;web服务器和客户端&#xff08;浏览器&…...

微软在Windows 11推出Copilot,将DALL-E 3集成在Bing!

美东时间9月21日&#xff0c;微软在美国纽约曼哈顿举办产品发布会&#xff0c;生成式AI成为重要主题之一。 微软表示&#xff0c;Copilot将于9月26日在Windows 11中推出&#xff1b;Microsoft 365 Copilot 将于11 月1日向企业客户全面推出&#xff1b;将OpenAI最新的文本生成图…...

SLAM从入门到精通(消息传递)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们只是编写了一个publisher节点&#xff0c;以及一个subscribe节点。有了这两个节点&#xff0c;它们之间就可以通信了。在实际生产中&#…...

思科路由器:NAT的基础配置

一直以来&#xff0c;对于华为、H3C、锐捷交换机的命令配置&#xff0c;不断的有朋友留言&#xff0c;三家交换机的配置命令容易弄混&#xff0c;经常在实际项目配置中出错&#xff0c;因此&#xff0c;找几个基础的示例来练练。 R1配置 Router>en Router>enable Rout…...

动态代理。

无侵入式的给代码增加额外的功能 代理的作用&#xff1a;对象如果干的事情太繁琐&#xff0c;就可以通过代理来转移部分职责&#xff1b;也就是相当于把对象的的方法拆开一些步骤分给代理做&#xff0c;对象做关键的就行了&#xff1b;并且代理做的这些繁琐的事情的名字也要和…...

Learn Prompt-GPT-4:能力

GPT-4能力大赏​ 常识知识推理​ 一个猎人向南走了一英里&#xff0c;向东走了一英里&#xff0c;向北走了一英里&#xff0c;最后回到了起点。他看到了一只熊&#xff0c;于是开枪打了它。这只熊是什么颜色的&#xff1f; 答案是白色&#xff0c;因为这种情况只可能发生在北…...

iOS——ViewController的生命周期

ViewController ViewController的生命周期是指在应用程序运行过程中&#xff0c;ViewController实例从创建到销毁的整个过程。在这个过程中&#xff0c;ViewController会经历一系列的生命周期方法&#xff0c;这些方法可以帮助开发者管理ViewController及其相关的视图和逻辑。…...

SkyWalking内置参数与方法

参数 全局指标 指标指标名称all_p99所有服务响应时间的 p99 值all_p95所有服务响应时间的 p95 值all_p90所有服务响应时间的 p90 值all_p75所有服务响应时间的 p75 值all_p70所有服务响应时间的 p70 值all_heatmap所有服务响应时间的热点图 服务指标 指标指标名称service_r…...

【C++面向对象侯捷】12.虚函数与多态 | 13.委托相关设计【设计模式 经典做法,类与类之间关联起来,太妙了,不断的想,不断的写代码】

文章目录 12.虚函数与多态举例&#xff1a;委托 继承【观察者模式】13.委托相关设计Composite 组合模式Prototype 原型模式 12.虚函数与多态 纯虚函数 一定要 子类重新定义的 继承和复合 关系下的构造和析构 举例&#xff1a;委托 继承【观察者模式】 13.委托相关设计 问题…...

基于若依ruoyi-nbcio增加flowable流程待办消息的提醒,并提供右上角的红字数字提醒(五)

1、下面提供给前端待办提醒消息的接口SysNoticeController&#xff0c;增加如下&#xff1a; /*** 补充用户数据&#xff0c;并返回系统消息* return*/Log(title "系统消息")GetMapping("/listByUser")public R<Map<String, Object>> listByU…...

hive数据初始化

mysql版本&#xff1a;3.1.3 hive版本&#xff1a; 8.0.31 hive连接配置 <property> <name>javax.jdo.option.ConnectionURL</name> <value>jdbc:mysql://node88:3306/hive?createDatabaseIfNotExisttrue</value> </pr…...

React+Node——next.js 构建前后端项目

一、安装全局依赖 npm i -g create-next-app二、创建next项目 create-next-app react-next-demo //或 create-next-app react-next-demo --typescript三、加载mysql依赖 npm i -S mysql2四、运行项目 npm run dev五、创建db文件目录&#xff0c;目录下创建index.ts import…...

CRM系统主要包括哪些功能?

CRM系统应该要包括的功能总结为3大方向—— 核心必须要具备的功能常见尽量要有的功能可选有了自然更好的功能 以我们公司用的简道云CRM系统模板为例&#xff1a;https://www.jiandaoyun.com 01 核心必须要具备的功能 核心功能决定了系统是否能够被纳入CRM类别&#xff0c;这些…...

Nginx location 精准匹配URL = /

Location是什么&#xff1f; Location是Nginx中的块级指令(block directive)&#xff0c;通过配置Location指令块&#xff0c;可以决定客户端发过来的请求URI如何处理&#xff08;是映射到本地文件还是转发出去&#xff09;及被哪个location处理。 匹配模式 分为两种模式&…...

使用JAXB将Java对象转xml

文章目录 使用JAXB将Java对象转xml1. 要求生成的xml2. Java对象3. 封装的工具类4. 测试 使用JAXB将Java对象转xml 1. 要求生成的xml <?xml version"1.0" encoding"UTF-8" ?> <root><result status"success" msg"成功&qu…...

Atlas 200 DK开发板问题总结

1.fatal error: acl/acl.h: No such file or directory 该问题是因为在设置的DDK环境变量下找不到头文件。 解决方法&#xff1a; 1&#xff09;输入echo $DDK&#xff0c;查看当前DDK地址 2&#xff09;在src文件夹下找到CMakeLists.txt文件&#xff0c;发现该文件有一个变量名…...

uniapp——实现二维码生成+保存二维码图片——基础积累

最近在做二维码推广功能&#xff0c;自从2020年下半年到今天&#xff0c;大概有三年没有用过uniapp了&#xff0c;而且我之前用uniapp开发的程序还比较少&#xff0c;因此很多功能都浪费了很多时间去查资料&#xff0c;现在把功能记录一下。 这里写目录标题 效果图1.根据接口返…...

零基础学前端(六)重点讲解 JavaScript

1. 该篇适用于从零基础学习前端的小白&#xff0c;完全从零基础角度出发 2. 我们学习时&#xff0c;应该主动向自己提问&#xff1f;只有你能提出问题&#xff0c;你才算是在编程中学习进步了。 3. 初学者不懂得问题很多&#xff0c;在自己在不懂时&#xff0c;一定要求助有经验…...

数据库问题记录(粗略版)oracle、mysql等主流数据库通用

1. ORA-00918&#xff1a;未明确定义列 该问题情况大致为&#xff1a;select 所取列名错误、重复等问题。 2. “select * from temp where 10; ”的含义 布尔值为FALSE&#xff0c;只返回表结构&#xff0c;不返回数据。 举一反三&#xff1a; select * from temp where 1&…...

ORACLE多列中取出数据最大的一条

1.需求说明&#xff1a; 当查询出来的数据存在多条数据时&#xff0c;想按照一定条件排序取出其中一条数据。 2.使用函数&#xff1a; row_number() over( partition by 分组字段 order by 排序字段 desc&#xff09; 3.示例&#xff1a; --根据table_a中的pk_house&#x…...

Xamarin.Android实现App内版本更新

目录 1、具体的效果2、代码实现2.1 基本原理2.2 开发环境2.3 具体代码2.3.1 基本设置2.3.2 系统的权限授予2.3.3 进度条的layout文件2.3.4 核心的升级文件 3、代码下载4、知识点5、参考文献 1、具体的效果 有事需要在程序内集成自动更新的功能&#xff0c;网上找了下&#xff…...

运维工程师面经

文章目录 前言RedisMongoDBPython中的GIL&#xff08;全局解释器锁&#xff09;Python算法总结 前言 本博客仅做学习笔记&#xff0c;如有侵权&#xff0c;联系后即刻更改 科普&#xff1a; Redis 参考网址 NoSQL技术 基于内存的数据库&#xff0c;并且提供一定的持久化功能…...

stm32之智能垃圾桶实战

之前用过51做过一个垃圾桶的小项目&#xff0c;这里用32重新搞了一下。视频的效果和之前一样&#xff0c;可参考这个垃圾桶效果 。 一、项目描述&#xff08;同51&#xff09; 项目主要是模拟不用手动打开垃圾桶盖&#xff0c;而进行自动操作。自动打开的条件如下&#xff1a…...

【C++面向对象侯捷下】2.转换函数 | 3.non-explicit-one-argument ctor

文章目录 operator double() const {} 歧义了 标准库的转换函数...

网络营销外包公司招聘/网站关键字排名优化

M1相二氧化钒VO2单晶薄膜 钒氧化物高质量外延单晶薄膜 钒氧化物是一种极其复杂的金属氧化物&#xff0c;且具有丰富的相结构&#xff0c;每一种相都有其独特的用途&#xff0c;如VO2高温R相到低温M相之间发生结构转变同时伴随着4-5量级的电阻率变化&#xff0c;高温下的VO2薄膜…...

石家庄计算机培训机构/合肥关键词优化平台

在PHP中&#xff0c;数组函数 array_fill_keys () 用于使用指定的键和值填充数组。 函数语法&#xff1a; array_fill_keys ( array $keys , mixed $value ) : array 函数参数说明&#xff1a; 参数描述keys必需。数组&#xff0c;其值将被用于填充数组的键名。value必需。规…...

哪个网站帮别人做ppt/苏州搜索引擎排名优化商家

作者 | 林逸来源 | 创业邦&#xff08;ID&#xff1a;ichuangyebang&#xff09;8月25日下午&#xff0c;蚂蚁科技集团科创板上市申请&#xff0c;获上交所受理&#xff0c;并同步向香港联交所递交上市申请&#xff0c;AH上市的进程来到了标志性的节点。回顾一个月前&#xff0…...

asp.net 网站管理工具/seo综合查询网站源码

MySQL8 创建用户,设置修改密码,授权 MySQL5.7可以 (创建用户,设置密码,授权) 一步到位 &#x1f447; GRANT ALL PRIVILEGES ON *.* TO 用户名% IDENTIFIED BY 密码 WITH GRANT OPTION&#x1f446;这样的语句在MySQL8.0中行不通, 必须 创设和授权 分步执行&#x1f447; CR…...

莘县网站制作/seo竞价

说明前端 vue 框架不知不觉就这样火起来了&#xff0c;生态圈也是逐渐在完善&#xff0c;后台也是彻底分离了数据给前端&#xff0c;就类似ios 和安卓客户端一样&#xff0c;令人惊奇的是也拥有了前端路由这个概念&#xff0c;更令人兴奋的是用 webpack 打包解决了包和包依赖的…...

wordpress option/东莞seo网站排名优化

项目简述&#xff1a;基于开源Hadoop2.0架构的集群网络&#xff0c;进行海量数据的分布式计算。由于Hadoop集群规模不断扩大&#xff0c;而搭建一个同等规模的测试集群需要一笔昂贵的开销。目前有100台左右物料&#xff0c;期望预测计算节点1500的集群网络性能&#xff0c;目前…...