《实用C++》第18课 局部变量和全局变量

本文通过生动比喻和代码实例,详细解释了局部变量与全局变量的概念及作用域,包括静态类型变量的特点,以及多文件间如何共享全局变量。

本文转载自:VC驿站

https://www.cctry.com/thread-287647-1-1.html

1、什么是局部变量,什么是全局变量?
举个例子:张三是张三村的村长,李四是李四村的村长,那么张三村的村民有什么事儿都可以去找张三村,张三也对该存有管辖权,对吧?李四呢,自然李四村的村民都能找到李四这个村长了。反过来,你让李四村的村民去找张三村的村长来解决问题那肯定是不行的,不是一个村子,自然不能相互公用了。那么这时候呢如果出来个王五镇长,那么就方便多了。张三村和李四村的村民都可以直接找王五镇长办事儿。

所以这里面的张三、张三村的村民、李四、李四村的村民都算是局部变量,只能在自己的范围内或者说作用域内活动,那么王五镇长呢,他就算是个全局变量,张三村和李四村的村民都可以访问。处在一个全局的位置。

不知道通过这个形象的比喻大家对于局部变量和全局变量的总体上是否有个大概的了解。下面具体用代码的方式来说下!

2、代码方式讲解局部变量与全局变量:
#include <iostream>
using namespace std;

int sum = 0;
int add(int a, int b)
{
        int ret = a+b;
        return ret;
}

int main()
{
        int x = 2, y = 5;
        sum = add(x, y);
        cout << "sum = " << sum << endl;
        return 0;
}

这里面的 x 和 y 是局部变量,ret 呢也是局部变量,a 和 b 也算是 局部变量。都是局部变量,那么全局变量呢?是谁?没错,相信大家都已经猜到了,就是 sum,他的定义既没有在 main 函数中,也没有在 add 函数中,处在一个全局的位置。在 add 函数中可以访问,在 main 函数中也可以访问,并且都可以改变这个全局变量 sum 的值。

注意点:
①、函数内部定义的局部变量也只能在该函数中使用,其他的函数无法调用,例如:main 函数中的 x 和 y 在 add 函数是无法访问的。add函数中的 ret 在 main 函数中也是无法访问的;
②、不同函数或者作用域内可以定义相同名字的局部变量名字,虽然名字相同,但都是不同的个体,互不影响,彼此独立;
③、一般局部变量的作用域就在他所在的一对花括号内,函数的形参作用域也在其函数的整个作用域内,也是局部变量;
int x = 2, y = 5;
sum = add(x, y);
if (sum > 5)
{
        int k = 10;
        ++k;
}
这里面的变量 k 就是一个局部变量,他的作用域也只局限于 if 的 {} 花括号内。
④、全局变量的定义要尽量靠上,不然在他上面的函数使用不了,因为本身编译器编译代码的顺序就是自上向下。

3、多文件共同访问一个全局变量:
方法(回复帖子后可见):
①、在其中一个C/Cpp文件中定义并初始化全局变量,例如:
int sum = 0;
②、在C/Cpp文件都能访问到的一个h头文件中声明该全局变量为支持多文件访问的:
extern int sum; //此时只是声明,所以不需要初始化
③、在其他C/Cpp文件中 #include 这个头文件之后,就可以直接使用这个全局变量 sum 了。

备注:不能在 h 文件中直接定义这个全局变量 int sum = 0; 之前看到很多网友都是这么做的,这样的话如果有多个C/Cpp文件 include 包含这个头文件的时候就会提示 sum 重复定义了。所以一定要在 C/Cpp文件中定义该全局变量,之后在 h 头文件中声明该全局变量才行哦。

4、static 静态类型变量:
#include <iostream>
using namespace std;

int sum = 0;
int add(int a, int b)
{
        static int num = 1;
        int ret = a+b;
        ret += num;
        ++num;
        return ret;
}

int main()
{
        int x = 2, y = 5;
        sum = add(x, y);
        cout << "sum = " << sum << endl;

        sum = add(x, y);
        cout << "sum = " << sum << endl;

        sum = add(x, y);
        cout << "sum = " << sum << endl;
        return 0;
}

大家看到代码了吧,在main函数中连续调用了3次 add 函数,之后调用 cout 进行打印输出,那么大家先猜一下,这三次打印的结果都是多少呢?
下面我来给大家演示一下,具体看视频!

