IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> UE4 generated.h文件生成过程模拟 -> 正文阅读

[游戏开发]UE4 generated.h文件生成过程模拟

为了探寻UE4的反射机制,我们先从简单处着手,手动模拟一下generated.h的生成过程,看看UHT到底干了哪些事情。

以一个很简单的类作为分析对象

UCLASS(meta=(IsBlueprintBase = "false"))
class RPGGAME_API ARpgPlayer : public ARpgGameCharacter
{

public:
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable)
		void BlueprintCallableFunction();

	UFUNCTION(BlueprintPure)
		int BlueprintPureFunction();

	UFUNCTION(BlueprintNativeEvent)
		void BlueprintNativeEventFunction();

	void BlueprintNativeEventFunction_Implementation();

public:
	UPROPERTY(BlueprintReadWrite)
		float PlayerHP = 2.0f;
};

GENERATED_BODY()相关定义如下

// This pair of macros is used to help implement GENERATED_BODY() and GENERATED_USTRUCT_BODY()
#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)

// Include a redundant semicolon at the end of the generated code block, so that intellisense parsers can start parsing
// a new declaration if the line number/generated code is out of date.
#define GENERATED_BODY_LEGACY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY_LEGACY);
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);

可以得出,GENERATED_BODY()被替换后,变成如下代码

UCLASS(meta=(IsBlueprintBase = "false"))
class RPGGAME_API ARpgPlayer : public ARpgGameCharacter
{
	CURRENT_FILE_ID_#__LINE__#_GENERATED_BODY
	
...
};

其中__LINE__是需要被替换成GENERATED_BODY宏实际所在的行数,我们这里在15行,所以变成

UCLASS(meta=(IsBlueprintBase = "false"))
class RPGGAME_API ARpgPlayer : public ARpgGameCharacter
{
	CURRENT_FILE_ID_15_GENERATED_BODY
	
...
};

CURRENT_FILE_ID是什么?秘密在RpgPlayer.generated.h里面,我们打开这个文件,可以看到头文件最后有

#define CURRENT_FILE_ID RpgGame_Source_RpgGame_RpgPlayer_h

将这个宏进行替换,于是变成

UCLASS(meta=(IsBlueprintBase = "false"))
class RPGGAME_API ARpgPlayer : public ARpgGameCharacter
{
	RpgGame_Source_RpgGame_RpgPlayer_h_15_GENERATED_BODY
	
...
};

而RpgGame_Source_RpgGame_RpgPlayer_h_15_GENERATED_BODY又是什么东西呢?它也是一个宏!同样定义在RpgPlayer.generated.h里面,如下

#define RpgGame_Source_RpgGame_RpgPlayer_h_15_GENERATED_BODY \
PRAGMA_DISABLE_DEPRECATION_WARNINGS \
public: \
	RpgGame_Source_RpgGame_RpgPlayer_h_15_PRIVATE_PROPERTY_OFFSET \
	RpgGame_Source_RpgGame_RpgPlayer_h_15_SPARSE_DATA \
	RpgGame_Source_RpgGame_RpgPlayer_h_15_RPC_WRAPPERS_NO_PURE_DECLS \
	RpgGame_Source_RpgGame_RpgPlayer_h_15_INCLASS_NO_PURE_DECLS \
	RpgGame_Source_RpgGame_RpgPlayer_h_15_ENHANCED_CONSTRUCTORS \
private: \
PRAGMA_ENABLE_DEPRECATION_WARNINGS

我们再进行替换

UCLASS(meta=(IsBlueprintBase = "false"))
class RPGGAME_API ARpgPlayer : public ARpgGameCharacter
{
	PRAGMA_DISABLE_DEPRECATION_WARNINGS 
public: 
	RpgGame_Source_RpgGame_RpgPlayer_h_15_PRIVATE_PROPERTY_OFFSET 
	RpgGame_Source_RpgGame_RpgPlayer_h_15_SPARSE_DATA 
	RpgGame_Source_RpgGame_RpgPlayer_h_15_RPC_WRAPPERS_NO_PURE_DECLS 
	RpgGame_Source_RpgGame_RpgPlayer_h_15_INCLASS_NO_PURE_DECLS 
	RpgGame_Source_RpgGame_RpgPlayer_h_15_ENHANCED_CONSTRUCTORS 
private: 
PRAGMA_ENABLE_DEPRECATION_WARNINGS
	
public:
	UFUNCTION(BlueprintCallable)
		void BlueprintCallableFunction();

