C语言实现栈和队列(源代码免费分享,同时解决相关算法题)

目录

1.栈和队列的相关重要概念

2.栈和队列的C语言源代码

2.1栈的实现代码(根据其特性,用数组实现)

2.2队列的C语言实现代码(根据其特性,用链表实现)

3.栈和队列的相关算法题

3.1.1 有效的括号

3.1.2 解题思路:利用栈的先进后出特性解答。

3.1.3解题代码

3.2.1 用队列实现栈

3.2.2 解题思路

3.2.3 解题代码

3.3.1 用栈实现队列

3.2.2 解题思路

3.3.3 解题代码

3.4.1 设计循环队列

3.4.2 解题思路

3.4.3 解题代码


1.栈和队列的相关重要概念

栈的定义:一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端

称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出LIFO(Last In First Out)的原则。
压栈:栈的插入操作叫做进栈/压栈/入栈,入数据在栈顶。
出栈:栈的删除操作叫做出栈。出数据也在栈顶。
队列的定义:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出 FIFO(First In First Out)
入队列:进行插入操作的一端称为队尾
出队列:进行删除操作的一端称为队头
以上就是栈和队列的主要定义和其特定的增加和删除的规则了。
如果还想深入了解栈和队列请查询相关书籍和其他人博客。
我这里是想记录下栈和队列实现的源代码和相关算法题的解题思路,希望对你有帮助。

 


2.栈和队列的C语言源代码

2.1栈的实现代码(根据其特性,用数组实现)

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

typedef int  STDataType;

typedef struct Stack {
	STDataType* a;
	int top;
	int capacity;
}ST;


void STInit(ST* pst);
void STDestroy(ST* pst);
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst);
// 获取栈中有效元素个数
int STSize(ST* pst);



void STInit(ST* pst) {
	assert(pst);
	pst->a = NULL;
	//pst->top = -1;   // top 指向栈顶数据
	pst->top = 0;   // top 指向栈顶数据的下一个位置
	pst->capacity = 0;
}

void STDestroy(ST* pst) {
	assert(pst);

	free(pst->a);
	pst->a = NULL;
	pst->capacity = 0;
	pst->top = 0;
}

void STPush(ST* pst, STDataType x) {
	assert(pst);

	if (pst->capacity == pst->top) {
		int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
		STDataType* ret = (STDataType*)realloc(pst->a,sizeof(STDataType) * newcapacity);
		if (ret == NULL) {
			perror("malloc is fail");
			return;
		}
		pst->a = ret;
		pst->capacity = newcapacity;
	}

	pst->a[pst->top] = x;
	pst->top++;
}

void STPop(ST* pst) {
	assert(pst);
	assert(!STEmpty(pst));

	pst->top--;
}
STDataType STTop(ST* pst) {
	assert(pst);
	assert(!STEmpty(pst));

	return pst->a[pst->top - 1];
}

bool STEmpty(ST* pst) {
	assert(pst);

	return pst->top == 0;
}

int STSize(ST* pst) {
	assert(pst);

	return pst->top;
}

2.2队列的C语言实现代码(根据其特性,用链表实现)

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

//用链表来实现队列
typedef int QDataType;

//队列节点
typedef struct QueueNode
{
	struct QueueNode* next;
	QDataType data;
}QNode;

//由于队列的特性是队尾插入队头删除,所以需要记录好队头和队尾的指针
typedef struct Queue
{
	QNode* phead;
	QNode* ptail;
	int size;//当前队列中的元素(节点)个数
}Queue;

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);


void QueueInit(Queue* pq) {
	assert(pq);

	pq->phead = NULL;
	pq->ptail = NULL;
	pq->size = 0;
}

void QueueDestroy(Queue* pq) {
	assert(pq);

	QNode* cur = pq->phead;
	while (cur) {
		QNode* next = cur->next;
		free(cur);
		cur = next;
	}
	pq->phead = pq->ptail = NULL;
	pq->size = 0;

}

