C语言数据结构—— 动态顺序表代码(超详解)

目录

顺序表的简单介绍

顺序表的基础功能

顺序表的初始化

顺序表的清空

顺序表的销毁

顺序表的插入功能

顺序表的头部插入

顺序表的尾部插入

顺序表指定位置的头部插入

顺序表指定位置的尾部插入

顺序表的指定位置插入

顺序表的删除功能

顺序表的头部删除

顺序表的尾部删除

顺序表指定位置的头部删除

顺序表指定位置的尾部删除

顺序表的指定位置删除

顺序表的查找功能


一.顺序表的简单介绍

        在正式开始介绍我们的顺序表之前我先带大家认识一个数据结构中的基础概念——线性表。

        线性表是多个具有相同特性的数据元素的有限序列,我们这里要理解的是线性表是一个比较宽泛的概念,线性表是一种在实际中广泛使用的数据结构。它是顺序表、链表、栈、队列、字符串等等数据结构的统称。
        线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构(内存中)上并不一定是连续的,所以我们在实现线性表的时候在物理结构上既可以选择连续的存储结构(顺序存储),也可以选择非连续的存储结构(链式存储)。

        我们今天的重点就是实现线性表中的一种——顺序表。

        顺序表又可以分为动态顺序表和静态顺序表,二者最大的差别就在于动态顺序表的可存储空间是可以变化的,可以更好的适应不同的需求。所以我们今天讲的重点也就是动态顺序表,只要能看懂今天的代码,那你再去理解静态顺序表的一定会非常容易。

        提前声明一下,为了使大家可以更好的理解函数和变量,我已经按照自己的标准将函数名,变量名用自己的方法做了限制,包括用到的单词都做了翻译。

        以下一段注释就是相关的翻译和限制,虽然会和网络上的其他文章有所出入,但是大同小异,而且数据结构更重要的是算法的思维,大家不必纠结于变量的名字。

//相关英语单词的翻译
/*
* list			列表
* controls		操作
* function		函数
* variate		变量
* initialization	初始化
* capacity		扩容
* specify		指定
* destruction	销毁
* Delete		删除
* clear			清空
* cycle			循环
* find			查找
* tail			尾部
* Data			数据
* Location		位置
* Address		地址
* Front			前面
* Insert		插入
*/

//函数以及变量命名规则
/*
* 顺序表(SL) :SequenceList
*
* 函数命名规则:对象+(_基础_)+功能
* 结构体对象的命名:名字前加“_”
* 宏定义常量的命名:前后均加“_”
* 定义函数(function)是所有传入的变量定义均以“f”开头
* 函数传入的参数通过"f"后“p”的数量来说明指针的级数
* 函数中非结构体有关变量必须包含该标识 variate 或其他标识说明符号
*/

        接下来我们看详细的代码:

        首先我们定义顺序表的结构体:

//我们这篇文章以整形为例向大家讲解
typedef int Goaltype;	/*用来记录目标处理的数据类型,方变修改*/

struct SequenceList
{
	Goaltype* _Orginal;	/*记录顺序表的初始地址*/
	int _Efficient;		/*用来记录现在已经存储掉的数量*/
	int _Global;		/*用来记录最大可以存储的数量*/
};

typedef struct SequenceList SequenceList;		/*结构体名字的定义*/
typedef struct SequenceList* pSequenceList;		/*结构体指针的定义*/

二.顺序表的基础功能

1.顺序表的初始化

/*顺序表的初始化*/
	/*返回值(pSequenceList):生成顺序表的地址*/
	/*功能:构造并初始化一个顺序表*/
pSequenceList SLInitialization()
{
	/*初始化分配一个内存空间的大小,不存储内容*/
	pSequenceList goal = (pSequenceList)malloc(sizeof(SequenceList));
	Goaltype* middle = (Goaltype*)malloc(sizeof(Goaltype));
	if (!(middle && goal))
		printf("申请内存空间失败,初始化失败\n");
	else
	{
		goal->_Orginal = middle;
		goal->_Efficient = 0;
		goal->_Global = 1;
	}
	return goal;
}

2.顺序表的清空

/*顺序表的清空*/
	/*参数:需要被清空的顺序表的地址*/
void SLClear(pSequenceList fps)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法操作\n");
	else
	{
		free(fps->_Orginal);
		fps->_Orginal = (Goaltype*)malloc(sizeof(Goaltype));
		fps->_Efficient = 0;
		fps->_Global = 1;
	}
}

3.顺序表的销毁