	UFUNCTION(BlueprintPure)
		int BlueprintPureFunction();

	UFUNCTION(BlueprintNativeEvent)
		void BlueprintNativeEventFunction();

	void BlueprintNativeEventFunction_Implementation();

public:
	UPROPERTY(BlueprintReadWrite)
		float PlayerHP = 2.0f;
};

这些宏都可以在RpgPlayer.generated.h里面找到。

  • 首先看PRAGMA_DISABLE_DEPRECATION_WARNINGS:这个宏,我们可以在引擎找到好几个定义的地方,但大致做的事情都是同一个,就是消除掉一些编译警告,不同平台进行代码编译时,会报不同的警告,所以这个宏被不同的平台进行了定义,无关痛痒,因此跳过这个宏不谈。

  • RpgGame_Source_RpgGame_RpgPlayer_h_15_PRIVATE_PROPERTY_OFFSET :这个也在RpgPlayer.generated.h定义了,不过是空值。

  • RpgGame_Source_RpgGame_RpgPlayer_h_15_SPARSE_DATA:定义为了空值。

  • RpgGame_Source_RpgGame_RpgPlayer_h_15_RPC_WRAPPERS_NO_PURE_DECLS:定义如下

    #define RpgGame_Source_RpgGame_RpgPlayer_h_15_RPC_WRAPPERS_NO_PURE_DECLS \
     \
    	DECLARE_FUNCTION(execBlueprintNativeEventFunction); \
    	DECLARE_FUNCTION(execBlueprintPureFunction); \
    	DECLARE_FUNCTION(execBlueprintCallableFunction);

    这里有个问题,我们定义了5个函数,这个宏里面为什么只有3个exe函数?从规则上看,BlueprintCallable、BlueprintPure、BlueprintNativeEvent都进行了处理,而BlueprintImplementableEvent和一个原生的BlueprintNativeEventFunction_Implementation函数没有处理,这两个函数跑哪去了?我们查看gen.cpp文件,发现如下代码

    	static FName NAME_ARpgPlayer_BlueprintImplementableEventFunction = FName(TEXT("BlueprintImplementableEventFunction"));
    	void ARpgPlayer::BlueprintImplementableEventFunction()
    	{
    		ProcessEvent(FindFunctionChecked(NAME_ARpgPlayer_BlueprintImplementableEventFunction),NULL);
    	}
    	static FName NAME_ARpgPlayer_BlueprintNativeEventFunction = FName(TEXT("BlueprintNativeEventFunction"));
    	void ARpgPlayer::BlueprintNativeEventFunction()
    	{
    		ProcessEvent(FindFunctionChecked(NAME_ARpgPlayer_BlueprintNativeEventFunction),NULL);
    	}

    可以看出,这两个函数被保留了!没有进行特殊处理。

  • RpgGame_Source_RpgGame_RpgPlayer_h_15_CALLBACK_WRAPPERS:定义为了空。

  • RpgGame_Source_RpgGame_RpgPlayer_h_15_INCLASS_NO_PURE_DECLS:

#define RpgGame_Source_RpgGame_RpgPlayer_h_15_INCLASS_NO_PURE_DECLS \
private: \
	static void StaticRegisterNativesARpgPlayer(); \
	friend struct Z_Construct_UClass_ARpgPlayer_Statics; \
public: \
	DECLARE_CLASS(ARpgPlayer, ARpgGameCharacter, COMPILED_IN_FLAGS(0 | CLASS_Config), CASTCLASS_None, TEXT("/Script/RpgGame"), NO_API) \
	DECLARE_SERIALIZER(ARpgPlayer)
  • RpgGame_Source_RpgGame_RpgPlayer_h_15_ENHANCED_CONSTRUCTORS:
    #define RpgGame_Source_RpgGame_RpgPlayer_h_15_ENHANCED_CONSTRUCTORS \
    	/** Standard constructor, called after all reflected properties have been initialized */ \
    	NO_API ARpgPlayer() { }; \
    private: \
    	/** Private move- and copy-constructors, should never be used */ \
    	NO_API ARpgPlayer(ARpgPlayer&&); \
    	NO_API ARpgPlayer(const ARpgPlayer&); \
    public: \
    	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ARpgPlayer); \
    DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ARpgPlayer); \
    	DEFINE_DEFAULT_CONSTRUCTOR_CALL(ARpgPlayer)

    为什么有些宏的define是空的?可能是因为我们的测试用例非常简单,里面只有一个变量和几个简单的函数,所以很多数据都没有,还没有进行更复杂的测试。

