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

5.12.3.2.1.1.  class-specifier的情形

Class-specifier具有规则:class-specifier: class-head { member-specification [opt] }

 

11855 static tree

11856 cp_parser_class_specifier (cp_parser* parser)                                             in parser.c

11857 {

11858   cp_token *token;

11859   tree type;

11860   tree attributes;

11861   int has_trailing_semicolon;

11862   bool nested_name_specifier_p;

11863   unsigned saved_num_template_parameter_lists;

11864   bool pop_p = false;

11865   tree scope = NULL_TREE;

11866

11867   push_deferring_access_checks (dk_no_deferred);

11868

11869   /* Parse the class-head.  */

11870   type = cp_parser_class_head (parser,

11871                           &nested_name_specifier_p,

11872                           &attributes);

11873   /* If the class-head was a semantic disaster, skip the entire body

11874     of the class.  */

11875   if (!type)

11876   {

11877     cp_parser_skip_to_end_of_block_or_statement (parser);

11878     pop_deferring_access_checks ();

11879     return error_mark_node;

11880   }

 

上面11867行,为这个class-specifier准备延迟访问控制检查,因为其类型为dk_no_deferred,那么在将这个节点从栈中弹出时,将立刻执行检查。

5.12.3.2.1.1.1.          解析class-head

class-head通过base-clause项描述了类之间的继承层次,除此之外,nested-name-specifier项描述了该类经被定义的绑定域。

class-head

class-key identifier [opt] base-clause [opt]

├ class-key nested-name-specifier [opt] base-clause [opt]

├ class-key nested-name-specifier [opt] template-id base-clause [opt]

GNU Ext ├ class-key attributes identifer [opt] base-clause [opt]

GNU Ext ├ class-key attributes nested-name-specifier [opt] base-clause [opt]

GNU Ext class-key attributes nested-name-specifier [opt] template-id base-clause [opt]

class

struct

union

因此在其处理函数中,参数nested_name_specifier_p被设为true,如果使用了某个涉及nested-name-specifier的产生式,否则就设为false。这里对于我们的例子中的第二条语句,它包含了class-headclass SingleThreaded”。

 

12030 static tree

12031 cp_parser_class_head (cp_parser* parser,                                                   in parser.c

12032                    bool* nested_name_specifier_p,

12033                    tree *attributes_p)

12034 {

12035   cp_token *token;

12036   tree nested_name_specifier;

12037   enum tag_types class_key;

12038   tree id = NULL_TREE;

12039   tree type = NULL_TREE;

12040   tree attributes;

12041   bool template_id_p = false;

12042   bool qualified_p = false;

12043   bool invalid_nested_name_p = false;

12044   bool invalid_explicit_specialization_p = false;

12045   bool pop_p = false;

12046   unsigned num_templates;

12047

12048   /* Assume no nested-name-specifier will be present.  */

12049   *nested_name_specifier_p = false;

12050   /* Assume no template parameter lists will be used in defining the

12051     type.  */

12052   num_templates = 0;

12053

12054   /* Look for the class-key.  */

12055   class_key = cp_parser_class_key (parser);

12056   if (class_key == none_type)

12057     return error_mark_node;

12058

12059   /* Parse the attributes.  */

12060   attributes = cp_parser_attributes_opt (parser);

12061

12062   /* If the next token is `::', that is invalid -- but sometimes

12063     people do try to write:

12064

12065        struct ::S {}; 

12066

12067     Handle this gracefully by accepting the extra qualifier, and then

12068     issuing an error about it later if this really is a

12069     class-head. If it turns out just to be an elaborated type

12070     specifier, remain silent.  */

12071   if (cp_parser_global_scope_opt (parser, /*current_scope_valid_p=*/false))

12072     qualified_p = true;

12073

12074   push_deferring_access_checks (dk_no_check);

12075

12076   /* Determine the name of the class. Begin by looking for an

12077     optional nested-name-specifier.  */

12078   nested_name_specifier

12079      = cp_parser_nested_name_specifier_opt (parser,

12080                                        /*typename_keyword_p=*/false,

12081                                        /*check_dependency_p=*/false,

12082                                        /*type_p=*/false,

12083                                        /*is_declaration=*/false);

 

跟在class-key后面的项应该是标识符或者属性。看到nested-name-specifier也是以标识符开头,解析器首先尝试nested-name-specifierNested-name-specifier可能包含以下成员。

nested-name-specifier

class-or-namespace-name::nested-name-specifier [opt]

namespace-name --- identifier

class-name

identifier

template-id

SingleTheaded”或者“SingleThreaded {”都不是有效的nested-name-specifiers,因为它要求“::”作为结尾的符号。在12079行,cp_parser_nested_name_specifier_opt将向nested_name_specifier返回NULL

 

14127 static tree

14128 cp_parser_global_scope_opt (cp_parser* parser, bool current_scope_valid_p) in parser.c

14129 {

14130   cp_token *token;

14131

14132   /* Peek at the next token.  */

14133   token = cp_lexer_peek_token (parser->lexer);

14134   /* If we're looking at a `::' token then we're starting from the

14135     global namespace, not our current location.  */

14136   if (token->type == CPP_SCOPE)

14137   {

14138     /* Consume the `::' token.  */

14139     cp_lexer_consume_token (parser->lexer);

14140     /* Set the SCOPE so that we know where to start the lookup.  */

14141     parser->scope = global_namespace;

14142     parser->qualifying_scope = global_namespace;

14143     parser->object_scope = NULL_TREE;

14144

14145     return parser->scope;

14146   }

14147   else if (!current_scope_valid_p)

14148   {

14149     parser->scope = NULL_TREE;

14150     parser->qualifying_scope = NULL_TREE;

14151     parser->object_scope = NULL_TREE;

14152   }

14153

14154   return NULL_TREE;

14155 }

 

parser中,项scope表示进行名字查找的域。如果它是NULL_TREE,那么我们要在源程序中当前打开的域里查找名字。如果它不是NULL,它要么是TYPE,要么是NAMESPACE_DECL,对应于我们要查找的域。而项object_scopequalifying_scope给出了上一次查找所在的域。如果使用了形如“x->y”或“x.y”的表达式,object_scope分别给出“*x”或者“x”的类型。而qualifying_scope用在形如“X::Y”的表达式,它指向X

如果上面函数的参数current_scope_valid_pfalse,表示依赖当前打开的域进行名字解析。对于类声明,这是显然的。

 

cp_parser_class_head (continue)

 

12084  /* If there was a nested-name-specifier, then there *must* be an

12085    identifier.  */

12086  if (nested_name_specifier)

12087  {

         

12143  }

12144  /* Otherwise, the identifier is optional.  */

12145  else

12146  {

12147    /* We don't know whether what comes next is a template-id,

12148      an identifier, or nothing at all.  */

12149    cp_parser_parse_tentatively (parser);

12150    /* Check for a template-id.  */

12151    id = cp_parser_template_id (parser,

12152                           /*template_keyword_p=*/false,

12153                           /*check_dependency_p=*/true,

12154                           /*is_declaration=*/true);

12155    /* If that didn't work, it could still be an identifier.  */

12156    if (!cp_parser_parse_definitely (parser))

12157    {

12158      if (cp_lexer_next_token_is (parser->lexer, CPP_NAME))

12159        id = cp_parser_identifier (parser);

12160      else

12161        id = NULL_TREE;

12162    }

12163    else

12164    {

12165      template_id_p = true;

12166      ++num_templates;

12167    }

12168  }

12169

12170  pop_deferring_access_checks ();

12171

12172  if (id)

12173    cp_parser_check_for_invalid_template_id (parser, id);

12174

12175  /* If it's not a `:' or a `{' then we can't really be looking at a

12176    class-head, since a class-head only appears as part of a

12177    class-specifier. We have to detect this situation before calling

12178    xref_tag, since that has irreversible side-effects.  */

12179  if (!cp_parser_next_token_starts_class_definition_p (parser))

12180  {

12181    cp_parser_error (parser, "expected `{' or `:'");

12182    return error_mark_node;

12183  }

12184

12185  /* At this point, we're going ahead with the class-specifier, even

12186    if some other problem occurs.  */

12187  cp_parser_commit_to_tentative_parse (parser);

12188  /* Issue the error about the overly-qualified name now.  */

12189  if (qualified_p)

12190    cp_parser_error (parser,

12191                  "global qualification of class name is invalid");

12192  else if (invalid_nested_name_p)

12193    cp_parser_error (parser,

12194                  "qualified name does not name a class");

12195  else if (nested_name_specifier)

12196  {

         

12232  }

12233  /* An explicit-specialization must be preceded by "template <>". If

12234    it is not, try to recover gracefully.  */

12235  if (at_namespace_scope_p ()

12236     && parser->num_template_parameter_lists == 0

12237     && template_id_p)

12238  {

12239    error ("an explicit specialization must be preceded by 'template <>'");

12240    invalid_explicit_specialization_p = true;

12241    /* Take the same action that would have been taken by

12242      cp_parser_explicit_specialization.  */

12243    ++parser->num_template_parameter_lists;

12244    begin_specialization ();

12245  }

12246  /* There must be no "return" statements between this point and the

12247    end of this function; set "type "to the correct return value and

12248    use "goto done;" to return.  */

12249  /* Make sure that the right number of template parameters were

12250    present.  */

12251  if (!cp_parser_check_template_parameters (parser, num_templates))

12252  {

12253    /* If something went wrong, there is no point in even trying to

12254      process the class-definition.  */

12255    type = NULL_TREE;

12256    goto done;

12257  }

 

base-clause之前是template-id,这是一个类模板的特化形式,例如:“template <> class A <int>: public B { … };”。对于普通的模板类,这应该是一个标识符。template-id是一个复杂的结构,我们以后来看它。

5.12.3.2.1.1.1.1.    合理性检查

在继续之前,需要检查是否有错误发生。首先,符号“<”不能跟在type-specifier之后,例程cp_parser_check_for_invalid_template_id检查这个错误的情况。

 

1883 static void

1884 cp_parser_check_for_invalid_template_id (cp_parser* parser,                        in parser.c

1885                                    tree type)

1886 {

1887   ptrdiff_t start;

1888   cp_token *token;

1889

1890   if (cp_lexer_next_token_is (parser->lexer, CPP_LESS))

1891   {

1892     if (TYPE_P (type))

1893       error ("`%T' is not a template", type);

1894     else if (TREE_CODE (type) == IDENTIFIER_NODE)

1895       error ("`%s' is not a template", IDENTIFIER_POINTER (type));

1896     else

1897       error ("invalid template-id");

1898     /* Remember the location of the invalid "<".  */

1899     if (cp_parser_parsing_tentatively (parser)

1900        && !cp_parser_committed_to_tentative_parse (parser))

1901     {

1902       token = cp_lexer_peek_token (parser->lexer);

1903       token = cp_lexer_prev_token (parser->lexer, token);

1904       start = cp_lexer_token_difference (parser->lexer,

1905                                   parser->lexer->first_token,

1906                                   token);

1907     }

1908     else

1909       start = -1;

1910     /* Consume the "<".  */

1911     cp_lexer_consume_token (parser->lexer);

1912     /* Parse the template arguments.  */

1913     cp_parser_enclosed_template_argument_list (parser);

1914     /* Permanently remove the invalid template arguments so that

1915       this error message is not issued again.  */

1916     if (start >= 0)

1917     {

1918       token = cp_lexer_advance_token (parser->lexer,

1919                                   parser->lexer->first_token,

1920                                   start);

1921       cp_lexer_purge_tokens_after (parser->lexer, token);

1922     }

1923   }

1924 }

 

然后还需要查看接下来的符号是否可以构成有效的class-head,它必须是“{”或“:”。

 

15199 static bool

15200 cp_parser_next_token_starts_class_definition_p (cp_parser *parser)              in parser.c

15201 {

15202   cp_token *token;

15203

15204   token = cp_lexer_peek_token (parser->lexer);

15205   return (token->type == CPP_OPEN_BRACE || token->type == CPP_COLON);

15206 }

 

接着对于我们的例子,变量template_id_p一直是false,因为声明不是一个template-id。并注意到现在变量num_templates仍旧是0

 

14018 static bool

14019 cp_parser_check_template_parameters (cp_parser* parser,                           in parser.c

14020                                 unsigned num_templates)

14021 {

14022   /* If there are more template classes than parameter lists, we have

14023     something like:

14024     

14025        template <class T> void S<T>::R<T>::f ();  */

14026   if (parser->num_template_parameter_lists < num_templates)

14027   {

14028     error ("too few template-parameter-lists");

14029     return false;

14030   }

14031   /* If there are the same number of template classes and parameter

14032     lists, that's OK.  */

14033   if (parser->num_template_parameter_lists == num_templates)

14034     return true;

14035  /* If there are more, but only one more, then we are referring to a

14036     member template. That's OK too.  */

14037   if (parser->num_template_parameter_lists == num_templates + 1)

14038      return true;

14039   /* Otherwise, there are too many template parameter lists. We have

14040     something like:

14041

14042      template <class T> template <class U> void S::f();  */

14043   error ("too many template-parameter-lists");

14044   return false;

14045 }

 

正如我们在前面章节看到的,parser的域num_template_parameter_lists记录了模板参数的嵌套级别,而num_templates记录了,在cp_parser_class_head中,已经看到的模板嵌套级数。对于一个有效的声明,它们要相互匹配。

5.12.3.2.1.1.1.2.    加入类SingleThreaded的标签

现在在合理性检查后,对于被解析的符号,这里有3种情况:1template-id,它可能构成了一个偏特化模板;2)没有发现nested-name-specified,它应该是一个类类型定义;3)发现nested-name-specifier,它可能是嵌套的类类型定义,或者类成员定义。我们需要一个一个来处理。

这里“标签”的作用,即是告诉编译器,此乃用户定义之类型。从此之后,这个类型即可用于type-specifier语法结构中(如果是前向声明,则是有限的使用,只能用于指针)。

 

cp_parser_class_head (continue)

 

12259   /* Look up the type.  */

12260   if (template_id_p)

12261   {

12262     type = TREE_TYPE (id);

12263     maybe_process_partial_specialization (type);

12264   }

12265   else if (!nested_name_specifier)

12266   {

12267     /* If the class was unnamed, create a dummy name.  */

12268     if (!id)

12269       id = make_anon_name ();

12270     type = xref_tag (class_key, id, /*globalize=*/false,

12271                   parser->num_template_parameter_lists);

12272   }

12273   else

12274   {

         

12316   }

 

这里对于我们的例子,我们已经看到,变量nested_name_specifierNULL,例程xref_tag被调用,其参数tag_code表示与name关联的标签(tag)的类型。而tag_type具有如下定义。

 

2954 enum tag_types {                                                                                      in cp-tree.h

2955   none_type = 0, /* Not a tag type.  */

2956   record_type,   /* "struct" types.  */

2957   class_type,    /* "class" types.  */

2958   union_type,    /* "union" types.  */

2959   enum_type,     /* "enum" types.  */

2960   typename_type  /* "typename" types.  */

2961 };

 

另外,当这个名字代表一个定义时,参数globalizefalse,仅在当前的作用域中查找该名字;当这个声明由一组模板参数前导,template_header_ptrue(这里它是1,来自parser->num_template_parameter_lists)。

 

9443 tree

9444 xref_tag (enum tag_types tag_code, tree name,                                                    in decl.c

9445         bool globalize, bool template_header_p)

9446 {

9447   enum tree_code code;

9448   tree t;

9449   struct cp_binding_level *b = current_binding_level;

9450   tree context = NULL_TREE;

9451

9452   timevar_push (TV_NAME_LOOKUP);

9453

9454   my_friendly_assert (TREE_CODE (name) == IDENTIFIER_NODE, 0);

9455

9456   switch (tag_code)

9457   {

9458     case record_type:

9459     case class_type:

9460       code = RECORD_TYPE;

9461       break;

9462     case union_type:

9463       code = UNION_TYPE;

9464       break;

9465     case enum_type:

9466       code = ENUMERAL_TYPE;

9467       break;

9468     default:

9469       abort ();

9470   }

9471

9472   if (! globalize)

9473   {

9474     /* If we know we are defining this tag, only look it up in

9475       this scope and don't try to find it as a type.  */

9476     t = lookup_tag (code, name, b, 1);

9477   }

9478   else

9479   {

         ...

9556   }

 

因为globalizefalse在调用lookup_tag的点向其参数thislevel_only传入1这个值表示仅查找指定的上下文但跳过任意sk_cleanup的上下文以找出对于标签有意义的上下文

 

2369 tree

2370 lookup_tag (enum tree_code form, tree name,                                       in name-lookup.c

2371           cxx_scope *binding_level, int thislevel_only)

2372 {

2373   struct cp_binding_level *level;

2374   /* Nonzero if, we should look past a template parameter level, even

2375     if THISLEVEL_ONLY.  */

2376   int allow_template_parms_p = 1;

2377   bool type_is_anonymous = ANON_AGGRNAME_P (name);

2378

2379  timevar_push (TV_NAME_LOOKUP);

2380   for (level = binding_level; level; level = level->level_chain)

2381   {

2382     tree tail;

2383     if (type_is_anonymous && level->type_decls != NULL)

2384     {

2385       tree type = binding_table_find_anon_type (level->type_decls, name);

2386       /* There is no need for error checking here, because

2387         anon names are unique throughout the compilation.  */

2388      if (type != NULL)

2389         POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, type);

2390     }

2391    else if (level->kind == sk_namespace)

2392       /* Do namespace lookup.  */

2393       for (tail = current_namespace; 1; tail = CP_DECL_CONTEXT (tail))

2394       {

2395         cxx_binding *binding =

2396             cxx_scope_find_binding_for_name (NAMESPACE_LEVEL (tail), name);

2397         tree old;

2398

2399         /* If we just skipped past a template parameter level,

2400           even though THISLEVEL_ONLY, and we find a template

2401           class declaration, then we use the _TYPE node for the

2402           template. See the example below.  */

2403        if (thislevel_only && !allow_template_parms_p

2404           && binding && binding->value

2405           && DECL_CLASS_TEMPLATE_P (binding->value))

2406         old = binding->value;

2407       else if (binding)

2408         old = select_decl (binding, LOOKUP_PREFER_TYPES);

2409       else

2410        old = NULL_TREE;

2411

2412       if (old)

2413      {

             ...

2429      }

2430       if (thislevel_only || tail == global_namespace)

2431         POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, NULL_TREE);

2432     }

2433     else if (level->type_decls != NULL)

2434     {

           ...

2449     }

2450     if (thislevel_only && level->kind != sk_cleanup)

2451     {

2452       if (level->kind == sk_template_parms && allow_template_parms_p)

2453       {

2454         /* We must deal with cases like this:

2455

2456           template <class T> struct S;

2457           template <class T> struct S {};

2458

2459           When looking up `S', for the second declaration, we

2460           would like to find the first declaration. But, we

2461           are in the pseudo-global level created for the

2462           template parameters, rather than the (surrounding)

2463           namespace level. Thus, we keep going one more level,

2464           even though THISLEVEL_ONLY is nonzero.  */

2465         allow_template_parms_p = 0;

2466         continue;

2467       }

2468       else

2469         POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, NULL_TREE);

2470     }

2471   }

2472   POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, NULL_TREE);

