超详细的数据结构2(初阶C语言版)顺序表和链表。

顺序表和链表

1. 线性表

1.线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是⼀种在实际中⼴泛使 ⽤的数据结构,常⻅的线性表:顺序表、链表、栈、队列、字符串…
2.线性表在逻辑上是线性结构,也就说是连续的⼀条直线。但是在物理结构上并不⼀定是连续的, 线性表在物理上存储时,通常以数组和链式结构的形式存储。

2.顺序表

(1).顺序表是⽤⼀段物理地址连续的存储单元依次存储数据元素的线性结构,⼀般情况下采⽤数组存储。
顺序表
(2).顺序表和数组的区别?
顺序表的底层结构是数组,对数组的封装,实现了常⽤的增删改查等接⼝。
顺序表和数组的区别

2.1 概念与结构

2.2 分类

2.2.1静态顺序表

概念:使用定长数组储存元素。

//静态顺序表
typedef int SLDataType;
#define  N 7;
typedef struct SeqList {
	SLDataType a[N];//定长数组
	int size;//有效数据个数
}SL;

静态顺序表
静态顺序表缺陷:空间给少了不够⽤,给多了造成空间浪费。

2.2.2动态顺序表
//动态顺序表--按需申请
typedef struct SeqList
{
	SLDataType* a;
	int size;   //有效数据个数
	int capacity;   //空间容量
}SL;

动态顺序表

2.3 动态顺序表的实现

seqlist.h

seqlist.h

#pragma once

#include<stdlib.h>

#include<stdio.h>

#include<assert.h>

//第一步定义动态顺序表结构

typedef int SLDatatype;

typedef struct SeqList {

	SLDatatype* arr;

	int capacity;//空间大小

	int size; // 有效数据个数

}SL;

//typedef struct SeqList SL;

//初始化

void SLInit(SL *ps);

//销毁

void SLDestroy(SL *ps);

//打印顺序表

void SLPrint(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,SLDatatype x,int pos);

//删除指定位置的数据

void SLErasee(SL*ps,int pos);



//查找

int SLFind(SL* ps, SLDatatype x);
seqlist.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "SeqList.h"

//初始化

void SLInit(SL *ps)

{

	ps->arr = NULL;

	ps->capacity = ps->size = 0;

}

//销毁

void SLDestroy(SL *ps)

{

	if (ps->arr)//相当于ps->arr!=NULL

	{

		free(ps->arr);

	}

	ps->arr = NULL;

	ps->capacity = ps->size = 0;

}

void SLcheckCapacity(SL* ps)

{

	//判断空间是否充足

	if (ps->size == ps->capacity)

	{

		//如何增容?增容一般是成倍数的增加,比如2.3.......

		//增容

		//增容//0*2=0;

		//若capacity为0,给个默认值,否则*2倍

		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;

		SLDatatype* tmp = (SLDatatype*)realloc(ps->arr, newCapacity * sizeof(SLDatatype));

		if (tmp == NULL)

		{

			perror("realloc fail!");

			exit(1);

		}

		ps->arr = tmp;

		ps->capacity = newCapacity;

	}

}

//插入数据

void SLPushBack(SL* ps, SLDatatype x)//尾插

{

	assert(ps);//等价于assert(ps!=NULL);

	//温柔的解决办法

	/*if (ps == NULL)

	{

		return;

	}*/

	SLcheckCapacity(ps);

	ps->arr[ps->size++] = x;

}

void SLPushFront(SL* ps, SLDatatype x)//头插

{



	assert(ps);//等价于assert(ps!=NULL);

	SLcheckCapacity(ps);

	//数据整体后移

	for (int i = ps->size; i > 0; i--)

	{

		ps->arr[i] = ps->arr[i - 1];

	}

	//小标为0的数据空出来了

	ps->arr[0] = x;

	ps->size++;

}

void SLPrint(SL* ps) {

	for (int i = 0; i < ps->size; i++)

	{

		printf("%d ", ps->arr[i]);

	}

	printf("\n");

}

//删除数据

void SLPopBack(SL* ps)//尾删

{

	assert(ps);

	assert(ps->size);

	/*ps->arr[ps->size - 1] = -1;*/ //多余了

	ps->size--;

}

void SLPopFront(SL* ps)//头删

{

	assert(ps);

	assert(ps->size);

	//将整体数据向前挪动一位

	for (int i=0;i<ps->size-1 ; i++)

	{

		ps->arr[i] = ps->arr[i + 1];

	}

	ps->size--;

}



//在指定位置之前插入数据

void SLInsert(SL* ps, SLDatatype x, int pos)

