十五、FreeRTOS_移植与内部实现_链表提前预习

1.链表操作

1.1普通链表操作

#include "usart.h"

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

struct person {
	char *name;
	int age;
	struct person *couple;
};

struct person w;
struct person h;

int main(int argc, char **arg)
{
    
    HAL_Init();
    
    MX_USART1_UART_Init();
	
	w.name = "w";
	w.age  = 30;
	w.couple = &h;

	h.name = "h";
	h.age  = 30;
	h.couple = &w;
    
	printf("w's couple is %s\r\n", w.couple->name);
	
    
    while(1)
    {
    }
}

/*
w's couple is h
*/
#include "usart.h"

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

struct person {
	char *name;
	int age;
	struct person *next;
};

struct list {
	char *name; /* A班 */
	struct person *next;
};

int main(int argc, char **arg)
{
	struct list a_list;

	struct person p1;	
	
	a_list.name = "A";
	a_list.next = NULL;  /* 空链表 */
	
	p1.name = "a1";
	p1.next = NULL;
	
	a_list.next = &p1;	    
	
	printf("a list: %s\r\n", a_list.next->name);
    
    while(1)
    {
    }
}

/*
a list: a1
*/

1.2普通链表的创建和添加操作

1.3普通链表的删除操作

1.4普通链表的排序

#include "usart.h"

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

struct person {
	char *name;
	int age;
	struct person *next;
};

struct list {
	char *name; /* A班 */
	struct person *next;
};

void InitList(struct list *pList, char *name)
{
	pList->name = name;
	pList->next = NULL;
}

void AddItemToList(struct list *pList, struct person *new_person)
{
	struct person *last;
	
	/* 如果是空链表 */
	if (pList->next == NULL)
	{
		pList->next = new_person;
		new_person->next =NULL;
		return;
	}
	
	last = pList->next;
	while (last->next)
	{
		last = last->next;
	}
	
	/* last->next == NULL */
	last->next = new_person;
	new_person->next =NULL;
}

void DelItemFromList(struct list *pList, struct person *person)
{
	struct person *p = pList->next;
	struct person *pre = NULL;
	
	/* 找到person */
	while (p != NULL && p != person)
	{
		/* 后面还有人, 移动到下一个 */
		pre = p;
		p = p->next;
	}
	
	/* 退出的条件: p==NULL, p == person */
	if (p == NULL)
	{
		printf("can not find the person to del\r\n");
		return;
	}
	
	if (pre == NULL) /* 前面无人, 表示要删除的是第1项 */
	{
		pList->next = p->next;
	}
	else
	{
		pre->next = p->next;
	}	
}

void SortList(struct list *pList)
{
	struct person *pre;
	struct person *next;
	
	char *tmp_name;
	int tmp_age;
	
	pre = pList->next;
	if (pre == NULL)
		return;
	
	while (pre)
	{
		next = pre->next;
		while (next)
		{
			if (pre->age > next->age)
			{
				/* 交换值 */
				tmp_name = pre->name;
				pre->name = next->name;
				next->name = tmp_name;

				tmp_age = pre->age;
				pre->age = next->age;
				next->age = tmp_age;
			}
			
			next = next->next;
		}
		
		pre = pre->next;
	}
	
}

void PrintList(struct list *pList)
{
	int i = 0;
	
	struct person *p = pList->next;
	
	while (p != NULL)
	{
		printf("person %d: %s is %d\r\n", i++, p->name, p->age);
		
		/* 后面还有人, 移动到下一个 */
		p = p->next;
	}
}

int main(int argc, char **arg)
{
	struct list a_list;
	int i;

	struct person p[] = {
		{"p1", 10, NULL},
		{"p2", 20, NULL},
		{"p3", 13, NULL},
		{"p4", 41, NULL},
		{"p5", 56, NULL},
		{"p6", 12, NULL},
		{"p7", 9, NULL},
		{"p8", 21, NULL},
		{NULL, 0, NULL},
	};
	
	
    HAL_Init();
    
    MX_USART1_UART_Init();

	InitList(&a_list, "A_class");

	i = 0;
	while (p[i].name != NULL)
	{
		AddItemToList(&a_list, &p[i]);
		i++;
	}

	printf("add all person:\r\n");
	PrintList(&a_list);
	
	DelItemFromList(&a_list, &p[3]);

	printf("del person %s:\r\n", p[3].name);
	PrintList(&a_list);

	DelItemFromList(&a_list, &p[0]);
	
	printf("del person %s:\r\n", p[0].name);
	PrintList(&a_list);
	
	SortList(&a_list);
	printf("sort list, all person:\r\n");
	PrintList(&a_list);
    
    while(1)
    {
    }
}

