LL1课程设计报告及代码

一.需求分析

1.问题的提出:

语法分析是编译过程的核心部分。他的任务是在词法分析识别单词符号串的基础上,分析并判断程序的的语法结构是否符合语法规则。语言的语法结构是用上下文无关文法描述的。因此语法分析器的工作的本质上就是按文法的产生式,识别输入符号串是否为一个句子。对于一个文法,当给你一串符号是,如何知道它是不是该文法的一个句子,这是这个课程设计所要解决的一个问题。

2.问题解决:

       其实要知道一串符号是不是该文法的一个句子,只要判断是否能从文法的开始符号出发推导出这个输入串。语法分析可以分为两类,一类是自上而下的分析法,一类是自下而上的分析法。自上而下的主旨是,对任何输入串,试图用一切可能的办法,从文法开始符号出发,自上而下的为输入串建立一棵语法树。或者说,为输入串寻找一个最左推倒,这种分析过程的本质是一种试探过程,是反复使用不同产生式谋求匹配输入串的过程我主要是自上而下的过程。

3.解决步骤:

       在自上而下的分析法中,主要是研究LL(1)分析法。它的解决步骤是首先接收到用户输入的一个文法,对文法进行检测和处理,消除左递归,得到LL(1)文法,这个文法应该满足:无二义性,无左递归,无左公因子。当文法满足条件后,再分别构造文法每个非终结符的FIRST和FOLLOW集合,然后根据FIRST和FOLLOW集合构造LL(1)分析表,最后利用分析表,根据LL(1)语法分析构造一个分析器。LL(1)的语法分析程序包含了三个部分,总控程序,预测分析表函数,先进先出的语法分析栈。

二.概要设计

1.设计原理:

所谓LL(1)分析法,就是指从左到右扫描输入串(源程序),同时采用最左推导,且对每次直接推导只需向前看一个输入符号,便可确定当前所应当选择的规则。实现LL(1)分析的程序又称为LL(1)分析程序或LL1(1)分析器。

我们知道一个文法要能进行LL(1)分析,那么这个文法应该满足:无二义性,无左递归,无左公因子。当文法满足条件后,再分别构造文法每个非终结符的FIRST和FOLLOW集合,然后根据FIRST和FOLLOW集合构造LL(1)分析表,最后利用分析表,根据LL(1)语法分析构造一个分析器。LL(1)的语法分析程序包含了三个部分,总控程序,预测分析表函数,先进先出的语法分析栈,本程序也是采用了同样的方法进行语法分析,该程序是采用了C++语言来编写,其逻辑结构图如下:

                           

LL(1)预测分析程序的总控程序在任何时候都是按STACK栈顶符号X和当前的输入符号a做哪种过程的。对于任何(X,a),总控程序每次都执行下述三种可能的动作之一:

(1)若X = a =‘#’,则宣布分析成功,停止分析过程。

(2)若X = a ‘#’,则把X从STACK栈顶弹出,让a指向下一个输入符号。

(3)若X是一个非终结符,则查看预测分析表M。若M[A,a]中存放着关于X的一个产生式,那么,首先把X弹出STACK栈顶,然后,把产生式的右部符号串按反序一一弹出STACK栈(若右部符号为ε,则不推什么东西进STACK栈)。若M[A,a]中存放着“出错标志”,则调用出错诊断程序ERROR。

事实上,LL(1)的分析是根据文法构造的,它反映了相应文法所定义的语言的固定特征,因此在LL(1)分析器中,实际上是以LL(1)分析表代替相应方法来进行分析的。

2.构造LL(1)分析表

考查文法G[E]:

E→E+T | T

              T→T*F | F

F→( E ) | i | x | y

我们容易看出此文法没有左公因子也没有二义性,但却存在两个直接左递归,这里我们利用引入新非终结符的方法来消除它使方法满足要求,即:

对形如:U→Ux|y的产生式(其中x,y V+ ,y不以U开头),引入一个新的非终结符U’后,可以等价地改写成为:

U→yU’

                            U’→x U’|ε

显然改写后,U和U’都不是左递归的非终结符。因此文法G[E]按上述方法消去左递归后可等价地写成:

E→TP

                P→+TP | ε

                T→FQ

                Q→*FQ | ε