{

	assert(ps);

	assert(pos>=0&&pos<=ps->size);

	SLcheckCapacity(ps);

	for ( int i = ps->size; i>pos ; i--)

	{

		ps->arr[i] = ps->arr[i -1];//pos+1 -> pos

	}

	ps->arr[pos] = x;

	ps->size++;

}

//删除指定位置的数据

void SLErasee(SL* ps, int pos)

{

	assert(ps);

	assert(pos >= 0 && pos < ps->size);

		//还有顺序表不能为空:如顺序表不能为空.....

		//pos之后的数据向前挪动一位

		for(int i = pos;i<ps->size-1 ; i++)

		{

			ps->arr[i] = ps->arr[i + 1];//size-2 -> size-1

		}

	ps->size--;

}

//查找

int SLFind(SL* ps, SLDatatype x)

{

	assert(ps);

	for ( int i = 0; i < ps->size; i++)

	{

		if (ps->arr[i] == x)

		{

			return i;

		}

	}

	//没有找到:返回无效下标

	return -1;

}

test .c

test .c 



#include"seqList.h"

void SLtest01()

{

	SL s;

	SLInit(&s);

	 //SLPushBack(&s,1);//尾插

	 //SLPushBack(&s,2);//尾插

	 //SLPushBack(&s,3);//尾插

	 //SLPushBack(&s,4);//尾插

	 //SLPushBack(&s,5);//尾插

	 //SLPushBack(NULL,5);//尾插

	SLPushFront(&s, 1);//头插

	SLPushFront(&s, 2);//头插

	SLPushFront(&s, 3);//头插

	SLPushFront(&s, 4);//头插

	///*SLPopBack(&s);

	//SLPopBack(&s);

	//SLPopBack(&s);

	//SLPopBack(&s);*/

	//SLPopFront(&s);//头删

	//SLPopFront(&s);

	//SLPopFront(&s);

	//SLPopFront(&s);

	//SLPopFront(&s);

	//SLInsert(&s,11, 0);//删除指定位置前

	/*SLErasee(&s, 0)*/;//删除指定位置的数据

	int find =SLFind(&s, 2);

	if (find < 0)

	{

		printf("找到了\n");

	}

	else

		printf("没找到\n");

	 SLPrint(&s);



	 



	SLDestroy(&s);

}

int main()

{

	SLtest01();



	return 0;

}

3. 单链表

3.1 概念与结构

概念:链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
单链表结构很像火车eg:
单链表结构
实际的单链表结构:
单链表结构

3.1.1 结点

结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。

3.1.2链表的性质

1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续。
2、结点⼀般是从堆上申请的。
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续。

3.1.3 链表的打印
void SLTPrint(SLTNode* phead){
SLTNode*pcur = phead;
while(pcur)
{
printf("%d ",pcur->data);
pcur = pcur->next;
}
printf(\n);
}

3.2 实现单链表

SList.h

SList.h

#pragma once

#include<stdio.h>

#include<stdlib.h>

#include<assert.h>

//定义链表(结点)的结构

typedef int SLTDataType;

typedef struct SListNode {

	SLTDataType data;

	struct SListNode* next;

}SLTNode;

void SLTPrint(SLTNode*phead);

//插入

void SLTPushback(SLTNode**pphead,SLTDataType x);

void SLTPushFront(SLTNode** pphead,SLTDataType x);

//删除

void SLTPopback(SLTNode** pphead);

void SLTPopFront(SLTNode** pphead);

//查找

SLTNode* SLTFind(SLTNode*phead, SLTDataType x);

 //在指定位置之前插⼊数据

 void SLTInsert(SLTNode * *pphead, SLTNode * pos, SLTDataType x);

 //删除pos结点

void SLTErase(SLTNode * *pphead, SLTNode * pos);

 //在指定位置之后插⼊数据

 void SLTInsertAfter(SLTNode * pos, SLTDataType x);

 //删除pos之后的结点

void SLTEraseAfter(SLTNode * pos);

 //销毁链表

 void SListDestroy(SLTNode * *pphead);

SList.c


SList.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "SList.h"

//链表的打印

void SLTPrint(SLTNode *phead)

{

	SLTNode* pcur = phead;

	while (pcur)

	{

		printf("%d->",pcur->data);

		pcur = pcur ->next;



	}

	printf("NULL\n");

}

//申请新节点

SLTNode* SLTBuyNode(SLTDataType x)

{

	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));

	if (node == NULL)

	{

		perror("malloc fail!");

		exit(1);

	}

	node->data = x;

	node->next = NULL;

}

