初阶数据结构——顺序表以及通讯录的实现

初阶数据结构——顺序表以及通讯录的实现

  1. 顺序表的分类
  • 顺序表和数组的区别:顺序表的底层结构是数组,是对数组的封装,实现了常用的增删改查等接口
  • 静态顺序表:
//使用宏定义数据类型,方便修改
#define SLDateType int
typedef struct SeqList
{
	SLDateType arr[10];//定长数组
	int size;//有效数据的个数
}SL;

注意:静态顺序表存在一定的缺陷,空间给少了造成数据丢失,空间给多了造成空间浪费。

  • 动态顺序表:
//使用宏定义数据类型,方便修改
#define SLDateType int
typedef struct SeqList
{
	SLDateType* arr;//大小可以动态变化
	int size;//有效数据的个数
	int capacity;//空间容量(即申请空间的总大小)
}SL;

注意:动态顺序表可以进行扩容,一般为原来容量的2~3倍。

  1. 动态顺序表的实现

为了实现动态顺序表,我们可以通过多文件的方式,让代码的可读性更高。

在这里我们使用两个文件 SL.h 头文件,用于声明顺序表(结构体)以及相关函数,和 SeqList.c 文件,用于实现相关函数。

文件 SL.h :

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

#define SLDataType int
typedef struct SeqList
{
	SLDataType* arr;//大小可以动态变化
	int size;//有效数据的个数
	int capacity;//总空间的大小
}SL;
//函数部分
void SLInit(SL* ps);
void SLCheckCapacity(SL* ps);
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPopFront(SL* ps);
void SLInsert(SL* ps, int pos, SLDataType x);
void SLErease(SL* ps, int pos);
void SLDestroy(SL* ps);

我们可以在头文件中包含头文件,方便后续测试。

注意:使用 SLDataType 来替换 int ,可以让代码适用于各种数据类型,例如整型、浮点型、结构体类型等,方便后期的修改。

文件 SeqList.c 函数讲解:

顺序表的初始化:

void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}

顺序表的空间检查:

void SLCheckCapacity(SL* ps)
{
	int NewCapacity = 0;
	if (ps->size == ps->capacity)
	{

		if (ps->capacity == 0)//这是刚初始化完成,在这里给他赋一个初值,要不然NewCapacity一直是0
		{
			NewCapacity = 4;
		}
		
		else//一般情况下,二倍扩容
		{
			NewCapacity = 2 * (ps->capacity);
		}
		//使用函数申请空间
		SLDataType* ptr = (SLDataType*)realloc(ps->arr, NewCapacity * sizeof(SLDataType));
		assert(ptr != NULL);
		ps->arr = ptr;
		ps->capacity = NewCapacity;
		ptr = NULL;
	}
}

当我们想要插入元素时,必须要检查该顺序表的空间是否足够,防止越界。

每执行一次函数功能,仅插入一个元素,因此不用考虑扩容后是否可以容纳全部元素。扩容会涉及到使用函数realloc( ),因此我们定义一个新的变量NewCapacity(包括后续的ptr),防止发生意外来影响我们原先的数据。

当我们第一次给顺序表中添加元素时,由于size和capacity均为0,我们可以给NewCapacity一个处初值4,后续通常扩容原本空间大小的2~4倍。

顺序表的尾插与头插:

//尾插
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);//检查并且修改空间,之后空间条件满足
	ps->arr[ps->size] = x;
	(ps->size)++;//记住size的值要发生变化
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps != NULL);
	SLCheckCapacity(ps);//检查并且修改空间,之后空间条件满足
	//将数据从后向前依次往后移动一位
	for (int i = 0; i < ps->size; i++)
	{
		ps->arr[ps->size - i] = ps->arr[ps->size - 1 - i];
	}
	ps->arr[0] = x;
	(ps->size)++;
}

需要注意size的变化和数据移动的问题,头插过程中,数据是从后向前,依次向后移动一位,这样可以避免数据被覆盖。

顺序表的尾删与头删:

