Studying note of GCC-3.4.6 source (119)

5.12.4.1.2.        Non-type parameter

The second and third parameters for the template are non-type template parameter, which have their type specified but varied value assigned at instantation or speclization. The type-specifier “std::size_t” is in form of simple-type-specifier. Further, the simple-type-specifier “std::size_t” contains two parts: “std::” forms the nested-name-specifier, and “size_t” is the type-name. The type “size_t” usualy is typedef as “unsigned long” in the system header file <stddef.h>, which normally is under directory “/usr/lib/gcc/`target`/`gcc-v`/include”. And this “size_t” is introduced into namespace “std” in <cstddef> by using directive: “using ::size_t;” which makes “size_t” visible within “std” too.

The call stack is: cp_parser_template_parameter_list à cp_parser_template_parameter à cp_parser_parameter_declaration à cp_parser_decl_specifier_seq à cp_parser_type_specifier à cp_parser_simple_type_specifier à cp_parser_type_name .

Then “chunkSize” and “maxSmallObjectSize” both are the declarator-id part. Call stack: cp_parser_parameter_declaration à cp_parser_declarator à cp_parser_direct_declarator à cp_parser_declarator_id à cp_parser_id_expression à cp_parser_unqualified_id à cp_parser_identifier creates the IDENTIFIER_NODEs for these declarators. Then it will return following nodes in cp_parser_parameter_declaration before handling the default argument.

(Click here for open )

Figure 105 : decl_specifiers & declarator created

 

cp_parser_parameter_declaration (continue)

 

11311   /* If the next token is `=', then process a default argument.  */

11312   if (cp_lexer_next_token_is (parser->lexer, CPP_EQ))

11313   {

11314     bool saved_greater_than_is_operator_p;

11315     /* Consume the `='.  */

11316     cp_lexer_consume_token (parser->lexer);

11317

11318     /* If we are defining a class, then the tokens that make up the

11319       default argument must be saved and processed later.  */

11320     if (!template_parm_p && at_class_scope_p ()

11321        && TYPE_BEING_DEFINED (current_class_type ))

11322     {

            …

11406     }

11407      /* Outside of a class definition, we can just parse the

11408       assignment-expression.  */

11409     else

11410     {

11411       bool saved_local_variables_forbidden_p;

11412

11413       /* Make sure that PARSER->GREATER_THAN_IS_OPERATOR_P is

11414         set correctly.  */

11415       saved_greater_than_is_operator_p

11416            = parser->greater_than_is_operator_p;

11417       parser->greater_than_is_operator_p = greater_than_is_operator_p;

11418        /* Local variable names (and the `this' keyword) may not

11419         appear in a default argument.  */

11420       saved_local_variables_forbidden_p

11421           = parser->local_variables_forbidden_p;

11422       parser->local_variables_forbidden_p = true;

11423       /* Parse the assignment-expression.  */

11424       default_argument = cp_parser_assignment_expression (parser);

11425        /* Restore saved state.  */

11426       parser->greater_than_is_operator_p

11427           = saved_greater_than_is_operator_p;

11428       parser->local_variables_forbidden_p

11429           = saved_local_variables_forbidden_p;

11430     }

11431     if (!parser->default_arg_ok_p)

11432     {

11433       if (!flag_pedantic_errors )

11434         warning ("deprecated use of default argument for parameter of non-function");

11435       else

11436       {

11437         error ("default arguments are only permitted for function parameters");

11438         default_argument = NULL_TREE;

11439       }

11440     }

11441   }

11442   else

11443     default_argument = NULL_TREE;

11444  

11445   /* Create the representation of the parameter.  */

11446   if (attributes)

11447     decl_specifiers = tree_cons (attributes, NULL_TREE, decl_specifiers);

11448   parameter = build_tree_list (default_argument,

11449                          build_tree_list (decl_specifiers,

11450                                       declarator));

11451

11452   return parameter;

11453 }

 

Above notice that the token “=” is consumed at line 11316, so at line 11424 tokens following are parsed by cp_parser_assignment_expression . Token “4096” (via DEFAULT_CHUNK_SIZE expansion) is a kind of conditional-expression, which is either “logical-or-expression” or “logical-or-expression? expression: assignment-expression”.

 

5161   static tree

