C语言中单链表的基本操作(创建、销毁、增删查改等)

目录
  • 链表分类
  • 单链表的介绍
  • 单链表的基本操作
    • 创建
    • 打印
    • 尾插
    • 头插
    • 尾删
    • 头删
    • 查找
    • 任意位置插入
    • 任意位置删除
    • 销毁
  • 完整代码
  • 总结

链表分类

链表主要有下面三种分类方法:

  • 单向或者双向
  • 带头或者不带头
  • 循环或者非循环综合来看链表有八种类型,本文主要针对的是不带头节点的非循环单链表。

单链表的介绍

typedef struct SListNode
{
	DataType data;//数据域
	struct SListNode *next;//结构体指针,指向下一个节点
}SListNode;//类型别名

如图

链表的每一个节点由数据域和指针域构成,数据域存放数据,指针域中的指针指向下一个节点。

plist表示链表的指针,指向链表的第一个节点,最后一个节点的指针为空。

单链表的基本操作

创建

创建单链表有几点需注意:

  • 链表与顺序表的区别是,顺序表是物理空间上连续的,而链表只在逻辑上连续,所以链表申请空间时是使用一个申请一个,顺序表则是一次申请一段空间,空间不足时进行扩容。
  • 如果在栈上申请空间,在函数调用结束后会释放,所以需要在堆区申请空间。
  • 每次申请一个节点都要存入数据,所以链表总是满的,而顺序表则可能有一段空间没有利用。
  • 函数的返回值是指向节点的结构体类型的指针
SListNode* BuySListNode(DataType x)
{
	SListNode* plist = (SListNode*)malloc(sizeof(SListNode));
	if (plist == NULL)
	{
		return NULL;//判断是否申请成功
	}
	plist->data = x;
	plist->next = NULL;
	return plist;
}

打印

遍历链表,进行打印

