UEC++ 基础类型及反射系统

目录

一,常用UEC++变量类型

二,理解UBT/UHT

三,反射系统

关键宏

用于生成

用于调用(运行时)

四,UClass 


一,常用UEC++变量类型

// 基础类型
bool bool_val = 1;
uint8 byte_val = 255;
int32 int32_val = -32;
int64 int64_val = -64;
double double_val = 3.14;

// UE_LOG 的第 3 个参数(Format)必须是格式化字符串字面量
UE_LOG(LogTemp, Error, TEXT("%d"), bool_val);
UE_LOG(LogTemp, Warning, TEXT("%d"), byte_val);
UE_LOG(LogTemp, Warning, TEXT("%d"), int32_val);
UE_LOG(LogTemp, Display, TEXT("%d"), int64_val);
UE_LOG(LogTemp, Display, TEXT("%f"), double_val);
// 字符串类型
FString str = TEXT("FString!");
FName name = TEXT("FName!");
FText text = FText::FromString(TEXT("FText!"));

UE_LOG(LogTemp, Display, TEXT("%s"), *str);
UE_LOG(LogTemp, Display, TEXT("%s"), *name.ToString());
UE_LOG(LogTemp, Display, TEXT("%s"), *text.ToString());
// 容器类型
TArray<int32> arr = { 1,2,3,4,5 };
TSet<int32> set = { 10,20,30,40,50 };
TMap<int32, FString> map = { {1, TEXT("one")}, {2, TEXT("two")} };

FString arr_ret;
for (auto num : arr)
arr_ret += FString::FromInt(num) + TEXT(" ");
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, arr_ret);

FString set_ret;
for (auto num : set) {
	set_ret += FString::FromInt(num) + TEXT(" ");
}
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, set_ret);

FString map_ret;
for (auto num : map)
map_ret += num.Value + TEXT(" ");
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, map_ret);
// CreateDefaultSubobject是UEC++中一个非常核心的函数,专门用于在构造函数中为Actor或Object创建默认子对象(通常是组件);
// 在构造阶段为对象创建子对象,并让引擎正确地管理它(包括反射、序列化、以及在蓝图编辑器中可见等);
// 注,只能在类的构造函数中调用;
UStaticMeshComponent* MeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("MyMeshComponent"));

二,理解UBT/UHT

  • UBT(Unreal Build Tool )虚幻编译工具,负责管理整个编译流程,包括收集所需编译的模块、处理依赖关系、调用 UHT,最后再调用标准的 C++ 编译器(如 MSVC)完成最终编译;

  • UHT(Unreal Header Tool)预处理,负责解析头文件中的特殊宏(如 UCLASS()UFUNCTION() 等),读取为 UObject 系统添加的“元数据”,并自动生成 *.generated.h 和 *.gen.cpp 等中间代码,从而支持反射、蓝图调用等功能;

点击编译 (Live Coding / 生成解决方案)
                │
                ▼
┌─────────────────────────────────────┐
│ 1. UBT (Unreal Build Tool) 启动     │
│    - 读取 .uproject 文件              │
│    - 读取 Target.cs 文件              │
│    - 解析模块依赖关系树               │
│    - 判断哪些文件需要重新编译          │
└─────────────────────────────────────┘
                │
                ▼  反射系统 = UHT(步骤 2~3)在编译期生成数据表 + UE 运行时库读取这些表
┌─────────────────────────────────────┐ 
│ 2. UBT 调用 UHT (Unreal Header Tool)│   
│    - 只扫描头文件 (.h) 或 .generated.h│
│    - 识别 UCLASS, UFUNCTION 等宏     │
│    - 验证反射规范的合法性             │
└─────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│ 3. UHT 生成中间代码                   │
│    - 为每个类生成 .generated.h        │
│    - 生成 .gen.cpp(包含反射数据)     │
│    - 生成函数参数封包代码              │
│    (这些文件放在 Intermediate 文件夹)│
└─────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│ 4. UBT 准备编译环境                   │
│    - 构建编译命令行参数                │
│    - 设置包含目录 (Include Paths)      │
│    - 配置预处理器宏                    │
│    - 确定编译配置 (Debug/Development) │
└─────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│ 5. UBT 调用底层编译器 (MSVC/Clang)   │
│    - 编译所有 .cpp + .gen.cpp        │
│    - 输出 .obj 文件                   │
│    - 链接成 .dll (模块) 或 .exe       │
└─────────────────────────────────────┘
                │
                ▼