将这一波宏替换后,得到如下代码

UCLASS(meta=(IsBlueprintBase = "false"))
class RPGGAME_API ARpgPlayer : public ARpgGameCharacter
{
public: 
	DECLARE_FUNCTION(execBlueprintNativeEventFunction); 
	DECLARE_FUNCTION(execBlueprintPureFunction); 
	DECLARE_FUNCTION(execBlueprintCallableFunction);

	void BlueprintNativeEventFunction();
	void BlueprintNativeEventFunction_Implementation();  
 
private: 
	static void StaticRegisterNativesARpgPlayer(); 
	friend struct Z_Construct_UClass_ARpgPlayer_Statics; 
public: 
	DECLARE_CLASS(ARpgPlayer, ARpgGameCharacter, COMPILED_IN_FLAGS(0 | CLASS_Config), CASTCLASS_None, TEXT("/Script/RpgGame"), NO_API) 
	DECLARE_SERIALIZER(ARpgPlayer)

	/** Standard constructor, called after all reflected properties have been initialized */ \
	NO_API ARpgPlayer() { }; 
private: 
	/** Private move- and copy-constructors, should never be used */ 
	NO_API ARpgPlayer(ARpgPlayer&&); 
	NO_API ARpgPlayer(const ARpgPlayer&); 
public: 
	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ARpgPlayer); 
    DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ARpgPlayer); 
	DEFINE_DEFAULT_CONSTRUCTOR_CALL(ARpgPlayer)
private: 
	
public:
	UPROPERTY(BlueprintReadWrite)
		float PlayerHP = 2.0f;
};