5162   cp_parser_assignment_expression (cp_parser* parser)                                in parser.c

5163   {

5164     tree expr;

5165  

5166     /* If the next token is the `throw' keyword, then we're looking at

5167       a throw-expression.  */

5168     if (cp_lexer_next_token_is_keyword (parser->lexer, RID_THROW))

5169       expr = cp_parser_throw_expression (parser);

5170     /* Otherwise, it must be that we are looking at a

5171       logical-or-expression.  */

5172     else

5173     {

5174       /* Parse the logical-or-expression.  */

5175       expr = cp_parser_logical_or_expression (parser);

5176       /* If the next token is a `?' then we're actually looking at a

5177         conditional-expression.  */

5178       if (cp_lexer_next_token_is (parser->lexer, CPP_QUERY))

5179         return cp_parser_question_colon_clause (parser, expr);

5180       else

5181       {

5182         enum tree_code assignment_operator;

5183  

5184         /* If it's an assignment-operator, we're using the second

5185           production.  */

5186         assignment_operator

5187             = cp_parser_assignment_operator_opt (parser);

5188         if (assignment_operator != ERROR_MARK)

5189         {

5190           tree rhs;

5191  

5192            /* Parse the right-hand side of the assignment.  */

5193           rhs = cp_parser_assignment_expression (parser);

5194           /* An assignment may not appear in a

5195             constant-expression.  */

5196           if (cp_parser_non_integral_constant_expression (parser,

5197                                                  "an assignment"))

5198             return error_mark_node;

5199            /* Build the assignment expression.  */

5200           expr = build_x_modify_expr (expr,

5201                                   assignment_operator,

5202                                   rhs);

5203         }

5204       }

5205     }

5206  

5207     return expr;

5208   }

 

The language offers a rich arithmetic and logic operators. And rule of priority and association are defined in accord with common sense. In books of compliation principle, we have seen the skill to design rules to embody these priorities. No exception, rule for C++ takes similar technique. For examlpe, rule for logical-or-expression is:

logical-or-expression:

    logical-and-expression

    logical-or-expression || logical-and-expression

Also most of the operators are binary operators and left associated, for example the “||” operator in logical-or-expression above. With these forms, the normal way to handle these operators with the correct priority and association is the deepth-first transversal into the rules.

 

14059 static tree

14060 cp_parser_binary_expression (cp_parser* parser,                                       in parser.c

14061                         const cp_parser_token_tree_map token_tree_map,

14062                         cp_parser_expression_fn fn)

14063 {

14064   tree lhs;

14065

14066   /* Parse the first expression.  */

14067   lhs = (*fn) (parser);

14068   /* Now, look for more expressions.  */

14069   while (true)

14070   {

14071     cp_token *token;

14072     const cp_parser_token_tree_map_node *map_node;

14073     tree rhs;

14074

14075      /* Peek at the next token.  */

14076     token = cp_lexer_peek_token (parser->lexer);

14077     /* If the token is `>', and that's not an operator at the

14078       moment, then we're done.  */

14079     if (token->type == CPP_GREATER

14080        && !parser->greater_than_is_operator_p)

14081       break ;

14082     /* If we find one of the tokens we want, build the corresponding

14083       tree representation.  */

14084     for (map_node = token_tree_map;

14085         map_node->token_type != CPP_EOF;

14086         ++map_node)

14087       if (map_node->token_type == token->type)

14088       {

14089          /* Assume that an overloaded operator will not be used.  */

14090         bool overloaded_p = false;

14091

14092         /* Consume the operator token.  */

14093         cp_lexer_consume_token (parser->lexer);

14094         /* Parse the right-hand side of the expression.  */

14095         rhs = (*fn) (parser);

14096         /* Build the binary tree node.  */

14097         lhs = build_x_binary_op (map_node->tree_type, lhs, rhs,

14098                              &overloaded_p);

14099          /* If the binary operator required the use of an

14100           overloaded operator, then this expression cannot be an

14101           integral constant-expression. An overloaded operator

14102           can be used even if both operands are otherwise

14103           permissible in an integral constant-expression if at

14104           least one of the operands is of enumeration type.  */

14105         if (overloaded_p

14106           && (cp_parser_non_integral_constant_expression

14107                           (parser, "calls to overloaded operators")))

14108            lhs = error_mark_node;

14109         break ;

14110       }

14111

14112     /* If the token wasn't one of the ones we want, we're done.  */

14113     if (map_node->token_type == CPP_EOF)

14114       break ;

14115   }

14116

14117   return lhs;

14118 }

 