void SLTPushback(SLTNode** pphead, SLTDataType x)

{

	assert(pphead);

	//申请新节点

	SLTNode* newnode = SLTBuyNode( x);

	if (*pphead == NULL)

	{

		*pphead = newnode;

	}

	else

	{

		//找尾节点

		SLTNode* pcur = *pphead;

		while (pcur->next)

		{

			pcur = pcur->next;



	 }

		//pcur newnode 

		pcur->next = newnode;

	}



}



//头插

void SLTPushFront(SLTNode** pphead, SLTDataType x)

{

	assert(pphead);

	SLTNode* newnode = SLTBuyNode( x);

	// newnode *pphead

	newnode->next = *pphead;

	*pphead = newnode;

}



//尾删

void SLTPopback(SLTNode** pphead)

{

	//链表为空:不可以删除

	assert(pphead &&*pphead);// list 第一个指针

	//处理只有一个结点的情况:要删除的就是头结点;

	if ((*pphead)->next == NULL)

	{

		free(*pphead);

		*pphead = NULL;

	}

	else

	{





		//找prev ptail

		SLTNode* ptail = *pphead;

		SLTNode* prev = NULL;

		while (ptail->next)

		{

			//先让prev指向ptail,再让ptail指向下一个值

			prev = ptail;

			ptail = ptail->next;

		}

		prev->next = NULL;

		free(ptail);

		ptail = NULL;

	}



}



//头删

void SLTPopFront(SLTNode** pphead)

{

	assert(*pphead && pphead);

	SLTNode* next = (*pphead)->next;

	//*pphead -->next

	free(*pphead); //释放pphead指向的空间

	*pphead = next; //让pphead指向 next

}



//查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)

{

	assert(phead);

	SLTNode* pcur = phead;

	while (pcur)

	{

		if (pcur->data==x)//对比

		{

			return pcur;

		}

		pcur = pcur ->next;

	}

	//没有找到

	return NULL;

}



//在指定位置之前插⼊数据

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)

{

	assert(pphead);

	assert(pos);

	if (pos == *pphead)

	{

		 SLTPushFront(pphead, x);

	}

	else

	{

		SLTNode* newnode = SLTBuyNode(x);

		//找prev:pos的前一个结点

		SLTNode* prev = *pphead;

		while (prev->next != pos)

		{

			prev = prev->next;

		}

		//prev newnode-->pos

		prev->next = newnode;

		newnode->next = pos;

	}

}

//在指定位置之后插⼊数据

void SLTInsertAfter(SLTNode* pos, SLTDataType x)

{

	assert(pos);

	SLTNode* newnode = SLTBuyNode(x);

	//pos newnode -->pos ->next

	newnode->next = pos->next;

	pos -> next= newnode;

}

//删除pos结点

void SLTErase(SLTNode** pphead, SLTNode* pos)

{

	assert(pphead && *pphead);//链表存在

	assert(pos);

	if (pos == *pphead)

	{

		SLTPopFront(pphead);

	}

	else

	{





		SLTNode* prev = *pphead;

		while (prev->next != pos)

		{

			prev = prev->next;

		}

		//prev pos pos->next

		prev->next = pos->next;

		free(pos);

		pos = NULL;

	}

}

//删除pos之后的结点

void SLTEraseAfter(SLTNode* pos)

{

	assert(pos&&pos->next);//条件:可删

	//pos pos->next pos->next->next

	SLTNode* del = pos->next;//保存一下pos->next

	pos->next = pos ->next->next;

	free(del);

	del = NULL;

}

void SListDestroy(SLTNode** pphead)

{

	assert(pphead && *pphead);//链表存在

	SLTNode* pcur = *pphead;

	while (pcur)

	{

		SLTNode* next = pcur->next;

		free(pcur);

		pcur = next;

	}

	*pphead = NULL;//pcur已经释放内存,pphead不要忘记释放内存;

}

test.c

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"SList.h"

//创建一个链表,并打印链表

void createSList()

{

	//链表是由一个一个的结点组成

	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));

	node1->data = 1;

	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));

	node2->data = 2;

	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));

	node3->data = 3;

	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));

	node4->data = 4;

	node1->next = node2;

	node2->next = node3;

	node3->next = node4;

	node4->next = NULL;



	//第一个结点的地址作为参数传递过去

	SLTNode* plist = node1;

	SLTPrint(node1);

}



void SListTest01()