void QueuePush(Queue* pq, QDataType x) {
	assert(pq);

	//创建一个链表节点
	QNode* newnode = (QNode*)malloc(sizeof(QNode));
	if (newnode == NULL) {
		perror("malloc fail");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;

	//当队列为空和不为空时的两种情况
	if (pq->ptail == NULL) {
		assert(pq->phead == NULL);//这里需要再次判断一下队头是否为空的情况
		pq->phead = pq->ptail = newnode;
	}
	else {
		pq->ptail->next = newnode;
		pq->ptail = newnode;
	}

	pq->size++;
}

void QueuePop(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));

	//一个节点和多个节点
	if (pq->phead->next == NULL) {//当只剩下一个节点的时候,其对头和队尾指针相同。这时需要注意判断
		free(pq->phead);
		pq->phead = pq->ptail = NULL;
	}
	else {
		QNode* next = pq->phead->next;
		free(pq->phead);
		pq->phead = next;
	}

	pq->size--;
}

QDataType QueueFront(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->phead->data;
}

QDataType QueueBack(Queue* pq) {
	assert(pq);
	assert(!QueueEmpty(pq));

	return pq->ptail->data;
}

int QueueSize(Queue* pq) {
	assert(pq);
	return pq->size;
}

bool QueueEmpty(Queue* pq) {
	assert(pq);
	
	return pq->phead == NULL && pq->ptail == NULL;
}


3.栈和队列的相关算法题

3.1.1 有效的括号

3.1.2 解题思路:利用栈的先进后出特性解答。