Argument token_tree_map is of type cp_parser_token_tree_map which holds a mapping from a token type to a corresponding tree node type.

 

1079   typedef struct cp_parser_token_tree_map_node                                                 in parser.c

1080   {

1081     /* The token type.  */

1082     ENUM_BITFIELD (cpp_ttype) token_type : 8;

1083     /* The corresponding tree code.  */

1084     ENUM_BITFIELD (tree_code) tree_type : 8;

1085   } cp_parser_token_tree_map_node;

1086  

1087   /* A complete map consists of several ordinary entries, followed by a

1088     terminator. The terminating entry has a token_type of CPP_EOF.  */

1089  

1090   typedef cp_parser_token_tree_map_node cp_parser_token_tree_map [];

 

Notice the invocation of fn at line 14067 and 14095, variables lhs and rhs have their names abbreviate from “left hand side” and “right hand side” respectively. They are the sub-trees built by handler of higher priority rules in turn.

For every function below, array map records the operator(s) the function handles, for which cp_parser_binary_expression will be used to organize lhs and rhs part to form the expression. Notice that CPP_EOF must be present as the last element of the array to ensure exitting FOR loop at line 14084 above when rhs is absent.

Also see that it is prefix order transversal of the expression. So for our case, token “4096” undergoes a deep call stack with following functions.

For operator “&&”, it is superior than “||”, so the rule applied is:

logical-or-expression:

   logical-and-expression

   logical-or-expression || logical-and-expression

 

5099   static tree

5100   cp_parser_logical_or_expression (cp_parser* parser)                                  in parser.c

5101   {

5102     static const cp_parser_token_tree_map map = {

5103       { CPP_OR_OR, TRUTH_ORIF_EXPR },

5104       { CPP_EOF, ERROR_MARK }

5105     };

5106  

5107     return cp_parser_binary_expression (parser,

5108                                   map,

5109                                   cp_parser_logical_and_expression );

5110   }

 

In turn, “|” has next higher priority with the rule:

logical-and-expression:

   inclusive-or-expression

   logical-and-expression && inclusive-or-expression

 

5078   static tree

5079   cp_parser_logical_and_expression (cp_parser* parser)                                in parser.c

5080   {

5081     static const cp_parser_token_tree_map map = {

5082       { CPP_AND_AND, TRUTH_ANDIF_EXPR },

5083       { CPP_EOF, ERROR_MARK }

5084     };

5085  

5086     return cp_parser_binary_expression (parser,

5087                                    map,

5088                                   cp_parser_inclusive_or_expression );

5089   }

 

Next is “^” as indicated by the rule:

inclusive-or-expression:

   exclusive-or-expression

   inclusive-or-expression | exclusive-or-expression

 

5057   static tree

5058   cp_parser_inclusive_or_expression (cp_parser* parser)                              in parser.c

5059   {

5060     static const cp_parser_token_tree_map map = {

5061       { CPP_OR, BIT_IOR_EXPR },

5062       { CPP_EOF, ERROR_MARK }

5063     };

5064  

5065     return cp_parser_binary_expression (parser,

5066                                   map,

5067                                   cp_parser_exclusive_or_expression );

5068   }

 

While the rule for exclusive-or-expression is as below:

exclusive-or-expression:

   and-expression

   exclusive-or-expression ^ and-expression

 

5035   static tree

5036   cp_parser_exclusive_or_expression (cp_parser* parser)                              in parser.c

5037   {

5038     static const cp_parser_token_tree_map map = {

5039       { CPP_XOR, BIT_XOR_EXPR },

5040       { CPP_EOF, ERROR_MARK }

5041     };

5042  

5043     return cp_parser_binary_expression (parser,

5044                                   map,

5045                                   cp_parser_and_expression );

5046   }

 

Then and-expression is the expression has following rule.

and-expression:

   equality-expression

   and-expression & equality-expression

 

5014   static tree

5015   cp_parser_and_expression (cp_parser* parser)                                                 in parser.c

