c-数据结构(顺序表、链表)

概念

对于n各元素的线性表,严格数学定义:其中任意一个数据元素a[i],有且仅有一个前驱a[i-1],有且仅有一个后继a[i+1];首元素a[0]无前驱,尾元素a[n-1]无后继。

顺序表

属于线性表,数据之间的空间是连续,如具名的栈数组,匿名的堆数组。

链表

属于线性表,数据之间的空间不连续(离散),如结构体。

单链表
  • 顺序表设计

    1. 顺序表容量;

    2. 顺序表当前最末元素下标位置;

    3. 顺序表指针;

    顺序表
  • 顺序表的相关数据操作代码
    //math文件夹
    ------------------------------------------------------------------------------------------
    seqlist.h
    ------------------------------------------------------------------------------------------
    #ifndef __SEQLIST_H
    #define __SEQLIST_H
        
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
        
    //定义顺序表的结构体
    typedef struct
    {
        int capacity;	//顺序表的容量(本质为数组);
        int last;		//末元素下标
        int* data;		//数据
    } seqList;
    
    //创建顺序表
    seqList *initList(int cap);
    
    //判断顺序表是否满了
    bool isFull(seqList *list);
    
    //向顺序表表头插入新数据
    bool insert(seqList *list, int data);
    
    //遍历顺序表元素
    void show(seqList *list);
    
    //删除顺序表指定元素
    bool removeNode(seqList *list, int data);
    
    //销毁顺序表
    void destroy(seqList *list);
    #endif
    ------------------------------------------------------------------------------------------
    seqlist.c
    ------------------------------------------------------------------------------------------ 
    #include "seqlist.h"
        
    //初始化顺序表
    seqList *initList(int cap)
    {
        //申请内存
        seqList *list = malloc(sizeof(seqList));
        
        //顺序表非空校验
        if(list != NULL)
        {
            //给数据区分配空间
            list->data = malloc(cap * sizeof(int));
            
            if(list->data == NULL)
            {
                free(list);
                return NULL;
            }
            
            list->capacity = cap;
            list->last = -1;
        }
    }
    
    //判断顺序表是否满了
    bool isFull(seqList *list)
    {
        return list->last == list->capacity - 1;	//尾结点与下标的对应关系
    }
     
    //向顺序表表头插入新数据
    bool insert(seqList *list, int data)
    {
        //判断顺序表是否满了
        if(isfull(list))
        {
            return false;
        }
        //将原有数据全部向后移动一位,腾出空间
        for(int i = list->last; i >= 0; i--)
        {
            list->data[i+1] = list->data[i];
        }
        //将数据插入表头
        list->data[0] = data;
        list->last++;
        
        return true;
    }
     
    //遍历顺序表元素
    void show(seqList *list)
    {
        //判断是否为空
        if(list->lats == -1)
        {
            return;
        }
        //遍历
        for(int i = 0; i <= list->last; i++)
        {
            printf("%d ", list->data[i]);
        }
        printf("\n");
    }
     
    //删除顺序表指定元素
    bool removeNode(seqList *list, int data)
    {
        //判断顺序表是否为空
        if(isFull(list))
        {
            return false;
        }
        //找到要产出元素结点
        int pos;
        for(int i = 0; i <= list->last; i++)
        {
            if(list->data[i] == data)
            {
                pos = i;
                break;
            }
        }
        //若找不到要删除的的节点
        if(i > list->last)
        {
            return false;
        }
        //将截至到删除节点后所有元素前移
        for(int i = pos; i < list->last; i++)
        {
            list->data[i] = list->data[i+1];
        }
        list->last--;
        return true;
    }
    
    //销毁顺序表
    void destroy(seqList *list)
    {
        if(list == NULL)
        {
            return;
        }
        free(list->data);	//销毁顺序表中的是数据
        free(list);			//销毁顺序表
    }
    
    ------------------------------------------------------------------------------------------
    seqlist_main.c
    ------------------------------------------------------------------------------------------
    #include "seqlist.h"
        
    int main()
    {
        //创建顺序表
        seqList *list = initList(10);
        
        if(lits == NULL)
        {
            perror("初始化顺序表失败!");
            //结束进程
            exit(0);
        }
        else
        {
            printf("初始化顺序表成功!");
        }
        
        int n;	//键盘录入整数
        printf("请输入人一个整数:\n");
        while(true)
        {
            scanf("%d", &n);
            //控制循环跳出
            if(n == 0)
            {
                printf("循环跳出\n");
                break;
            }
            else if(n > 0)
            {
                //插入数据
                if(!insert(list,n))
                {
                    printf("容量已满,插入失败!\n");
                    continue;
                }
            }
            else
            {
                //删除数据
                if(!removeNode(list,-n))
                {
                    printf("查无此数,删除失败!\n");
                    continue;
                }
            }
            //遍历顺序表
            show(list);
        }
        //销毁
        destroy(list);
    }
    顺序表优缺点
  • 优点

    1. 无需多余信息来记录数据间关系,数据存储密度高;

    2. 存储空间连续,随机访问速度快;

  • 缺点

    1. 插入删除数据时,需要成片移动数据,很不方便;

    2. 数据节点较多时,需要一整片较大连续空间;

    3. 数据节点数量变化剧烈,内存的释放和分配不灵活;

  • 单链表基本操作

    1. 节点设计;

    2. 初始化空链表;

    3. 增删节点;

    4. 链表遍历;

    5. 销毁链表;

  • 单链表
    单链表相关数据操作代码
    //math1文件夹
    ----------------------------------------------------------------------------------------------
    slist.h
    ----------------------------------------------------------------------------------------------
    #ifndef	__SLIST_H
    #define __SLIST_H
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    
    typedef int DATA;
    
    //创建单链表的结构体
    typedef struct Node
    {
        DATA data;			//存储数据
        struct Node *next;	//存储下一节点地址
    }NODE;
    
    //初始化单链表结构体
    int slist_create(NODE**, DATA);
    
    //向单链表插入一个数据(头插法)
    int slist_addHead(NODE**, DATA);
    
    //向单链表插入一个数据(尾插法)
    int slist_addTail(NODE**, DATA);
    
    //向单链表插入一个数据(中间插法)
    int slist_insert(NODE**, DATA, DATA);
    
    //删除单链表中的数据
    int slist_delete(NODE**, DATA);	参考中间插法
    
    //查找链表中的数据
    NODE* slist_find(const NODE*, DATA);
    
    //修改更新链表数据
    int slist_update(const NODE*, DATA, DATA);
    
    //遍历链表
    void slist_showAll(const NODE*);
    
    //销毁单链表
    void slist_destroy(NODE**);
    
    #endif
    ----------------------------------------------------------------------------------------------
    slist.c
    ----------------------------------------------------------------------------------------------
    #include "slist.h"
    
    
    /*
    @function:   int slist_create(NODE** head,DATA data);
    @berif:     创建单项链表
    @argument:   head: 指向头指针变量的地址,用来接收首节点地址
                 data: 存储在节点中的数据
    @return :   成功返回 0
                 失败返回 -1
    */
    //初始化单链表结构体
    int slist_create(NODE** head, DATA data)
    {
        NODE* p = (NODE*)malloc(sizeof(NODE));
        //非空校验
        if(!p)
        {
            return -1;
        }
        //单链表赋初值
        p->data = data;
        p->next = NULL;
    
        *head = p;
        return 0;
    }
    
    
    /*
    @function:   int slist_addHead(NODE** head,DATA data);
    @berif:     向链表头部插入一个节点数据
    @argument:   head: 指向头指针变量的地址,用来接收首节点地址
                 data: 存储在新节点中的数据
    @return :   成功返回 0
                 失败返回 -1
    */
    //向单链表插入一个数据(头插法)
    int slist_addHead(NODE** head, DATA data)
    {
        NODE* p = (NODE*)malloc(sizeof(NODE));
        if(!p)
        {
            return -1;
        }
        p->data = data;
        p->next = *head;
    
        *head = p;
        return 0;
    }
    
    
    /*
    @function:   int slist_addTail(NODE** head,DATA data);
    @berif:     向链表尾部插入一个节点数据
    @argument:   head: 指向头指针变量的地址,用来接收首节点地址
                 data: 存储在新节点中的数据
    @return :   成功返回 0
                 失败返回 -1
    */
    //向单链表插入一个数据(尾插法)
    int slist_addTail(NODE** head, DATA data)
    {
        NODE* pNew = (NODE*)malloc(sizeof(NODE));
        if(!pNew)
        {
            return -1;
        }
        pNew->data = data;
        pNew->next = NULL;
    
        //寻找尾节点,默认头节点为尾节点,并创建临时变量q
        NODE *p = *head, *q = NULL;
        if(!p)
        {
            *head = pNew;
            return 0;
        }
    
        while(p)
        {
            q = p;
            p = p->next;
        }
    
        q->next = pNew;	//将要插入的数放在原先尾节点的后面作为新的尾节点
    
        return 0;
    }
    
    
    /*
    @function:   int slist_insert(NODE** head,DATA pos ,DATA data);
    @berif:     向链表节点值为pos的位置插入一个节点数据data
    @argument:   head: 指向头指针变量的地址
                 pos: 插入节点位置的节点数据
                 data: 存储在新节点中的数据
    @return :   成功返回 0
                 失败返回 -1
    */
    //向单链表插入一个数据(中间插法)
    int slist_insert(NODE** head, DATA pos, DATA data)
    {
        //创建新节点
        NODE *pNew = (NODE*)malloc(sizeof(NODE));
        if(!pNew)
        {
            return -1;
        }
        //给新结点赋值
        pNew->data = data;
        pNew->next = NULL;
    
        NODE *p = *head, *q = NULL;
        //第一种情况,若原链表无任何一个节点
        if(!p)
        {
            *head = pNew;	//新节点作为头节点
            return 0;
        }
        //第二种情况,原链表只有一个头节点
        if(memcmp(&(p->data),&pos,sizeof(DATA)) == 0)
        {
            pNew->next = *head;		//头插法
            *head = pNew;
            return 0;
        }
        //第三种情况,原链表有多个节点
        while(p)
        {
            //尾插法
            if(memcmp(&(p->data),&pos,sizeof(DATA)) == 0)
            {
                //两句顺序不可调换,否则会覆盖
                pNew->next = p;	
                q->next = pNew;
                return 0;
            }
            //此时,q为前驱,p为后继;依次往后走
            q = p;
            p = p->next;
        }
        //第四种情况,要插入的位置pos不存在
        q->next = pNew;
        return 0;
    }
    
    
    /*
    @function:   int slist_delete(NODE** head,DATA data);
    @berif:     删除链表中节点值为data的节点
    @argument:   head: 指向头指针变量的地址
                 data: 删除节点中的数据
    @return :   成功返回 0
                 失败返回 -1
    */
    //删除单链表中的数据
    int slist_delete(NODE** head, DATA data)
    {
    	NODE* p = *head, *q = NULL;
        
        //第一种情况,若原链表无任何一个节点
        if(!p)
        {
            return -1;
        }
        //第二种情况,要删除的刚好是头节点
        if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
        {
            *head = p->next;
            free(p);
            return 0;
        }
        //第三种情况,逐个查找要删除的节点
        while(p)
        {
            if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
            {
                q->next = p->next;
                //这一步释放p并不影响数据的删除
                free(p);
                return 0;
            }
            q = p;
            p = p->next;
        }
        return -1;
    }
    
    
    /*
    @function:   NODE* slist_find(const NODE* head,DATA data);
    @berif:     查找链表数据data
    @argument:   head: 指向头指针变量
                 data: 待查找的数据
    @return :   成功返回节点的地址
                 失败返回NULL
    */
    //查找链表中的数据
    NODE* slist_find(const NODE* head, DATA data)
    {
    	const NODE* p = head;
        
        while(p)
        {
            if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
            {
                return (NODE*)p;
            }
            p = p->next;
        }
        return NULL;
    }
    
    
    /*
    @function:   int slist_update(const NODE* head,DATA old,DATA newdata);
    @berif:     更新链表数据old 为 newdata
    @argument:   head: 指向头指针变量
                 old: 待更新的节点数据
                 newdata: 更新后的节点数据
    @return :   成功返回 0
                 失败返回 -1
    */
    //修改更新链表数据
    int slist_update(const NODE* head, DATA old_data, DATA new_data)
    {
        NODE* p = NULL;
        
        if( !(p = slist_find(head,old_data)) )
        {
            return -1;
        }
        p->data = new_data;
        return 0;
    }
    
    
    /*
    @function:   void slist_showAll(const NODE* head);
    @berif:     遍历链表数据
    @argument:   head: 指向头指针变量
    @return :   无
    */
    //遍历链表
    void slist_showAll(const NODE* head)
    {
        const NODE* p = head;
    
        while(p)
        {
            printf("%d ", p->data);
            p = p->next;
        }
        printf("\n");
    }
    
    
    /*
    @function:   void slist_destroy(NODE** head);
    @berif:     回收链表
    @argument:   head: 指向头指针变量的地址
    @return :   无
    */
    //销毁单链表
    void slist_destroy(NODE** head)
    {
        NODE* p = *head, *q = NULL;
    
        while(p)	//逐个遍历回收
        {
            q = p;
            p = p->next;
            free(q);
        }
        *head = NULL;
    }
    ----------------------------------------------------------------------------------------------
    slist_main.c
    ----------------------------------------------------------------------------------------------
    #include "slist.h"
    #define DELETE 1
    
    int main()
    {
        NODE *head = NULL;
    
        if (slist_create(&head, 888) < 0)
        {
            perror("单链表创建失败!");
            // exit(0);
            return -1;
        }
        printf("单链表创建成功!\n");
    
        slist_addTail(&head, 999);
        slist_addTail(&head, 222);
        slist_addTail(&head, 666);
        slist_addTail(&head, 777);	// 运行效果:999 222 666 777
    
        slist_addHead(&head, 555);	// 运行效果:555 999 222 666 777
    
        slist_insert(&head, 555, 1024);	// 运行效果:1024 555 999 222 666 777
        slist_insert(&head, 222, 1080);	// 运行效果:1024 555 999 1080 222 666 777
        slist_showAll(head);
    
        DATA data;
    
        while (1)
        {
    #ifdef DELETE
            printf("请输入要删除的数据:");
            scanf("%d", &data);
            if (data == -1)
                break;
            if (slist_delete(&head, data) < 0)
            {
                puts("删除失败,请重试");
                continue;
            }
            slist_showAll(head);
    #else
            NODE *pFind = NULL;
            DATA newdata = 512;
    
            printf("请输入要查找的数据:");
            scanf("%d", &data);
            if (data == -1)
                break;
            // if(!(pFind = slist_find(head,data)))
            if (slist_update(head, data, newdata) == -1)
            {
                puts("查找的数据不存在,请重试");
                continue;
            }
            // printf("查找数据:%d  内存地址:%p\n",pFind -> data, &(pFind -> data));
            slist_showAll(head);
    #endif
        }
    
        slist_destroy(&head);
        puts("====销毁后====");
        slist_showAll(head);
    
        return 0;
    }
    双链表
    双链表相关数据操作代码
    //math2文件夹
    ----------------------------------------------------------------------------------------------
    dlist.h
    ----------------------------------------------------------------------------------------------
    #ifndef __DLIST_H
    #define __DLIST_H
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdbool.h>
    
    typedef int DATA;
    
    //定义双向链表结构体
    typedef struct node
    {
        DATA data;          //数据
        struct node *prev;  //前驱指针
        struct node *next;  //后继指针
    }NODE;
    
    //创建双向链表
    int dlist_create(NODE**, DATA);
    
    //向双链表插入数据(头插法)
    int dlist_addHead(NODE**, DATA);
    
    //向双链表插入数据(尾插法)
    int dlist_addTail(NODE**, DATA);
    
    //向双链表插入数据(中间插法)
    int dlist_insert(NODE**, DATA, DATA);
    
    //链表数据查询
    NODE* dlist_find(const NODE*, DATA);
    
    //链表数据更新
    int dlist_update(const NODE*, DATA, DATA);
    
    //链表数据遍历
    void dlist_showAll(const NODE*);
    
    //链表数据删除
    int dlist_delete(NODE**, DATA);
    
    //销毁双链表
    void dlist_destroy(NODE**);
    
    #endif
    ----------------------------------------------------------------------------------------------
    dlist.c
    ----------------------------------------------------------------------------------------------
    #include "dlist.h"
    
    //创建双向链表
    int dlist_create(NODE** head, DATA data)
    {
        //创建新节点
        NODE* pNew = (NODE*)malloc(sizeof(NODE));
        //判断是否申请成功
        if(!pNew)
        {
            return -1;
        }
        //给节点赋初值
        pNew->data = data;              //数据
        pNew->prev = pNew->next = NULL; //前驱后继指向空
        
        *head = pNew;                   //新节点作为头节点
    
        return 0;
    }
    
    
    //向双链表插入数据(头插法)
    int dlist_addHead(NODE** head, DATA data)
    {
        //创建新节点,申请内存空间
        NODE *pNew = (NODE*)malloc(sizeof(NODE));
        //判断是否申请成功
        if(!pNew)
        {
            return -1;
        }
        //给新节点赋值
        pNew->data = data;
        pNew->prev = NULL;
        pNew->next = *head;
    
        //如果头节点存在
        if(*head)
        {
            (*head)->prev = pNew;
        }
    
        *head = pNew;
    
        return 0;
    }
    
    
    //向双链表插入数据(尾插法)
    int dlist_addTail(NODE** head, DATA data)
    {
        //创建新节点,申请内存
        NODE *pNew = (NODE*)malloc(sizeof(NODE));
        //判断是否申请成功
        if(!pNew)
        {
            return -1;
        }
        //给新节点赋初值
        pNew->data = data;
        pNew->next = pNew->prev = NULL;
    
        NODE *p = *head;
        //第一种情况,头节点不存在
        if(!p)
        {
            *head = pNew;
            return 0;
        }
        //第二种情况,有一个或多个结点
        while (p->next) //循环来找到尾结点
        {
            p = p->next;
        }
    
        //尾节点的后继指向新插入节点
        p->next = pNew;
        //新插入节点的前驱指向尾节点
        pNew->prev = p;
        
        return 0;
    }
    
    
    //向双链表插入数据(中间插法)
    int dlist_insert(NODE** head, DATA pos, DATA data)
    {
        //创建新节点,申请空间
        NODE* pNew = (NODE*)malloc(sizeof(NODE));
        //判断是否申请成功
        if(!pNew)
        {
            return -1;
        }
        //给新节点赋初值
        pNew->data = data;
        pNew->next = pNew->prev = NULL;
    
        NODE* p = *head, *q = NULL;
        //第一种情况,没有头节点
        if(!p)
        {
            *head = pNew;
            return 0;
        }
        //第二种情况,只有一个头节点
        if(memcmp(&(p->data),&pos,sizeof(DATA)) == 0)
        {
            pNew->next = p;
            p->prev = pNew;
            *head = pNew;
            return 0;
        }
        //第三种情况,有多个节点
        while (p)
        {
            if(memcmp(&(p->data),&pos,sizeof(DATA)) == 0)
            {
                /*
                顺序:先自己再别人,先后再前
                */
                pNew->next = p;
                pNew->prev = q;
                p->prev = pNew;
                q->next = pNew;
    
                return 0;
            }
            q = p;
            p = p->next;
        }
        //第四种情况,找不到要插入的位置(也就是找不到pos)
        q->next = pNew;
        pNew->prev = q;
        
        return 0;
    }
    
    
    //链表数据查询
    NODE* dlist_find(const NODE* head, DATA data)
    {
        const NODE* p = head;
    
        while (p)
        {
            if (memcmp(&(p->data),&data,sizeof(DATA)) == 0)
            {
                return (NODE*)p;
            }
            //向后查询
            p = p->next;
            //向前查询
            //p = p->prev;
        }
        return NULL;
    }
    
    
    //链表数据更新
    int dlist_update(const NODE* head, DATA old_data, DATA new_data)
    {
        NODE* p = NULL;
    
        if( !(p = dlist_find(head,old_data)))
        {
            return -1;
        }
        p->data = new_data;
        return 0;
    }
    
    
    //链表数据遍历
    void dlist_showAll(const NODE* head)
    {
        const NODE* p = head;
        while(p)
        {
            //两种遍历方式不可同时使用
            printf("%d ", p->data);
            //向后遍历
            p = p->next;
            //向前遍历
            //p = p->prev;
        }
        printf("\n");
    }
    
    
    //链表数据删除
    int dlist_delete(NODE** head, DATA data)
    {
        NODE* p = *head;
    
        //第一种情况,原链表没有节点
        if(!p)
        {
            return -1;
        }
        //第二种情况,要删除的刚好是头节点
        if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
        {
            //1.链表中只有一个节点
            if(p->next == NULL)
            {
                free(p);
                *head = NULL;
                return 0;
            }
            //2.链表中有两个以上节点
            *head = p->next;        //此前head和p都指向头节点
            p->next->prev = NULL;   //解除p的引用关系
    
            free(p);
    
            return 0;
        }
        //第三种情况,逐个查找要删除节点
        while (p)
        {
            if(memcmp(&(p->data),&data,sizeof(DATA)) == 0)
            {
                p->prev->next = p->next;    //解除上一个节点和被删节点的关系
                //要删除的p刚好是尾节点
                if(p->next == NULL)
                {
                    p->prev->next = NULL;
                }
                else    //要删除的p不是尾节点
                {
                    p->next->prev = p->prev;
                }
    
                free(p);
                return 0;
            }
            p = p->next;    //改变循环条件
        }
        return -1;  
    }
    
    
    //销毁双链表
    void dlist_destroy(NODE** head)
    {
        NODE* p = *head, *q = NULL;
    
        while (p)
        {
            //实现指针尾随
            q = p;
            p = p->next;
            
            free(q);
        }
        *head = NULL;
    }
    ----------------------------------------------------------------------------------------------
    dlist_main.c
    ----------------------------------------------------------------------------------------------
    #include "dlist.h"
    #define OP   2  
    
    int main(void)
    {
        NODE*  head = NULL;
    
        int a[] = {1,3,5,7,9};
        int n = sizeof a / sizeof a[0];
    
        register int i = 0;
        for(; i < n; i++)
        {
            dlist_addTail(&head,a[i]);
        }
        dlist_showAll(head);
    
        while(1)
        {
    #if (OP == 0)
            DATA   data;
            NODE   *pFind = NULL;
            printf("请输入要查找的数据(-1 退出):");
            scanf("%d",&data);
            if(data == -1)
               break;
            if(!(pFind = dlist_find(head,data)))
            {
                puts("查找的数据不存在,请重试...");
                continue;
            }  
            printf("在内存地址为 %p 的内存空间中找到了 %d\n",&(pFind->data),pFind->data);
    #elif (OP == 1)   
            DATA   data;
            NODE   *pFind = NULL;
            printf("请输入要插入位置的数据(-1 退出):");
            scanf("%d",&data);
            if(data == -1)
               break;
            if(dlist_insert(&head,data,407))
            {
                puts("插入失败,请重试...");
                continue;
            }
            dlist_showAll(head);
    #else
            DATA   data;
            NODE   *pFind = NULL;
            printf("请输入要删除位置的数据(-1 退出):");
            scanf("%d",&data);
            if(data == -1)
               break;
            if(dlist_delete(&head,data))
           {
                puts("删除失败,请重试...");
                continue;
           }
            dlist_showAll(head);
    #endif
       }
         
        dlist_destroy(&head);
        puts("=====回收后====");
        dlist_showAll(head);
        
        return 0;
    }
    链表优缺点
  • 优点

    1. 插入删除数据仅需调整几个指针,较为便捷;

    2. 数据节点较多时,无需整片连续空间,可利用离散内存;

    3. 节点变化剧烈时,内存的分配和释放灵活、速度快;

  • 缺点

    1. 不支持立即随机访问任意随机数据;

    2. 需要多余指针记录节点间的关联关系;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值