void Error_Handler(void)
{
    printf("Error\r\n");
    while(1)
    {
    }
}


/*
add all person:
person 0: p1 is 10
person 1: p2 is 20
person 2: p3 is 13
person 3: p4 is 41
person 4: p5 is 56
person 5: p6 is 12
person 6: p7 is 9
person 7: p8 is 21
del person p4:
person 0: p1 is 10
person 1: p2 is 20
person 2: p3 is 13
person 3: p5 is 56
person 4: p6 is 12
person 5: p7 is 9
person 6: p8 is 21
del person p1:
person 0: p2 is 20
person 1: p3 is 13
person 2: p5 is 56
person 3: p6 is 12
person 4: p7 is 9
person 5: p8 is 21
sort list, all person:
person 0: p7 is 9
person 1: p6 is 12
person 2: p3 is 13
person 3: p2 is 20
person 4: p8 is 21
person 5: p5 is 56
*/

1.5普通链表的改进

#include "usart.h"

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

struct person {
	char *name;
	int age;
	struct person *next;
};

struct list {
	char *name; /* A班 */
	struct person head;
};

void InitList(struct list *pList, char *name)
{
	pList->name = name;
	pList->head.next = NULL;
}

void AddItemToList(struct list *pList, struct person *new_person)
{
	struct person *last = &pList->head;
	
	while (last->next)
	{
		last = last->next;
	}
	
	/* last->next == NULL */
	last->next = new_person;
	new_person->next =NULL;
}

void AddItemAfter(struct person *pre, struct person *new_person)
{
	new_person->next = pre->next;
	pre->next = new_person;
}

void DelItemFromList(struct list *pList, struct person *person)
{
	struct person *pre = &pList->head;
	
	/* 找到person */
	while (pre != NULL && pre->next != person)
	{
		pre = pre->next;
	}
	
	/* 没找到 */
	if (pre == NULL)
		return;
	else
		pre->next = person->next;
}

void SortList(struct list *pList)
{
	struct person *pre1 = &pList->head;
	struct person *pre2;
	struct person *cur = pre1->next;
	struct person *next;
	struct person *tmp;
		
	while (cur)
	{
		pre2 = cur;
		next = cur->next;
		while (next)
		{
			if (cur->age > next->age)
			{
				/* 交换节点 */
				/* 1. del cur */
				DelItemFromList(pList, cur);
				
				/* 2. del next */
				DelItemFromList(pList, next);
				
				/* 3. 在pre1之后插入next */
				AddItemAfter(pre1, next);
				
				/* 4. 在pre2之后插入cur */
				if (pre2 == cur)
					AddItemAfter(next, cur);
				else
					AddItemAfter(pre2, cur);
				
				/* 5. cur/next指针互换 */
				tmp = cur;
				cur = next;
				next = tmp;				
			}
			
			pre2 = next;
			next = next->next;
		}
		
		pre1 = cur;
		cur = cur->next;
	}
	
}

void PrintList(struct list *pList)
{
	int i = 0;
	
	struct person *p = pList->head.next;
	
	while (p != NULL)
	{
		printf("person %d: %s is %d\r\n", i++, p->name, p->age);
		
		/* 后面还有人, 移动到下一个 */
		p = p->next;
	}
}