┌─────────────────────────────────────┐
│ 6. UBT 处理资源/其他                  │
│    - 编译着色器(如果是首次/有变更)    │
│    - 复制生成的 .dll 到正确位置        │
│    - 更新模块热重载信息(如有)         │
└─────────────────────────────────────┘
                │
                ▼
         引擎可以运行新代码

三,反射系统

反射 是程序在运行时检查、访问和操作自身结构(类、属性、函数等)的能力,其在UE中的主要用途:

  • 编辑器交互(最核心),能直接在编辑器的Details面板里修改它的值,无需任何额外代码,如在UPROPERTY(EditAnywhere)
  • 蓝图与C++交互,可直接在蓝图节点中调用C++函数,如UFUNCTION(BlueprintCallable);反之,C++也能调用蓝图中实现的事件如BlueprintImplementableEvent;
  • 序列化(保存与加载),保存游戏时,反射系统能自动遍历对象上所有UPROPERTY()标记的变量,把值写入磁盘,加载时再自动恢复;
  • 垃圾回收UPROPERTY()最重要的职责之一是阻止垃圾回收,被标记的指针,GC就知道“这是正在使用的”,不会错误地清理掉,防止内存泄漏和野指针的关键;
  • 网络复制UPROPERTY(Replicated)可让某个属性在服务器和所有客户端之间自动同步,省去了手写大量网络通信代码的麻烦;

注:必须是UObject派生类,完整的反射功能(如垃圾回收)只在继承自UObject的类上生效,普通的C++类不行;

关键宏
// 创建新类型时“对外声明”,告诉构建工具“该类需要被纳入反射系统”;否则UBT在扫描头文件时会直接忽略该类,不会为它生成 .generated.h 和 .gen.cpp 文件;
// 可在其后附加说明符,用于该类与引擎编辑器及蓝图系统的沟通交互(如Blueprintable,BlueprintType,meta=(...));
// 标记类型错误(如USTRUCT用在了UObject上),UHT会编译错误;
UCLASS():对继承自UObject的类反射,提供完整反射支持(如UFUNCTION、垃圾回收、网络复制等);
USTRUCT():对普通C++结构体轻量级反射(支持UPROPERTY、序列化、蓝图作为数据类型,但不支持函数暴露和垃圾回收);
UENUM():对C++枚举反射(可在蓝图、编辑器下拉菜单和序列化中直接使用);
UINTERFACE():接口类,标记可被蓝图实现的接口,必须配合GENERATED_BODY();

// “对内填充”,在类体内补齐必要的代码;
// 提供必要的函数实现(如StaticClass(),返回本类专属的UClass指针),必要的类型定义(如Super);
GENERATED_BODY()

// 对类型成员的反射,标记需要进行反射的宏
// 必须用于被UCLASS或USTRUCT标记的类型内部成员
// 不可用于全局变量或全局函数,也不可用于普通 C++ 类/结构体中;
UPROPERTY():反射成员变量(支持序列化、蓝图访问、网络复制等),用于UCLASS/USTRUCT;
UFUNCTION():反射函数(支持蓝图调用、网络远程调用、控制台命令等),用于UCLASS/UINTERFACE;
UPROPERTY() FString GlobalName; // ❌ 错误:全局变量不能用

class MyNormalClass {
    UPROPERTY() int Value;      // ❌ 错误:普通C++类不能用
};

UCLASS()                        // ✅ 正确:在UCLASS内部使用
class AMyActor : public AActor {
    GENERATED_BODY()
    UPROPERTY() int Score;        
    UFUNCTION() void DoAction();  
};

USTRUCT()                      // ✅ 正确:在USTRUCT内部使用
struct FMyData {
    GENERATED_BODY()
    UPROPERTY() FString Name;  
};
用于生成

UPROPERTY 和 UFUNCTION 通过反射系统将对应属性或函数暴露给蓝图;