看起来代码清晰了一些,但还有些宏的存在,我们继续替换

  • DECLARE_CLASS:这个宏定义在ObjectMacros.h中
    #define DECLARE_CLASS( TClass, TSuperClass, TStaticFlags, TStaticCastFlags, TPackage, TRequiredAPI  ) \
    private: \
        TClass& operator=(TClass&&);   \
        TClass& operator=(const TClass&);   \
    	TRequiredAPI static UClass* GetPrivateStaticClass(); \
    public: \
    	/** Bitwise union of #EClassFlags pertaining to this class.*/ \
    	enum {StaticClassFlags=TStaticFlags}; \
    	/** Typedef for the base class ({{ typedef-type }}) */ \
    	typedef TSuperClass Super;\
    	/** Typedef for {{ typedef-type }}. */ \
    	typedef TClass ThisClass;\
    	/** Returns a UClass object representing this class at runtime */ \
    	inline static UClass* StaticClass() \
    	{ \
    		return GetPrivateStaticClass(); \
    	} \
    	/** Returns the package this class belongs in */ \
    	inline static const TCHAR* StaticPackage() \
    	{ \
    		return TPackage; \
    	} \
    	/** Returns the static cast flags for this class */ \
    	inline static EClassCastFlags StaticClassCastFlags() \
    	{ \
    		return TStaticCastFlags; \
    	} \
    	/** For internal use only; use StaticConstructObject() to create new objects. */ \
    	inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags) \
    	{ \
    		return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags); \
    	} \
    	/** For internal use only; use StaticConstructObject() to create new objects. */ \
    	inline void* operator new( const size_t InSize, EInternal* InMem ) \
    	{ \
    		return (void*)InMem; \
    	}

    DECLARE_SERIALIZER:同样定义在ObjectMacros.h中,

    #define DECLARE_SERIALIZER( TClass ) \
    	friend FArchive &operator<<( FArchive& Ar, TClass*& Res ) \
    	{ \
    		return Ar << (UObject*&)Res; \
    	} \
    	friend void operator<<(FStructuredArchive::FSlot InSlot, TClass*& Res) \
    	{ \
    		InSlot << (UObject*&)Res; \
    	}

    DECLARE_FUNCTION:定义在ObjectMacros.h中。从注释中很容易看出来,这个宏很简单,没什么嵌套,就是包装了函数的声明。

    // This macro is used to declare a thunk function in autogenerated boilerplate code
    #define DECLARE_FUNCTION(func) static void func( UObject* Context, FFrame& Stack, RESULT_DECL )

  • 我们把这几个宏替换掉,得到如下代码

    UCLASS(meta=(IsBlueprintBase = "false"))
    class RPGGAME_API ARpgPlayer : public ARpgGameCharacter
    {
    public: 
    static void execBlueprintNativeEventFunction( UObject* Context, FFrame& Stack, RESULT_DECL );
    static void execBlueprintPureFunction( UObject* Context, FFrame& Stack, RESULT_DECL );
    static void execBlueprintCallableFunction( UObject* Context, FFrame& Stack, RESULT_DECL );
    
    	void BlueprintNativeEventFunction();
    	void BlueprintNativeEventFunction_Implementation();  
    private: 
    	static void StaticRegisterNativesARpgPlayer(); 
    	friend struct Z_Construct_UClass_ARpgPlayer_Statics; 
    public: 
    	private: 
        ARpgPlayer& operator=(ARpgPlayer&&);   
        ARpgPlayer& operator=(const ARpgPlayer&);   
    	NO_API static UClass* GetPrivateStaticClass(); 
    public: 
    	/** Bitwise union of #EClassFlags pertaining to this class.*/ 
    	enum {StaticClassFlags=COMPILED_IN_FLAGS(0 | CLASS_Config)}; 
    	/** Typedef for the base class ({{ typedef-type }}) */ 
    	typedef ARpgGameCharacter Super;
    	/** Typedef for {{ typedef-type }}. */ 
    	typedef ARpgPlayer ThisClass;
    	/** Returns a UClass object representing this class at runtime */ 
    	inline static UClass* StaticClass() 
    	{ 
    		return GetPrivateStaticClass(); 
    	} 
    	/** Returns the package this class belongs in */ 
    	inline static const TCHAR* StaticPackage() 
    	{ 
    		return TEXT("/Script/RpgGame"); 
    	} 
    	/** Returns the static cast flags for this class */ 
    	inline static EClassCastFlags StaticClassCastFlags() 
    	{ 
    		return CASTCLASS_None; 
    	} 
    	/** For internal use only; use StaticConstructObject() to create new objects. */ 
    	inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags) 
    	{ 
    		return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags); 
    	} 
    	/** For internal use only; use StaticConstructObject() to create new objects. */ 
    	inline void* operator new( const size_t InSize, EInternal* InMem ) 
    	{ 
    		return (void*)InMem; 
    	}
    		
    	friend FArchive &operator<<( FArchive& Ar, ARpgPlayer*& Res ) 
    	{ 
    		return Ar << (UObject*&)Res; 
    	} 
    	friend void operator<<(FStructuredArchive::FSlot InSlot, ARpgPlayer*& Res) 
    	{ 
    		InSlot << (UObject*&)Res; 
    	}
    
    	/** Standard constructor, called after all reflected properties have been initialized */ \
    	NO_API ARpgPlayer() { }; 
    private: 
    	/** Private move- and copy-constructors, should never be used */ 
    	NO_API ARpgPlayer(ARpgPlayer&&); 
    	NO_API ARpgPlayer(const ARpgPlayer&); 
    public: 
    	DECLARE_VTABLE_PTR_HELPER_CTOR(NO_API, ARpgPlayer); 
        DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(ARpgPlayer); 
    	DEFINE_DEFAULT_CONSTRUCTOR_CALL(ARpgPlayer)
    private: 
    	
    public:
    	UPROPERTY(BlueprintReadWrite)
    		float PlayerHP = 2.0f;
    };
    

    还有几个宏,我们继续探测

  • #define DECLARE_VTABLE_PTR_HELPER_CTOR(API, TClass) \
    	/** DO NOT USE. This constructor is for internal usage only for hot-reload purposes. */ \
    	API TClass(FVTableHelper& Helper);
  • ?DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER
#define DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER_DUMMY() \
	static UObject* __VTableCtorCaller(FVTableHelper& Helper) \
	{ \
		return nullptr; \
	}

#if WITH_HOT_RELOAD
	#define DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(TClass) \
		static UObject* __VTableCtorCaller(FVTableHelper& Helper) \
		{ \
			return new (EC_InternalUseOnlyConstructor, (UObject*)GetTransientPackage(), NAME_None, RF_NeedLoad | RF_ClassDefaultObject | RF_TagGarbageTemp) TClass(Helper); \
		}
#else // WITH_HOT_RELOAD
	#define DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER(TClass) \
		DEFINE_VTABLE_PTR_HELPER_CTOR_CALLER_DUMMY()