/*顺序表的销毁*/
	/*参数:需要销毁的顺序表的地址*/
void SLDestruction(pSequenceList* const fpps)
{
	if (fpps == NULL)
		printf("您所传入的为无效操作指针,无法销毁\n");
	else
	{
		free((*fpps)->_Orginal);/*所申请空间的释放*/
		free(*fpps);
		*fpps = NULL;
	}
}

4.顺序表的所有内容的输出

/*顺序表中所有内容的输出*/
	/*参数:需要被输出的顺序表的地址*/
void SLPrintf(pSequenceList const fps)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法输出\n");
	else
	{
		if (fps->_Efficient == 0)
			printf("该顺序表存在但未存储相关数据");
		else
		{
			for (int i = 0; i < fps->_Efficient; i++)
				printf("%3d", *(fps->_Orginal + i));
			printf("\n");
		}
	}
}

5.调用同一函数对顺序表的每一个元素进行同一操作

/*调用同一函数对顺序表的每一个元素进行同一操作*/
	/*参数1:要操作的顺序表*/ 
	/*参数2:调用的函数*/
void SLCycle(pSequenceList fps, void (*function)(Goaltype))
{
	for (int i = 0; i < fps->_Efficient; i++)
		function(*(fps->_Orginal + i));
	printf("\n");
}

6.内存的扩容申请

/*内存的扩容申请*/
	/*返回值:扩容成功->1,扩容失败->0 */
	/*参数:需要被初始化的顺序表的地址*/
int SLCapacity(pSequenceList const fps)//每次扩容为原来可存储空间的两倍
{
	if (fps == NULL)
	{
		printf("您所传入的为无效操作指针,无法扩容\n");
		return 0;
	}
	else
	{
		Goaltype* middle = (Goaltype*)realloc(fps->_Orginal, sizeof(Goaltype) * 2 * (fps->_Global));
		if (middle == NULL)
		{
			printf("申请内存空间失败\n");
			return 0;
		}
		else
		{
			fps->_Orginal = middle;
			fps->_Global = 2 * fps->_Global;
			return 1;
		}
	}
}

7.指定大小的内存扩容

/*指定大小的内存扩容*/
	/*返回值:扩容成功->1,扩容失败->0 */
	/*参数1:需要被初始化的顺序表的地址*/
	/*参数2:指定的可以多存储的数量*/
int SLSpecifyCapacity(pSequenceList const fps, const int fnum)
{
	if (fps == NULL)
	{
		printf("您所传入的为无效操作指针,无法扩容\n");
		return 0;
	}
	else
	{
		Goaltype* middle = (Goaltype*)realloc(fps->_Orginal, sizeof(Goaltype) * ((fps->_Global) + fnum));
		if (middle == NULL)
		{
			printf("申请内存空间失败\n");
			return 0;
		}
		else
		{
			fps->_Orginal = middle;
			fps->_Global = fnum + fps->_Global;
			return 1;
		}
	}
}

三.顺序表的查找功能

        我们在使用数据的过程中一定会先查找数据,那么查找数据就会涉及到数据的逻辑位置,物理地址,以及数据本身,我们的第三部分就用代码将这三个部分联系起来。

1.返回指定数据的位置

/*返回指定数据的位置*/
	/*返回值:查找成功->位置,查找失败->0 */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的数据*/
int SLFindLocationOfData(pSequenceList fps, Goaltype fvariate)
{
	if (fps == NULL)
	{
		printf("您所传入的为无效操作指针,无法查找\n");
		return 0;
	}
	else
	{
		for (int i = 0; i < fps->_Efficient; i++)
			if (*(fps->_Orginal + i) == fvariate)
				return i + 1;
		return 0;
	}
}

2.返回指定数据的地址

/*返回指定数据的地址*/
	/*返回值:查找成功->地址,查找失败->NULL */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的数据*/
void* SLFindAddressOfData(pSequenceList fps, Goaltype fvariate)
{
	if (fps == NULL)
	{
		printf("您所传入的为无效操作指针,无法查找\n");
		return NULL;
	}
	else
	{
		for (int i = 0; i < fps->_Efficient; i++)
			if (*(fps->_Orginal + i) == fvariate)
				return fps->_Orginal + i;
		return NULL;
	}
}

3.返回指定位置的地址

/*返回指定位置的地址*/
	/*返回值:查找成功->地址,查找失败->NULL */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的位置*/
