从头到尾实现deque

关于deque内部数据结构的说明:
deque是能够同时在收尾进行插入和删除的数据结构。其表现形式看起来和vector差不多,但是在处理队头插入时,其表现的时间复杂度要远远地优越于vector。这个得益于deque的内部数据结构地实现。deque使用分段的连续空间组成。为了管理这个分段的连续空间,所以必须要有一个中控器来记录各个分段空间的首地址,然后需要一个长度来记录该中控器的大小。为了表现和vector相似,所以需要包含2个迭代器分别用来指向首尾元素。关于迭代器的内存元素:首先要有一个指针指向我们待表示的数据。然后就是该数据在那一个缓冲区上,为了在外面上表现为连续的空间,我们必须要记录该缓冲区的首尾地址,这样重载++的时候,能够方便的找到下一块地址。


其结构示意图如下:


其代码实现如下:

//deque的特性:(这段话中别人那copy过来的,因为感觉写的还不错)
//   对于任何一个迭代器i
//     i.node是map array中的某元素的地址. i.node的内容是一个指向某个结点的头的指针
//     i.first == *(i.node)
//     i.last  == i.first + node_size
//     i.cur是一个指向[i.first, i.last)之间的指针
//       注意: 这意味着i.cur永远是一个可以解引用的指针,
//            即使其是一个指向结尾后元素的迭代器
//
//   起点和终点总是非奇异(nonsingular)的迭代器.
//     注意: 这意味着空deque一定有一个node, 而一个具有N个元素的deque
//          (N是Buffer Size)一定有有两个nodes
//
//   对于除了start.node和finish.node之外的每一个node, 每一个node中的元素
//   都是一个初始化过的对象. 如果start.node == finish.node,
//   那么[start.cur, finish.cur)都是未初始化的空间.
//   否则, [start.cur, start.last)和[finish.first, finish.cur)都是初始化的对象,
//   而[start.first, start.cur)和[finish.cur, finish.last)是未初始化的空间
//
//   [map, map + map_size)是一个合法的非空区间
//   [start.node, finish.node]是内含在[map, map + map_size)区间的合法区间
//   一个在[map, map + map_size)区间内的指针指向一个分配过的node,
//   当且仅当此指针在[start.node, finish.node]区间内


#ifndef ITERATOR_DEQUE_H_INCLUDED
#define ITERATOR_DEQUE_H_INCLUDED
#include"my_iterator_base.h"
namespace juine
{
     //用来决定缓存区的大小
    inline size_t __deque_buf_size(size_t n, size_t sz)
    {
        return n!=0?n:(sz<512?size_t(512/sz):size_t(1));
    }

    template<class T,size_t buf_size=0>
    class Iterator_deque
    {
    public:
        typedef T value_type;
        typedef value_type* pointer;
        typedef value_type& reference;
        typedef ptrdiff_t difference_type;
        typedef random_access_iterator_tag iterator_category;
        typedef T** map_pointer;

        pointer data;  //所要表示节点的位置
        pointer first;  //所在分区的起始位置
        pointer last;   //所在分区的末尾位置
        map_pointer node;  //所在分区

    public:
        size_t buff_size() {  return __deque_buf_size(buf_size,64); }
        //设定节点的位置
        void set_node(map_pointer new_node)
        {
            node=new_node;
            first=0;
            last=0;
            if(new_node!=0)
            {
                first=*new_node;
                last=*new_node+difference_type(buff_size()); //表明又是左闭右开区间
            }
        }
        //construct
        Iterator_deque(pointer _data=0,map_pointer _node=0):data(_data){    set_node(_node);  }
        Iterator_deque(const Iterator_deque& iter):data(iter.data),first(iter.first),last(iter.last),node(iter.node){}
        Iterator_deque& operator=(const Iterator_deque& iter)
        {
            data=iter.data;
            first=iter.first;
            last=iter.last;
            node=iter.node;
            return *this;
        }
        //重载解引用,==,!=
        reference operator*() { return *data;}
        pointer operator->()  { return &(operator*());}
        bool operator==(Iterator_deque iter){ return data==iter.data;}
        bool operator!=(Iterator_deque iter){ return data!=iter.data; }
        bool operator<=(Iterator_deque iter)
        {
            if(node==iter.node)
                return data<=iter.data;
            return node<=iter.node;
        }

