GCC-3.4.6源代码学习笔记(55)

4.3.1.7.4.            创建std名字空间
4.3.1.7.4.1.      加入全局名字空间

std名字空间是C++标准的一部分。这一名字空间亦是C++的运行时环境的一部分,例如:异常机制、运行时类型识别、标准库等都出自此处。因此,接下来就要创建std名字空间并将其加入全局名字空间。这里std_identifierstd名字空间的全局唯一标识符,它在initialize_predefined_identifiers中已被创建。

 

cxx_init_decl_processing (continue)

 

2995     /* Create the `std' namespace.  */

2996     push_namespace (std_identifier);

2997     std_node = current_namespace;

2998     pop_namespace ();

 

函数push_namespace包办了创建std名字空间对象,将其加入全局名字空间(准确地说应该是当前名字空间),并使其成为当前作用域。注意下面3075行,如果name为空,这个名字空间就是匿名名字空间。匿名名字空间只能通过以下方式访问:

namespace A {

   int x1;

   namespace {      // first annoymous namespace

      int x2;         // the only way to visit x2

      ... // definition

      namespace {  // another annoymous namespace

         ...

      }

   }

}

A::x1 = 5;              // no applicable to annoymous namespace

注意不同层次的匿名名字空间可以有多个。虽然匿名名字空间对于用户来说是无名的,编译器在内部必须为其命名。变量anonymous_namespace_name就是匿名名字空间的内部名字,而且如果有多个匿名名字空间,它们共享这个名字。又,因为匿名名字空间只能以上面的方式访问,因此匿名名字空间使用同一个名字不存在任何问题。

 

3059   void

3060   push_namespace (tree name)                                                             in name-lookup.c