{

	SLTNode* plist = NULL;

	//尾插

	//SLTPushback(&plist, 1);

	//SLTPrint(plist);

	//SLTPushback(&plist, 2);

	//SLTPushback(&plist, 3);

	//SLTPrint(plist);

	/*SLTPushback(NULL, 4);*/

	SLTPushFront(&plist, 1);

	SLTPushFront(&plist, 2);

	SLTPushFront(&plist, 3);

	SLTPushFront(&plist, 4); //4 3 2 1	null

	//尾删

	/* SLTPopback(&plist);

	 SLTPopback(&plist);

	 SLTPopback(&plist);

	 SLTPopback(&plist);*/

	//头删

	/*SLTPopFront(&plist);

	SLTPopFront(&plist);

	SLTPopFront(&plist);*/

	SLTNode*find=SLTFind(plist, 2);//用find接收

	//if (find == NULL)

	//{

	//	printf("没找到\n");

	//}

	//else

	//{



	//	printf("找到了\n");

	//}

	 //在指定位置之前插⼊数据

	/*SLTInsert(&plist,find,11);*/

	 //在指定位置之后插⼊数据

	/*SLTInsertAfter(find, 11);*/

	//删除pos结点

	/*SLTErase(&plist, find);*/

	/*SLTEraseAfter(find);*/

	SListDestroy(&plist);

	SLTPrint(plist);

}

int main()

{

	/*createSList();*/

	SListTest01();

	return 0;

}


3.3 链表的分类

链表的结构⾮常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:
链表结合方式
链表说明:
1.单向或双向

单向
双向
2.带头或不带头
不带头
带头
3.循环或者不循环
不循环
循环
最常⽤还是两种结构:单链表和双向带头循环链表。

  1. ⽆头单向⾮循环链表:结构简单,⼀般不会单独⽤来存数据。实际中更多是作为其他数据结构的⼦结构,如哈希桶、图的邻接表等等。
  2. 带头双向循环链表:结构最复杂,⼀般⽤在单独存储数据。实际中使⽤的链表数据结构,都是带头双向循环链表。

4. 双向链表

4.1 概念与结构

带头双向循环链表
双向链表
带头链表⾥的头结点,实际为“哨兵位”,哨兵位结点不存储任何有效元素,只是站在这⾥“放哨的”。

4.2 实现双向链表

list.h

list.h

#pragma once

#include<stdio.h>

#include<stdlib.h>

#include<assert.h>

#include<stdbool.h>

//定义双向链表节点的结构

typedef int LTDataType;

typedef struct ListNode {

	 LTDataType data;

	 struct ListNode* next;

	 struct ListNode* prev;

}LTNode;

//为了保持接口一致性,优化接口为一级指针

//初始化

//void LTInit(LTNode** pphead);

LTNode* LTInit();

//销毁

void LTDesTory(LTNode**pphead);

void LTDesTory2(LTNode*phead);//传一级,需要手动将plsit置为Null



//插入

//第一个参传一级还是二级,要看pphead指向的节点会不会发生改变

//要想发生改变,那么pphead的改变要影响实参,传二级

//如果不发生改变,pphead不会影响实参,传一级

//尾插

void LTPpushBack(LTNode* phead, LTDataType x);

//头插(在第一个有效位置之前插入,也就是哨兵位之后)

void LTPpushFront(LTNode* phead, LTDataType x);

//打印链表

void LTPrint(LTNode*phead);

//删除

void LTPopBack(LTNode*phead);



void LTPopFront(LTNode*phead);



//判断是否为空

bool LTEmpty(LTNode*phead);



//查找

LTNode* LTfind(LTNode*phead,LTDataType x);

void LTInsert(LTNode*pos,LTDataType x);

//删除指定位置节点

void LTErase(LTNode*pos);

list.c

list.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "List.h"

//申请空间

LTNode* LTBuyNode(int x) {

	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));

	if (newnode == NULL)

	{

		perror("malloc fail!");

		exit(1);

	}

	newnode->data = x;

	//prev next

	newnode->next = newnode->prev = newnode;

	return newnode;

}

//初始化

//void LTInit(LTNode** pphead)

//{

//	//创建头结点(哨兵位)

//	*pphead = LTBuyNode(-1);

//}

LTNode* LTInit()

{

	LTNode* phead = LTBuyNode(-1);

	return phead;

}



//插入

//第一个参传一级还是二级,要看pphead指向的节点会不会发生改变

//要想发生改变,那么pphead的改变要影响实参,传二级

//如果不发生改变,pphead不会影响实参,传一级

void LTPpushBack(LTNode* phead, LTDataType x) {

	LTNode* newnode = LTBuyNode(x);

	//phead pehad->prev(当前尾节点) newnode

	//先从newnode开始,因为这样不影响各个节点

	newnode->prev = phead->prev;

	newnode->next = phead;

	phead->prev = newnode;

	phead->prev->next = newnode;



}