int main(int argc, char **arg)
{
	struct list a_list;
	int i;

	struct person p[] = {
		{"p1", 10, NULL},
		{"p2", 20, NULL},
		{"p3", 13, NULL},
		{"p4", 41, NULL},
		{"p5", 56, NULL},
		{"p6", 12, NULL},
		{"p7", 9, NULL},
		{"p8", 21, NULL},
		{NULL, 0, NULL},
	};
	
	
    HAL_Init();
    
    MX_USART1_UART_Init();

	InitList(&a_list, "A_class");

	i = 0;
	while (p[i].name != NULL)
	{
		AddItemToList(&a_list, &p[i]);
		i++;
	}

	printf("add all person:\r\n");
	PrintList(&a_list);
	
	DelItemFromList(&a_list, &p[3]);

	printf("del person %s:\r\n", p[3].name);
	PrintList(&a_list);

	DelItemFromList(&a_list, &p[0]);
	
	printf("del person %s:\r\n", p[0].name);
	PrintList(&a_list);
	
	SortList(&a_list);
	printf("sort list, all person:\r\n");
	PrintList(&a_list);
    
    while(1)
    {
    }
}

void Error_Handler(void)
{
    printf("Error\r\n");
    while(1)
    {
    }
}


/*
add all person:
person 0: p1 is 10
person 1: p2 is 20
person 2: p3 is 13
person 3: p4 is 41
person 4: p5 is 56
person 5: p6 is 12
person 6: p7 is 9
person 7: p8 is 21
del person p4:
person 0: p1 is 10
person 1: p2 is 20
person 2: p3 is 13
person 3: p5 is 56
person 4: p6 is 12
person 5: p7 is 9
person 6: p8 is 21
del person p1:
person 0: p2 is 20
person 1: p3 is 13
person 2: p5 is 56
person 3: p6 is 12
person 4: p7 is 9
person 5: p8 is 21
sort list, all person:
person 0: p7 is 9
person 1: p6 is 12
person 2: p3 is 13
person 3: p2 is 20
person 4: p8 is 21
person 5: p5 is 56
*/

1.5.1普通链表为什么要改进 ?

没改之前,pre的头是list,p1的头是person,两个头不一样。

改进之后, pre的头是person,p1的头也是person,两个头是一致的,方便后期统一操作。

看代码,找区别!

没改之前

/*没改之前,主要区别是struct list结构体中的*next指针*/
/*InitList函数,pList->head = NULL; pList的头是list结构体*/

struct person {
	char *name;
	int age;
	struct person *next;
};

struct list {
	char *name; /* A班 */
	struct person *next;
};

void InitList(struct list *pList, char *name)
{
	pList->name = name;
	pList->next = NULL;
}

改进之后

/*改进之后,主要区别是struct list结构体中的next指针*/
/*InitList函数,pList->head.next = NULL; pList的头是person结构体*/

struct person {
	char *name;
	int age;
	struct person *next;
};

struct list {
	char *name; /* A班 */
	struct person head;
};

void InitList(struct list *pList, char *name)
{
	pList->name = name;
	pList->head.next = NULL;
}

1.6通用链表

#include "usart.h"

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

struct node_t {
	struct node_t *next;
};

struct person {
	char *name;
	int age;
	struct node_t node;
};

struct list {
	char *name; /* A班 */
	struct node_t head;
};

void InitList(struct list *pList, char *name)
{
	pList->name = name;
	pList->head.next = NULL;
}

void AddItemToList(struct list *pList, struct node_t *new_node)
{
	struct node_t *last = &pList->head;
	
	while (last->next)
	{
		last = last->next;
	}
	
	/* last->next == NULL */
	last->next = new_node;
	new_node->next =NULL;
}

void AddItemAfter(struct node_t *pre, struct node_t *new_node)
{
	new_node->next = pre->next;
	pre->next = new_node;
}

void DelItemFromList(struct list *pList, struct node_t *node)
{
	struct node_t *pre = &pList->head;
	
	/* 找到node */
	while (pre != NULL && pre->next != node)
	{
		pre = pre->next;
	}
	
	/* 没找到 */
	if (pre == NULL)
		return;
	else
		pre->next = node->next;
}

int CmpPersonAge(struct node_t *pre, struct node_t *next)
{
	struct person *p;
	struct person *n;
	
	p = (struct person *)((char *)pre - (unsigned int)&((struct person *)0)->node);
	n = (struct person *)((char *)next - (unsigned int)&((struct person *)0)->node);
	
	if (p->age < n->age)
		return -1;
	else
		return 0;
}