//尾删
void SLPopBack(SL* ps)
{
	assert(ps != NULL);
	assert(ps->size != 0);//该顺序表至少存在一个元素
	(ps->size)--;//直接改变size即可,反正没有赋值的元素是随机值
}
//头删
void SLPopFront(SL* ps)
{
	assert(ps != NULL);
	assert(ps->size != 0);//该顺序表至少存在一个元素
	//将数据从前向后依次向前移动一位
	for (int i = 1; i < ps->size; i++)
	{
		ps->arr[i - 1] = ps->arr[i];
	}
	(ps->size)--;
}

尾删直接让size减一即可,头删需要让数据从前向后,依次向前移动一位。

指定位置删除与销毁:

//在指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps != NULL);
	assert(pos < ps->size && pos >= 0);
	SLCheckCapacity(ps);//检查并且修改空间,之后空间条件满足
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	(ps->size)++;
}
//在指定位置删除数据
void SLErease(SL* ps, int pos)
{
	assert(ps != NULL);
	assert(pos < ps->size && pos >= 0);
	//将数据从前往后依次向前移动一个单位
	for (int i = 0; i < (ps->size - pos); i++)
	{
		ps->arr[pos + i] = ps->arr[pos + i + 1];
	}
	(ps->size)--;
}

与之前所述的插入与删除相同,通过分析我们可以简单得出数据移动的方向。

顺序表的销毁:

//顺序表的销毁
void SLDestroy(SL* ps)
{
	assert(ps != NULL);
	if (ps->arr != NULL)
	{
		free(ps->arr);
		ps->arr = NULL;
	}
	ps->size = ps->capacity = 0;
}

动态内存获取的数据如果不销毁,就会造成内存泄漏。

  • 完整版SeqList.c
//顺序表的初始化
void SLInit(SL* ps)
{
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
//顺序表的空间检查——在修改数据之前,应当先检查空间
void SLCheckCapacity(SL* ps)
{
	int NewCapacity = 0;
	//当元素个数和最大容量相等时,想要插入元素必须扩容
	//一次检查,仅仅插入一个元素,不用考虑插入元素的个数,多个元素循环插入
	if (ps->size == ps->capacity)
	{

		if (ps->capacity == 0)//这是刚初始化完成,在这里给他赋一个初值,要不然NewCapacity一直是0
		{
			NewCapacity = 4;
		}
		
		else//一般情况下,二倍扩容
		{
			NewCapacity = 2 * (ps->capacity);
		}
		//使用函数申请空间
		SLDataType* ptr = (SLDataType*)realloc(ps->arr, NewCapacity * sizeof(SLDataType));
		assert(ptr != NULL);
		ps->arr = ptr;
		ps->capacity = NewCapacity;
		ptr = NULL;
	}
}
//尾插插入数据
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps);
	SLCheckCapacity(ps);//检查并且修改空间,之后空间条件满足
	ps->arr[ps->size] = x;
	(ps->size)++;//记住size的值要发生变化
}
//头插插入数据
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps != NULL);
	SLCheckCapacity(ps);//检查并且修改空间,之后空间条件满足
	//将数据从后向前依次往后移动一位
	for (int i = 0; i < ps->size; i++)
	{
		ps->arr[ps->size - i] = ps->arr[ps->size - 1 - i];
	}
	ps->arr[0] = x;
	(ps->size)++;
}
//尾删删除数据
void SLPopBack(SL* ps)
{
	assert(ps != NULL);
	assert(ps->size != 0);//该顺序表至少存在一个元素
	(ps->size)--;//直接改变size即可,反正没有赋值的元素是随机值
}
//头删删除数据
void SLPopFront(SL* ps)
{
	assert(ps != NULL);
	assert(ps->size != 0);//该顺序表至少存在一个元素
	//将数据从前向后依次向前移动一位
	for (int i = 1; i < ps->size; i++)
	{
		ps->arr[i - 1] = ps->arr[i];
	}
	(ps->size)--;
}
//在指定位置插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	assert(ps != NULL);
	assert(pos < ps->size && pos >= 0);
	SLCheckCapacity(ps);//检查并且修改空间,之后空间条件满足
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	(ps->size)++;
}
//在指定位置删除数据
void SLErease(SL* ps, int pos)
{
	assert(ps != NULL);
	assert(pos < ps->size && pos >= 0);
	//将数据从前往后依次向前移动一个单位
	for (int i = 0; i < (ps->size - pos); i++)
	{
		ps->arr[pos + i] = ps->arr[pos + i + 1];
	}
	(ps->size)--;
}
//顺序表的销毁
void SLDestroy(SL* ps)
{
	assert(ps != NULL);
	if (ps->arr != NULL)
	{
		free(ps->arr);
		ps->arr = NULL;
	}
	ps->size = ps->capacity = 0;
}
  1. 动态顺序表的应用——简易通讯录的实现