2473 }

 

首先看到如果thislevel_only0,将从当前作用域直到全局名字空间,查找这个标签;而如果它是非0值,在指定的域中完成查找后,函数即退出。现在我们正在域“sk_template_parms”中进行声明(看到该域为begin_template_parm_list所加入),因此即便thislevel_only1,我们也将重新进入这个循环。那么查找在名字空间中“Loki”中进行。当然cxx_scope_find_binding_for_name不应该在其中找到这个名字。作为结果lookup_tag返回NULL_TREE

 

xref_tag (continue)

 

9558   if (! t)

9559   {

9560     /* If no such tag is yet defined, create a forward-reference node

9561       and record it as the "definition".

9562       When a real declaration of this type is found,

9563       the forward-reference will be altered into a real type.  */

9564     if (code == ENUMERAL_TYPE)

9565     {

9566       error ("use of enum `%#D' without previous declaration", name);

9567       POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, error_mark_node);

9568     }

9569     else

9570     {

9571       t = make_aggr_type (code);

9572       TYPE_CONTEXT (t) = context;

9573       pushtag (name, t, globalize);

9574     }

9575   }

9576   else

9577  {

9578     if (!globalize && processing_template_decl && IS_AGGR_TYPE (t))

9579       redeclare_class_template (t, current_template_parms);

9580     else if (!processing_template_decl

9581           && CLASS_TYPE_P (t)

9582           && CLASSTYPE_IS_TEMPLATE (t))

9583     {

9584       error ("redeclaration of `%T' as a non-template", t);

9585       t = error_mark_node;

9586     }

9587   }