5016   {

5017     static const cp_parser_token_tree_map map = {

5018       { CPP_AND, BIT_AND_EXPR },

5019        { CPP_EOF, ERROR_MARK }

5020     };

5021  

5022     return cp_parser_binary_expression (parser,

5023                                   map,

5024                                   cp_parser_equality_expression );

5025   }

 

Operators “==” and “!=” have the lowest priority among relational operators.

equality-expression:

   relational-expression

   equality-expression == relational-expression

   equality-expression != relational-expression

 

4992   static tree

4993   cp_parser_equality_expression (cp_parser* parser)                                     in parser.c

4994   {

4995     static const cp_parser_token_tree_map map = {

4996       { CPP_EQ_EQ, EQ_EXPR },

4997       { CPP_NOT_EQ, NE_EXPR },

4998       { CPP_EOF, ERROR_MARK }

4999     };

5000  

5001     return cp_parser_binary_expression (parser,

5002                                   map,

5003                                   cp_parser_relational_expression );

5004   }

 

GNU provides extensional relational operators “<?” and “>?”, with which “<?” returns the smaller value within the two operands, and “>?” returns the larger one.

relational-expression:

     shift-expression

      relational-expression < shift-expression

     relational-expression > shift-expression

     relational-expression <= shift-expression

     relational-expression >= shift-expression

GNU Extension:

  relational-expression:

      relational-expression <? shift-expression

      relational-expression >? shift-expression

 

4965   static tree

4966   cp_parser_relational_expression (cp_parser* parser)                                   in parser.c

4967   {

4968     static const cp_parser_token_tree_map map = {

4969       { CPP_LESS, LT_EXPR },

4970       { CPP_GREATER, GT_EXPR },

4971       { CPP_LESS_EQ, LE_EXPR },

4972       { CPP_GREATER_EQ, GE_EXPR },

4973       { CPP_MIN, MIN_EXPR },

4974       { CPP_MAX, MAX_EXPR },

4975       { CPP_EOF, ERROR_MARK }

4976     };

4977  

4978     return cp_parser_binary_expression (parser,

4979                                   map,

4980                                   cp_parser_shift_expression );

4981   }

 

Then shift-expression:

   additive-expression

   shift-expression << additive-expression

   shift-expression >> additive-expression

 

4934   static tree

4935   cp_parser_shift_expression (cp_parser* parser)                                         in parser.c

4936   {

4937     static const cp_parser_token_tree_map map = {

4938       { CPP_LSHIFT, LSHIFT_EXPR },

4939       { CPP_RSHIFT, RSHIFT_EXPR },

4940       { CPP_EOF, ERROR_MARK }

4941     };

4942  

4943     return cp_parser_binary_expression (parser,

4944                                   map,

4945                                   cp_parser_additive_expression );

4946   }

 

additive-expression:

   multiplicative-expression

   additive-expression + multiplicative-expression

   additive-expression - multiplicative-expression

 

4911   static tree

4912   cp_parser_additive_expression (cp_parser* parser)                                     in parser.c

4913   {

4914     static const cp_parser_token_tree_map map = {

4915       { CPP_PLUS, PLUS_EXPR },

4916       { CPP_MINUS, MINUS_EXPR },

4917       { CPP_EOF, ERROR_MARK }

4918     };

4919  

4920     return cp_parser_binary_expression (parser,

4921                                   map,

4922                                   cp_parser_multiplicative_expression );

4923   }

 

mulitplicative-expression:

   pm-expression

   multiplicative-expression * pm-expression

   multiplicative-expression / pm-expression

   multiplicative-expression % pm-expression

 

4887   static tree

4888   cp_parser_multiplicative_expression (cp_parser* parser)                                    in parser.c

4889   {

4890     static const cp_parser_token_tree_map map = {

4891       { CPP_MULT, MULT_EXPR },

4892       { CPP_DIV, TRUNC_DIV_EXPR },

4893       { CPP_MOD, TRUNC_MOD_EXPR },

4894       { CPP_EOF, ERROR_MARK }

4895     };

4896  

4897     return cp_parser_binary_expression (parser,

4898                                   map,

4899                                   cp_parser_pm_expression );

4900   }

 

Pm-expression is the abbreviation of pointer-to-member expression which may contain the pointer-to-member operators “.*” or “->*”.