// 编辑器可编辑性(Edit / Visible)
UPROPERTY(EditAnywhere) 	     // 实例和类默认值(蓝图/CDO)均可编辑
UPROPERTY(EditDefaultsOnly)	     // 仅类默认值可编辑,实例不可
UPROPERTY(EditInstanceOnly)	     // 仅实例可编辑,类默认值不可
UPROPERTY(VisibleAnywhere)	     // 始终可见但不可编辑(只读显示),实例和默认值均可见
UPROPERTY(VisibleDefaultsOnly)	 // 仅类默认值中可见(只读)
UPROPERTY(VisibleInstanceOnly)	 // 仅实例中可见(只读)
// 蓝图访问控制(Blueprint)
UPROPERTY(BlueprintReadWrite)    // 蓝图可读写,即可get/set
UPROPERTY(BlueprintReadOnly)     // 蓝图只读,即只可get
UPROPERTY(BlueprintGetter) 	     // 指定一个自定义函数作为 Getter(替代默认的读操作)
UPROPERTY(BlueprintSetter) 	     // 指定一个自定义函数作为 Setter(替代默认的写操作)
// 网络复制(Replication)
UPROPERTY(Replicated)	        // 属性会进行网络复制(需在 GetLifetimeReplicatedProps 中注册)
UPROPERTY(ReplicatedUsing)	    // 属性复制时,会调用指定的回调函数(通常在客户端执行)
// 序列化与持久化
UPROPERTY(SaveGame)	            // 属性会被包含在 SaveGame 存档系统中
UPROPERTY(Transient)	        // 属性不会被序列化(保存/加载时忽略),加载后为默认值
UPROPERTY(DuplicateTransient)	// 复制(Duplicate)对象时,此属性不会被复制到新对象
UPROPERTY(TextExportTransient)	// 导出为文本(如复制到剪贴板)时忽略
UPROPERTY(NonTransactional)	    // 在编辑器中操作(如移动、修改)时不参与 Undo/Redo 系统
// 资产与元数据
UPROPERTY(Category = "名称")        // 指定属性在编辑器 Details 面板中所属的分类
UPROPERTY(SimpleDisplay)	        // 优先显示在简单列表中(而不是高级折叠页)
UPROPERTY(AdvancedDisplay)	        // 默认折叠到“高级”区域
UPROPERTY(AssetRegistrySearchable)	// 属性会加入资产注册表,允许在内容浏览器中搜索
// 实例化与引用
UPROPERTY(Instanced)	// 对于 UObject* 或 TSubclassOf,每个实例都会拥有自己独立的对象,而不是共享默认对象
UPROPERTY(Ref)	        // 标记一个引用参数(通常用于函数参数,很少用在 UPROPERTY)
UPROPERTY(Export)	    // 将引用的对象作为子对象序列化(内嵌)而不是通过引用路径
// 配置相关
UPROPERTY(Config)	        // 此属性可从配置文件(*.ini)读取/写入
UPROPERTY(GlobalConfig)	    // 类似 Config,但保存在全局基类配置文件中(不保存在子类)
UPROPERTY(Localized)	    // 属性是本地化的(UI 文本等)
// 其他实用说明符
UPROPERTY(meta = (DisplayName = "xxx"))	                // 在编辑器中显示的名称,而不是变量名
UPROPERTY(meta = (ClampMin = "0", ClampMax = "100"))	// 限制数值编辑范围(仅对浮点和整数)
UPROPERTY(meta = (AllowPrivateAccess = "true"))	        // 允许蓝图访问私有成员(不推荐,但有时有用)
UPROPERTY(Native)	                                    // 仅用于结构体,标记为本地类型
UPROPERTY(NoClear)	                                    // 对象引用不能设置为 None(必须总是有效)
// 蓝图交互
UFUNCTION(BlueprintCallable)           // 蓝图可调用,允许蓝图节点调用此C++函数,是暴露函数最常见的方式;
UFUNCTION(BlueprintPure)               // 蓝图纯函数(无副作用),在蓝图中不显示执行引脚,直接连出数据,const函数默认隐含此标记;
UFUNCTION(BlueprintNativeEvent)        // C++默认实现,蓝图可重写,C++需在函数名后加_Implementation实现;
UFUNCTION(BlueprintImplementableEvent) // 纯蓝图实现,C++声明无实现,完全由蓝图实现;
// 网络与权限
UFUNCTION(NetMulticast)                // 网络多播
UFUNCTION(Server)                      // 服务器执行
UFUNCTION(Client)                      // 客户端执行