        //随机迭代器必须重载++,--
        Iterator_deque operator++()
        {
            data++;
            if(data==last)
            {
                set_node(node+1);
                data=first;
            }
            return *this;
        }
        Iterator_deque operator++(int)   //后置式的标准写法
        {
            Iterator_deque iter=*this;
            ++*this;
            return iter;

        }
        Iterator_deque operator--()
        {
            if(data==first)
            {
                set_node(node-1);
                data=last;   //左闭右开
            }
            data--;
            return *this;
        }
        Iterator_deque operator--(int)
        {
            Iterator_deque iter=*this;
            --*this;
            return iter;
        }


// 将迭代器向前移动n个元素, n可以为负

//                     operator+=(difference_type n)
//                                   ↓
//                      offset = n + (cur - first)
//                                   |
//                                   |---------- offset > 0 ? &&
//                                   |           移动后是否超出当前缓冲区?
//               ----------------------------
//           No  |                          |  Yes
//               |                          |
//               ↓                          |---------- offset > 0?
//           cur += n;                      |
//                              ----------------------------
//                          Yes |                          | No
//                              |                          |
//                              ↓                          |
//                   计算要向后移动多少个缓冲区                |
//                   node_offset =                         |
//                   offset / difference_type              |
//                   (buffer_size());                      ↓
//                              |           计算要向前移动多少个缓冲区
//                              |           node_offset = -difference_type
//                              |           ((-offset - 1) / buffer_size()) - 1;
//                              |                          |
//                              ----------------------------
//                                           |
//                                           |
//                                           ↓
//                                       调整缓冲区
//                              set_node(node + node_offset);
//                                    计算并调整cur指针

    // 以下实现随机存取。迭代器可以直接跳跃n个距离
        Iterator_deque operator+=(difference_type n)
        {
            difference_type offset=n+(data-first);
            if(offset>=0&&offset<difference_type(buff_size()))
               //目标位置在同一个缓冲区内
                data+=n;
            else
            {
                //目标位置不在同一个缓冲区中
                difference_type node_offset=
                offset>0?offset/difference_type(buff_size()):-difference_type((-offset-1)/buff_size())-1;
                //切换所在缓冲区
                set_node(node+node_offset);
                //切换所在目录
                data=first+(offset-node_offset*difference_type(buff_size()));
            }
            return *this;
        }
        Iterator_deque operator+(size_t n)
        {
            Iterator_deque iter=*this;
            return iter+=n;
        }
        /*
        自己写的,发现效率还是比STL的低,而且STL中是将其+=,-=一起处理的
        Iterator_deque operator-=(size_t n)
        {
            if(size_t(data-first)>=n)
                data-=n;
            else
            {
                //表示已经越界了该缓存区。
                set_node(node-1);
                n-=data-first;
                data=last;
                while(n>buff_size())
                {
                    set_node(node-1);
                    n-=buff_size();
                    data=last;
                }
                data-=n;
            }
            return *this;
        }*/
        Iterator_deque operator-=(size_t n)  {  return *this+=-n;}
        Iterator_deque operator-(size_t n)
        {
            Iterator_deque iter=*this;
            iter-=n;
            return iter;
        }

        difference_type operator-(Iterator_deque iter)
        {
            difference_type offet=(node==iter.node)?data-iter.data:((data-first)+(iter.last-iter.data));
            if(node-iter.node>1)
                offet+=(node-iter.node-1)*buff_size();
            return offet;
        }
    };
}


#endif // ITERATOR_DEQUE_H_INCLUDED

容器deque代码如下:

#ifndef MY_DEQUE_H_INCLUDED
#define MY_DEQUE_H_INCLUDED

#include"iterator_deque.h"
#include<algorithm>
#include<cstdlib>
#include"simple_allocator.h"
using std::cout;
using std::endl;

namespace juine
{

    template<class T,class Alloc=my_alloc,size_t buf_size=0>
    class my_deque
    {
    public:
        typedef T value_type;
        typedef T* pointer;
        typedef T& reference;
        typedef size_t size_type;
        typedef T** map_pointer;