3061   {

3062     tree d = NULL_TREE;

3063     int need_new = 1;

3064     int implicit_use = 0;

3065     bool anon = !name;

3066  

3067     timevar_push (TV_NAME_LOOKUP);

3068    

3069     /* We should not get here if the global_namespace is not yet constructed

3070       nor if NAME designates the global namespace: The global scope is

3071       constructed elsewhere.  */

3072     my_friendly_assert (global_namespace != NULL && name != global_scope_name,

3073                         20030531);

3074  

3075     if (anon)

3076     {

3077       /* The name of anonymous namespace is unique for the translation

3078         unit.  */

3079       if (!anonymous_namespace_name)

3080         anonymous_namespace_name = get_file_function_name ('N');

3081       name = anonymous_namespace_name;

3082       d = IDENTIFIER_NAMESPACE_VALUE (name);

3083       if (d)

3084         /* Reopening anonymous namespace.  */

3085         need_new = 0;

3086       implicit_use = 1;

3087     }

3088     else

3089     {

3090       /* Check whether this is an extended namespace definition.  */

3091       d = IDENTIFIER_NAMESPACE_VALUE (name);

3092       if (d != NULL_TREE && TREE_CODE (d) == NAMESPACE_DECL)

3093       {

3094         need_new = 0;

3095         if (DECL_NAMESPACE_ALIAS (d))

3096         {

3097           error ("namespace alias `%D' not allowed here, assuming `%D'",

3098                d, DECL_NAMESPACE_ALIAS (d));

3099           d = DECL_NAMESPACE_ALIAS (d);

3100         }

3101       }

3102     }

 

确定了名字空间的名字,具体工作交由IDENTIFIER_NAMESPACE_VALUE

 

264    #define IDENTIFIER_NAMESPACE_VALUE(NODE) /                             in cp-tree.h

265      namespace_binding ((NODE), current_namespace)

 

current_namespace的名称具有欺骗性,实际上这个宏得到的是当前使用的名字空间或者上一次使用的名字空间(当前作用域不是名字空间)。

 

719    #define current_namespace scope_chain->old_namespace                             in cp-tree.h

 

注意对于全局名字空间,scope_chain是一个空节点,因此current_namespace将返回null。这个特殊值在名字空间查找时就意味着全局名字空间。

 

2943   tree

2944   namespace_binding (tree name, tree scope)                                         in name-lookup.c

2945   {

2946     cxx_binding *binding;

2947  

2948     if (scope == NULL)

2949       scope = global_namespace;

2950     scope = ORIGINAL_NAMESPACE (scope);

2951     binding = cxx_scope_find_binding_for_name (NAMESPACE_LEVEL (scope), name);

2952  

2953     return binding ? binding->value : NULL_TREE;

2954   }

 

对于各种*_DECL节点,域abstract_origin指向该声明作为一个实例的初始(抽象)声明节点;或者如果该声明不是其他声明的实例,则为NULL。对于下面的例子,在一个内联函数的一个嵌套声明中,该域指回该函数的定义(这段代码通不过编译,因为嵌套的f未定义)。

inline int f (void) { return 0; }
int main (void)
{
  int f();
  return f ();
}

 

2090   #define DECL_NAMESPACE_ALIAS(NODE) /                                        in cp-tree.h

2091        DECL_ABSTRACT_ORIGIN (NAMESPACE_DECL_CHECK (NODE))

2092   #define ORIGINAL_NAMESPACE(NODE)  /

2093     (DECL_NAMESPACE_ALIAS (NODE) ? DECL_NAMESPACE_ALIAS (NODE) : (NODE))

 

1407   #define DECL_ABSTRACT_ORIGIN(NODE) (DECL_CHECK (NODE)->decl.abstract_origin)

 

ORIGINAL_NAMESPACE涉及的是C++中名字空间别名的特性。根据【3】,名字空间别名有以下规则:

1. 一个名字空间别名定义根据以下规则为一个名字空间声明了一个替代的名字

namespace-alias:

identifier

namespace-alias-definition:

namespace identifier = qualified-namespace-specifier ;

qualified-namespace-specifier:

::opt nested-name-specifieropt namespace-name

2. 在一个名字空间别名定义中的标识符是,由名字空间限定符所标记的,名字空间名的代名词,并成为一个名字空间别名。【注意:当在一个名字空间别名定义中查找一个namespace-name(注:包括名字空间名及别名)时,仅考虑名字空间的名字,参见3.4.6节】

3. 在一个声明域中,一个名字空间别名定义可以用于重新定义一个声明于这个声明域的名字空间别名,但仅限于引用该别名已经引用的名字空间。例如:以下声明都是合法的:

namespace Company_with_very_long_name { /* ... */ }

namespace CWVLN = Company_with_very_long_name;

namespace CWVLN = Company_with_very_long_name; // OK: duplicate

namespace CWVLN = CWVLN;

4. 一个名字空间名或别名不能与同一声明域内的其他实体同名。在全局域内定义的名字空间名不能与程序在全局域中的其他实体同名。由于声明在不同的编译单元而导致违反该规则,不要求编译器能对此作出诊断。

因此在上面的2950行,ORIGNINAL_NAMESPACE返回名字空间定义(而不是别名),随后的NAMESPACE_LEVEL返回的是一个cp_binding_level对象,也即是名字空间所对应的作用域对象。

 

1587   NAMESPACE_LEVEL #define NAMESPACE_LEVEL(NODE) /                in cp-tree.h

1588     (DECL_LANG_SPECIFIC (NODE)->decl_flags.u.level)

 

1444   #define DECL_LANG_SPECIFIC(NODE) (DECL_CHECK (NODE)->decl.lang_specific)

 

函数cxx_scope_find_binding_for_name 则在scope指定的作用域查找name所指示的声明,并返回对应的cxx_binding实例。在当前编译器的实现中,该函数用于名字空间作用域中的查找。

 

1877   static inline cxx_binding *

1878   cxx_scope_find_binding_for_name (cxx_scope *scope, tree name)               in name-lookup.c

1879   {

1880     cxx_binding *b = IDENTIFIER_NAMESPACE_BINDINGS (name);

1881     if (b)

1882     {

1883       /* Fold-in case where NAME is used only once.  */

1884       if (scope == b->scope && b->previous == NULL)

1885         return b;

1886       return find_binding (scope, b);

1887     }

1888     return NULL;

1889   }

 

IDENTIFIER_NAMESPACE_BINDINGS访问标识符节点的namespace_bindings域,这个域记录的是该标识符在所有名字空间中的声明(注意,在C++中名字空间构成最外层的作用域,名字空间不能出现在类定义中,亦不能出现在函数定义里)。在这个标识符中另有bindings域,这个域从标识符的最内层作用域记起,并最终与namespace_bindings连接。

 

369    #define IDENTIFIER_NAMESPACE_BINDINGS(NODE)    /                    in cp-tree.h

370      (LANG_IDENTIFIER_CAST (NODE)->namespace_bindings)

 

238    #define LANG_IDENTIFIER_CAST(NODE) /

339         ((struct lang_identifier*)IDENTIFIER_NODE_CHECK (NODE))          in cp-tree.h

 

我们已经知道标识符节点中的cxx_binding链表将同名的不同声明链接在一起,函数find_bindings从这个链表中找出声明于scope作用域中的对象的cxx_binding实例。

 

1863 static inline cxx_binding *

1864 find_binding (cxx_scope *scope, cxx_binding *binding)                        in name-lookup.c

1865 {

1866   timevar_push (TV_NAME_LOOKUP);

1867

1868   for (; binding != NULL; binding = binding->previous)

1869     if (binding->scope == scope)

1870       POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, binding);

1871

1872   POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, (cxx_binding *)0);