void* SLFindAddressOfLocation(pSequenceList fps, int flocation)
{
	if (fps == NULL)
	{
		printf("您所传入的为无效操作指针,无法查找\n");
		return NULL;
	}
	else
	{
		pSequenceList middle = fps;
		if (flocation < 1 || fps->_Efficient < flocation)
			return NULL;
		else
			return fps->_Orginal + flocation - 1;
	}
}

4.返回指定位置的数据

/*返回指定位置的数据*/
	/*返回值:查找成功->数据,查找失败->-1(如果实际数据中-1为有效值可以更改) */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的位置*/
Goaltype SLFindDataOfLocation(pSequenceList fps, int flocation)
{
	return *(fps->_Orginal + flocation - 1);
}

5.返回指定地址的数据

/*返回指定地址的数据*/
	/*返回值:查找成功->数据,查找失败->-1(如果实际数据中-1为有效值可以更改) */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的地址*/
Goaltype SLFindDataOfAddress(pSequenceList fps, Goaltype* fpaddress)
{
	return *fpaddress;
}

6.返回指定地址的位置

/*返回指定地址的位置*/
	/*返回值:查找成功->位置,查找失败->0 */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的地址*/
int SLFindLocationOfAddress(pSequenceList fps, Goaltype* fpaddress)
{
	int goal = (int)(fpaddress - fps->_Orginal) + 1;
	if (goal >= 1 && goal <= fps->_Efficient)
		return goal;
	else
		return 0;
}

四.顺序表的插入功能

1.顺序表的头部插入

/*顺序表的头部插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
void SLFrontInsert(pSequenceList const fps, Goaltype fvariate)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法插入\n");
	else
	{
		int judge = 1;/*用于判断内存的状态*/
		if ((fps->_Global) == (fps->_Efficient))
			judge = SLCapacity(fps);
		if (judge == 0)
			printf("内存不足且扩容失败,插入失败\n");
		else
		{
			for (int i = fps->_Efficient; i > 0; i--)
				fps->_Orginal[i] = fps->_Orginal[i - 1];
			fps->_Orginal[0] = fvariate;
			fps->_Efficient++;
		}
	}
}

2.顺序表的尾部插入

/*顺序表的尾部插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
void SLTailInser(pSequenceList fps, Goaltype fvariate)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法插入\n");
	else
	{
		int judge = 1;/*用于判断内存的状态*/
		if ((fps->_Global) == (fps->_Efficient))
			judge = SLCapacity(fps);
		if (judge == 0)
			printf("内存不足且扩容失败,插入失败\n");
		else
		{
			fps->_Orginal[fps->_Efficient] = fvariate;
			fps->_Efficient++;
		}
	}
}

3.顺序表指定位置的头部插入

/*顺序表的指定位置的头部插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
	/*参数3:需要被指定插入的位置*/
void SLSpecifyFrontInsert(pSequenceList fps, Goaltype fvariate, int flocation)
{
	if (fps == NULL)
	printf("您所传入的为无效操作指针,无法插入\n");
	else
	{
		int judge = 1;/*用于判断内存的状态*/
		if ((fps->_Global) == (fps->_Efficient))
			judge = SLCapacity(fps);
		if (judge == 0)
			printf("内存不足且扩容失败,插入失败\n");
		else
		{
			if (fps->_Efficient < flocation || flocation < 1)
				printf("随机目标不存在,无法执行插入操作\n");
			else
			{
				for (int i = fps->_Efficient; i >= flocation; i--)
					*(fps->_Orginal + i) = *(fps->_Orginal + i - 1);
				*(fps->_Orginal + flocation - 1) = fvariate;
				fps->_Efficient++;
			}
		}
	}
}

4.顺序表指定位置的尾部插入

/*顺序表的指定尾部插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
	/*参数3:需要被指定插入的位置*/
void SLSpecifyTailInser(pSequenceList fps, Goaltype fvariate, int flocation)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法插入\n");
	else
	{
		int judge = 1;/*用于判断内存的状态*/
		if ((fps->_Global) == (fps->_Efficient))
			judge = SLCapacity(fps);
		if (judge == 0)
			printf("内存不足且扩容失败,插入失败\n");
		else
		{
			if (fps->_Efficient < flocation || flocation < 1)
				printf("随机目标不存在,无法执行插入操作\n");
			else
			{ 
				for (int i = fps->_Efficient ; i > flocation; i--)
					*(fps->_Orginal + i) = *(fps->_Orginal + i - 1);
				*(fps->_Orginal + flocation) = fvariate;
				fps->_Efficient++;
			}
		}
	}
}