为什么会有这个结果呢?就是因为这里面定义了一个 static 类型的静态局部变量!他有什么特性呢?
static 静态类型局部变量只初始化一次,之后的调用都不进行初始化!

相关细节分析详见视频教程讲解!

第18课视频教程下载地址:

https://www.cctry.com/thread-287647-1-1.html

<think>我们计划编写一个Cppcheck自定义规则来检查变量命名风格: 1. 全局变量:以'g'开头(例如:g_GlobalVar) 2. 本地全局变量(文件作用域):以'l'开头(例如:l_LocalGlobalVar) 3. 局部静态变量:以's'开头(例如:s_StaticVar) 注意:我们不考虑类成员变量(因为类成员变量通常有另外的命名约定,如m_) 步骤: 1. 识别变量声明。 2. 确定变量的作用域(全局、文件作用域、局部静态)。 3. 检查变量名是否符合约定。 我们将遍历所有token,当遇到变量声明(token.str为变量名,且token.isName为True,并且token.variable为True)时,我们检查其作用域存储类型。 作用域判断: - 全局变量:在函数外部声明,无任何函数作用域。 - 文件作用域(本地全局变量):在函数外部声明,但使用了static关键字(C++中表示内部链接)。 - 局部静态变量:在函数内部声明,使用了static关键字。 注意:在C++中,全局变量静态变量初始化可能涉及复杂的表达式,但我们只关心变量名。 实现思路: 1. 使用一个栈来跟踪当前作用域(函数作用域、全局作用域等)。但Cppcheck的token遍历是线性的,我们可以通过记录当前是否在函数内来判断。 2. 我们维护一个状态:current_scope,可以是'global'、'function'。 3. 当遇到'{'时,如果当前在函数内部,则进入局部作用域(但我们关心的是函数作用域,所以只需要知道是否在函数内)。 4. 当遇到函数定义时,将当前作用域设置为函数作用域。 更简单的方法: - 在函数外部:全局作用域。 - 在函数内部:函数作用域。 对于变量声明,我们通过token的作用域信息(如果有)或者通过上下文判断。 但是,Cppcheck的token可能提供作用域信息。我们可以使用token.scope属性(如果存在)来获取作用域。然而,为了兼容性,我们也可以使用以下方法: 我们计划: 1. 记录当前函数深度(遇到函数定义时+1,遇到函数结束的'}'时-1)。 2. 在全局作用域(函数深度为0)且变量声明有static:文件作用域(本地全局变量) 3. 在函数作用域(函数深度>0)且变量声明有static:局部静态变量 注意:全局变量(无static)文件作用域变量(有static)都在全局作用域(函数外部)声明。 变量声明模式: 通常,变量声明的token序列为: [类型限定符] 类型 变量名 [= 初始化] ... ; 或者 static 类型 变量名 ... ; 我们可以通过以下方式识别: - 找到类型名(可能包含多个token,如unsigned int)。 - 然后找到变量名token(token.variable为True)。 但是,Cppcheck的tokenlist已经将变量声明标记出来:token.variable为True的token就是变量名。 因此,我们遍历token,当遇到token.variable为True,则: - 判断当前作用域(通过函数深度) - 检查该变量声明是否有static修饰(查看之前的token中是否有static) - 然后根据作用域static判断期望的前缀。 具体: - 如果函数深度为0(全局作用域)且没有static:全局变量 -> 前缀'g' - 如果函数深度为0(全局作用域)且有static:文件作用域变量 -> 前缀'l' - 如果函数深度>0(函数作用域)且有static:局部静态变量 -> 前缀's' - 注意:函数参数普通局部变量我们不检查(根据要求) 注意:我们只检查这三种变量,其他的忽略(如函数参数、普通局部变量、类成员变量)。 实现步骤: 1. 初始化: function_depth = 0 # 函数深度,0表示全局作用域 in_function = False # 或者用function_depth>0 reported = set() # 避免重复报告 2. 遍历token,处理以下情况: - 遇到函数名(token.function为True)时,我们接下来会进入函数体,但函数体可能不会立即出现(可能有模板等)。 - 更简单:遇到'{',并且此时我们处于函数名之后(即当前在函数定义中),则function_depth++。但是,我们无法判断这个'{'是函数体的还是其他块的。 - 另一种:当遇到函数定义的起始括号(可能是参数列表后的'{')时,我们进入函数作用域。 3. 但是,我们可以通过函数深度来跟踪: - 每遇到一个'{',depth++(包括函数体、结构体、类、命名空间等)。但我们只关心函数体,因此需要区分。 - 我们可以这样:当我们检测到函数定义(token.function为True),然后跳过参数列表,找到函数体的'{',然后标记进入函数(设置function_depth=1)。但是这很复杂。 4. 简化:我们只关心变量声明的位置,而不关心具体的块。我们可以通过: - 全局作用域:function_depth==0 - 函数作用域:function_depth>0 但是,如何计算function_depth? - 当我们遇到一个函数名(token.function为True),然后后面遇到'{'(函数体开始)时,function_depth++。 - 当我们遇到'}'时,如果这个'}'是函数体的结束(我们可以通过depth归零来判断),则function_depth--。 但是,嵌套函数(C++不支持)所以不考虑,但可能有多个函数嵌套调用?不,C++函数不能嵌套定义。 因此,我们可以用大括号深度来近似函数深度。但是,全局作用域也可能有结构体定义、命名空间等。 5. 更好的方法是:使用token.scope属性(如果可用)。在Cppcheck中,token可能有scope属性,表示作用域。 6. 为了简单,我们使用以下规则: - 如果变量声明在函数外(即没有遇到任何函数体的大括号),则认为是全局作用域。 - 但是,我们如何知道当前在函数内部?我们可以在遇到函数定义时,设置一个标志,直到匹配的'}'. 7. 我们采用一个函数作用域深度计数器(只针对函数体): - 当我们检测到一个函数定义(token.function为True),并且我们找到了函数体的起始'{'时,函数深度+1。 - 当我们遇到'}',并且这个'}'使函数深度的括号匹配归零时,函数深度-1。 但这样实现复杂。 8. 替代方案:使用token.isGlobaltoken.isLocal属性?但是token.isLocal可能表示局部变量,token.isGlobal表示全局变量。 9. 查阅Cppcheck文档:token有varId属性,但并没有直接提供作用域。但是,我们可以通过token.scope(字符串)来获取作用域名称。但是,这需要知道作用域名称,并且不同版本可能不同。 10. 我们换一种思路:使用Cppcheck提供的SymbolDatabase来获取作用域。但是,自定义规则可能无法使用SymbolDatabase。 11. 简单实现:我们只使用大括号深度(包括所有类型的大括号)来区分全局函数内部。虽然不精确,但可以接受。 - 大括号深度为0:全局作用域 - 大括号深度>=1:局部作用域(可能是函数内部,也可能是类、结构体内部) 但是,我们不想检查类成员变量。所以,我们还需要判断是否在类或结构体定义中。 12. 由于复杂性,我们决定只检查三种变量的命名,而忽略类成员变量。我们额外记录是否在类/结构体定义中(通过遇到class/struct关键字)。 13. 实现: depth = 0 # 大括号深度 in_class = False # 是否在类/结构体内部 遍历token: - 遇到class/struct/union:设置in_class=True,直到遇到';'或者匹配的'}' - 遇到'{':depth++,如果遇到class/struct/union后的第一个'{',则标记为类定义开始(in_class_depth=depth) - 遇到'}':depth--,如果depth==in_class_depth,则in_class=False 但是,这样也很复杂。 14. 我们回到token.variable的作用域属性。在Cppcheck的Python接口中,token有isGlobalisLocal属性。我们可以使用: - token.isGlobal:全局变量(包括文件作用域的static变量?实际上,文件作用域的static变量也是全局存储,但链接性是内部的) - token.isLocal:函数内部的变量(包括局部静态变量) 但是,我们需要区分: 全局变量(无static): token.isGlobal为True,但没有static关键字 文件作用域变量(有static): token.isGlobal为True,同时有static关键字 局部静态变量:token.isLocal为True,同时有static关键字 那么,我们如何知道变量是否有static关键字? 我们可以检查变量声明前的token,直到遇到分号或上一个变量声明。 15. 步骤: 对于每个变量token(token.variable为True): - 如果token.isGlobal为True: 检查该声明是否包含static关键字(在变量名之前的token中查找) 如果有static,则是文件作用域变量(期望前缀'l') 否则,是全局变量(期望前缀'g') - 如果token.isLocal为True: 检查是否有static关键字,如果有,则是局部静态变量(期望前缀's') 否则,跳过(普通局部变量) 注意:类成员变量?token.isLocaltoken.isGlobal都为False?或者token.isClass?我们需要避免检查类成员变量。 实际上,在Cppcheck中,类成员变量不会被标记为isGlobal或isLocal。所以,我们可以这样: if token.isGlobal or (token.isLocal and token.static): ... # 检查 但是,类成员变量不会被标记为isGlobal或isLocal,因此我们可以安全地跳过。 16. 因此,我们只需要检查: - token.isGlobal == True 的变量(包括文件作用域的static全局变量) - token.isLocal == True 且 有static修饰的变量(局部静态变量) 17. 判断是否有static修饰: - 从当前token向前遍历(token.previous),直到遇到类型开始的token(或者遇到分号、逗号等,但变量声明通常从存储类说明符开始) - 如果遇到static关键字,则说明有static。 18. 注意:在全局作用域,static关键字出现在类型前面,如: static int a; -> 全局作用域有static,则是文件作用域变量 int b; -> 无static,全局变量 在函数内部: static int c; -> 有static,局部静态变量 另外,可能还有extern、thread_local等,我们只关心static。 19. 代码步骤: for token in all_tokens: if token.variable and not (token.isClass or token.isStruct or token.isUnion): # 实际上,token没有isClass等属性,所以我们换一种方式跳过类成员变量:检查是否有作用域操作符(::)?很难。 我们使用: if token.variable and (token.isGlobal or token.isLocal): if token.isGlobal: # 检查是否在声明中有static(在变量名前) has_static = False prev = token.previous while prev and prev.str not in [';', '{', '}']: # 向前搜索直到遇到上一个声明的结束 if prev.str == 'static': has_static = True break prev = prev.previous if has_static: # 文件作用域变量,期望前缀'l' if not token.str.startswith('l_'): report_error(token, "File-scope variable should start with 'l_'") else: # 全局变量,期望前缀'g' if not token.str.startswith('g_'): report_error(token, "Global variable should start with 'g_'") elif token.isLocal: # 检查是否有static has_static = False prev = token.previous while prev and prev.str not in [';', '{', '}']: if prev.str == 'static': has_static = True break prev = prev.previous if has_static: # 局部静态变量,期望前缀's' if not token.str.startswith('s_'): report_error(token, "Static local variable should start with 's_'") 20. 注意:多个变量声明在同一行?例如: static int a, b; 此时,token ab的previous都是',',然后继续往前是'int',然后是'static'。所以,对于b,我们也能找到static。 21. 但是,变量声明可能有复杂的修饰,如: static constexpr auto* a = ...; 所以,我们只要在变量名前找到static关键字即可。 22. 另外,变量名可能有多个token(如指针、引用)?但token.variable指向的是变量名的token。所以,变量名token的前面可能是*、&、::等,但static关键字一定在类型前面。 23. 因此,我们向前搜索直到遇到分号或大括号(表示上一个声明结束)或遇到static。 24. 为了避免搜索太远,我们可以限制在同一个声明语句内(从当前变量名到该声明语句的开始)。但简单向前搜索到分号或大括号即可。 25. 但是,在同一个声明语句中的多个变量,我们只需要报告每一个变量一次(即使同一个声明中有多个变量不符合,分别报告)。 26. 实现: 注意:Cppcheck的token可能没有isGlobalisLocal属性?我们需要查看文档或调试。 为了兼容,我们可以先判断属性是否存在。 如果不存在,则采用其他方法(如通过大括号深度判断)。 因此,我们编写一个备用方案:使用大括号深度static关键字判断作用域。 方案二(备用): 我们记录: brace_depth = 0 # 大括号深度 in_function_scope = False # 是否在函数内部(当brace_depth>0且不在类定义中,则认为是函数内部?但可能不准确) 但是,我们需要区分全局文件作用域(都用static)?全局作用域就是brace_depth为0。 步骤: if token.variable: # 检查是否在类定义中?跳过类成员变量(暂时不管,因为难以区分) prev = token.previous has_static = False while prev and prev.str not in [';', '{', '}']: if prev.str == 'static': has_static = True break prev = prev.previous if brace_depth == 0: # 全局作用域 if has_static: # 文件作用域 if not token.str.startswith('l_'): report_error(...) else: if not token.str.startswith('g_'): report_error(...) else: # 局部作用域 if has_static: # 局部静态变量 if not token.str.startswith('s_'): report_error(...) 但是,如何避免类成员变量?在类定义中的变量也在全局作用域(brace_depth可能为1,因为类定义在大括号内)?所以,我们需要记录是否在类定义中。 因此,我们还需要: class_depth = 0 # 类定义深度(嵌套类) 当遇到class/struct/union时,设置in_class=True,并记录class_depth=brace_depth+1(因为接下来会进入类定义的大括号) 当遇到匹配的'}'且brace_depth等于class_depth时,结束类定义。 这变得复杂,所以我们暂时先不检查类成员变量,如果发现变量在类定义中,则跳过。 考虑到时间,我们先使用第一种方案(token.isGlobaltoken.isLocal),如果这些属性不存在,则使用第二种方案。 代码结构: 我们将编写一个检查器,遍历所有token,对每个变量token: if token.variable is True: if hasattr(token, 'isGlobal') and token.isGlobal: ... # 全局或文件作用域 elif hasattr(token, 'isLocal') and token.isLocal: ... # 局部变量 else: # 可能是类成员,跳过 continue 然后,在全局作用域中,我们查找static关键字,从而区分全局变量(g_)文件作用域变量(l_)。 在局部作用域中,我们只检查有static的情况(s_)。 另外,注意变量名可能是引用或指针,但变量名本身是一个token,我们检查这个token的字符串。 代码实现: </think>以下是一个 Cppcheck 自定义规则,用于检查代码中的变量命名风格是否符合规范:全局变量以 `g_` 开头,文件作用域变量以 `l_` 开头,局部静态变量以 `s_` 开头。 ```python import cppcheck import re import sys # 配置选项 DEBUG_ENABLED = False # 设为 True 启用调试输出 REPORT_DETAILS = True # 报告详细命名错误信息 def debug_print(message): """调试输出函数""" if DEBUG_ENABLED: print(f"[DEBUG] {message}", file=sys.stderr) def is_static_variable(token): """检查变量是否被声明为 static""" # 向前查找 static 关键字 prev = token.previous while prev and prev.str not in (';', '{', '}'): if prev.str == 'static': return True prev = prev.previous return False def is_global_scope(token): """检查 token 是否在全局作用域""" # 向后查找函数体或命名空间 current = token while current: if current.str in ('{', '}'): # 遇到大括号,说明在某个作用域内 return False if current.str == 'namespace' or current.function: # 遇到命名空间或函数定义 return False current = current.next return True def is_file_scope(token): """检查变量是否在文件作用域(全局 static 变量)""" return is_global_scope(token) and is_static_variable(token) def is_local_static(token): """检查是否是局部静态变量""" # 在函数作用域内且声明为 static if not is_global_scope(token) and is_static_variable(token): # 确保不是函数参数 current = token while current and current.str != '(' and current.str != '{': if current.str == ')': # 已经离开参数列表 break current = current.previous return current and current.str == '{' # 在函数体内 return False def get_variable_name(token): """获取变量的完整名称(处理指针、引用等情况)""" # 基本变量名 var_name = token.str # 检查是否有作用域限定符 if token.previous and token.previous.str == '::': prev_prev = token.previous.previous if prev_prev and prev_prev.isName: return f"{prev_prev.str}::{var_name}" return var_name def validate_naming(token, var_name): """检查变量名是否符合命名规范""" if is_file_scope(token): # 文件作用域变量(全局 static 变量) prefix = 'l_' scope = "file-scope (global static)" elif is_local_static(token): # 局部静态变量 prefix = 's_' scope = "local static" elif is_global_scope(token): # 全局变量 prefix = 'g_' scope = "global" else: # 其他变量(普通局部变量)不检查 return None # 检查变量名是否以指定前缀开头 if not var_name.startswith(prefix): return f"{scope} variable '{var_name}' should start with '{prefix}'" return None @cppcheck.checker def check_variable_naming(cfg, data): """ 检查变量命名是否符合规范: - 全局变量: g_ 开头 - 文件作用域变量: l_ 开头 - 局部静态变量: s_ 开头 """ # 确保有 tokenlist if not hasattr(cfg, 'tokenlist') or not cfg.tokenlist: return token = cfg.tokenlist[0] reported = set() # 避免重复报告 while token: # 只检查变量名 if token.variable: var_name = get_variable_name(token) error_msg = validate_naming(token, var_name) if error_msg: # 避免重复报告同一变量的多个位置 error_key = (token.linenr, var_name) if error_key not in reported: # 添加详细位置信息 if REPORT_DETAILS: location = f" (line {token.linenr})" full_msg = f"Naming convention violation: {error_msg}{location}" else: full_msg = f"Naming convention violation: {error_msg}" cppcheck.reportError(token, 'style', full_msg) reported.add(error_key) debug_print(f"Reported: {full_msg}") token = token.next # 兼容不同版本的 Cppcheck 插件 API if hasattr(cppcheck, 'register'): @cppcheck.register def naming_convention(cfg, data): return check_variable_naming(cfg, data) else: cppcheck.checkers.append(('naming_convention', check_variable_naming)) # 调试信息 if DEBUG_ENABLED: debug_print("Variable naming convention checker loaded") ``` ### 代码功能说明 1. **变量作用域检测**: ```python def is_global_scope(token): """检查 token 是否在全局作用域""" current = token while current: if current.str in ('{', '}'): return False current = current.next return True ``` 通过检查后续是否有大括号来判断是否在全局作用域。 2. **静态变量检测**: ```python def is_static_variable(token): """检查变量是否被声明为 static""" prev = token.previous while prev and prev.str not in (';', '{', '}'): if prev.str == 'static': return True prev = prev.previous return False ``` 向前遍历 token 搜索 `static` 关键字。 3. **变量类型分类**: ```python def validate_naming(token, var_name): if is_file_scope(token): prefix = 'l_' scope = "file-scope (global static)" elif is_local_static(token): prefix = 's_' scope = "local static" elif is_global_scope(token): prefix = 'g_' scope = "global" else: return None ``` 根据变量作用域存储类型确定所需的前缀。 4. **命名规范检查**: ```python if not var_name.startswith(prefix): return f"{scope} variable '{var_name}' should start with '{prefix}'" ``` 检查变量名是否以正确的前缀开头。 ### 使用示例 假设有以下 C++ 代码: ```cpp // 全局变量 - 应以 g_ 开头 int globalVar = 10; // 错误:缺少 g_ 前缀 // 文件作用域变量 - 应以 l_ 开头 static int fileScoped = 20; // 错误:缺少 l_ 前缀 void myFunction() { // 局部静态变量 - 应以 s_ 开头 static int localStatic = 30; // 错误:缺少 s_ 前缀 // 普通局部变量 - 不检查 int localVar = 40; } ``` 运行 Cppcheck 时,该规则会报告三个错误: 1. `Global variable 'globalVar' should start with 'g_'` 2. `File-scope variable 'fileScoped' should start with 'l_'` 3. `Local static variable 'localStatic' should start with 's_'` ### 配置选项 1. **调试模式**: ```python DEBUG_ENABLED = True # 开启详细调试输出 ``` 2. **报告详细级别**: ```python REPORT_DETAILS = False # 关闭详细位置信息 ``` 3. **排除特定变量**: 在 `validate_naming` 函数中添加: ```python # 排除特定变量 EXCLUDED_VARIABLES = {'main', 'argv', 'argc'} if var_name in EXCLUDED_VARIABLES: return None ``` 4. **自定义前缀**: ```python # 自定义前缀 PREFIXES = { 'global': 'global_', 'file_scope': 'static_', 'local_static': 'static_local_' } if is_global_scope(token): prefix = PREFIXES['global'] ``` ### 适用范围 1. **支持的语言特性**: - 全局变量 - 文件作用域静态变量 - 函数内的静态局部变量 - 命名空间内的变量 - 类外部定义的变量 2. **不检查的情况**: - 函数参数 - 类/结构体成员变量 - 普通局部变量 - 常量表达式(constexpr) - 模板变量 此规则专注于项目级别的变量命名规范检查,可作为代码质量保障的一部分集成到 CI/CD 流程中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值