1873 }

 

名字空间别名不能用于定义名字空间,例如:

namespace A { .. }

namespace aliasA = A;

namespace aliasA { ... } // error: namespace alias “aliasA” not allowed here, assuming “A”

将触发3097行的错误信息。在3099行,编译器对此进行修正以期能捕捉进一步的错误。

push_namespace根据查找的结果进行以下的操作。

 

push_namespace (continue)

 

3104     if (need_new)

3105     {

3106       /* Make a new namespace, binding the name to it.  */

3107       d = build_lang_decl (NAMESPACE_DECL, name, void_type_node);

3108       DECL_CONTEXT (d) = FROB_CONTEXT (current_namespace);

3109       d = pushdecl (d);

3110       if (anon)

3111       {

3112         /* Clear DECL_NAME for the benefit of debugging back ends.  */

3113         SET_DECL_ASSEMBLER_NAME (d, name);

3114         DECL_NAME (d) = NULL_TREE;

3115       }

3116       begin_scope (sk_namespace, d);

3117     }

3118     else

3119       resume_scope (NAMESPACE_LEVEL (d));

3120  

3121     if (implicit_use)

3122       do_using_directive (d);

3123    /* Enter the name space.  */

3124     current_namespace = d;

3125  

3126     timevar_pop (TV_NAME_LOOKUP);

3127   }

 

若该名字空间已经在当前名字空间中存在(即3091行的d不为NULL),只需按如下方式,将其置为当前作用域即可(即1414行)。

 

1406 static void

1407 resume_scope (struct cp_binding_level* b)                                                  in name-lookup.c

1408 {

1409   /* Resuming binding levels is meant only for namespaces,

1410     and those cannot nest into classes.  */

1411   my_friendly_assert(!class_binding_level, 386);

1412   /* Also, resuming a non-directly nested namespace is a no-no.  */

1413   my_friendly_assert(b->level_chain == current_binding_level, 386);

1414   current_binding_level = b;

1415   if (ENABLE_SCOPE_CHECKING)

1416   {

1417       b->binding_depth = binding_depth;

1418       indent (binding_depth);

1419       cxx_scope_debug (b, input_location.line, "resume");

1420       is_class_level = 0;

1421       binding_depth++;

1422   }

1423 }

 

若不然,调用pushdecl将该名字空间对象加入当前作用域(注意,也是名字空间)。

 

566    tree

567    pushdecl (tree x)                                                                                     in name-lookup.c