F→( E ) | i | x | y

在构造LL(1)预测分析表之前,首先要构造该文法的每个非终结符的FIRST和FOLLOW集合,按照下面描述的算法来构造这两个集合。

①FIRST集合的构造算法:

(1)若X∈VT,则FIRST(X)={X}。

(2)若X∈VN,且有产生式X→a……,则把a加入到FIRST(X)中;若X→ε也是一条产生式,则把ε也加到FIRST(X)中。

(3)若X→Y……是一个产生式且Y∈VN,则把FIRST(Y)中的所有非ε-元素都加到FIRST(X)中;若X→Y1Y2…Yk是一个产生式,Y1,…,Yi-1都是非终结符,而且,对于任何j,1≤j≤i-1,FIRST(Yj)都含有ε(即Y1…Yi-1* ε),则把FIRST(Yj)中的所有非ε-元素都加到FIRST(X)中;特别是,若所有的FIRST(Yj)均含有ε,j=1,2,…,k,则把ε加到FIRST(X)中。

连续使用上面的规则,直至每个集合FIRST不再增大为止。

②FOLLOW集合的构造算法:

(1)对于文法的开始符号S,置#于FOLLOW(S)中;

(2)若A→αBβ是一个产生式,则把FIRST(β)| {ε}加至FOLLOW(B)中;

(3)若A→αB是一个产生式,或A→αBβ是一个产生式而β ε(即ε∈FIRST(β)),则把FOLLOW(A)加至FOLLOW(B)中。

连续使用上面的规则,直至每个集合FOLLOW不再增大为止。

       根据以上描述的算法,可以构造文法G[E]的FIRST和FOLLOW集合如下:

        FIRST(E) = {( , i,x,y }             FOLLOW(E) = { ) , # }

        FIRST(P) = { + ,ε}                          FOLLOW(P) = { ) , # }

        FIRST(T) = { ( , i,x,y }               FOLLOW(T) = { + , ) , # }

        FIRST(Q) = { * , ε}                    FOLLOW(Q) = { + , ) , # }

        FIRST(F) = { ( , i,x,y }               FOLLOW(F) = { * , + , ) , # }

 

       现在来构造G[E]的LL(1)预测分析表。预测分析表M[A, a]是如下形式的一个矩阵。A为非终结符,a是终结符或‘#’。矩阵元素 M[A, a]中存放这一条关于A的产生式,指出当A面临输入符号a是所应采用的规则。M[A, a]也可能存放一条“出错标志”,指出当A根本不该面临输入符号a。文法G[E]的LL(1) 预测分析表如下:

 

 

 

i

 

+

 

    x

 

   y

 

   *

 

  (

 

   )        #

 

E

 

E→TP

 

ERROR

 

E→TP

 

E→TP

 

ERROR

 

E→TP

 

ERROR   ERROR

 

P

 

ERROR

 

E→+TP

 

ERROR

 

ERROR

 

ERROR

 

ERROR

 

P→ε       P→ε

 

T

 

T→FQ

 

ERROR

 

T→FQ

 

T→FQ

 

ERROR

 

T→FQ

 

ERROR   ERROR

 

Q

 

ERROR

 

Q→ε

 

ERROR

 

ERROR

 

Q→*FQ

 

ERROR

 

Q→ε       Q→ε

 

F

 

F→i

 

ERROR

 

F→x

 

F→y

 

ERROR

 

F→(E)

 

ERROR   ERROR

其中,E、P 、T、Q、F为方法G[E]的非终结符,i、+、x、y、*、(、),为方法G[E]的终结符,值得注意的是,“#”不管有没有ε产生式,我们在构造分析表时都不能省去。

3.利用分析表进行预测分析的步骤

       对于这个文法,假设输入串为i*i+i,利用分析表进行预测分析的步骤为:

 

步骤                       符号栈                     输入串             所用产生式

               0                         #E                        i*i+i#

               1                         #PT                      i*i+i#                    E→TP

             2                         #PQF                    i*i+i#                    T→FQ

               3                         #PQi                     i*i+i#                    F→i

               4                         #PQ                      *i+i#

               5                         #PQF*                  *i+i#                   Q→*FQ

               6                         #PQF                      i+i#

               7                         #PQi                       i+i#                   F→i

               8                         #PQ                         +i#

               9                         #P                           +i#                  Q→ε

               10                       #PT+                       +i#                  P→+TP

               11                       #PT                             i#

               12                       #PQF                           i#                  T→FQ

               13                       #PQi                            i#                  F→i

               14                       #PQ                             #

               15                       #P                               #                  Q→ε

               16                       #                                 #                  P→ε

 

三. 详细设计

1.程序流程图:

在对程序各个模块分析之前。先给出整个程序的流程图。以便于在分析过程中更好的对各个模块之间的联系进行了解。

程序的流程图如下:

 

2.消除左递归

1)消除左递归时主要经历以下步骤:

a)对文法按推导字母顺序的顺序排列,且将开始符置于数组最前部,这里采用冒泡算法。

b)查看文法是否含有左递归,如果没有,则终止。