5.顺序表的指定位置插入

/*顺序表的指定插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
	/*参数3:需要被指定插入的位置*/
void SLSpecifyInser(pSequenceList fps, Goaltype fvariate, int flocation)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法插入\n");
	else
	{
		int judge = 1;/*用于判断内存的状态*/
		if ((fps->_Global) == (fps->_Efficient))
			judge = SLCapacity(fps);
		if (judge == 0)
			printf("内存不足且扩容失败,插入失败\n");
		else
		{
			if (fps->_Efficient + 1 < flocation || flocation < 1)
				printf("无法在此位置执行插入操作\n");
			else
			{
				for (int i = fps->_Efficient; i >= flocation; i--)
					*(fps->_Orginal + i) = *(fps->_Orginal + i - 1);
				*(fps->_Orginal + flocation - 1) = fvariate;
				fps->_Efficient++;
			}
		}
	}
}

五.顺序表的删除功能

1.顺序表的头部删除

/*顺序表的头删*/
	/*参数:需要被删除的顺序表的地址*/
void SLFrontDelete(pSequenceList fps)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法操作");
	else
	{
		for (int i = 0; i < fps->_Efficient - 1; i++)
			*(fps->_Orginal + i) = *(fps->_Orginal + i + 1);
		fps->_Efficient--;
	}
}

2.顺序表的尾部删除

/*顺序表的尾删*/
	/*参数:需要被删除的顺序表的地址*/
void SLTailDelete(pSequenceList fps)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法操作");
	else
		fps->_Efficient--;
}

3.顺序表指定位置的头部删除

/*顺序表的指定位置的头部删除*/
	/*参数1:需要被删除的顺序表的地址*/
	/*参数2:需要被指定删除的位置(非下标)*/
void SLSpecifyFrontDelete(pSequenceList fps, int flocation)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法删除");
	else
	{
		if (fps->_Efficient < flocation || flocation <= 1)
			printf("随机目标不存在,无法执行删除操作");
		else
		{
			for (int i = flocation; i <= fps->_Efficient ; i++)
				*(fps->_Orginal + i - 2) = *(fps->_Orginal + i - 1);
			fps->_Efficient--;
		}
	}
}

4.顺序表指定位置的尾部删除

/*顺序表的指定位置的尾部删除*/
	/*参数1:需要被删除的顺序表的地址*/
	/*参数2:需要被指定删除的位置*/
void SLSpecifyTailDelete(pSequenceList fps, int flocation)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法删除");
	else
	{
		if (flocation >= fps->_Efficient||flocation<1)
		{
			printf("该随机目标无法执行该删除动作");
		}
		else
		{
			for (int i = flocation; i < fps->_Efficient ; i++)
			{
				*(fps->_Orginal + i) = *(fps->_Orginal + i + 1);
			}
			fps->_Efficient--;
		}
	}
}

5.顺序表的指定位置删除

/*顺序表的指定删除*/
	/*参数1:需要被删除的顺序表的地址*/
	/*参数2:需要被指定删除的位置*/
void SLSpecifyDelete(pSequenceList fps, int flocation)
{
	if (fps == NULL)
		printf("您所传入的为无效操作指针,无法删除");
	else
	{
		if (flocation > fps->_Efficient || flocation <= 0)
		{
			printf("该随机目标不在存储范围内,无法执行该删除动作");
		}
		else
		{
			for (int i = flocation; i < fps->_Efficient; i++)
			{
				*(fps->_Orginal + i - 1) = *(fps->_Orginal + i);
			}
			fps->_Efficient--;
		}
	}
}

六.头文件展示

#include <stdio.h>
#include <stdlib.h>

//顺序表的定义  

typedef int Goaltype;	/*用来记录目标处理的数据类型*/

struct SequenceList
{
	Goaltype* _Orginal;	/*记录顺序表的初始地址*/
	int _Efficient;		/*用来记录现在已经存储掉的数量*/
	int _Global;		/*用来记录最大可以存储的数量*/
};

typedef struct SequenceList SequenceList;		/*结构体名字的定义*/
typedef struct SequenceList* pSequenceList;		/*结构体指针的定义*/
 
//顺序表的功能
/*顺序表的初始化生成*/
	/*返回值(pSequenceList):生成顺序表的地址*/
	/*构造并初始化一个顺序表,对顺序表提供可存储一个数据的空间*/
pSequenceList SLInitialization();
 
/*顺序表的销毁*/
	/*参数:需要被销毁的顺序表地址的地址*/