568    {

569      tree t;

570      tree name;

571      int need_new_binding;

572   

573      timevar_push (TV_NAME_LOOKUP);

574   

575      need_new_binding = 1;

        ...

604      name = DECL_NAME (x);

605      if (name)

606      {

607        int different_binding_level = 0;

608   

609        if (TREE_CODE (x) == FUNCTION_DECL || DECL_FUNCTION_TEMPLATE_P (x))

610          check_default_args (x);

611    

612        if (TREE_CODE (name) == TEMPLATE_ID_EXPR)

613          name = TREE_OPERAND (name, 0);

614   

615        /* In case this decl was explicitly namespace-qualified, look it

616          up in its namespace context.  */

617       if (DECL_NAMESPACE_SCOPE_P (x) && namespace_bindings_p ())

618          t = namespace_binding (name, DECL_CONTEXT (x));

619        else

620          t = lookup_name_current_level (name);

 

617行的namespace_bindings_p找出当前作用域是否包含在名字空间中。而DECL_NAMESPACE_SCOPE_P如果非0,表明要加入的对象是名字空间声明。

 

1482   bool

1483   namespace_bindings_p (void)                                                            in name-lookup.c

1484   {

1485     struct cp_binding_level *b = innermost_nonclass_level ();

1486  

1487     return b->kind == sk_namespace;

1488   }

 

1427   static cxx_scope *

1428   innermost_nonclass_level (void)                                                        in name-lookup.c

1429   {

1430     cxx_scope *b;

1431  

1432     b = current_binding_level;

1433     while (b->kind == sk_class)

1434       b = b->level_chain;

1435  

1436     return b;

1437   }

 

618行的DECL_CONTEXT (x) 指向包含x的上下文。如果DECL_CONTEXTNULL,则表明其上下文是全局名字空间,这正是我们的场景。而且namespace_binding将返回的tNULL,那么下面代码片段将被执行。

 

pushdecl (continue)

 

828        /* This name is new in its binding level.

829          Install the new declaration and return it.  */

830        if (namespace_bindings_p ())

831        {

832          /* Install a global value.  */

833

834         /* If the first global decl has external linkage,

835            warn if we later see static one.  */

836          if (IDENTIFIER_GLOBAL_VALUE (name) == NULL_TREE && TREE_PUBLIC (x))

837            TREE_PUBLIC (name) = 1;

838

839          /* Bind the name for the entity.  */

840          if (!(TREE_CODE (x) == TYPE_DECL && DECL_ARTIFICIAL (x)

841              && t != NULL_TREE)

842                && (TREE_CODE (x) == TYPE_DECL

843                    || TREE_CODE (x) == VAR_DECL

844                    || TREE_CODE (x) == ALIAS_DECL

845                    || TREE_CODE (x) == NAMESPACE_DECL

846                    || TREE_CODE (x) == CONST_DECL

847                    || TREE_CODE (x) == TEMPLATE_DECL))

848            SET_IDENTIFIER_NAMESPACE_VALUE (name, x);

            ...

872        }

873        else

874        {

            ...

1003       }

1004      

1005       if (TREE_CODE (x) == VAR_DECL)

1006         maybe_register_incomplete_var (x);

1007     }

1008     

1009     if (need_new_binding)

1010       add_decl_to_level (x,

1011                      DECL_NAMESPACE_SCOPE_P (x)

1012                      ? NAMESPACE_LEVEL (CP_DECL_CONTEXT (x))

1013                      : current_binding_level);

1014  

1015     POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, x);

1016   }

 

上面的IDENTIFIER_GLOBAL_VALUESET_IDENTIFIER_NAMESPACE_VALUE具有以下定义。而在一个IDENTIFIER_NODE中(即对应于name,而x对应于声明),如果TREE_PUBLIC0,表示在内部作用域,这个名字的外部定义已经被看到,而且它能从该模块外访问。

 

264  #define IDENTIFIER_GLOBAL_VALUE(NODE) /                                      in cp-tree.h

265    namespace_binding ((NODE), global_namespace)

266  #define SET_IDENTIFIER_NAMESPACE_VALUE(NODE, VAL) /

267    set_namespace_binding ((NODE), current_namespace, (VAL))

 

函数set_namespace_binding将视情况创建出cxx_binding节点,将声明和作用域绑定一起。其中参数val对应声明,name对应于标识符,scope对应于绑定的作用域。

 

2958   void

2959   set_namespace_binding (tree name, tree scope, tree val)                       in name-lookup.c