        typedef simple_allocator<T,Alloc>  data_container;
        typedef simple_allocator<pointer,Alloc>  map_container;

        typedef Iterator_deque<T,buf_size> iterator;
    protected:
        size_t map_size;    //申请map_node节点个数,再根据_map能获得map_node的尾节点
        map_pointer _map;   //_map是申请的map_node节点的首地址
        iterator start;
        iterator finish;

        size_t buff_size() {  return __deque_buf_size(buf_size,64); }
        size_type initial_map_size() {    return 8 ;}
        //构造函数时,将deque的结构安排好,初始化deque数据结构

        void set_null(map_pointer first,map_pointer last)
        {
            //map节点是用来保存缓冲区地起始地址,
            //但是占为使用的map节点应该保存NULL
            map_pointer cur;
            for(cur=first;cur<last;cur++)
                *cur=NULL;
        }

        void create_map_and_nodes(size_t n)
        {
            cout<<"buff_size:"<<buff_size()<<endl;
            size_type num_nodes=n/buff_size()+1;//当整除时也是要加1的
            //map中控器的大小
            //map_size=std::max(initial_map_size(),num_nodes+2);
            //为了测试,map_size特殊化
            map_size=num_nodes;
            _map=map_container::alloc(map_size);

            //以下是令nstart和nfinish指向map所拥有之全部节点的最中央区段
            //保持在最中央,可使头尾两端扩充的能量一样大,每一节点可对应一个缓冲区
            //(出现这种情况是因为,我们申请的节点数,是大于初始化时所需要的节点数,
            //因此,需要将节点移动中间,使首尾平衡)
            map_pointer nstart=_map+(map_size-num_nodes)/2;
            map_pointer nfinish=nstart+num_nodes-1;
            //申请缓冲区空间,申请节点为刚才所找到的平衡位置
            map_pointer cur;
            //申请到的node,暂时不指向缓冲区的应该将其值为NULL
            set_null(_map,nstart);
            set_null(nfinish,_map+map_size);

            for(cur=nstart;cur<=nfinish;cur++)
                *cur=data_container::alloc(buff_size());

            //接下来是配置迭代器start,finish
            start.set_node(nstart);
            finish.set_node(nfinish);
            start.data=*nstart;
            finish.data=*nfinish+n%buff_size();
        }

        void fill_uninitialized()
        {
            //默认设定20个指针
            _map=map_container::alloc(20);
            map_size=20;
        }
        void fill_uninitialized(size_t n,value_type value)
        {
            //构造deque时,首先是产生deque的结构并安排组织好
            create_map_and_nodes(n);
            /*
            以下注释部分自己写的,考虑不充分:自己强制规定map的大小,
            当所需数据刚好为buf_size时,处理地方式也与STL不一样
            _map=map_container::alloc(20);
            map_size=20;
            map_pointer temp=_map;
            int remainder=n%buf_size;
            int num=(remainder==0?n/bufsize:(n/buf_size+1));

            while(num)
            {
                pointer p=data_container::alloc(buf_size);
                if(num==1)
                    uninitialized_fill_n(p,remainder==0?buf_size:remainder,value);  //最后一组只能填充remainder个元素
                else
                    uninitialized_fill_n(p,buf_size,value);
                num--;
                *temp=p; //将容器整个结构连接起来
                temp++;
            }
            //对迭代器first,last进行初始化
            iterator temp(*_map,*_map,*_map+buf_size-1,_map);
            first=temp;
            if(remainder!=0)
            {
                iterator temp1(*(_map+n/buf_size)+remainder-1,*(_map+n/buf_size),*(_map+n/buf_size)+remainder-1,_map+n/buf_size );
                last=temp1;
            }
            else
            {
                iterator temp1(*(_map+n/buf_size-1)+buf_size-1,*(_map+n/buf_size),*(_map+n/buf_size-1)+buf_size-1,_map+n/buf_size-1 );
                last=temp1;
            }*/
            //开始对申请到的节点进行初始化
            map_pointer cur;
            for(cur=start.node;cur<finish.node;cur++)
                uninitialized_fill_n(*cur,buff_size(),value);
            uninitialized_fill_n(finish.first,n%buff_size(),value);
        }
        template<class InputIterator>
        void fill_uninitialized(InputIterator first,InputIterator last)
        {
            size_type num=last-first;
            create_map_and_nodes(num);
            //deque结构体已构造好,然后需要赋值
            iterator iter=start;
            while(first!=last)
            {
                construct(iter.data,*first);
                iter++;
                first++;
            }
        }