首先,从头开始遍历字符串。当字符串为左符号时,将其进行入栈操作。
当其为右符号时,这时判断栈顶元素的符号是否与其匹配,并同时进行弹栈操作。
如果匹配,则继续,直到遍历完字符串。最后,再查看栈是否为空,是则有效,不为空则无效。
否则,此字符串无效。( 需注意只有一个右符号和左符号的情况,需要单独解决。

3.1.3解题代码

define _CRT_SECURE_NO_WARNINGS

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


typedef char STDataType;

typedef struct Stack {

	STDataType* a;
	int top;
	int capacity;

}ST;

void StackInit(ST* ps);

void StackDestory(ST* ps);

void StackPush(ST* ps,STDataType x);

void StackPop(ST* ps);

STDataType StackTop(ST* ps);

bool StackEmpty(ST* ps);

int StackSize(ST* ps);

void StackPrint(ST* ps);


void StackInit(ST* ps) {
	
	assert(ps);
	ps->a = NULL;
	ps->top = 0;//top如果是从0开始,那么每次top指向的是插入节点的后一个位置处
	ps->capacity = 0;

}

void StackDestory(ST* ps) {
	
	assert(ps);
	free(ps->a);//初始化ps->a数组里面存储的数组
	ps->a = NULL;
	ps->top = 0;
	ps->capacity = 0;

}

void StackPush(ST* ps, STDataType x) {

	assert(ps);

	if (ps->capacity == ps->top) {

		int newcapacity = (ps->capacity == 0) ? 4 : ps->capacity * 2;
		STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
		if (tmp == NULL) {
			printf("realloc fail");
			exit(-1);
		}
		else {
			ps->a = tmp;
			ps->capacity = newcapacity;
		}

	}
	
		ps->a[ps->top] = x;
		ps->top++;
	

}

void StackPop(ST* ps) {

	assert(ps);
	assert(!StackEmpty(ps));

	ps->top--;

}

STDataType StackTop(ST* ps) {
	
	assert(ps);
	assert(!StackEmpty(ps));

	
    return ps->a[ps->top-1];

}

bool StackEmpty(ST* ps) {

	assert(ps);
	return ps->top == 0;
}

int StackSize(ST* ps) {
	assert(ps);
	return ps->top;
}

void StackPrint(ST* ps){
	
	assert(ps);

	while (ps->top != 0) {
		ps->top--;
		printf("%d ", ps->a[ps->top]);

	}

}


//以上是自己创建好的一个栈


bool isValid(char * s){
       
             //*******栈中只存储左括号*********
			 //首先,先判断符号是左括号还是右括号,是左括号的话就先入栈
			 //碰到右括号就弹栈看是否匹配
			 //如不匹配,则返回false
			 //匹配则继续进行下一对的判断
             //当结束后看栈是否为空,为空则说明全部匹配完成。
             //另外还得继续考虑只有一个左括号和只有一个右括号的两种特殊情况。

       ST st;//首先,先创建一个栈
       StackInit(&st);//初始化栈

    while(*s){
          
					//将右括号先入栈
        if(*s == '(' || *s == '[' || *s == '{'){
        
            StackPush(&st,*s);
						*s++;
      }else{
        
				//预防只有一个左边的括号时的情况
				if(StackEmpty(&st)){
					return false;
				}

        STDataType tmp = StackTop(&st);   
        StackPop(&st);
        
				//判断从栈中取出的右括号与下一个左括号是否对应,如不对应,直接返回false。对应的话,则继续判断下一个
        if((tmp == '{' && *s == '}')||(tmp == '(' && *s == ')')||(tmp == '[' && *s == ']')){
            ++s;
        }else{
             return false; 
        }
      }
    }

//预防只有一个右边的括号时的情况
if(StackEmpty(&st)){
	return true;
}
else{
	return false;
}

}

3.2.1 用队列实现栈

3.2.2 解题思路

两个队列,假设一个队列为空,一个队列已经存储数据了。(重要前提)

当需要压栈的时候,找到不为空的队列,直接尾插完成。

当需要删除元素的时候,找到不为空的队列,将其只留一个元素,其他全部出队列到空队列中去。

最后留下的元素就是需要弹栈的元素。这时,将其直接出队列并返回其值。

当两个队列都为空,其栈就是空

3.2.3 解题代码

// 用链表来实现队列
typedef int QDataType;

// 队列节点
typedef struct QueueNode {
    struct QueueNode* next;
    QDataType data;
} QNode;

// 由于队列的特性是队尾插入队头删除,所以需要记录好队头和队尾的指针
typedef struct Queue {
    QNode* phead;
    QNode* ptail;
    int size; // 当前队列中的元素(节点)个数
} Queue;

void QueueInit(Queue* pq);
void QueueDestroy(Queue* pq);
void QueuePush(Queue* pq, QDataType x);
void QueuePop(Queue* pq);
QDataType QueueFront(Queue* pq);
QDataType QueueBack(Queue* pq);
int QueueSize(Queue* pq);
bool QueueEmpty(Queue* pq);

void QueueInit(Queue* pq) {
    assert(pq);

    pq->phead = NULL;
    pq->ptail = NULL;
    pq->size = 0;
}

void QueueDestroy(Queue* pq) {
    assert(pq);

    QNode* cur = pq->phead;
    while (cur) {
        QNode* next = cur->next;
        free(cur);
        cur = next;
    }
    pq->phead = pq->ptail = NULL;
    pq->size = 0;
}

void QueuePush(Queue* pq, QDataType x) {
    assert(pq);

    // 创建一个链表节点
    QNode* newnode = (QNode*)malloc(sizeof(QNode));
    if (newnode == NULL) {
        perror("malloc fail");
        return;
    }
    newnode->data = x;
    newnode->next = NULL;

    // 当队列为空和不为空时的两种情况
    if (pq->ptail == NULL) {
        assert(pq->phead == NULL); // 这里需要再次判断一下队头是否为空的情况
        pq->phead = pq->ptail = newnode;
    } else {
        pq->ptail->next = newnode;
        pq->ptail = newnode;
    }

    pq->size++;
}

void QueuePop(Queue* pq) {
    assert(pq);
    assert(!QueueEmpty(pq));

    // 一个节点和多个节点
    if (pq->phead->next ==
        NULL) { // 当只剩下一个节点的时候,其对头和队尾指针相同。这时需要注意判断
        free(pq->phead);
        pq->phead = pq->ptail = NULL;
    } else {
        QNode* next = pq->phead->next;
        free(pq->phead);
        pq->phead = next;
    }

    pq->size--;
}

QDataType QueueFront(Queue* pq) {
    assert(pq);
    assert(!QueueEmpty(pq));

    return pq->phead->data;
}

QDataType QueueBack(Queue* pq) {
    assert(pq);
    assert(!QueueEmpty(pq));

    return pq->ptail->data;
}

int QueueSize(Queue* pq) {
    assert(pq);
    return pq->size;
}

bool QueueEmpty(Queue* pq) {
    assert(pq);

    return pq->phead == NULL && pq->ptail == NULL;
}
//以上是用C语言实现的队列
//这题主要算法:
//一个队列为空,另一个队列不让它为空。

//当需要压栈的时候,只需要让不为空的队列尾插即可。

//当需要弹栈的时候,需要先找到两个队列中哪个是不为空的,然后将不为空的队列中的数据只留一个,其他的全部
//将其插入到空队列中去。这时,原先不为空的队列中的剩下的最后一个数据就是需要弹栈的数据了,也符合栈的LIFO特性


typedef struct {//定义两个队列q1和q2
    Queue q1;
    Queue q2;
} MyStack;

MyStack* myStackCreate() {
    MyStack* ret = (MyStack*)malloc(sizeof(MyStack));
    if (ret == NULL) {
        perror("malloc fail");
        return NULL;
    }

    QueueInit(&ret->q1);
    QueueInit(&ret->q2);

    return ret;
}

void myStackPush(MyStack* obj, int x) {

    Queue* EmptyQueue = &obj->q1;
    Queue* NoEmptyQueue = &obj->q2;

    if (!QueueEmpty(EmptyQueue)) {
        EmptyQueue = &obj->q2;
        NoEmptyQueue = &obj->q1;
    }

    QueuePush(NoEmptyQueue, x);
}

int myStackPop(MyStack* obj) {

    Queue* EmptyQueue = &obj->q1;
    Queue* NoEmptyQueue = &obj->q2;

    if (!QueueEmpty(EmptyQueue)) {
        EmptyQueue = &obj->q2;
        NoEmptyQueue = &obj->q1;
    }

    while (NoEmptyQueue->size != 1) {
        int x = QueueFront(NoEmptyQueue);
        QueuePush(EmptyQueue, x);
        QueuePop(NoEmptyQueue);
    }
    int ret = QueueFront(NoEmptyQueue);
    QueuePop(NoEmptyQueue);

    return ret;
}

int myStackTop(MyStack* obj) {

    Queue* EmptyQueue = &obj->q1;
    Queue* NoEmptyQueue = &obj->q2;

    if (!QueueEmpty(EmptyQueue)) {
        EmptyQueue = &obj->q2;
        NoEmptyQueue = &obj->q1;
    }

    return QueueBack(NoEmptyQueue);
}

bool myStackEmpty(MyStack* obj) {
    return QueueEmpty(&obj->q1) && QueueEmpty(&obj->q2);
}

void myStackFree(MyStack* obj) {
    QueueDestroy(&obj->q1);
    QueueDestroy(&obj->q2);
    free(obj);
}

/**
 * Your MyStack struct will be instantiated and called as such:
 * MyStack* obj = myStackCreate();
 * myStackPush(obj, x);

 * int param_2 = myStackPop(obj);

 * int param_3 = myStackTop(obj);

 * bool param_4 = myStackEmpty(obj);

 * myStackFree(obj);
*/

3.3.1 用栈实现队列

3.2.2 解题思路

两个栈,设置一个栈为入栈,一个栈为出栈。(重要前提,入栈只负责插入数据,出栈只负责删除数据。)

当队列需要插入数据时,直接在入栈中插入即可。

当需要删除数据时,首先先查看出栈是否为空。

若为空,则需要将入栈中的数据全部弹栈出来,并将其插入到出栈中。完成后出栈的栈顶元素就是需要删除的元素。

若不为空,则直接找到出栈的栈顶元素弹出即可。

当出栈和入栈都为空,则队列为空。

3.3.3 解题代码

typedef int STDataType;

typedef struct Stack {
    STDataType* a;
    int top;
    int capacity;
} ST;

void STInit(ST* pst);
void STDestroy(ST* pst);
void STPush(ST* pst, STDataType x);
void STPop(ST* pst);
STDataType STTop(ST* pst);
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool STEmpty(ST* pst);
// 获取栈中有效元素个数
int STSize(ST* pst);

void STInit(ST* pst) {
    assert(pst);
    pst->a = NULL;
    // pst->top = -1;   // top 指向栈顶数据
    pst->top = 0; // top 指向栈顶数据的下一个位置
    pst->capacity = 0;
}

void STDestroy(ST* pst) {
    assert(pst);

    free(pst->a);
    pst->a = NULL;
    pst->capacity = 0;
    pst->top = 0;
}

void STPush(ST* pst, STDataType x) {
    assert(pst);

    if (pst->capacity == pst->top) {
        int newcapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
        STDataType* ret =
            (STDataType*)realloc(pst->a, sizeof(STDataType) * newcapacity);
        if (ret == NULL) {
            perror("malloc is fail");
            return;
        }
        pst->a = ret;
        pst->capacity = newcapacity;
    }

    pst->a[pst->top] = x;
    pst->top++;
}

void STPop(ST* pst) {
    assert(pst);
    assert(!STEmpty(pst));

    pst->top--;
}
STDataType STTop(ST* pst) {
    assert(pst);
    assert(!STEmpty(pst));

    return pst->a[pst->top - 1];
}

bool STEmpty(ST* pst) {
    assert(pst);

    return pst->top == 0;
}

int STSize(ST* pst) {
    assert(pst);

    return pst->top;
}
//以上是用C语言实现的栈

//本题算法重点:

//将两个栈分为一个只为入栈,一个只为出栈

//当出栈为空时,需要将入栈中的数据全部弹出并保存到出栈中。
//当出栈不为空时,则不需要去改变它,需要入数据则只需要再入栈中push数据即可。



//出队列需要考虑两种情况,一种就是出栈不为空,则直接弹出。否则,则需要将入栈中的数据全部弹出并保存到出栈中。
//入队列最简单,直接在入栈中插入数据就好。
typedef struct {
    ST Enter;
    ST Out;
} MyQueue;

MyQueue* myQueueCreate() {
    MyQueue* ret = (MyQueue*)malloc(sizeof(MyQueue));

    if (ret == NULL) {
        perror("malloc fail");
        return NULL;
    }

    STInit(&ret->Enter);
    STInit(&ret->Out);
    return ret;
}

void myQueuePush(MyQueue* obj, int x) { STPush(&obj->Enter, x); }



int myQueuePop(MyQueue* obj) {

    if (STEmpty(&obj->Out)) {
        while (!STEmpty(&obj->Enter)) {
            STDataType x = STTop(&obj->Enter);
            STPush(&obj->Out, x);
            STPop(&obj->Enter);
        }
        STDataType ret = STTop(&obj->Out);
        STPop(&obj->Out);
        return ret;
    } else {
        STDataType ret = STTop(&obj->Out);
        STPop(&obj->Out);
        return ret;
    }
}
int myQueuePeek(MyQueue* obj) {

    if (STEmpty(&obj->Out)) {
        while (!STEmpty(&obj->Enter)) {
            STDataType x = STTop(&obj->Enter);
            STPush(&obj->Out, x);
            STPop(&obj->Enter);
        }
        
        return STTop(&obj->Out);
        
    } else {
        return STTop(&obj->Out);
    }
    
}

bool myQueueEmpty(MyQueue* obj) {
    return STEmpty(&obj->Enter) && STEmpty(&obj->Out);
}

void myQueueFree(MyQueue* obj) {
    STDestroy(&obj->Enter);
    STDestroy(&obj->Out);
    free(obj);
}

/**
 * Your MyQueue struct will be instantiated and called as such:
 * MyQueue* obj = myQueueCreate();
 * myQueuePush(obj, x);

 * int param_2 = myQueuePop(obj);

 * int param_3 = myQueuePeek(obj);

 * bool param_4 = myQueueEmpty(obj);

 * myQueueFree(obj);
*/

3.4.1 设计循环队列

3.4.2 解题思路

3.4.3 解题代码

typedef struct {
    int* a;
    int front;
    int rear;//指向队尾数据的下一个位置
    int k;
} MyCircularQueue;


MyCircularQueue* myCircularQueueCreate(int k) {
    MyCircularQueue* ret = (MyCircularQueue*)malloc(sizeof(MyCircularQueue));
    if(ret == NULL){
        perror("malloc fail");
        return NULL;
    }

    int* tmp = (int*)malloc(sizeof(int) * (k + 1));//创建为k + 1个长度的数组,以便于进行判断是否为满的状态。因为rear + 1 如果等于front就说明其为满
    ret->a = tmp;
    ret->front = 0;
    ret->rear = 0;
    ret->k = k;//数组能存储的最大元素个数

    return ret;
}

bool myCircularQueueIsEmpty(MyCircularQueue* obj) {
    return obj->front == obj->rear;
}

bool myCircularQueueIsFull(MyCircularQueue* obj) {
    return (obj->rear + 1) % (obj->k + 1) == obj->front;//当rear的下一个位置与front重合时,就说明其满了
}

bool myCircularQueueEnQueue(MyCircularQueue* obj, int value) {
    if(myCircularQueueIsFull(obj)){
            return false;
    }else{
        obj->a[obj->rear] = value;
        obj->rear++;
        if(obj->rear == obj->k + 1){//当创建的k + 1长度的数组全部存满了时候,这时候需要形成循环,所以需要把它转移到开头的位置继续加。
            obj->rear = 0;
        }

        return true;
    }
}

bool myCircularQueueDeQueue(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)){
        return false;
    }else{
        obj->front++;
        if(obj->front == obj->k + 1){//当创建的k + 1长度的数组全部存满了时候,这时候需要形成循环,所以需要把它转移到开头的位置继续加。
            obj->front = 0;
        }

        return true;
    }
}

int myCircularQueueFront(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)){
        return -1;
    }else{
        return obj->a[obj->front];
    }
}

int myCircularQueueRear(MyCircularQueue* obj) {
    if(myCircularQueueIsEmpty(obj)){
        return -1;
    }else{
        //当rear为0时,说明其应该为下标为k的位置
       if(obj->rear == 0){
        return obj->a[obj->k];
       }else{//否则,就是rear前面一个的元素
        return obj->a[obj->rear - 1];
       }
    }
}

void myCircularQueueFree(MyCircularQueue* obj) {
    free(obj->a);
    obj->rear = obj->front = obj->k = 0;
}

/**
 * Your MyCircularQueue struct will be instantiated and called as such:
 * MyCircularQueue* obj = myCircularQueueCreate(k);
 * bool param_1 = myCircularQueueEnQueue(obj, value);
 
 * bool param_2 = myCircularQueueDeQueue(obj);
 
 * int param_3 = myCircularQueueFront(obj);
 
 * int param_4 = myCircularQueueRear(obj);
 
 * bool param_5 = myCircularQueueIsEmpty(obj);
 
 * bool param_6 = myCircularQueueIsFull(obj);
 
 * myCircularQueueFree(obj);
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值