通过上述动态顺序表的实现,我们已经获得了两个文件:SL.h 和 SeqList.c 文件,接下来我们就可以使用这两个文件来实现通讯录项目。

  • 通讯录基本的功能包括:增加联系人、删除联系人、修改联系人、查找联系人、展示联系人。

由于联系人是一个复杂对象,因此我们需要用到自定义类型——结构体去实现。

再头文件 contact.h 中创造一个联系人结构体,用于储存联系人的信息:

//定义一个联系人的结构体
#define NAME_MAX 20
#define TEL_MAX 20

typedef struct personInfo
{
	char name[NAME_MAX];
	char tel[TEL_MAX];
}pInfo;

typedef struct Seqlist Contant;//将原本的数据表改为通讯录,更加直观

注意:该处使用代码:typedef struct Seqlist Contant,而不使用:typrdef SL Contant。

  • 补充说明:结构体的提前声明
#include<stdio.h>
typedef struct SeqList Contact;

void Fun(Contact* s)
{
	printf("struct");
}
struct SeqList
{
	int* arr;
	int size;
	int capacity;
};
int main()
{
	Contact* C1 = { 0 };
	Fun(C1);
	return 0;
}

函数Fun( ) 可以成功执行。

我们在声明结构体前使用typedef给结构体重命名,这就是结构体的提前声明,对应到通讯录中也是一样:

//头文件Contac.h内容
typedef struct personInfo
{
	//...
}pInfo;
typedef struct SeqList Contact; //声明部分
void ConInit(Contact* con); //函数声明部分

//头文件SeqList.h头文件
#include"Contact.h"
typedef pInfo SLdt;
typedef struct SeqList //定义部分
{
	//...
}SL;
void SLInit(SL* ps);

Contact.h中函数的实现:

我们将这些函数的实现封装在文件 ContactFun.c 文件中。

  • 初始化数据和销毁数据
//初始化数据
int R = 0;//仅用于接受scanf函数的返回值
void ConInit(Contact* con)
{
	assert(con != NULL);
	SLInit(con);
}
//销毁数据
void ConDestory(Contact* con)
{
	SLDestory(con);
}
  • 添加联系人

注意:以下函数出现的全局变量R,仅用来接受scanf( ) 函数的返回值。

//增加联系人(name+tel)
void ConAdd(Contact* con)
{
	pInfo In = { 0 };
	//用户手动输入以下信息
	printf("请输入你想要添加的联系人的个数:");
	int num = 0;
	R = scanf("%d", &num);
	assert(num >= 1);
	for (int i = 1; i <= num; i++)
	{
		printf("输入第%d个要添加联系人的名字和电话:\n",i);
		R = scanf("%s %s", In.name,In.tel);
		//用SL中函数添加信息
		SLPushBack(con, In);
	}
	printf("%d个联系人已添加完毕\n",num);
}

我们在之前定义了pInfo结构变量,用于储存联系人的信息。

使用SeqList中的尾插方法来添加联系人。

  • 联系人ID的查找:
//联系人的查找
int FindName(Contact* con)
{
	char nameF[NAME_MAX] = { 0 };
	R = scanf("%s", nameF);
	for (int i = 0; i < con->size; i++)
	{
		if (strcmp(nameF, con->arr[i].name) == 0)
		{
			return i;
		}
	}
	return -1;
}

该函数用来寻找联系人的ID,和判断是否存在该联系人。使用函数strcmp( ) 函数比较两个字符串是否相等,若存在相等的字符串则返回该联系人在数组中的ID,否则返回-1表示不存在。便于后续函数功能的实现。

  • 修改联系人
void ConChange(Contact* con)
{
	//输入需要修改的联系人姓名
	printf("输入想要修改的联系人姓名:");
	int ret = FindName(con);
	if (ret == -1)
	{
		printf("未发现该联系人\n");
	}
	else//已经发现了,该元素的ID就是ret
	{
		printf("输入修改后的名字与电话:");
		R = scanf("%s %s", con->arr[ret].name, con->arr[ret].tel);
		printf("修改成功\n");
		return;
	}
}

利用了上述联系人ID查找函数FindName( ) ,找到想要修改的联系人后,输入新的名字与电话覆盖即可。

注意:在代码中,Con就表示我们的SeqList结构体(改名后为Contact),通过SeqList的结构体指针找到对应储存联系人信息的数组(即SLdt* arr),找到对应的ID(即arr [ ret ] ),访问得到数组pInfo结构体的元素,即可修改。

  • 删除联系人
//删除联系人
void ConDeleat(Contact* con)
{
	printf("输入想要删除的联系人姓名:");
	int ret = FindName(con);
	if (ret == -1)
	{
		printf("未发现该联系人\n");
	}
	else//已经发现了,该元素的ID就是ret
	{
		SLErease(con, ret);
		printf("删除成功\n");
		return;
	}
}

使用SeqList.h文件中的删除元素的函数即可。

  • 展示联系人
//展示联系人
void ConShow(Contact* con)
{
	for (int i = 0; i < con->size; i++)
	{
		printf("姓名:%s 电话:%s\n", con->arr[i].name, con->arr[i].tel);
	}
	printf("已展示全部联系人\n");
}
  • 查找联系人
//查找联系人是否存在
void ConFind2(Contact* con)
{
	int m = FindName(con);
	if (m >= 0)
	{
		printf("输入你要查找联系人的姓名:\n");
		printf("发现该联系人:\n");
		printf("姓名:%s 电话:%s\n", con->arr[m].name, con->arr[m].tel);
	}
	else
	{
		printf("未发现该联系人\n");
	}
}

至此,ContactFun.c 文件中的主要函数就写完了。

下面我们通过main( ) 函数中的程序实现该通讯录:

为了方便实验,我们在SL.h 头文件中编写菜单函数menu( ) :

void menu()
{
	printf("****************************\n");
	printf("1.增加联系人	2.删除联系人\n");
	printf("3.修改联系人	4.查找联系人\n");
	printf("5.展示联系人	0.退出通讯录\n");
	printf("****************************\n");
}

由于我们在SL.h头文件中包含过Contact.h等文件,在主函数文件中包含一个头文件即可。

#include"SL.h"
int main()
{
	int R = 0;//仅用于接受scanf函数的返回值
	Contact C = { 0 };
	int x = 0;
	do
	{
		menu();
		printf("输入你所需要的功能:");
		R = scanf("%d", &x);
		switch (x)
		{
		case 1:
			ConAdd(&C);
			break;
		case 2:
			ConDeleat(&C);
			break;
		case 3:
			ConChange(&C);
		case 4:
			ConFind2(&C);
			break;
		case 5:
			ConShow(&C);
			break;
		case 0:
			ConDestory(&C);
			break;
		default:
			printf("输入错误,请重新输入\n");
		}
	} while (x);
	return 0;
}
  1. 完整代码
  • SL.h 头文件:
#pragma once
//Seqlist.h
//可用一个头文件包含多个头文件
#include"Contact.h"
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
typedef pInfo SLdt;