pm-expression:

   cast-expression

   pm-expression .* cast-expression

   pm-expression ->* cast-expression

 

4864   static tree

4865   cp_parser_pm_expression (cp_parser* parser)                                            in parser.c

4866   {

4867     static const cp_parser_token_tree_map map = {

4868       { CPP_DEREF_STAR, MEMBER_REF },

4869       { CPP_DOT_STAR, DOTSTAR_EXPR },

4870       { CPP_EOF, ERROR_MARK }

4871     };

4872  

4873     return cp_parser_binary_expression (parser, map,

4874                                   cp_parser_simple_cast_expression );

4875   }

 

On both sides of pointer-to-member operator can be the cast-expression.

cast-expression:

   unary-expression

   ( type-id ) cast-expression

 

14595 static tree

14596 cp_parser_simple_cast_expression (cp_parser *parser)                               in parser.c

14597 {

14598   return cp_parser_cast_expression (parser, /*address_p=*/ false);

14599 }

 

Cast-expression contains non-cast expression of unary-expression, which is the expression token “4096” belongs to.

 

4751   static tree

4752   cp_parser_cast_expression (cp_parser *parser, bool address_p)                   in parser.c

4753   {

4754     /* If it's a `(', then we might be looking at a cast.  */

4755     if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_PAREN))

4756     {

          …

4848     }

4849  

4850     /* If we get here, then it's not a cast, so it must be a

4851       unary-expression.  */

4852     return cp_parser_unary_expression (parser, address_p);

4853   }

 

Unary-epxression contains rich rules, and GNU also supports serval extensions.

unary-expression:

     postfix-expression

     ++ cast-expression

     -- cast-expression

     unary-operator cast-expression

     sizeof unary-expression

     sizeof ( type-id )

     new-expression

     delete-expression

GNU Extensions:

   unary-expression:

     __extension__ cast-expression

     __alignof__ unary-expression

     __alignof__ ( type-id )

     __real__ cast-expression

     __imag__ cast-expression

     && identifier

 

4235   static tree

4236   cp_parser_unary_expression (cp_parser *parser, bool address_p)                in parser.c

4237   {

4238     cp_token *token;

4239     enum tree_code unary_operator;

4240  

4241     /* Peek at the next token.  */

4242     token = cp_lexer_peek_token (parser->lexer);

        …

4406     return cp_parser_postfix_expression (parser, address_p);

4407   }

 

postfix-expression:

     primary-expression

      postfix-expression [ expression ]

     postfix-expression ( expression-list [opt] )

     simple-type-specifier ( expression-list [opt] )

     typename :: [opt] nested-name-specifier identifier ( expression-list [opt] )

     typename :: [opt] nested-name-specifier template [opt] template-id (expression-list [opt])

     postfix-expression . template [opt] id-expression

     postfix-expression -> template [opt] id-expression

     postfix-expression . pseudo-destructor-name

     postfix-expression -> pseudo-destructor-name

     postfix-expression ++

     postfix-expression --

     dynamic_cast < type-id > ( expression )

     static_cast < type-id > ( expression )

     reinterpret_cast < type-id > ( expression )

     const_cast < type-id > ( expression )

     typeid ( expression )

     typeid ( type-id )

GNU Extension:

   postfix-expression:

     ( type-id ) { initializer-list , [opt] }

Postfix-expression can be used in: subscripting, function call, explicit type conversion (functional notation), pseudo destructor call, class member access, increment and decrement, dynamic cast, type identification, static cast, reinterpret cast, constant cast.

 

3414   static tree

3415   cp_parser_postfix_expression (cp_parser *parser, bool address_p)                      in parser.c