c)准备两个字符串数组:tweenStrArr和tmpGrammerArr,tweenStrArr用以

存放每一个非终止符作为左侧推导项时临时分析结果,tmpGrammerArr则用以存放去除左递归后的文法。

       接下来即可进行消除左递归的过程,核心算法框架如下:

       FOR   i: = 1    To  n  Do

BEGIN

FOR   j:=1  To  i–1  Do

              把形如Pi®Pj γ的规则改写成:                           

Pid®1 γ |d2 γ |…|dk γ ,其中

                     Pjd®1|d2|…|dk是关于Pi的所有规则;    消除关于Pi规则的直接左递归性

END

2)消除左递归关键代码

void recur(char *point)

{                     /*完整的产生式在point[]中*/

    int j,m=0,n=3,k;

       char temp[20],ch;

       ch=c();           /*得到一个非终结符*/

       k=strlen(non_ter);

       non_ter[k]=ch;

       non_ter[k+1]='/0';

       for(j=0;j<=strlen(point)-1;j++)

       {    

              if(point[n]==point[0])

              {                          /*如果‘|’后的首符号和左部相同*/

                     for(j=n+1;j<=strlen(point)-1;j++)

                     {

                          while(point[j]!='|'&&point[j]!='/0')

                                temp[m++]=point[j++];

                            left[count]=ch;

                            memcpy(right[count],temp,m);

                            right[count][m]=ch;

                            right[count][m+1]='/0';

                            m=0;

                            count++;

                            if(point[j]=='|')

                            {

                                   n=j+1;

                                   break;

                            }

                     }

              }

              else

              {                          /*如果‘|’后的首符号和左部不同*/

                     left[count]=ch;

                     right[count][0]='^';

                     right[count][1]='/0';

                     count++;

                     for(j=n;j<=strlen(point)-1;j++)

                     {

                         if(point[j]!='|')

                             temp[m++]=point[j];

                         else

                            {

                                left[count]=point[0];

                                memcpy(right[count],temp,m);

                                right[count][m]=ch;

                                right[count][m+1]='/0';

                                   printf(" count=%d ",count);

                                   m=0;

                                count++;

                            }

                     }

            left[count]=point[0];

                  memcpy(right[count],temp,m);

                  right[count][m]=ch;

                 right[count][m+1]='/0';

                     count++;

                  m=0;

              }

       }

}

 

3.得到预测分析表

消除左递归之后,我们需要判断高文法是否为LL(1)文法,如果不是则结束程序,否则创建FIRST和FOLLOW集合,然后根据FIRST和FOLLOW集合的到预测分析表

本模块的流程图如下:

 

1)创建FIRST和FOLLOW集合程序代码如下:

void First(int U)

{

 int i,j;

 for(i = 0; i < PNum; i++)

 {

  if(P[i].lCursor == U)

  {

   struct pRNode* pt;

   pt = P[i].rHead;

   j = 0;

   while(j < P[i].rLength)

   {

    if(100 > pt->rCursor)

    {

     /*注:此处因编程出错,使空产生式时

     rlength同样是1,故此处同样可处理空产生式*/

     AddFirst(U, pt->rCursor);

     break;

    }

    else

    {

     if(NULL == first[pt->rCursor - 100])

     {

      First(pt->rCursor);

     }    

     AddFirst(U, pt->rCursor);

     if(!HaveEmpty(pt->rCursor))

     {

      break;

     }

     else

     {

      pt = pt->next;

     }

    }

    j++;

   }

   if(j >= P[i].rLength) /*当产生式右部都能推出空时*/

    AddFirst(U, -1);

  }

 }

}