typedef struct SeqList
{
	SLdt* arr;
	int size;
	int capacity;
}SL;

void SLInit(SL* ps);

void SLCheckCapacity(SL* ps);

void SLPushBack(SL* ps, SLdt x);
void SLPushFront(SL* ps, SLdt x);

void SLPopBack(SL* ps);
void SLPopFront(SL* ps);

void SLErease(SL* ps, int pos);
void SLInsert(SL* ps, int pos, SLdt x);

void SLDestory(SL* ps);
  • Contact.h 头文件:
#pragma once
//定义一个联系人的结构体
#define NAME_MAX 20
#define TEL_MAX 20

typedef struct personInfo
{
	char name[NAME_MAX];
	char tel[TEL_MAX];
}pInfo;

typedef struct SeqList Contact;

void ConInit(Contact* con);
void ConAdd(Contact* con);

int FindName(Contact* con);

void ConFind2(Contact* con);
void ConChange(Contact* con);
void ConDeleat(Contact* con);
void ConShow(Contact* con);
void ConDestory(Contact* con);
void menu();

  • SeqList.c 文件
#include<assert.h>
#include<stdio.h>
#include<stdlib.h>
#include "SL.h"
//顺序表的初始化
void SLInit(SL* ps)
{
	//assert(ps != NULL);
	ps->arr = NULL;
	ps->capacity = 0;//空间总个数
	ps->size = 0;//元素个数
}
//空间的检查与申请
void SLCheckCapacity(SL* ps)
{
	int NewCapacity = 0;
	if (ps->capacity == ps->size)//相等则表示内存不够了
	{
		if (ps->capacity == 0)//刚初始化完成的情况
			NewCapacity = 4;
		else
			NewCapacity = (ps->capacity) * 2;//一般情况

		SLdt* ptr = (SLdt*)realloc(ps->arr, NewCapacity * sizeof(SLdt));//申请内存
		
		assert(ptr != NULL);
		ps->arr = ptr;
		ps->capacity = NewCapacity;
		ptr = NULL;
	}
}
//元素的尾插
void SLPushBack(SL* ps, SLdt x)
{
	assert(ps != NULL);
	SLCheckCapacity(ps);//检查内存
	ps->arr[ps->size] = x;
	(ps->size)++;
}
//元素的头插
void SLPushFront(SL* ps, SLdt x)
{
	assert(ps != NULL);
	SLCheckCapacity(ps);//检查内存
	//元素从后向前,依次向后移动一位
	for (int i = 0; i < ps->size; i++)
	{
		ps->arr[ps->size - i] = ps->arr[ps->size - 1 - i];
	}
	ps->arr[0] = x;
	(ps->size)++;
}
//元素的尾删
void SLPopBack(SL* ps)
{
	assert(ps != NULL);
	(ps->size)--;
}
//元素的头删
void SLPopFront(SL* ps)
{
	assert(ps != NULL);
	assert(ps->size != 0);
	//所有元素从前向后,依次向前移动一位
	for (int i = 1; i < ps->size; i++)
	{
		ps->arr[i - 1] = ps->arr[i];
	}
	(ps->size)--;
}
//指定位置删除数据
void SLErease(SL* ps, int pos)
{
	assert(ps != NULL);
	assert(ps->size != 0);
	assert(pos >= 0 && pos < ps->size);
	//该数据之后的数据,从前往后,依次向前移动一位
	for (int i = pos; i < ps->size; i++)
	{
		ps->arr[i] = ps->arr[i + 1];
	}
	(ps->size)--;
}
//指定位置插入数据
void SLInsert(SL* ps, int pos, SLdt x)
{
	assert(ps != NULL);
	assert(pos >= 0 && pos < ps->size);
	assert(ps->size != 0);
	//该数据之后的数据,从后向前,依次向后移动一位
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1];
	}
	ps->arr[pos] = x;
	(ps->size)++;
}
//顺序表的销毁
void SLDestory(SL* ps)
{
	assert(ps != NULL);
	free(ps->arr);
	ps->arr = NULL;
	ps->capacity = ps->size = 0;
}
  • ContactFun.c 文件