        //释放内存
        void dealloc()
        {
            cout<<"deque容器已释放"<<endl;
            //先遍历map释放所有缓冲区
            size_t i;
            for(i=0;i<map_size;i++)
                if(*(_map+i))   //当节点值为空时,表示其所指向缓冲区为空,不需要释放
                {
                    map_pointer temp=_map+i;
                    destroy(*temp,*temp+buff_size());
                    data_container::dealloc(*(_map+i));
                }

            //删除_map
            map_container::dealloc(_map);
        }
    public:
        //construct
        my_deque(){    fill_uninitialized();}
        my_deque(size_t n,value_type value) { fill_uninitialized(n,value);}
        template<class InputIterator>
        my_deque(InputIterator first,InputIterator last){ fill_uninitialized(first,last);}
        ~my_deque(){ dealloc() ;}

        iterator begin(){   return start; }
        iterator end(){     return finish; }
        reference operator[](size_type n)
        {
            iterator iter=start+n;
            return *iter;
        }
        reference at(size_type n)
        {
            iterator iter=start+n;
            if(finish<=iter)
            {
                cout<<"out of memory"<<endl;
                exit(1);
            }
            return iter;
        }
        reference front(){  return *start; }
        reference back(){   return *(finish-1); }
        size_type size() {  return finish-start; }
        bool empty(){   return start==finish; }

        void push_front(value_type value){  insert(start,value); }
        void push_back(value_type value){   insert(finish,value);}

        //插入函数(考虑缓冲区大小够不,Map大小够不,还有效率-左移还是右移)
        void insert(iterator position,value_type value)
        {
            if(position-start<=finish-position)  //表示需要左移动
            {
                iterator temp,new_start;
                temp=new_start=insert_aux_left(1,position);
                iterator temp1=start;
                //这个地方赋值,利用了迭代器重载函数++,使其能够自动在片段连续空间上移动
                while(temp1!=position)  //移动元素
                    *temp++=*temp1++;
                *temp=value;  //最后插入元素
                start=new_start;   //插入之后,迭代器要更新,Temp保存的就是最新的位子
            }
            else
            {
                iterator temp,new_finish;
                temp=new_finish=insert_aux_right(1,position);
                iterator temp1=finish;
                //这个地方赋值,利用了迭代器重载函数++,使其能够自动在片段连续空间上移动
                while(temp1!=position)  //移动元素
                    *--temp=*--temp1;
                *position=value;  //最后插入元素
                finish=new_finish;   //插入之后,迭代器要更新,Temp保
            }

        }

        template<class InputIterator>
        void insert(iterator position,InputIterator first,InputIterator last)
        {
            int n=last-first;
            if(position-start<=finish-position)  //表示需要左移动
            {
                iterator temp,new_start;
                temp=new_start=insert_aux_left(n,position);
                iterator temp1=start;
                //这个地方赋值,利用了迭代器重载函数++,使其能够自动在片段连续空间上移动
                while(temp1!=position)  //移动元素
                    *temp++=*temp1++;
                while(temp!=position)
                    *temp++=*first++;  //最后插入元素
                start=new_start;
            }
            else
            {
                iterator temp,new_finish;
                temp=new_finish=insert_aux_right(n,position);
                iterator temp1=finish;
                //这个地方赋值,利用了迭代器重载函数++,使其能够自动在片段连续空间上移动
                while(temp1!=position)  //移动元素
                    *--temp=*--temp1;
                while(first!=last)
                    *position++=*first++;  //最后插入元素
                finish=new_finish;
            }
        }