void SListPrint(SListNode* plist)
{
	assert(plist);
	SListNode* cur = plist;
	while (cur)
	{
		printf("%d-->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

尾插

尾插的操作步骤:

  • 若链表为空,*pplist指向新插入的节点
  • 链表不为空则遍历链表,找到最后一个节点
  • 令最后一个节点的next指向新插入的节点
  • 新插入的节点next指向NULL

注意事项:

  • 因为插入元素要改变原链表中指针的指向,故传参时要传入二级指针。
  • assert(pplist)是判断链表是否存在,因为pplist是指向链表的指针的地址,若pplist为空,说明链表的地址不存在,即链表不存在;而如果(*pplist)为空,表示的是该链表是空链表。
void SListPushBack(SListNode** pplist, DataType x)
{
	//改变指针指向,参数传二级指针
	assert(pplist);//判断链表是否存在,与链表是否为空不同
	//1.若链表为空,*pplist指向插入的节点
	if (*pplist == NULL)
	{
		*pplist = BuySListNode(x);
	}
	else {
		//2.链表不为空,指针移动到链表最后一个节点,其next指向插入的节点
		SListNode* cur = *pplist;
		while (cur->next)
		{
			cur = cur->next;//cur的next为空时,cur指向最后一个节点
		}
		cur->next = BuySListNode(x);
	}
}

头插

头插操作顺序:

  • 申请一个新节点
  • 新节点的next指向原来的第一个节点,即*pplist
  • 改变*pplist指向,使其指向新增的节点

进行头插时,要注意各个步骤的顺序,如果直接令*pplist指向了新增的的节点,会导致原有的第一个节点无法找;另外,链表为空时的操作方法与链表非空时代码可以合并,不用再分开写各种情况。

void SListPushFront(SListNode** pplist, DataType x)
{
	assert(pplist);
	//if (NULL == *pplist)
	//{
	//	//链表为空
	//	*pplist = BuySListNode(x);
	//}
	//else
	//{
	//	SListNode* temp = *pplist;//temp指向链表原来的第一个节点
	//	*pplist = BuySListNode(x);//plist指向新增的节点
	//	(*pplist)->next = temp;//新增的节点指向原来的第一个节点
	//}
	//上面两种情况代码可以合并
	SListNode* node = BuySListNode(x);//申请一个新节点
	node->next = *pplist;//新增的节点的next指向原来的第一个节点
	*pplist = node;//*pplist指向新增的节点
}

尾删

尾删步骤:

  • 判断链表是否为空或只有一个结点
  • 遍历找到最后一个节点的前驱结点prev
  • 令prev的next指向NULL
  • 释放原来最后一个节点申请的空间

注意事项:

  • 区分链表为空、单个结点、多个结点各种情况
  • 不能找到最后一个节点并将其置空,而是要找到其前驱节点,断开与最后一个节点的连接
  • 删除节点后要释放空间,避免内存泄漏
void SListPopBack(SListNode** pplist)
{
	assert(pplist);
	//1.链表为空
	if (NULL== *pplist)
	{
		return;
	}
	//2.链表只有一个元素
	else if (NULL == (*pplist)->next)
	{
		free(*pplist);
		*pplist = NULL;
	}
	//3.链表有多个元素
	else
	{
		SListNode* prev = NULL;
		SListNode* cur = *pplist;
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;//循环结束时cur指向最后一个节点
		}
		//cur= NULL;//这里不能写cur=NULL,需要找到cur的前一个节点,将其next置空\
		否则前一个结点的next依然指向原来的最后一个节点
		prev->next = NULL;//prev成为最后一个节点
		free(cur);//释放原来最后一个节点的空间
	}

头删

头删的操作步骤:

  • 保存第一个节点的指针信息
  • 令*pplist指向第二个节点
  • 释放原来的第一个节点的空间

同样的,头删也要注意保存原来第一个节点的位置,否则*pplist指向改变后,原来的第一个节点就找不到了,会无法释放空间造成内存泄漏。

void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	//1.单链表为空
	if (NULL == *pplist)
	{
		return;
	}
	2.单链表有一个节点
	//else if (NULL == (*pplist)->next)
	//{
	//	*pplist = NULL;//删除后链表为空
	//}
	3.单链表有多个节点
	//else
	//{
	//*pplist= (*pplist)->next;
	//}

	//两种情况可以合并,只有一个节点时,*pplist的next为空
	else
	{
		SListNode* delNode = *pplist;
		*pplist = delNode->next;
		free(delNode);//释放删除节点的空间
	}
}

查找

用于查找某一元素是否存在于链表中,若存在则返回其第一次出现在链表中的位置,不存在则返回NULL。

遍历时注意循环条件。

SListNode* SListFind(SListNode* plist, DataType x)
{
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return	NULL;
}

任意位置插入

pos节点后插入的步骤:

  • 申请一个新的节点
  • 新增节点的next指向原pos的next
  • pos的next指向新增的节点

注意事项

  • 任意位置的插入操作只能在给定节点的后面插入,前面的节点无法同通过给出的节点找到。
  • 要注意插入的操作顺序,否则原来链表pos后的节点可能会找不到
void SListInsertAfter(SListNode* pos, DataType x)
{
	assert(pos);//指针合法性校验
	SListNode* newNode = BuySListNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

任意位置删除

与任意位置的插入相同,只能删除给定节点pos后面的节点

void SListDeleteAfter(SListNode* pos)
{
	assert(pos);
	 //1.链表有一个节点
	if (NULL == pos->next)
	{
		return;
	}
	//2.链表有多个节点
	else
	{
		SListNode* temp = pos->next;
		pos->next = temp->next;
		free(temp);
	}
}

销毁

链表的销毁,遍历一遍,逐个释放空间

void SListDestroy(SListNode** pplist)
{
	assert(pplist);//链表是否存在
	//1.链表为空
	if (NULL == *pplist)
	{
		return;
	}
	else
	{
		SListNode* cur = NULL;
		while (*pplist)
		{
			cur = *pplist;
			*pplist = (*pplist)->next;
			free(cur);
		}
	}
}

完整代码

work.h

头文件包含,函数声明,定义结构体

#pragma once
#include<stdio.h>
#include<Windows.h>
#include<assert.h>
#include<assert.h>

typedef int DataType;
typedef struct SListNode
{
	DataType data;//数据域
	struct SListNode *next;//结构体指针,指向下一个节点
}SListNode;//类型别名

//函数声明
SListNode* BuySListNode(DataType x);//节点申请
void SListPrint(SListNode* pst);//单链表遍历打印
void SListPushBack(SListNode** pplist, DataType x);//单链表尾插
void SListPushFront(SListNode** pplist, DataType x);//单链表头插
void SListPopBack(SListNode** pplist);//单链表尾删
void SListPopFront(SListNode** pplist);//单链表头删
SListNode* SListFind(SListNode* plist, DataType x);//单链表查找
void SListInsertAfter(SListNode* pos, DataType x);//pos后位置的插入
void SListDeleteAfter(SListNode* pos);//pos后位置的删除
void SListDestroy(SListNode** pplist);//释放链表空间

work.c

各操作函数的具体实现

#include"work.h"

//链表与顺序表的区别是,顺序表是物理空间上连续的
//而链表只在逻辑上连续,所以链表申请空间时是使用一个申请一个
//顺序表则是一次申请一段空间
SListNode* BuySListNode(DataType x)
{
	//若在栈申请内存函数调用结束后会释放,所以使用动态申请
	SListNode* plist = (SListNode*)malloc(sizeof(SListNode));
	if (plist == NULL)
	{
		return NULL;//判断是否申请成功
	}
	plist->data = x;
	plist->next = NULL;
	return plist;
}

void SListPrint(SListNode* plist)
{
	assert(plist);
	SListNode* cur = plist;
	while (cur)
	{
		printf("%d-->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

//尾插法
void SListPushBack(SListNode** pplist, DataType x)
{
	//改变指针指向,参数传二级指针
	assert(pplist);//判断链表是否存在,与链表是否为空不同

	//1.若链表为空,*pplist指向插入的节点
	if (*pplist == NULL)
	{
		*pplist = BuySListNode(x);
	}
	else {
		//2.链表不为空,指针移动到链表最后一个节点,其next指向插入的节点
		SListNode* cur = *pplist;
		while (cur->next)
		{
			cur = cur->next;//cur的next为空时,cur指向最后一个节点
		}
		cur->next = BuySListNode(x);
	}
}

//头插法
void SListPushFront(SListNode** pplist, DataType x)
{
	assert(pplist);
	//if (NULL == *pplist)
	//{
	//	//链表为空
	//	*pplist = BuySListNode(x);
	//}
	//else
	//{
	//	SListNode* temp = *pplist;//temp指向链表原来的第一个节点
	//	*pplist = BuySListNode(x);//plist指向新增的节点
	//	(*pplist)->next = temp;//新增的节点指向原来的第一个节点
	//}

	//链表为空的情况可以和不为空合并
	SListNode* node = BuySListNode(x);//申请一个新节点
	node->next = *pplist;//新增的节点的next指向原来的第一个节点
	*pplist = node;//*pplist指向新增的节点

}

//尾删法?
void SListPopBack(SListNode** pplist)
{
	assert(pplist);

	//1.链表为空
	if (NULL== *pplist)
	{
		return;
	}
	//2.链表只有一个元素
	else if (NULL == (*pplist)->next)
	{
		free(*pplist);
		*pplist = NULL;
	}
	//3.链表有多个元素
	else
	{
		SListNode* prev = NULL;
		SListNode* cur = *pplist;
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;//循环结束时cur指向最后一个节点
		}
		//cur= NULL;//这里不能写cur=NULL,需要找到cur的前一个节点,将其next置空\
		否则前一个结点的next依然指向原来的最后一个节点
		prev->next = NULL;//prev最后一个节点
		free(cur);//释放原来最后一个节点的空间
	}
}

//头删
void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	//1.单链表为空
	if (NULL == *pplist)
	{
		return;
	}
	2.单链表有一个节点
	//else if (NULL == (*pplist)->next)
	//{
	//	*pplist = NULL;//删除后链表为空
	//}
	3.单链表有多个节点
	//else
	//{
	//*pplist= (*pplist)->next;
	//}

	//两种情况可以合并,只有一个节点时,*pplist的next为空
	else
	{
		SListNode* delNode = *pplist;
		*pplist = delNode->next;
		free(delNode);//释放删除节点的空间
	}
}

//单链表查找
SListNode* SListFind(SListNode* plist, DataType x)
{
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		else
		{
			cur = cur->next;
		}
	}
	return	NULL;

}
//任意位置的插入
//只能在pos的后面插入
void SListInsertAfter(SListNode* pos, DataType x)
{
	assert(pos);//指针合法性校验
	SListNode* newNode = BuySListNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

//任意位置的删除
//只能删除给定的pos后面的节点
void SListDeleteAfter(SListNode* pos)
{
	assert(pos);
	 //1.链表有一个节点
	if (NULL == pos->next)
	{
		return;
	}
	//2.链表有多个节点
	else
	{
		SListNode* temp = pos->next;
		pos->next = temp->next;
		free(temp);
	}
}

// 链表空间释放
void SListDestroy(SListNode** pplist)
{
	assert(pplist);//链表是否存在
	//1.链表为空
	if (NULL == *pplist)
	{
		return;
	}
	else
	{
		SListNode* cur = NULL;
		while (*pplist)
		{
			cur = *pplist;
			*pplist = (*pplist)->next;
			free(cur);
		}
	}
}

main.c

程序入口,测试用例

#include"work.h"
void Test()
{
	SListNode* node = NULL;//定义一个结构体指针
	//尾插法插入五个节点
	SListPushBack(&node, 1);
	SListPushBack(&node, 2);
	SListPushBack(&node, 3);
	SListPushBack(&node, 4);
	SListPushBack(&node, 5);
	SListPrint(node);//遍历打印
	SListPushFront(&node, 0);//头插一个节点
	SListPrint(node);//遍历打印
	SListPopBack(&node);//尾删最后一个节点
	SListPrint(node);//遍历打印
	SListPopFront(&node);//头删第一个节点
	SListPrint(node);//遍历打印
	printf("%p\n",  SListFind(node, 4));//查找3在链表中的位置
	printf("%p\n",  SListFind(node, 99));//查找99在链表中的位置
	SListInsertAfter(SListFind(node, 4), 99);//4后面插入一个节点99
	SListPrint(node);//遍历打印
	SListDeleteAfter(SListFind(node, 4));//删除4的下一个节点
	SListPrint(node);//遍历打印
}

int main()
{
	Test();
	system("pause");
	return 0;
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • C语言实现单链表的基本操作分享

    目录 导语 单链表 单链表的特点 定义 初始化操作 头插法 尾插法 删除第i个元素 在第i个位置插入 导语 无论是顺序存储结构还是链式存储结构,在内存中进行存放元素的时候,不仅需要存放该元素的相关信息,还需要存放该元素和其他元素之间的关系,而我们之前所学的顺序表“与生俱来”的物理结构自然地能够表达出元素和元素之间的关系,不需要额外的信息去表达元素和元素之间的关系,而对于链式存储这种非顺序存储的结构,需要额外附加指针去表示这种关系. 单链表 每个结点除了存放数据元素外,还要存储指向下一个节点的指针

  • C语言数据结构之单链表操作详解

    目录 1.插入操作 2.删除操作 3.查找操作 4.修改操作 5.完整代码 1.插入操作 (1)创建一个新的要插入的结点 (2)将新结点的 next 指针指向插入位置后的结点 (3)将插入位置前的节点指针 next 指向新的结点 注意:步骤(2)(3)的顺序不能颠倒,否则会导致插入位置后的部分链表丢失. 插入位置一共分三种,分别是头部插入.中间插入和尾部插入. 如图: 代码: link* insertElem(link* p,int elem,int pos){ link* temp = p;/

  • C语言中单链表(不带头结点)基本操作的实现详解

    目录 一.单链表的概念 二.单链表的基本操作 1.创建单个结点 2.创建具有n个结点的链表 3.打印单链表 4.尾插 5.尾删 6.头插 7.头删 8.查找某个结点 9.在某个结点后面插入 10.在某个结点前面插入 11.删除某个位置后面的结点 12.删除某个结点 13.销毁单链表 三.测试代码 通过对顺序表的学习,我们可以发现顺序表有以下几点缺陷: 1.空间不够时需要扩容,扩容尤其是用realloc进行异地扩容时,是有一定代价的,其次还可能存在一定空间浪费. 2.头部或者中间插入删除,需要挪动

  • C语言数据结构之单链表的查找和建立

    目录 单链表的查找 按位查找 按值查找 单链表的建立 尾插法 头插法建立单链表 单链表的查找 其实在单链表的插入和删除中,我们已经使用过单链表的查找方法,因为插入和删除的前提都是先找到对应的结点,所以这里就不再多解释 按位查找 GetElem(L, i):按位查找操作.获取表 L 中第 i 个位置的元素的值 //按位查找 LNode * GetElem(LinkList L, int i) { if (i < 0) return false; LNode *p; //指针p指向当前扫描到的结点

  • C语言单链表的图文示例讲解

    目录 一.单链表的结构 二.单链表的函数接口 1. 申请结点及打印单链表 2. 尾插尾删 3. 头插头删 4. 中间插入和删除 1. 在 pos 指向的结点之后插入结点 2. 在 pos 指向的结点之前插入结点 3. 删除 pos 指向的结点的后一个结点 4. 删除 pos 指向的结点 6. 查找 7. 销毁单链表 在上一篇所讲述的 动态顺序表 中存在一些缺陷 1.当空间不够时需要扩容,扩容是有一定的消耗的 如果每次空间扩大一点,可能会造成空间的浪费,而空间扩小了,又会造成频繁的扩容2.在顺序表

  • C语言单链表遍历与求和示例解读

    目录 单链表的遍历 单链表的求和 单链表的遍历 描述: 牛牛从键盘输入一个长度为 n 的数组,问你能否用这个数组组成一个链表,并顺序输出链表每个节点的值. 输入描述: 第一行输入一个正整数 n ,表示数组的长度 第二行输入n个数据 输出描述: 制作一个链表然后输出这个链表的值 输入: 4 5 4 2 1 输出: 5 4 2 1 #include<stdio.h> #include<stdlib.h> typedef int DataType; typedef struct link

  • C语言数据结构之单链表存储详解

    目录 1.定义一个链表结点 2.初始化单链表 3.输出链表数据 4.完整代码 如果说,顺序表的所占用的内存空间是连续的,那么链表则是随机分配的不连续的,那么为了使随机分散的内存空间串联在一起形成一种前后相连的关系,指针则起到了关键性作用. 单链表的基本结构: 头指针:永远指向链表第一个节点的位置. 头结点:不存任何数据的空节点,通常作为链表的第一个节点.对于链表来说,头节点不是必须的,它的作用只是为了方便解决某些实际问题. 首元结点:首个带有元素的结点. 其他结点:链表中其他的节点. 1.定义一

  • C语言中单链表的基本操作(创建、销毁、增删查改等)

    目录 链表分类 单链表的介绍 单链表的基本操作 创建 打印 尾插 头插 尾删 头删 查找 任意位置插入 任意位置删除 销毁 完整代码 总结 链表分类 链表主要有下面三种分类方法: 单向或者双向 带头或者不带头 循环或者非循环综合来看链表有八种类型,本文主要针对的是不带头节点的非循环单链表. 单链表的介绍 typedef struct SListNode { DataType data;//数据域 struct SListNode *next;//结构体指针,指向下一个节点 }SListNode;

  • C语言中单链表的基本操作指南(增删改查)

    目录 1.链表概述 2.链表的基本使用 2.0 准备工作 2.1 创建节点(结构体) 2.2 全局定义链表头尾指针 方便调用 2.3 创建链表,实现在链表中增加一个数据(尾添加)----增 2.4 遍历链表 -----查 2.5 查询指定的节点 (遍历 一个个找) 2.6 链表清空------全部删除 2.7.在指定位置插入节点 ----在指定位置增 2.8尾删除----删 2.9 删除头------删 2.10 删除指定节点 3. 测试主程序 总结 1.链表概述 链表是一种常见的数据结构.它与

  • 详解Go语言中单链表的使用

    目录 链表 单链表结构 创建节点 遍历链表 头插法 尾插法 遍历方法 链表长度 链表转数组 数组转链表 链表 一种物理存储单元上非连续.非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的.链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成.每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域.使用链表结构可以避免在使用数组时需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理.但是链表失去

  • C语言 单向链表的增删查改快速掌握

    目录 前言 一.创建 二.单向链表的函数声明 三.函数实现 1.创建节点 2.尾插节点 3.头插 4.尾删 5.头删 6.查找节点 7.修改 总结 前言 链表是线性表的链式存储结构,它可以以O(1)的时间复杂度进行插入或者删除,同时由于是链式结构相比顺序表而言,不会存在空间浪费的情况.而链表又分为带头单向链表,不带头单向链表,带头循环链表,不带头循环链表,带头双向循环链表,不带头双向循环链表,带头双向链表,不带头双向链表,总共有八种,其中结构最简单的是不带头单向链表,也是实现起来最容易出错的.并

  • C语言如何建立链表并实现增删查改详解

    前言 以下是本人完成的一个C语言建立链表并进行增删查改操作的程序,为方便学习,本人将整个程序分为头文件和主函数两部分: 1.头文件(函数部分) (1)初始化函数 #include <stdio.h> #include <stdlib.h> typedef struct { int *head; int length; int capacity; } Toslist; //Toslist类型 //初始化顺序表 Toslist initSeqlist() { Toslist list;

  • Java数据结构之链表的增删查改详解

    一.链表的概念和结构 1.1 链表的概念 简单来说链表是物理上不一定连续,但是逻辑上一定连续的一种数据结构 1.2 链表的分类 实际中链表的结构非常多样,以下情况组合起来就有8种链表结构. 单向和双向,带头和不带头,循环和非循环.排列组合和会有8种. 但我这只是实现两种比较难的链表,理解之后其它6种就比较简单了 1.单向不带头非循环链表 2.双向不带头非循环链表 二.单向不带头非循环链表 2.1 创建节点类型 我们创建了一个 ListNode 类为节点类型,里面有两个成员变量,val用来存储数值

  • C语言 超详细模拟实现单链表的基本操作建议收藏

    目录 1 链表的概念及结构 2 链表的分类 3 链表的实现无头+单向+非循环链表增删查改实现 3.1 链表的定义 3.2 链表数据的打印 3.3 链表的尾插 3.4 链表空间的动态申请 3.5 链表的头插 3.6 链表的尾删 3.7 链表的头删 3.8 链表任意位置的前插入 3.9 链表任意位置的后插入 3.10 链表的任意位置的删除 3.11 链表的任意位置的前删除 3.12 链表的任意位置的后删除 3.13 链表的销毁 3.14 链表的总结 1 链表的概念及结构 概念:链表是一种物理存储结构

  • C语言 超详细模拟实现单链表的基本操作建议收藏

    目录 1 链表的概念及结构 2 链表的分类 3 链表的实现无头+单向+非循环链表增删查改实现 3.1 链表的定义 3.2 链表数据的打印 3.3 链表的尾插 3.4 链表空间的动态申请 3.5 链表的头插 3.6 链表的尾删 3.7 链表的头删 3.8 链表任意位置的前插入 3.9 链表任意位置的后插入 3.10 链表的任意位置的删除 3.11 链表的任意位置的前删除 3.12 链表的任意位置的后删除 3.13 链表的销毁 3.14 链表的总结 1 链表的概念及结构 概念:链表是一种物理存储结构

  • PHP中模拟链表和链表的基本操作示例

    模拟链表: <?php /** * PHP实现链表的基本操作 */ class linkList { /** * 姓名 * @var string */ public $name = ''; /** * 编号 * @var int */ public $id = 0; /* * 引用下一个对象 */ public $next = null; /** * 构造函数初始化数据 * @param int $id * @param string $name */ public function __co

随机推荐