3416   {

3417     cp_token *token;

3418     enum rid keyword;

3419     cp_id_kind idk = CP_ID_KIND_NONE;

3420     tree postfix_expression = NULL_TREE;

3421     /* Non-NULL only if the current postfix-expression can be used to

3422       form a pointer-to-member. In that case, QUALIFYING_CLASS is the

3423       class used to qualify the member.  */

3424     tree qualifying_class = NULL_TREE;

3425  

3426     /* Peek at the next token.  */

3427     token = cp_lexer_peek_token (parser->lexer);

3428     /* Some of the productions are determined by keywords.  */

3429     keyword = token->keyword;

3430     switch (keyword)

3431     {

          ….

3566       default :

3567       {

3568         tree type;

3569  

3570         /* If the next thing is a simple-type-specifier, we may be

3571           looking at a functional cast. We could also be looking at

3572           an id-expression. So, we try the functional cast, and if

3573           that doesn't work we fall back to the primary-expression.  */

3574         cp_parser_parse_tentatively (parser);

3575         /* Look for the simple-type-specifier.  */

3576         type = cp_parser_simple_type_specifier (parser,

3577                                          CP_PARSER_FLAGS_NONE,

3578                                           /*identifier_p=*/ false);

3579          /* Parse the cast itself.  */

3580         if (!cp_parser_error_occurred (parser))

3581           postfix_expression

3582                  = cp_parser_functional_cast (parser, type);

3583         /* If that worked, we're done.  */

3584         if (cp_parser_parse_definitely (parser))

3585           break ;

3586  

3587          /* If the functional-cast didn't work out, try a

3588           compound-literal.  */

3589         if (cp_parser_allow_gnu_extensions_p (parser)

3590            && cp_lexer_next_token_is (parser->lexer, CPP_OPEN_PAREN))

3591         {

              …

3634         }

3635  

3636         /* It must be a primary-expression.   */

3637         postfix_expression = cp_parser_primary_expression (parser,

3638                                                    &idk,

3639                                                    &qualifying_class);

3640       }

3641       break ;

3642     }

3643  

3644     /* If we were avoiding committing to the processing of a

3645       qualified-id until we knew whether or not we had a

3646       pointer-to-member, we now know.  */

3647     if (qualifying_class)

3648     {

           …

3666     }

3667  

3668     /* Keep looping until the postfix-expression is complete.  */

3669     while (true)

3670     {

3671       if (idk == CP_ID_KIND_UNQUALIFIED

3672          && TREE_CODE (postfix_expression) == IDENTIFIER_NODE

3673          && cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_PAREN))

3674         /* It is not a Koenig lookup function call.  */

3675         postfix_expression

3676               = unqualified_name_lookup_error (postfix_expression);

3677        

3678       /* Peek at the next token.  */

3679       token = cp_lexer_peek_token (parser->lexer);

3680  

3681       switch (token->type)

3682       {

            …

4003         default :

4004           return postfix_expression;

4005       }

4006     }

4007  

4008     /* We should never get here.  */

4009     abort ();

4010     return error_mark_node;

4011   }

 

Above, for token “4096”, it is not either form mentioned above, besides it neither is simple-type-specifier, nor is functional cast, parser then needs to handle it as primary-expression with following rules.

primary-expression:

     literal

     this

     ( expression )

     id-expression

GNU Extensions:

   primary-expression:

     ( compound-statement )

     __builtin_va_arg ( assignment-expression , type-id )

   literal:

     __null

 

2373   static tree

2374   cp_parser_primary_expression (cp_parser *parser,                                     in parser.c

2375                             cp_id_kind *idk,

2376                             tree *qualifying_class)

2377   {

2378     cp_token *token;

2379  

2380     /* Assume the primary expression is not an id-expression.  */

2381     *idk = CP_ID_KIND_NONE;

2382     /* And that it cannot be used as pointer-to-member.  */

2383     *qualifying_class = NULL_TREE;

2384  

2385     /* Peek at the next token.  */

2386     token = cp_lexer_peek_token (parser->lexer);

2387     switch (token->type)

2388     {

2389       /* literal:

2390         integer-literal

2391         character-literal

2392         floating-literal

2393         string-literal

2394         boolean-literal  */

2395       case CPP_CHAR:

2396       case CPP_WCHAR:

2397       case CPP_STRING:

2398       case CPP_WSTRING:

2399       case CPP_NUMBER:

2400         token = cp_lexer_consume_token (parser->lexer);

2401         return token->value;

          …

2648     }

2649   }

 

Clearly, token “4096” is the integer-literal of literal. In fact, when lexer reads in this token, an integer constant tree node has been created and set as the value of the token. Refer to section 5.6.1.1.2. Number for detail. After creating nodes for the default argument, the sub-tree for the template argument is built up as following.

(Click here for open )

Figure 106 : tree for non-type parameter

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值