        //该函数功能:如容量不够,则扩充容量然后更新该deque结构
        //该函数返回值,是表示在插入后,start迭代器所在正确位置
        iterator insert_aux_left(size_t number,iterator& position)    //number表示插入的数据,而且该数据是左移的。
        {
            iterator result;
            if(start.data-number<=start.first-1)  //表示前端缓冲区已满,因此要增加缓冲区
            {
                //要增加的缓冲区
                int left_need_size=number-(start.data-start.first);
                int need_map_nodes=left_need_size/buff_size()+1;
                if(start.node-_map<need_map_nodes)  //表示中控器前端node节点不够了
                {
                    //虽然前面的不够,但是整个_map空余空间够,可以通过移动_map节点来实现
                    //不用重新分配新的节点。
                    if(size_t(finish.node-start.node+1+need_map_nodes)<=map_size)
                    {
                        cout<<"移动map达到要求"<<endl;
                       //offset为map移动偏移量
                        int offset=need_map_nodes-(start.node-_map);
                        map_pointer temp=finish.node;
                        while(temp>=start.node)
                        {
                            *(temp+offset)=*temp;
                            temp--;
                        }
                        //前面_map移动后,finish中所存的Node,已经不是最后一个节点所在位置
                        //故要移动,更新finish
                        finish.node=finish.node+offset;
                        //map前端node节点已够用,现在是要申请缓冲区内存
                        map_pointer malloc_node=_map;
                        while(malloc_node<(start.node+offset))
                        {
                            *malloc_node=data_container::alloc(buff_size());
                            malloc_node++;
                        }
                        result.set_node(_map);
                        result.data= *_map+buff_size()-left_need_size%buff_size();
                    }
                    else //map节点不够,需要重新分配其map_nodea
                    {
                        //map大小,简单以2倍来扩充
                        map_pointer new_malloc_map=map_container::alloc(map_size*2);
                        map_pointer item=new_malloc_map+map_size/2;
                        map_pointer old_node=start.node;

                        set_null(new_malloc_map,item);
                        set_null(new_malloc_map+map_size,new_malloc_map+map_size*2);

                        //将旧的map复制过来
                        while(old_node<=finish.node)
                            *item++=*old_node++;
                        //因为map重新申请了,所以position也应该换到新map对应的位置上
                        position.node=new_malloc_map+map_size/2+(position.node-start.node);

                        //删除旧的map,释放内存
                        map_container::dealloc(_map);
                        start.node=new_malloc_map+map_size/2;
                        finish.node=item-1;
                        _map=new_malloc_map;
                        map_size*=2;


                         //需要申请内存
                        map_pointer new_node=start.node-1;
                        while(new_node>=start.node-need_map_nodes)
                        {
                            *new_node=data_container::alloc(buff_size());
                            new_node--;
                        }
                        result.set_node(start.node-need_map_nodes);
                        result.data= *result.node+buff_size()-left_need_size%buff_size();


                    }
                }
                else //缓冲区不够,但是map前端所需node是够的
                {
                    //需要申请内存
                    map_pointer new_node=start.node-1;
                    while(new_node>=start.node-need_map_nodes)
                    {
                        *new_node=data_container::alloc(buff_size());
                        new_node--;
                    }
                    result.set_node(start.node-need_map_nodes);
                    result.data= *result.node+buff_size()-left_need_size%buff_size();

                }
            }
            else //缓冲区是够的
            {
                result.data=start.data-number;
                result.set_node(start.node);
            }
            return result;
        }