#include"SL.h"
//增加联系人、删除联系人、修改联系人、查找联系人、展示联系人
//初始化数据
int R = 0;//仅用于接受scanf函数的返回值
void ConInit(Contact* con)
{
	assert(con != NULL);
	SLInit(con);
}
//增加联系人(name+tel)
void ConAdd(Contact* con)
{
	pInfo In = { 0 };
	//用户手动输入以下信息
	printf("请输入你想要添加的联系人的个数:");
	int num = 0;
	R = scanf("%d", &num);
	assert(num >= 1);
	for (int i = 1; i <= num; i++)
	{
		printf("输入第%d个要添加联系人的名字和电话:\n",i);
		R = scanf("%s %s", In.name,In.tel);
		//用SL中函数添加信息
		SLPushBack(con, In);
	}
	printf("%d个联系人已添加完毕\n",num);
}
//联系人的查找
int FindName(Contact* con)//该函数仅用来找ID
{
	char nameF[NAME_MAX] = { 0 };
	R = scanf("%s", nameF);
	for (int i = 0; i < con->size; i++)
	{
		if (strcmp(nameF, con->arr[i].name) == 0)
		{
			return i;
		}
	}
	return -1;
}
//修改联系人
void ConChange(Contact* con)
{
	//输入需要修改的联系人姓名
	printf("输入想要修改的联系人姓名:");
	int ret = FindName(con);
	if (ret == -1)
	{
		printf("未发现该联系人\n");
	}
	else//已经发现了,该元素的ID就是ret
	{
		printf("输入修改后的名字与电话:");
		R = scanf("%s %s", con->arr[ret].name, con->arr[ret].tel);
		printf("修改成功\n");
		return;
	}
}
//删除联系人
void ConDeleat(Contact* con)
{
	printf("输入想要删除的联系人姓名:");
	int ret = FindName(con);
	if (ret == -1)
	{
		printf("未发现该联系人\n");
	}
	else//已经发现了,该元素的ID就是ret
	{
		SLErease(con, ret);
		printf("删除成功\n");
		return;
	}
}
//展示联系人
void ConShow(Contact* con)
{
	for (int i = 0; i < con->size; i++)
	{
		printf("姓名:%s 电话:%s\n", con->arr[i].name, con->arr[i].tel);
	}
	printf("已展示全部联系人\n");
}
//查找联系人是否存在
void ConFind2(Contact* con)
{
	int m = FindName(con);
	if (m >= 0)
	{
		printf("输入你要查找联系人的姓名:\n");
		printf("发现该联系人:\n");
		printf("姓名:%s 电话:%s\n", con->arr[m].name, con->arr[m].tel);
	}
	else
	{
		printf("未发现该联系人\n");
	}
}
//销毁数据
void ConDestory(Contact* con)
{
	SLDestory(con);
}
//菜单
void menu()
{
	printf("****************************\n");
	printf("1.增加联系人	2.删除联系人\n");
	printf("3.修改联系人	4.查找联系人\n");
	printf("5.展示联系人	0.退出通讯录\n");
	printf("****************************\n");
}
  • 实现文件:
#include"SL.h"
int main()
{
	int R = 0;//仅用于接受scanf函数的返回值
	Contact C = { 0 };
	int x = 0;
	do
	{
		menu();
		printf("输入你所需要的功能:");
		R = scanf("%d", &x);
		switch (x)
		{
		case 1:
			ConAdd(&C);
			break;
		case 2:
			ConDeleat(&C);
			break;
		case 3:
			ConChange(&C);
		case 4:
			ConFind2(&C);
			break;
		case 5:
			ConShow(&C);
			break;
		case 0:
			ConDestory(&C);
			break;
		default:
			printf("输入错误,请重新输入\n");
		}
	} while (x);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值