9588

9589   POP_TIMEVAR_AND_RETURN (TV_NAME_LOOKUP, t);

9590 }

 

当回到xref_tag时,在9558行,变量t将是NULLRECORD_TYPE类型的树节点为该类型所创建。并看到到该名字将在9573行被pushtag加入到名字空间“Loki”中。

现在current_binding_level指向cxx_scope对象sk_template_parms。因此在下面4596行的WHILE循环中,b上升到名字空间“Loki”的作用域。

 

4589 void

4590 pushtag (tree name, tree type, int globalize)                                          in name-lookup.c

4591 {

4592   struct cp_binding_level *b;

4593

4594   timevar_push (TV_NAME_LOOKUP);

4595   b = current_binding_level;

4596   while (/* Cleanup scopes are not scopes from the point of view of

4597           the language.  */

4598        b->kind == sk_cleanup

4599        /* Neither are the scopes used to hold template parameters

4600          for an explicit specialization. For an ordinary template

4601          declaration, these scopes are not scopes from the point of

4602          view of the language -- but we need a place to stash

4603          things that will go in the containing namespace when the

4604          template is instantiated.  */

4605        || (b->kind == sk_template_parms && b->explicit_spec_p)

4606        || (b->kind == sk_class

4607          && (globalize

4608              /* We may be defining a new type in the initializer

4609                of a static member variable. We allow this when

4610                not pedantic, and it is particularly useful for

4611                type punning via an anonymous union.  */

4612              || COMPLETE_TYPE_P (b->this_entity))))

4613     b = b->level_chain;

4614

4615   if (b->type_decls == NULL)

4616     b->type_decls = binding_table_new (SCOPE_DEFAULT_HT_SIZE);

4617   binding_table_insert (b->type_decls, name, type);

 

这里name指向标识符“SingleThreaded”的节点,该名字之前没有声明,因此在下面的4622行,作为结果IDENTIFIER_TYPE_VALUE返回NULL。同样看到在xref_tag中,对于该节点,其TYPE_CONTEXTNULL

 

pushtag (continue)

 

4619   if (name)

4620   {

4621     /* Do C++ gratuitous typedefing.  */

4622     if (IDENTIFIER_TYPE_VALUE (name) != type)

4623     {

4624       tree d = NULL_TREE;

4625       int in_class = 0;

4626       tree context = TYPE_CONTEXT (type);

4627

4628       if (! context)

4629       {

4630          tree cs = current_scope ();

4631

4632          if (! globalize)

4633            context = cs;

4634         else if (cs != NULL_TREE && TYPE_P (cs))

4635            /* When declaring a friend class of a local class, we want

4636              to inject the newly named class into the scope

4637              containing the local class, not the namespace scope.  */

4638            context = decl_function_context (get_type_decl (cs));

4639        }

4640        if (!context)

4641          context = current_namespace;

4642

4643       if (b->kind == sk_class

4644          || (b->kind == sk_template_parms

4645            && b->level_chain->kind == sk_class))

4646         in_class = 1;

4647

4648       if (current_lang_name == lang_name_java)

4649         TYPE_FOR_JAVA (type) = 1;

4650

4651       d = create_implicit_typedef (name, type);

4652       DECL_CONTEXT (d) = FROB_CONTEXT (context);

4653       if (! in_class)

4654         set_identifier_type_value_with_scope (name, d, b);

 

例程current_scope将返回非空值,仅当当前的封装该类型的是类,或者函数。这里它返回NULL。那么在4641行,变量context被设置为名字空间“Loki”。

52:加入类SingleThreaded的标签第一步

4654行向名字空间“Loki”加入这个名字后,中间树的布局大致如上图。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值