void SortList(struct list *pList)
{
	struct node_t *pre1 = &pList->head;
	struct node_t *pre2;
	struct node_t *cur = pre1->next;
	struct node_t *next;
	struct node_t *tmp;
		
	while (cur)
	{
		pre2 = cur;
		next = cur->next;
		while (next)
		{
			if (CmpPersonAge(cur, next) == 0)
			{
				/* 交换节点 */
				/* 1. del cur */
				DelItemFromList(pList, cur);
				
				/* 2. del next */
				DelItemFromList(pList, next);
				
				/* 3. 在pre1之后插入next */
				AddItemAfter(pre1, next);
				
				/* 4. 在pre2之后插入cur */
				if (pre2 == cur)
					AddItemAfter(next, cur);
				else
					AddItemAfter(pre2, cur);
				
				/* 5. cur/next指针互换 */
				tmp = cur;
				cur = next;
				next = tmp;				
			}
			
			pre2 = next;
			next = next->next;
		}
		
		pre1 = cur;
		cur = cur->next;
	}
	
}

void PrintList(struct list *pList)
{
	int i = 0;
	
	struct node_t *node = pList->head.next;
	struct person *p;
	
	while (node != NULL)
	{
		p = (struct person *)((char *)node - (unsigned int)&((struct person *)0)->node);;
		printf("person %d: %s is %d\r\n", i++, p->name, p->age);
		
		/* 后面还有人, 移动到下一个 */
		node = node->next;
	}
}

int main(int argc, char **arg)
{
	struct list a_list;
	int i;

	struct person p[] = {
//		{"p1", 10, {NULL}},
//		{"p2", 20, {NULL}},
//		{"p3", 13, {NULL}},
//		{"p4", 41, {NULL}},
//		{"p5", 56, {NULL}},
//		{"p6", 12, {NULL}},
//		{"p7", 9, {NULL}},
//		{"p8", 21, {NULL}},
//		{"p9", 22, {NULL}},
//		{"p10", 21, {NULL}},
//		{"p11", 20, {NULL}},
//		{NULL, 0, {NULL}},
		
		{"p1", 10, NULL},
		{"p2", 20, NULL},
		{"p3", 13, NULL},
		{"p4", 41, NULL},
		{"p5", 56, NULL},
		{"p6", 12, NULL},
		{"p7", 9, NULL},
		{"p8", 21, NULL},
		{"p9", 22, NULL},
		{"p10", 21, NULL},
		{"p11", 20, NULL},
		{NULL, 0, NULL},
	};
	
	
    HAL_Init();
    
    MX_USART1_UART_Init();

	InitList(&a_list, "A_class");

	i = 0;
	while (p[i].name != NULL)
	{
		AddItemToList(&a_list, &p[i].node);
		i++;
	}

	printf("add all person:\r\n");
	PrintList(&a_list);
	
	DelItemFromList(&a_list, &p[3].node);

	printf("del person %s:\r\n", p[3].name);
	PrintList(&a_list);

	DelItemFromList(&a_list, &p[0].node);
	
	printf("del person %s:\r\n", p[0].name);
	PrintList(&a_list);
	
	SortList(&a_list);
	printf("sort list, all person:\r\n");
	PrintList(&a_list);
    
    while(1)
    {
    }
}

void Error_Handler(void)
{
    printf("Error\r\n");
    while(1)
    {
    }
}

/*
add all person:
person 0: p1 is 10
person 1: p2 is 20
person 2: p3 is 13
person 3: p4 is 41
person 4: p5 is 56
person 5: p6 is 12
person 6: p7 is 9
person 7: p8 is 21
person 8: p9 is 22
person 9: p10 is 21
person 10: p11 is 20
del person p4:
person 0: p1 is 10
person 1: p2 is 20
person 2: p3 is 13
person 3: p5 is 56
person 4: p6 is 12
person 5: p7 is 9
person 6: p8 is 21
person 7: p9 is 22
person 8: p10 is 21
person 9: p11 is 20
del person p1:
person 0: p2 is 20
person 1: p3 is 13
person 2: p5 is 56
person 3: p6 is 12
person 4: p7 is 9
person 5: p8 is 21
person 6: p9 is 22
person 7: p10 is 21
person 8: p11 is 20
sort list, all person:
person 0: p7 is 9
person 1: p6 is 12
person 2: p3 is 13
person 3: p11 is 20
person 4: p2 is 20
person 5: p10 is 21
person 6: p8 is 21
person 7: p9 is 22
person 8: p5 is 56
*/

 1.6.1普通链表与通用链表的比较