        iterator insert_aux_right(size_t number,iterator& position)    //number表示插入的数据,而且该数据是右移的。
        {
            iterator result;
            if(finish.data+number>=finish.last)  //表示前端缓冲区已满,因此要增加缓冲区
            {
                //要增加的缓冲区
                int left_need_size=number-(finish.last-finish.data-1);
                int need_map_nodes=left_need_size/buff_size()+1;
                if(_map+map_size-finish.node<need_map_nodes)   //表示中控器后端node节点不够了
                {
                    //虽然前面的不够,但是整个_map空余空间够,可以通过移动_map节点来实现
                    //不用重新分配新的节点。
                    if(size_t(finish.node-start.node+1+need_map_nodes)<=map_size)
                    {
                        cout<<"移动map达到要求"<<endl;
                        //offset为map移动偏移量
                        int offset=need_map_nodes-(_map-finish.node);
                        map_pointer temp=start.node;
                        while(temp<=finish.node)
                        {
                            *(temp-offset)=*temp;
                            temp++;
                        }
                        //前面_map移动后,finish中所存的Node,已经不是最后一个节点所在位置
                        //故要移动,更新finish
                        start.node=start.node-offset;
                        //map前端node节点已够用,现在是要申请缓冲区内存
                        map_pointer malloc_node=_map+map_size;
                        while(malloc_node>(finish.node-offset))
                        {
                            *malloc_node=data_container::alloc(buff_size());
                            malloc_node--;
                        }
                        result.set_node(_map+map_size);
                        result.data= *(_map+map_size)+left_need_size%buff_size();
                    }
                    else //map节点不够,需要重新分配其map_nodea
                    {
                        //map大小,简单以2倍来扩充
                        map_pointer new_malloc_map=map_container::alloc(map_size*2);
                        map_pointer item=new_malloc_map+map_size/2;
                        map_pointer old_node=start.node;

                        set_null(new_malloc_map,item);
                        set_null(new_malloc_map+map_size,new_malloc_map+map_size*2);

                        //将旧的map复制过来
                        while(old_node<=finish.node)
                            *item++=*old_node++;
                        //因为map重新申请了,所以position也应该换到新map对应的位置上
                        position.node=new_malloc_map+map_size/2+(position.node-start.node);

                        //删除旧的map,释放内存
                        map_container::dealloc(_map);
                        start.node=new_malloc_map+map_size/2;
                        finish.node=item-1;
                        _map=new_malloc_map;
                        map_size*=2;

                         //需要申请内存
                        map_pointer new_node=finish.node+1;
                        while(new_node<=finish.node+need_map_nodes)
                        {
                            *new_node=data_container::alloc(buff_size());
                            new_node++;
                        }
                        result.set_node(finish.node+need_map_nodes);
                        result.data= *result.node+left_need_size%buff_size();


                    }
                }
                else //缓冲区不够,但是map前端所需node是够的
                {
                    //需要申请内存
                    map_pointer new_node=finish.node+1;
                    while(new_node<=finish.node+need_map_nodes)
                    {
                        *new_node=data_container::alloc(buff_size());
                        new_node++;
                    }
                    result.set_node(finish.node+need_map_nodes);
                    result.data= *result.node+left_need_size%buff_size();

                }
            }
            else //缓冲区是够的
            {
                result.data=finish.data+number;
                result.set_node(finish.node);
            }
            return result;
        }

        //删除元素(为了简单,只写一种)
        void erase(iterator position)
        {
            iterator temp;
            if(position-start<=finish-position)  //左边的元素右移动
            {
                temp=position-1;
                while(start<=temp)
                {
                    *(temp+1)=*temp;
                    temp--;
                }
                start++;
                if((start-1).node!=start.node)
                    *(start-1).node=NULL;


            }
            else
            {
                temp=position+1;
                while(temp!=finish)
                {
                    *(temp-1)=*temp;
                    temp++;
                }
                finish--;
                if((finish+1).node!=finish.node)
                    *(finish+1).node=NULL;
            }
        }
        void pop_back(){ erase(finish-1);}
        void pop_front(){ erase(start);   }
    };
}

#endif // MY_DEQUE_H_INCLUDED
测试代码如下:

#include<iostream>
#include<cstdlib>
#include<vector>
#include"my_deque.h"
using namespace std;
using namespace juine;
int main()
{
    int a[9]={1,2,3,4,5,6,7,8,9};
    my_deque<int> tt(a,a+9);
    tt.front()+=tt.back();
    tt.back()+=tt.front();
    my_deque<int>::iterator iter=tt.begin()+2;
    tt.insert(iter,a,a+9);
    tt.insert(tt.begin(),11);
    tt.push_front(12);
    tt.push_back(25);
    tt.erase(tt.begin()+4);
    tt.pop_front();

    for(iter=tt.begin();iter!=tt.end();iter++)
        cout<<*iter<<endl;
    cout<<"size:"<<tt.size()<<endl;
    return 0;
}

测试结果为:


已基本达到要求,下一章实现序列容器。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值