2960   {

2961     cxx_binding *b;

2962  

2963     timevar_push (TV_NAME_LOOKUP);

2964     if (scope == NULL_TREE)

2965       scope = global_namespace;

2966     b = binding_for_name (NAMESPACE_LEVEL (scope), name);

2967     if (!b->value || TREE_CODE (val) == OVERLOAD || val == error_mark_node)

2968       b->value = val;

2969     else

2970       supplement_binding (b, val);

2971     timevar_pop (TV_NAME_LOOKUP);

2972   }

 

函数binding_for_name也只用于将声明与名字空间绑定。在下面,可以很清楚地看到,如果是新的声明,其cxx_binding实例将加入对应的标识符的namespace_bindings队列头。

 

1894   static cxx_binding *

1895   binding_for_name (cxx_scope *scope, tree name)                                in name-lookup.c

1896   {

1897     cxx_binding *result;

1898  

1899     result = cxx_scope_find_binding_for_name (scope, name);

1900     if (result)

1901       return result;

1902     /* Not found, make a new one.  */

1903     result = cxx_binding_make (NULL, NULL);

1904     result->previous = IDENTIFIER_NAMESPACE_BINDINGS (name);

1905     result->scope = scope;

1906     result->is_local = false;

1907     result->value_is_inherited = false;

1908     IDENTIFIER_NAMESPACE_BINDINGS (name) = result;

1909     return result;

1910   }

 

上面由binding_for_name返回的cxx_bindingvalue域是null,因为在下面的cxx_binding_make里,注意参数valuetype都是null。在返回set_namespace_binding后,value域被设置为声明节点。

 

326    static cxx_binding *

327    cxx_binding_make (tree value, tree type)                                            in name-lookup.c

328    {

329      cxx_binding *binding;

330      if (free_bindings)

331      {

332        binding = free_bindings;

333        free_bindings = binding->previous;

334      }

335      else

336        binding = ggc_alloc (sizeof (cxx_binding));

337   

338      binding->value = value;

339      binding->type = type;

340      binding->previous = NULL;

341   

342      return binding;

343    }

 

一般而言,在同一个域中出现多个同名的声明即意味着语法错误,不过【2】给出了一个例外:

一个类名(9.1)或枚举名(7.2)可以被同名的,声明在同一个域的,一个对象,函数,或枚举值隐藏。如果一个类或枚举名,及一个对象、函数、或枚举值在同一个域以相同名字声明(以任意次序),该类或枚举名,在对象、函数、或枚举值可见之处,被隐藏。

函数supplement_binding设计用来处理这个例外,后面我们再来看它。

pushdecl1009行,need_new_binding被设置为1。而在add_decl_to_level中,名字空间的声明将被链入对应cxx_scope实例的namespaces域,532行则对应vtable的情况,剩下的537行处理其他的非名字空间声明。

 

523    static void

524    add_decl_to_level (tree decl, cxx_scope *b)                                        in name-lookup.c

525    {

526      if (TREE_CODE (decl) == NAMESPACE_DECL

527          && !DECL_NAMESPACE_ALIAS (decl))

528      {

529        TREE_CHAIN (decl) = b->namespaces;

530        b->namespaces = decl;

531      }

532      else if (TREE_CODE (decl) == VAR_DECL && DECL_VIRTUAL_P (decl))

533      {

534        TREE_CHAIN (decl) = b->vtables;

535        b->vtables = decl;

536      }

537     else      

538      {

539        /* We build up the list in reverse order, and reverse it later if

540          necessary.  */

541        TREE_CHAIN (decl) = b->names;

542        b->names = decl;

543        b->names_size++;

544   

545        /* If appropriate, add decl to separate list of statics. We

546          include extern variables because they might turn out to be

547          static later. It's OK for this list to contain a few false

548          positives. */

549        if (b->kind == sk_namespace)

550          if ((TREE_CODE (decl) == VAR_DECL

551               && (TREE_STATIC (decl) || DECL_EXTERNAL (decl)))

552             || (TREE_CODE (decl) == FUNCTION_DECL

553               && (!TREE_PUBLIC (decl) || DECL_DECLARED_INLINE_P (decl))))

554            VARRAY_PUSH_TREE (b->static_decls, decl);

555     }

556    }

 

 

34std及全局名字空间

到这里,上图显示了std与全局名字空间的部分关系。