void SLDestruction(pSequenceList* fpps);

/*顺序表的清空*/
	/*参数:需要被清空的顺序表的地址*/
void SLClear(pSequenceList fps);

/*顺序表中所有内容的输出*/
	/*参数:需要被输出的顺序表的地址*/
void SLPrintf(pSequenceList fps);
 
/*调用同一函数对顺序表的每一个元素进行同一操作*/
	/*参数1:要操作的顺序表*/
	/*参数2:调用的函数*/
void SLCycle(pSequenceList fps, void (*function)(Goaltype));

/*内存的扩容申请*/
	/*返回值:扩容成功->1,扩容失败->0 */
	/*参数:需要被初始化的顺序表的地址*/
int SLCapacity(pSequenceList fps);//每次扩容为原来可存储空间的两倍

/*指定大小的内存扩容*/
	/*返回值:扩容成功->1,扩容失败->0 */
	/*参数1:需要被初始化的顺序表的地址*/
	/*参数2:指定的可以多存储的数量*/
int SLSpecifyCapacity(pSequenceList const fps, const int fnum);

/*返回指定数据的位置*/
	/*返回值:查找成功->位置,查找失败->0 */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的数据*/
int SLFindLocationOfData(pSequenceList fps, Goaltype fvariate);

/*返回指定数据的地址*/
	/*返回值:查找成功->地址,查找失败->NULL */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的数据*/
void* SLFindAddressOfData(pSequenceList fps, Goaltype fvariate);

/*返回指定位置的地址*/
	/*返回值:查找成功->地址,查找失败->NULL */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的位置*/
void* SLFindAddressOfLocation(pSequenceList fps, int flocation);

/*返回指定位置的数据*/
	/*返回值:查找成功->数据,查找失败->-1(如果实际数据中-1为有效值可以更改) */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的位置*/
Goaltype SLFindDataOfLocation(pSequenceList fps, int flocation);

/*返回指定地址的数据*/
	/*返回值:查找成功->数据,查找失败->-1(如果实际数据中-1为有效值可以更改) */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的地址*/
Goaltype SLFindDataOfAddress(pSequenceList fps, Goaltype* fpaddress);

/*返回指定地址的位置*/
	/*返回值:查找成功->位置,查找失败->0 */
	/*参数1:需要被查找的顺序表的地址*/
	/*参数2:需要被查找的地址*/
int SLFindLocationOfAddress(pSequenceList fps, Goaltype* fpaddress);

/*顺序表的头部插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
void SLFrontInsert(pSequenceList fps, Goaltype fvariate);

/*顺序表的尾部插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
void SLTailInser(pSequenceList fps, Goaltype fvariate);

/*顺序表的指定位置的头部插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
	/*参数3:需要被指定插入的下标*/
void SLSpecifyFrontInsert(pSequenceList fps, Goaltype fvariate, int flocation);

/*顺序表的指定位置的尾部插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
	/*参数3:需要被指定插入的位置*/
void SLSpecifyTailInser(pSequenceList fps, Goaltype fvariate, int flocation);

/*顺序表的指定插入*/
	/*参数1:需要被插入的顺序表的地址*/
	/*参数2:需要被插入的内容*/
	/*参数3:需要被指定插入的位置*/
void SLSpecifyInser(pSequenceList fps, Goaltype fvariate, int flocation);

/*顺序表的头删*/
	/*参数:需要被删除的顺序表的地址*/
void SLFrontDelete(pSequenceList fps);

/*顺序表的尾删*/
	/*参数:需要被删除的顺序表的地址*/
void SLTailDelete(pSequenceList fps);

/*顺序表的指定位置的头部删除*/
	/*参数1:需要被删除的顺序表的地址*/
	/*参数2:需要被指定删除的位置*/
void SLSpecifyFrontDelete(pSequenceList fps, int flocation);

/*顺序表的指定位置的尾部删除*/
	/*参数1:需要被删除的顺序表的地址*/
	/*参数2:需要被指定删除的位置*/
void SLSpecifyTailDelete(pSequenceList fps, int flocation);

/*顺序表的指定删除*/
	/*参数1:需要被删除的顺序表的地址*/
	/*参数2:需要被指定删除的位置*/
void SLSpecifyDelete(pSequenceList fps, int flocation); 

以上就是所有内容,如发现有内容错误请及时联系作者改正,或者有其他问题也可以在评论区讨论,同时作者也会尽快推出下一篇的内容。

下一篇内容将发布——链表

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值