普通链表

普通链表的缺点

AddItemToList函数的第二个参数是person结构体,如果后面要加入Dog结构体,或者加入Bird结构体,AddItemToList函数就不够通用,此时需要对Dog,Bird重写加入链表的函数,所以需要修改AddItemToList函数,使其更加通用。

通用链表

此时在链表头里面加入struct node_t head;让head存放next指向p1的node,p1的node存放next指向p2......依次类推。

这样做的好处就是:后面有Dog、Bird结构体时,链表头不需要改动,链表添加函数也不需要改动,只需要定义出Dog结构体就可以了,后面的添加链表函数通用。

struct Dog {

        char *name;

        int age;

        struct node_t node;

}

使用结构体的0地址偏移,找到结构体的首地址。 

1.7通用链表的三种实现方式

1. 引入概念container

struct person {
    struct node_t node;
    char *name;
    int age;
};

struct dog {
    struct node_t node;
    char *name;
    int age;
    char *class;
};

person里含有node,person就是node的"容器"、"container"。

dog里含有node,dog就是node的"容器"、"container"。

核心在于:怎么根据node找到container。

方法1

结构体中,node一定放在container中最前面:

struct person {
    struct node_t node;
    char *name;
    int age;
};

struct dog {
    struct node_t node;
    char *name;
    int age;
    char *class;
};

方法2

结构体中,根据node反算出container位置:适用于Linux,RT-Threah

struct person {
    char *name;
    int age;
    struct node_t node;
    char *address;
};

struct dog {
    char *name;
    int age;
    char *class;
    struct node_t node;    
};

方法3

node中,保存container地址:适用于FreeRTOS

struct node_t {
    void *container;
	struct node_t *next;
};

1.8双向链表

1.结构图

 2.判断尾部

上如中,person3是链表中最后一个链表项,它的下一个person是head:

person3.node.next == &list.head

3. 怎么插入新项

new_node->pre   = left_node;
new_node->next  = right_node;
left_node->next = new_node;
right_node->pre = new_node;

4. 怎么删除项 

left_node  = del_node->pre;
right_node = del_node->next;
left_node->next = right_node;
right_node->pre = left_node;

5.  初始化

6.  添加person节点

7. 在链表后面添加节点

#include "usart.h"

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

#define container_of(ptr, type, member) \
	(type *)((char *)ptr - (unsigned int)&((type *)0)->member)

struct node_t {
	struct node_t *pre;
	struct node_t *next;
};

struct person {
	char *name;
	int age;
	struct node_t node;
};

struct list {
	char *name; /* A班 */
	struct node_t head;
};

void InitList(struct list *pList, char *name)
{
	pList->name = name;
	pList->head.next = &pList->head;
	pList->head.pre  = &pList->head;
}

void AddItemToList(struct list *pList, struct node_t *new_node)
{
	struct node_t *last = pList->head.pre;
	
	new_node->next  = &pList->head;
	last->next      = new_node;
	new_node->pre   = last;
	pList->head.pre = new_node;
}

void AddItemAfter(struct node_t *pre, struct node_t *new_node)
{
	struct node_t *right = pre->next;
	
	pre->next = new_node;
	new_node->next = right;
	
	right->pre = new_node;
	new_node->pre = pre;
}

void DelItemFromList(struct list *pList, struct node_t *node)
{
	struct node_t *left  = node->pre;
	struct node_t *right = node->next;
	
	left->next = right;
	right->pre = left;
}

int CmpPersonAge(struct node_t *pre, struct node_t *next)
{
	struct person *p;
	struct person *n;
	
	p = container_of(pre, struct person, node);
	n = container_of(next, struct person, node);
	
	if (p->age < n->age)
		return -1;
	else
		return 0;
}