UFUNCTION(Reliable / Unreliable) 	   // 配合RPC使用,保证/不保证网络调用必达;
UFUNCTION(BlueprintAuthorityOnly) 	   // 限制蓝图代码仅在拥有网络权限的机器上执行;
UFUNCTION(BlueprintCosmetic) 	       // 标记为装饰性函数,在专用服务器上会被跳过;
// 其他实用功能
UFUNCTION(Exec)	                        // 将函数暴露为游戏内控制台命令
UFUNCTION(CallInEditor)	                // 在编辑器中选中对象,便可在细节面板看到调用按钮;
UFUNCTION(Category = "Name")	        // 在蓝图节点面板中,为函数指定分类名称;
UFUNCTION(meta = (DisplayName="..."))	// 自定义函数在蓝图中的显示名称
UFUNCTION(BlueprintSetter)	            // 当蓝图修改特定UPROPERTY时,关联并自动调用此函数;
用于调用(运行时)
  • 在程序运行期间,通过字符串名字或类型信息动态地查找、访问、调用对象的属性和方法;

主要用途:

  • 运行时类型识别(RTTI)

    • 判断对象是否为某个类型或其子类:MyActor->IsA(AActor::StaticClass());

    • 判断类之间的继承关系:MyClass->IsChildOf(UObject::StaticClass());

  • 反射访问属性(UPROPERTY
    • 通过 FindPropertyByName 获取 FProperty 描述符;

    • 结合对象实例,读写成员变量的值(通过 ContainerPtrToValuePtr 或 CopyCompleteValue);

  • 反射调用函数(UFUNCTION
    • 通过 FindFunctionByName 获取 UFunction

    • 调用 UObject::ProcessEvent 动态执行函数(支持参数和返回值);

  • 动态创建对象
    • NewObject<UObject>(Outer, UClass*):创建任意 UObject 派生对象;

    • World->SpawnActor<AActor>(UClass*, ...):在关卡中生成 Actor

  • 获取类默认对象(CDO)
    • UClass::GetDefaultObject() 返回该类的默认实例;

    • 用途:

      • 读取/修改类的默认属性值(影响所有新实例);

      • 比较对象属性是否与默认值不同(用于网络同步、序列化差异);

      • 快速获取类的一些静态配置;

  • 支撑引擎底层系统
    • 序列化:遍历 UClass 的所有 UPROPERTY,自动保存/加载;

    • 网络复制:标记 Replicated 的属性会通过 UClass 查找并同步;

    • 垃圾回收:通过 UClass 遍历对象引用的属性;

    • 编辑器 Details 面板:根据 UClass 中的属性元数据动态生成 UI;

    • 蓝图虚拟机:通过 UClass 查找函数并执行 ProcessEvent

反射入口 → UClass (StaticClass / GetClass)
            ↓
属性:FindPropertyByName → FProperty → ContainerPtrToValuePtr → 读写
函数:FindFunctionByName → UFunction → ProcessEvent → 调用
类型:IsA / IsChildOf
创建:NewObject / SpawnActor
// 获取 UClass(元数据入口)
T::StaticClass()	                                    // 获取已知 C++ 类 T 的 UClass(编译期)
UObject::GetClass()	                                    // 获取实例对象所属的 UClass(运行时)
FindObject<UClass>(ANY_PACKAGE, TEXT("MyClass"))	    // 通过名字(或路径)在包中查找类
LoadClass<T>(nullptr, TEXT("/Game/MyClass.MyClass_C"))	// 加载蓝图类(常与动态生成配合)
// 访问属性(UPROPERTY)
UClass::FindPropertyByName(FName("PropName"))	    // 按名称查找 FProperty
TFieldIterator<FProperty>	                        // 遍历类的所有属性(含继承)
FProperty::ContainerPtrToValuePtr<void>(Object)	    // 获取对象中该属性的内存指针
FProperty::CopyCompleteValue(Dest, Src)	            // 复制属性值(不关心类型)
FProperty::GetValue_InContainer(Object, &OutVal)	// 读取属性值(模板版)
FProperty::SetValue_InContainer(Object, &NewVal)	// 设置属性值
// 调用函数(UFUNCTION)
UClass::FindFunctionByName(FName("FuncName"))	// 按名称查找 UFunction
UObject::ProcessEvent(UFunction*, void* Parms)	// 执行函数(支持参数/返回值)
UFunction::Invoke(Object, Parms)	            // 另一个调用方式(较少用)
TFieldIterator<UFunction>	                    // 遍历类的所有函数
// 动态创建对象
NewObject<UClass>(Outer, UClass*)	                 // 创建 UObject 派生对象
World->SpawnActor<AActor>(UClass*, FTransform, ...)	 // 在世界中生成 Actor
ConstructObject<UClass>()	                         // 低级别创建(不常用)
// 类型判断与转换
UObject::IsA(UClass*)	                            // 判断对象是否属于某类(或子类)
UClass::IsChildOf(UClass*)	                        // 判断类是否继承自另一个类
Cast<T>(UObject*)	                                // 安全转换(编译期已知类型)
DynamicCast<T>(UObject*)	                        // 运行时反射转换(类似 Cast)
StaticClass()->IsChildOf(AActor::StaticClass())	    // 类继承检查
// 其他辅助
FindField<FProperty>(UClass*, FName)	// 等价于 FindPropertyByName
FindField<UFunction>(UClass*, FName)	// 等价于 FindFunctionByName
UClass::GetDefaultObject()	            // 获取类默认对象(CDO)
UClass::GetSuperClass()	                // 获取父类
UObject::GetFName()	                    // 获取对象名称(可用于反射查找)
// 常用宏(编译期帮助反射生成)
GENERATED_BODY()	                        // 必须在类声明中,展开反射所需的代码
DECLARE_DYNAMIC_MULTICAST_DELEGATE(...)	    // 声明动态委托(支持蓝图)
DECLARE_FUNCTION	                        // 自定义 UFUNCTION 底层(极少用)

四,UClass 

  • 是描述一个 UObject 派生类元数据(属性、函数、父类、大小等)的核心类型
    • 是UE反射系统为每一个 UCLASS(即带有 UCLASS() 宏的 UObject 派生类)自动生成的元数据类型(继承链UObject -> UField -> UStruct -> UClass );
    • 可理解为C++ type_info的超级增强版——不仅包含类型名称、父类信息,还存储了该类的所有属性(UPROPERTY)、函数(UFUNCTION)、以及用于序列化、网络复制、实例化等的额外数据;
    • 该类型是引擎核心代码中已经写好的(位于 UObject.h / Class.h);
  • 是UE中“类的类”,提供了运行时对 C++ 类结构(属性、函数、继承、默认值)的完整描述和操作能力,是反射、动态创建、编辑器集成等所有高级功能的基础;
  • 每个 UObject 派生类都有一个唯一的 UClass 对象(单例);
    • UClass 对象是模块加载时创建的,其元数据内容由UHT生成提供;
AMyActor 编译
    |
    |  UHT 扫描 .h 文件
    |
生成 .generated.h 和 .gen.cpp
    |
    |  .gen.cpp 中包含:
    |  1. 静态结构体:TClassCompiledInDefer<AMyActor> 的实例
    |  2. 该类的元数据(属性名、偏移量、函数指针等)
    |
┌─────────────────────────────────────────────────────────────┐
│  模块加载 / 热重载(DLL 加载)                               |
└─────────────────────────────────────────────────────────────┘
    |
    |  ① 静态对象 TClassCompiledInDefer<AMyActor> 的构造函数执行
    |     → 将类名、大小、CDO 创建函数指针等信息,
    |        添加到一个全局的“待注册类列表”中(GetDeferredClassRegistration)
    |
    |  ② 模块加载完成后,统一处理所有登记的类,引擎调用 ProcessNewlyLoadedUObjects()
    |     → 遍历全局“待注册类列表”
    |     → 对每个类调用 UClass::CreateClass()
    |
生成:UClass 对象(分配内存,存入元数据表)
    |  ① 填充属性 (FProperty)、函数 (UFunction) 列表
    |  ② 链接父类 (UClass::Super)
    |  ③ 将该 UClass* 注册到全局名称映射表 (GUObjectArray)
    |
    |  UClass 准备就绪,调用 UClass::CreateDefaultObject()
    |     → 内部调用 StaticConstructObject_Internal
    |     → 调用 AMyActor 的 C++ 构造函数(FObjectInitializer.bIsCDO = true)
    |          ├─ 初始化成员变量
    |          └─ CreateDefaultSubobject → NewObject(组件)  ✅ 真正分配内存
    |     → 调用 PostInitProperties()
    |          ├─ 将组件注册到 UClass 的子对象列表
    |          └─ 处理 config 文件覆盖
    |
生成:CDO 对象(Class Default Object)
    |  ├─ 标记 RF_ClassDefaultObject,加入 Root Set 防回收
    |  └─ 编辑器中修改 "Class Defaults" 即修改此 CDO 实例的属性值
    |
运行时:AMyActor* NewActor = GetWorld()->SpawnActor<AMyActor>(...);
    |  → SpawnActor 内部调用 UClass::GetDefaultObject()(即 CDO)
    |  → 为 Actor 本体分配原始内存(FMemory::Malloc)
    |  → 通过 memcpy 复制 CDO 的内存布局(极快的浅拷贝)
    |  → 再次调用 C++ 构造函数(FObjectInitializer.bIsCDO = false,且携带 CDO 指针)
    |       ├─ 修正虚表指针(指向正确的子类虚函数表)
    |       ├─ CreateDefaultSubobject → DuplicateObject(组件)  ✅ 深拷贝组件
    |       └─ 基础类型变量不修正,保持 memcpy 的值(除非显式赋值)
    |  → 调用 PostInitProperties()
    |       ├─ 将深拷贝的组件注册到当前实例的 UObject 链表
    |       └─ 触发 OnSubobjectAdded(编辑器感知)
    |  → SpawnActor 后处理:
    |       ├─ PostSpawnInitialize(设置 Owner/Instigator,执行碰撞调整)
    |       └─ OnConstruction(如果允许运行时构建)
    |  → 将 Actor 添加到 World 的 Actor 数组(开启 Tick 和复制,同时建立 GC 引用链,防止被回收)
    |
生成:AMyActor 的运行时实例
// 简化版 UClass 伪代码
class UClass : public UStruct
{
    // 反射元数据
    FName ClassName;                     // 类名,如 "MyActor"
    UClass* SuperClass;                  // 父类指针
    UObject* ClassDefaultObject;         // CDO(类默认对象)
    TArray<FProperty*> Properties;       // 该类所有的 UPROPERTY 属性
    TArray<UFunction*> Functions;        // 该类所有的 UFUNCTION 函数
    uint32 ClassFlags;                   // 类标志(如可蓝图创建、抽象类等)
    UObject* (*ClassConstructor)(const FObjectInitializer&); // 构造器函数指针

    // 常用接口
    UClass* GetSuperClass() const { return SuperClass; }
    FName GetFName() const { return ClassName; }
    UObject* GetDefaultObject() const { return ClassDefaultObject; }
    
    // 创建实例的静态辅助函数(通常由 NewObject<> 调用)
    UObject* CreateInstance(const FObjectInitializer& OI) const
    {
        return (*ClassConstructor)(OI);
    }

    // 查找属性
    FProperty* FindPropertyByName(FName PropName) const
    {
        for (FProperty* Prop : Properties)
            if (Prop->GetFName() == PropName)
                return Prop;
        return nullptr;
    }

    // 反射调用函数
    void CallFunction(UObject* Obj, UFunction* Func, void* Params)
    {
        Obj->ProcessEvent(Func, Params);
    }
};

// 注:UStruct 是 UClass 的父类,包含了字段(属性)的通用管理逻辑
class UStruct : public UField
{
    UStruct* SuperStruct;                // 继承结构体的父结构体
    TArray<FProperty*> ChildProperties;  // 当前结构体直属的属性
    int32 MinAlignment;                  // 内存对齐要求
    int64 PropertySize;                  // 结构体实例的总字节大小
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值