/*加入first集*/

void AddFirst(int U, int nCh) /*当数值小于100时nCh为Vt*/

/*当处理非终结符时,AddFirst不添加空项(-1)*/

{

 struct collectNode *pt, *qt;

 int ch; /*用于处理Vn*/

 pt = NULL;

 qt = NULL;

 if(nCh < 100)

 {

  pt = first[U - 100];

  while(NULL != pt)

  {

   if(pt->nVt == nCh)

    break;

   else

   {

    qt = pt;

    pt = pt->next;

   }

  }

  if(NULL == pt)

  {

   pt = (struct collectNode *)malloc(sizeof(struct collectNode));

   pt->nVt = nCh;

   pt->next = NULL;

   if(NULL == first[U - 100])

   {

    first[U - 100] = pt;

   }

   else

   {

    qt->next = pt; /*qt指向first集的最后一个元素*/

   }

   pt = pt->next;

  }

 }

 else

 {

  pt = first[nCh - 100];

  while(NULL != pt)

  {

   ch = pt->nVt;

   if(-1 != ch)

   {

    AddFirst(U, ch);

   }

   pt = pt->next;

  }

 }

}

/*判断first集中是否有空(-1)*/

bool HaveEmpty(int nVn)

{

 if(nVn < 100) /*为终结符时(含-1),在follow集中用到*/

  return false;

 struct collectNode *pt;

 pt = first[nVn - 100];

 while(NULL != pt)

 {

  if(-1 == pt->nVt)

   return true;

  pt = pt->next;

 }

 return false;

}

/*计算follow集,例:U->xVy,U->xV.(注:初始符必含#——"-1")*/

void Follow(int V)

{

 int i;

 struct pRNode *pt ;

 if(100 == V) /*当为初始符时*/

  AddFollow(V, -1, 0 );

 for(i = 0; i < PNum; i++)

 {

  pt = P[i].rHead;

  while(NULL != pt && pt->rCursor != V) /*注此不能处理:U->xVyVz的情况*/

   pt = pt->next;

  if(NULL != pt)

  {

   pt = pt->next; /*V右侧的符号*/

   if(NULL == pt) /*当V后为空时V->xV,将左符的follow集并入V的follow集中*/

   {

    if(NULL == follow[P[i].lCursor - 100] && P[i].lCursor != V)

    {

     Follow(P[i].lCursor);

    }

    AddFollow(V, P[i].lCursor, 0);

   }

   else /*不为空时V->xVy,(注意:y->),调用AddFollow加入Vt或y的first集*/

   {

    while(NULL != pt && HaveEmpty(pt->rCursor))

    {

     AddFollow(V, pt->rCursor, 1); /*y的前缀中有空时,加如first集*/

     pt = pt->next;

    }

    if(NULL == pt) /*当后面的字符可以推出空时*/

    {

     if(NULL == follow[P[i].lCursor - 100] && P[i].lCursor != V)

     {

      Follow(P[i].lCursor);

     }

     AddFollow(V, P[i].lCursor, 0);

    }

    else /*发现不为空的字符时*/

    {

     AddFollow(V, pt->rCursor, 1);

    }

   }

  }

 }

}

 

/*当数值小于100时nCh为Vt*/

/*#用-1表示,kind用于区分是并入符号的first集,还是follow集

kind = 0表加入follow集,kind = 1加入first集*/

void AddFollow(int V, int nCh, int kind)