//头插(在第一个有效位置之前插入,也就是哨兵位之后)

void LTPpushFront(LTNode* phead, LTDataType x)

{

	assert(phead);

	LTNode* newnode = LTBuyNode(x);

	//phead phead->next(d1) newnode

	newnode->next = phead->next;

	newnode->prev = phead;

	phead->next = newnode;

	newnode->next->prev = newnode;

}

bool LTEmpty(LTNode* phead)

{

	assert(phead);

	return phead->next == phead;

}

//删除

void LTPopBack(LTNode* phead)

{

	assert(phead);

	assert(!LTEmpty(phead));

	LTNode* del = phead->prev;

	LTNode* prev = del->prev;



	// phead del(phead->prev) prev

	prev->next = phead;

	phead->prev = prev;

	free(del);

	del = NULL;

}



void LTPopFront(LTNode* phead)

{

	assert(phead);

	assert(!LTEmpty(phead));

	LTNode* del = phead->next;

	//phead del del->next

	phead->next = del->next;

	del->next->prev = phead;

	free(del);

	del = NULL;

}

//查找

LTNode* LTfind(LTNode* phead, LTDataType x)

{

	assert(phead);

	LTNode* pcur = phead->next;

	while (pcur!=phead)

	{

		if (pcur->data == x)

		{

			return pcur;

		}

		pcur = pcur->next;

	}

	return NULL;

}

//在pos位置之后插入节点

void LTInsert(LTNode* pos, LTDataType x)

{

	assert(pos);

	LTNode* newnode = LTBuyNode(x);

	// pos newnode pos->next

	newnode->next = pos->next;

	newnode->prev = pos;

	pos->next = newnode;

	pos->next->prev = newnode;

}



//删除指定位置节点

void LTErase(LTNode* pos)

{

	assert(pos);

	//pos->prev pos pos->next

	pos->prev->next = pos->next;

	pos->next->prev = pos->prev;

	free(pos);

	pos = NULL;



}

//销毁

void LTDesTory(LTNode**pphead)

{

	assert(pphead && *pphead);

	LTNode* pcur = (*pphead)->next;

	while (pcur != *pphead)

	{

		LTNode* Next = pcur->next;

		free(pcur);

		pcur = Next;

	}

	//销毁哨兵位节点

	free(*pphead);

	*pphead = NULL;

	pcur = NULL;

}

void LTDesTory2(LTNode* phead)

{

	assert(phead);

	LTNode* pcur = phead->next;

	while (pcur!=phead)

	{

		LTNode* Next = pcur->next;

		free(pcur);

		pcur = Next;

	}

	free(phead);

	phead = pcur = NULL;



}

test.c

test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include"List.h"

void ListTest01()

{

	//创建双向链表变量

	//LTNode* plist = NULL;

	//LTInit(&plist);

	LTNode* plist = LTInit();

	//插入

	/*LTPpushBack(plist, 1);

	LTPpushBack(plist, 2);

	LTPpushBack(plist, 3);

	LTPpushBack(plist, 4);*/

	LTPpushFront(plist, 1);

	LTPpushFront(plist, 2);

	LTPpushFront(plist, 3);

	LTPpushFront(plist, 4);

	LTPrint(plist);

	//删除

	/*LTPopBack(plist);

	LTPopBack(plist);

	LTPopBack(plist);

	LTPopBack(plist);

	LTPopBack(plist);*/

	/*LTPopFront(plist);

	LTPopFront(plist);

	LTPopFront(plist);

	LTPopFront(plist);*/

	LTPrint(plist);

	LTNode* pos = LTfind(plist, 3);

	/*if (pos == NULL)

	{

		printf("没找到\n");

	}

	else

	{

		printf("找到了\n");

	}*/

	/*LTInsert(pos, 5);*/

	LTErase(pos);

	LTPrint(plist);

	/*LTDesTory(&plist);*/

	void LTDesTory2(LTNode * phead);

	plist = NULL;

}

//打印链表

void LTPrint(LTNode* phead)

{

	LTNode* pcur = phead->next;

	while (pcur!=phead)

	{

		printf("%d->", pcur->data);

		pcur = pcur->next;

	}

	printf("\n");

}

int main()

{

	

	ListTest01();

	return 0;

}

5. 顺序表与链表的分析

顺序表和链表的分析

总结

以上就是顺序表和链表的详细内容。喜欢的话可以点赞,收藏文章,激励博主。

  • 20
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懒羊羊大王&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值