#endif // WITH_HOT_RELOAD
  • DEFINE_DEFAULT_CONSTRUCTOR_CALL
    #define DEFINE_DEFAULT_CONSTRUCTOR_CALL(TClass) \
    	static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())TClass; }
  • UPROPERTY。可以看出这些宏只是辅助UHT扫描源码用的,实际没有任何有效的定义。

    // These macros wrap metadata parsed by the Unreal Header Tool, and are otherwise
    // ignored when code containing them is compiled by the C++ compiler
    #define UPROPERTY(...)
    #define UFUNCTION(...)
    #define USTRUCT(...)
    #define UMETA(...)
    #define UPARAM(...)
    #define UENUM(...)
    #define UDELEGATE(...)
    #define RIGVM_METHOD(...)

    全部替换后,大致代码如下

UCLASS(meta=(IsBlueprintBase = "false"))
class RPGGAME_API ARpgPlayer : public ARpgGameCharacter
{
public: 
static void execBlueprintNativeEventFunction( UObject* Context, FFrame& Stack, void*const Z_Param__Result);
static void execBlueprintPureFunction( UObject* Context, FFrame& Stack, void*const Z_Param__Result);
static void execBlueprintCallableFunction( UObject* Context, FFrame& Stack, void*const Z_Param__Result);

	void BlueprintNativeEventFunction();
	void BlueprintNativeEventFunction_Implementation();  
private: 
	static void StaticRegisterNativesARpgPlayer(); 
	friend struct Z_Construct_UClass_ARpgPlayer_Statics; //定义在gen.cpp中
public: 
	private: 
    ARpgPlayer& operator=(ARpgPlayer&&);        //禁止移动赋值
    ARpgPlayer& operator=(const ARpgPlayer&);   //禁止拷贝赋值
	NO_API static UClass* GetPrivateStaticClass(); 
public: 
	/** Bitwise union of #EClassFlags pertaining to this class.*/ 
	enum {StaticClassFlags=COMPILED_IN_FLAGS(0 | CLASS_Config)}; 
	/** Typedef for the base class ({{ typedef-type }}) */ 
	typedef ARpgGameCharacter Super;
	/** Typedef for {{ typedef-type }}. */ 
	typedef ARpgPlayer ThisClass;
	/** Returns a UClass object representing this class at runtime */ 
	inline static UClass* StaticClass() 
	{ 
		return GetPrivateStaticClass(); 
	} 
	/** Returns the package this class belongs in */ 
	inline static const TCHAR* StaticPackage() 
	{ 
		return TEXT("/Script/RpgGame"); 
	} 
	/** Returns the static cast flags for this class */ 
	inline static EClassCastFlags StaticClassCastFlags() 
	{ 
		return CASTCLASS_None; 
	} 
	/** For internal use only; use StaticConstructObject() to create new objects. */ 
	inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags) 
	{ 
		return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags); 
	} 
	/** For internal use only; use StaticConstructObject() to create new objects. */ 
	inline void* operator new( const size_t InSize, EInternal* InMem ) 
	{ 
		return (void*)InMem; 
	}
		
	friend FArchive &operator<<( FArchive& Ar, ARpgPlayer*& Res ) 
	{ 
		return Ar << (UObject*&)Res; 
	} 
	friend void operator<<(FStructuredArchive::FSlot InSlot, ARpgPlayer*& Res) 
	{ 
		InSlot << (UObject*&)Res; 
	}

	/** Standard constructor, called after all reflected properties have been initialized */ \
	NO_API ARpgPlayer() { }; 
private: 
	/** Private move- and copy-constructors, should never be used */ 
	NO_API ARpgPlayer(ARpgPlayer&&);         
	NO_API ARpgPlayer(const ARpgPlayer&);    
public: 
	NO_API ARpgPlayer(FVTableHelper& Helper);
	static UObject* __VTableCtorCaller(FVTableHelper& Helper) 
	{ 
		return nullptr; 
	}
		
	static void __DefaultConstructor(const FObjectInitializer& X) { new((EInternal*)X.GetObj())ARpgPlayer; }
private: 
	
public:
	float PlayerHP = 2.0f;
};

至此,我们generated.h生成过程大致模拟完毕,下一篇文章,我们将对替换后的代码进行简单的分析。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 16:47:10  更:2022-03-03 16:48:38 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 16:23:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码