{

 struct collectNode *pt, *qt;

 int ch; /*用于处理Vn*/

 pt = NULL;

 qt = NULL;

 if(nCh < 100) /*为终结符时*/

 {

  pt = follow[V - 100];

  while(NULL != pt)

  {

   if(pt->nVt == nCh)

    break;

   else

   {

    qt = pt;

    pt = pt->next;

   }

  }

  if(NULL == pt)

  {

   pt = (struct collectNode *)malloc(sizeof(struct collectNode));

   pt->nVt = nCh;

   pt->next = NULL;

   if(NULL == follow[V - 100])

   {

    follow[V - 100] = pt;

   }

   else

   {

    qt->next = pt; /*qt指向follow集的最后一个元素*/

   }

   pt = pt->next;

  }

 }

 else /*为非终结符时,要区分是加first还是follow*/

 {

  if(0 == kind)

  {

   pt = follow[nCh - 100];

   while(NULL != pt)

   {

    ch = pt->nVt;

    AddFollow(V, ch, 0);

    pt = pt->next;

   }

  }

  else

  {

   pt = first[nCh - 100];

   while(NULL != pt)

   {

    ch = pt->nVt;

    if(-1 != ch)

    {

     AddFollow(V, ch, 1);

    }

    pt = pt->next;

   }

  }

 }

}

2)构造预测分析表     的程序代码如下:

void CreateAT()

{

 int i;

 struct pRNode *pt;

 struct collectNode *ct;

 for(i = 0; i < PNum; i++)

 {

  pt = P[i].rHead;

  while(NULL != pt && HaveEmpty(pt->rCursor))

  {

   /*处理非终结符,当为终结符时,定含空为假跳出*/

   ct = first[pt->rCursor - 100];

   while(NULL != ct)

   {

    if(-1 != ct->nVt)

     analyseTable[P[i].lCursor - 100][ct->nVt] = i;

    ct = ct->next;

   }

   pt = pt->next;

  }

  if(NULL == pt)

  {

   /*NULL == pt,说明xyz->,用到follow中的符号*/

   ct = follow[P[i].lCursor - 100];

   while(NULL != ct)

   {

    if(-1 != ct->nVt)

     analyseTable[P[i].lCursor - 100][ct->nVt] = i;

    else /*当含有#号时*/

     analyseTable[P[i].lCursor - 100][vtNum] = i;

    ct = ct->next;

   }

  }

  else

  {

   if(100 <= pt->rCursor) /*不含空的非终结符*/

   {

    ct = first[pt->rCursor - 100];

    while(NULL != ct)

    {

     analyseTable[P[i].lCursor - 100][ct->nVt] = i;

     ct = ct->next;

    }

   }

   else /*终结符或者空*/

   {

    if(-1 == pt->rCursor) /*-1为空产生式时*/

    {

     ct = follow[P[i].lCursor - 100];

     while(NULL != ct)

     {

      if(-1 != ct->nVt)

       analyseTable[P[i].lCursor - 100][ct->nVt] = i;

      else /*当含有#号时*/

       analyseTable[P[i].lCursor - 100][vtNum] = i;

      ct = ct->next;

     }

    }

    else /*为终结符*/

    {

     analyseTable[P[i].lCursor - 100][pt->rCursor] = i;

    }

   }

  }

 }

}

4.利用分析表进行预测分析

1)总程序的算法描述如下:

BEGIN

       首先把‘#’然后把文法开始符号推进STACK栈;

       把第一个输入符号读进a;

       FLAG:=TRUE;

       WHILE  FLAG  DO

       BEGIN

              把栈顶符号出栈到X中;  

              IF  XÎVT THEN

                    IF X =a  THEN  把下一输入符号读进a

                    ELSE  ERROR

              ELSE  IF  X= ‘#’  THEN       

                     IF  X=a  THEN  FLAG:=FALSE

                     ELSE  ERROR

              ELSE  IF  M[A, a]={X®x1x2…xk} THEN

           把xk, xk–1,…, x1依次进栈

                         /*若x1, x2 …xk=e,则不进栈*/

              ELSE ERROR

       END OF WHILE;

       STOP  /*分析成功,过程结束*/

END

2)代码实现如下:

void Identify(char *st)