4.3.1.7.4.1.      退回到全局名字空间

一旦std_identifieNAMESPACE_DECL及关联的绑定上下文被创建,std_node被设为这个NAMESPACE_DECL,在上面的图中,从这个节点出发,很容易就能到达其包含的,及包含它的作用域。接着,就要通过pop_namespace回到全局名字空间。

 

3131   void

3132   pop_namespace (void)                                                                       in name-lookup.c

3133   {

3134     my_friendly_assert (current_namespace != global_namespace, 20010801);

3135     current_namespace = CP_DECL_CONTEXT (current_namespace);

3136     /* The binding level is not popped, as it might be re-opened later.  */

3137     leave_scope ();

3138   }

 

leave_scope将退到当前作用域的上一级作用域,并将其设为当前作用域。在1356行,class_binding_level指向当前有效的最近的类作用域。如果1356行的条件满足,则表明我们正在退出一个名字空间的定义,同时我们也在一个类定义中,这几乎就表示某错误(因为class_binding_level不为null会设置is_class_level,将在1370行给出错误信息)。这种情况下,编译器试图修改current_binding_level,不过在下面的1376行,current_binding_level再次被更改,1357行的修改没有什么意义。

 

1351   cxx_scope *

1352   leave_scope (void)                                                                            in name-lookup.c

1353   {

1354     cxx_scope *scope = current_binding_level;

1355  

1356     if (scope->kind == sk_namespace && class_binding_level)

1357       current_binding_level = class_binding_level;

1358  

1359    /* We cannot leave a scope, if there are none left.  */

1360     if (NAMESPACE_LEVEL (global_namespace))

1361       my_friendly_assert (!global_scope_p (scope), 20030527);

1362    

1363     if (ENABLE_SCOPE_CHECKING)

1364     {

1365       indent (--binding_depth);

1366       cxx_scope_debug (scope, input_location.line, "leave");

1367       if (is_class_level != (scope == class_binding_level))

1368       {

1369         indent (binding_depth);

1370         verbatim ("XXX is_class_level != (current_scope == class_scope)/n");

1371       }

1372      is_class_level = 0;

1373     }

1374  

1375     /* Move one nesting level up.  */

1376     current_binding_level = scope->level_chain;

1377  

1378     /* Namespace-scopes are left most probably temporarily, not completely;

1379       they can be reopen later, e.g. in namespace-extension or any name

1380       binding activity that requires us to resume a namespace. For other

1381       scopes, we just make the structure available for reuse.  */

1382     if (scope->kind != sk_namespace)

1383     {

1384       scope->level_chain = free_binding_level;

1385       if (scope->kind == sk_class)

1386         scope->type_decls = NULL;

1387       else

1388         binding_table_free (scope->type_decls);

1389       my_friendly_assert (!ENABLE_SCOPE_CHECKING

1390                             || scope->binding_depth == binding_depth,

1391                             20030529);

1392       free_binding_level = scope;

1393     }

1394  

1395    /* Find the innermost enclosing class scope, and reset

1396       CLASS_BINDING_LEVEL appropriately.  */

1397     for (scope = current_binding_level;

1398          scope && scope->kind != sk_class;

1399          scope = scope->level_chain)

1400       ;

1401     class_binding_level = scope && scope->kind == sk_class ? scope : NULL;

1402  

1403     return current_binding_level;

1404   }

 

注意1382行,如果作用域不是名字空间,这个对象将被释放给free_binding_level。但名字空间不如是。这是因为名字空间是最外层的作用域,退出后几乎可以肯定会还会进入该名字空间,又所有的名字空间被保留下来,其间的关系亦然,这大大加快了随后的对这些名字空间对象的查找。这也是为什么在标识符节点中设计了namespace_bindings这个指针。而不保留非名字空间作用域对象的原因可能有:1)这样的对象很多,尤其当我们设计了很多类、函数、及使用了很深的{}块;2)其中的大部分可能访问一次——对于函数及{}块确实如此。这样保持作用域树尽量小,减少了出错的可能,也减少了内存的使用。同时也使得重新进入这些非名字空间作用域的处理变得简单——重新加入作用域树就是了。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值