void SortList(struct list *pList)
{
	struct node_t *pre1 = &pList->head;
	struct node_t *pre2;
	struct node_t *cur = pre1->next;
	struct node_t *next;
	struct node_t *tmp;
		
	while (cur != &pList->head)
	{
		pre2 = cur;
		next = cur->next;
		while (next != &pList->head)
		{
			if (CmpPersonAge(cur, next) == 0)
			{
				/* 交换节点 */
				/* 1. del cur */
				DelItemFromList(pList, cur);
				
				/* 2. del next */
				DelItemFromList(pList, next);
				
				/* 3. 在pre1之后插入next */
				AddItemAfter(pre1, next);
				
				/* 4. 在pre2之后插入cur */
				if (pre2 == cur)
					AddItemAfter(next, cur);
				else
					AddItemAfter(pre2, cur);
				
				/* 5. cur/next指针互换 */
				tmp = cur;
				cur = next;
				next = tmp;				
			}
			
			pre2 = next;
			next = next->next;
		}
		
		pre1 = cur;
		cur = cur->next;
	}
	
}

void PrintList(struct list *pList)
{
	int i = 0;
	
	struct node_t *node = pList->head.next;
	struct person *p;
	
	while (node != &pList->head)
	{
		p = container_of(node, struct person, node);
		printf("person %d: %s is %d\r\n", i++, p->name, p->age);
		
		/* 后面还有人, 移动到下一个 */
		node = node->next;
	}
}

int main(int argc, char **arg)
{
	struct list a_list;
	int i;

	struct person p[] = {
		{"p1", 10, {NULL}},
		{"p2", 20, {NULL}},
		{"p3", 13, {NULL}},
		{"p4", 41, {NULL}},
		{"p5", 56, {NULL}},
		{"p6", 12, {NULL}},
		{"p7", 9, {NULL}},
		{"p8", 21, {NULL}},
		{"p9", 22, {NULL}},
		{"p10", 21, {NULL}},
		{"p11", 20, {NULL}},
		{NULL, 0, {NULL}},
	};
	
	
    HAL_Init();
    
    MX_USART1_UART_Init();

	InitList(&a_list, "A_class");

	i = 0;
	while (p[i].name != NULL)
	{
		AddItemToList(&a_list, &p[i].node);
		i++;
	}

	printf("add all person:\r\n");
	PrintList(&a_list);
	
	DelItemFromList(&a_list, &p[3].node);

	printf("del person %s:\r\n", p[3].name);
	PrintList(&a_list);

	DelItemFromList(&a_list, &p[0].node);
	
	printf("del person %s:\r\n", p[0].name);
	PrintList(&a_list);
	
	SortList(&a_list);
	printf("sort list, all person:\r\n");
	PrintList(&a_list);
    
    while(1)
    {
    }
}

void Error_Handler(void)
{
    printf("Error\r\n");
    while(1)
    {
    }
}


/*
add all person:
person 0: p1 is 10
person 1: p2 is 20
person 2: p3 is 13
person 3: p4 is 41
person 4: p5 is 56
person 5: p6 is 12
person 6: p7 is 9
person 7: p8 is 21
person 8: p9 is 22
person 9: p10 is 21
person 10: p11 is 20
del person p4:
person 0: p1 is 10
person 1: p2 is 20
person 2: p3 is 13
person 3: p5 is 56
person 4: p6 is 12
person 5: p7 is 9
person 6: p8 is 21
person 7: p9 is 22
person 8: p10 is 21
person 9: p11 is 20
del person p1:
person 0: p2 is 20
person 1: p3 is 13
person 2: p5 is 56
person 3: p6 is 12
person 4: p7 is 9
person 5: p8 is 21
person 6: p9 is 22
person 7: p10 is 21
person 8: p11 is 20
sort list, all person:
person 0: p7 is 9
person 1: p6 is 12
person 2: p3 is 13
person 3: p11 is 20
person 4: p2 is 20
person 5: p10 is 21
person 6: p8 is 21
person 7: p9 is 22
person 8: p5 is 56
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值