{

 int current,step,r; /*r表使用的产生式的序号*/

 printf("/n%s的分析过程:/n", st);

 printf("步骤/t分析符号栈/t当前指示字符/t使用产生式序号/n");

 

 step = 0;

 current = 0; /*符号串指示器*/

 printf("%d/t",step);

 ShowStack();

 printf("/t/t%c/t/t- -/n", st[current]);

 

 while('#' != st[current])

 {

  if(100 > analyseStack[topAnalyse]) /*当为终结符时*/

  {

   if(analyseStack[topAnalyse] == IndexCh(st[current]))

   {

    /*匹配出栈,指示器后移*/

    Pop();

    current++;

    step++;

    printf("%d/t", step);

    ShowStack();

    printf("/t/t%c/t/t出栈、后移/n", st[current]);

   }

   else

   {

    printf("%c-%c不匹配!", analyseStack[topAnalyse], st[current]);

    printf("此串不是此文法的句子!/n");

    return;

   }

  }

  else /*当为非终结符时*/

  {

   r = analyseTable[analyseStack[topAnalyse] - 100][IndexCh(st[current])];

   if(-1 != r)

   {

    Push(r); /*产生式右部代替左部,指示器不移动*/

    step++;

    printf("%d/t", step);

    ShowStack();

    printf("/t/t%c/t/t%d/n", st[current], r);

   }

   else

   {

    printf("无可用产生式,此串不是此文法的句子!/n");

    return;

   }

  }

 }

 if('#' == st[current])

 {

 

  if(0 == topAnalyse && '#' == st[current])

  {

   step++;

   printf("%d/t", step);

   ShowStack();

   printf("/t/t%c/t/t分析成功!/n", st[current]);

   printf("%s是给定文法的句子!/n", st);

  }

  else

  {

   while(topAnalyse > 0)

   {

    if(100 > analyseStack[topAnalyse]) /*当为终结符时*/

    {

     printf("无可用产生式,此串不是此文法的句子!/n");

     return;

    }

    else

    {

     r = analyseTable[analyseStack[topAnalyse] - 100][vtNum];

     if(-1 != r)

     {

      Push(r); /*产生式右部代替左部,指示器不移动*/

      step++;

      printf("%d/t", step);

      ShowStack();

      if(0 == topAnalyse && '#' == st[current])

      {

       printf("/t/t%c/t/t分析成功!/n", st[current]);

       printf("%s是给定文法的句子!/n", st);

      }

      else     

       printf("/t/t%c/t/t%d/n", st[current], r);

     }

     else

     {

      printf("无可用产生式,此串不是此文法的句子!/n");

      return;

     }

    }

   }

  }

 }

}

四.运行结果

 

 

    以上两个试验从较大呈度上说明了程序运行的正确性及稳定性,当然,对程序本身还有待进行更进一步的严格测试。

 

 

五.总结分析

 

 

<script id="dwr-st-0" src="http://ud.blog.163.com/lulirui527@126/dwr/call/plaincall/BlogBean.getCommentsByBlog.dwr?callCount=1&scriptSessionId=%24%7BscriptSessionId%7D07da48ea4b7f4035cf30b314900cd6b6&c0-scriptName=BlogBean&c0-methodName=getCommentsByBlog&c0-id=0&c0-param0=string%3Afks_087075086086087067093087083095092083089069081084083&c0-param1=string%3A858652520080754332266&c0-param2=boolean%3Afalse&c0-param3=boolean%3Afalse&batchId=0"></script> <script id="dwr-st-0" src="http://ud.blog.163.com/lulirui527@126/dwr/call/plaincall/BlogBean.getRelateBlogsCircles.dwr?callCount=1&scriptSessionId=%24%7BscriptSessionId%7D07da48ea4b7f4035cf30b314900cd6b6&c0-scriptName=BlogBean&c0-methodName=getRelateBlogsCircles&c0-id=0&c0-param0=string%3Afks_087075086086087067093087083095092083089069081084083&c0-param1=number%3A8586525&c0-param2=boolean%3Afalse&batchId=1"></script> <script id="dwr-st-0" src="http://ud.blog.163.com/lulirui527@126/dwr/call/plaincall/YoudaoBean.getPostSense.dwr?callCount=1&scriptSessionId=%24%7BscriptSessionId%7D07da48ea4b7f4035cf30b314900cd6b6&c0-scriptName=YoudaoBean&c0-methodName=getPostSense&c0-id=0&c0-param0=string%3Afks_087075086086087067093087083095092083089069081084083&c0-param1=string%3Ablog%252Fstatic%252F858652520080754332266&c0-param2=number%3A1199771514481&c0-param3=boolean%3Afalse&c0-param4=boolean%3Afalse&batchId=3"></script>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值