第一章 UE4框架基础(下)

文章目录

  • 第一章 UE4框架基础(下)
    • 六、反射应用详解
      • 获取UENUM反射的枚举对象
      • 获取蓝图反射对象
      • 获取UPROPERTY反射的属性对象
      • 获取UFUNCTION反射的对象
        • 方法一:FScriptDelegate
        • 方法二:TBaseDelegate
        • 方法三:UFunction
    • 七、资源同步、异步加载
    • 八、异步加载UClass类型
    • 九、LatentAction潜在事件

六、反射应用详解

常用反射宏

  • UCLASS
  • USTRUCT
  • UENUM
  • UPROPERTY
  • UFUNCTION

通过反射获取实例的函数

  • StaticLoadClass
  • StaticLoadObject
  • LoadClass
  • LoadObject
  • FObjectFinder
  • FClassFinder
  • FindObject

UObject和UClass的关系
UObject是继承UObject实例对象的父类,UE4的大部分核心功能和实例对象均继承此类。UObject也是UClass的基类,UClass保存有对象的反射数据(元数据)。每个类对应一个UClass对象,如果同一个类有多个实例,也是只有一个UClass反射对象,这个UClass反射对象中存储这个类所有实例的反射数据。
UObject及其子类可以通过(UClass*)将其强转为UClass类型
这里解释一下Cast<>和()两种强转的区别:
Cast<>只能转换有逻辑的转换关系,一般为父类转子类,转换成功返回有效指针,转换失败返回NULL;
而()这种强转是真的强行转换,若两个类之间没有父子(多层)继承关系,即使转换成功,很有可能会在调用时出错。

获取UENUM反射的枚举对象

UENUM()
enum class ERefState : uint8
{None,Active,Disable
}/*通过反射获取枚举类的对象,
第一个表示在所有包中查找,
第二个为枚举类名(将FString字符串转换为TCHAR指针),
第三个参数是否与传入的类完全匹配*/
UEnum* EnumPtr = FindObject<UEnum>((UObject*)ANY_PACKAGE, *FString("ERefState"), true);
EnumPtr->GetEnumName((int32)1);      //返回枚举第2个值-Active

获取蓝图反射对象

//获取指定蓝图对象
UBlueprint* ActorBP = LoadObject<UBlueprint>(NULL, TEXT("Blueprint'/Game/Blueprint/GameFrame/NewBlueprint.NewBlueprint'"));
//获取蓝图对象对应的UClass,UClass保存类的元数据,可以通过其生成对象
UClass* ActorClass = (UClass*)ActorBP->GeneratedClass;
GetWorld()->SpawnActor<AActor>(ActorClass, FVector::ZeroVector, FRotator::ZeroRotator);

获取UPROPERTY反射的属性对象

//先声明一个拥有UPROPERTY属性反射的类
UCLASS()
class XXX_API AMyActorClass : public AActor
{...
public:UPROPERTY(EditAnywhere)FString ActorName;UPROPERTY(EditAnywhere)bool IsActive;
...
}//先获取到AMyActorClass的实例对象
TArray<AActor*> Actors;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AMyActorClass::StaticClass(), Actors);
if(Actors.Num() > 0)
{AMyActorClass* MyActor = Cast<AMyActorClass>(Actors[0]);UObject* MyObject = (UObject*)MyActor;//迭代UPROPERTY反射的字段对象,通过GetClass函数获取到类的元数据,其中包含类的反射信息for(TFieldIterator<UProperty> ProIt(MyObject->GetClass()); ProIt; ++ProIt){UProperty* Property = *ProIt;       //获取当前指向的UProperty对象//操作FString类型if(Property->GetNameCPP().Equals("ActorName"))    //获取UPROPERTY反射的属性变量名并判断是否与ActorName字符串一致{//如果与ActorName一致,则此对象保存的是FString类型属性的反射信息,将其转换为UStrProperty对象UStrProperty* StrProperty = Cast<UStrProperty>(Property);if(StrProperty)        //如果转换成功{//获取反射对象包含的值的地址void* ValPtr = Property->ContainerPtrToValuePtr<uint8>(MyObject);//void* ValPtr = Property->ContainerPtrToValuePtr<void*>(MyObject);        //这个也可以//获取到反射对象包含的值FString ActorName = StrProperty->GetPropertyValue(ValPtr);//修改为新的值StrProperty->SetPropertyValue(ValPtr, FString("New Value"));}}//操作bool类型if(Property->GetNameCPP().Equals("IsActive")){UBoolProperty* BoolProperty = Cast<UBoolProperty>(Property);if(BoolProperty){void* BoolValPtr = Property->ContainerPtrToValuePtr<uint8>(MyObject);bool IsActive = BoolValPtr->GetPropertyValue(BoolValPtr);BoolValPtr->SetPropertyValue(BoolValPtr, false);}}}
}

获取UFUNCTION反射的对象

方法一:FScriptDelegate

//声明一个包含UFUNCTION反射的类
UCLASS()
class XXX_API AMyActor : public AActor
{...
public://FScriptDelegate方法调用的函数不能有返回值,如果要修改,只能通过引用参数修改UFUNCTION()void MyFuncOne();UFUNCTION()void MyFuncTwo(FString Info, int32 Count);
...
}//调用函数
AMyActor* MyActor;          //获取AMyActor指针对象,此处定义省略
FScriptDelegate MyDelegate;
//绑定并调用无参函数
MyDelegate.BindUFunction(MyActor, FName("MyFuncOne"));
MyDelegate.ProcessDelegate<AMyActor>(NULL);//绑定并调用有参函数
MyDelegate.BindUFunction(MyActor, FName("MyFuncTwo"));
//声明并定义结构体用于传参
struct {FString InfoStr;int32 Count;
} FuncTwoParam;
FuncTwoParam.InfoStr = FString("Hello");
FuncTwoParam.Count = 1024;
MyDelegate.ProcessDelegate<AMyActor>(&FuncTwoParam);

方法二:TBaseDelegate

//声明一个包含UFUNCTION反射的类
UCLASS()
class XXX_API AMyActor : public AActor
{...
public://此方法不能使用引用类型UFUNCTION()bool MyFunc(FString InfoStr, int32 Count);
...
}//调用函数
AMyActor* MyActor;          //获取AMyActor指针对象,此处定义省略
TBaseDelegate<bool, FString, int32> FuncDelegate = TBaseDelegate<bool, FString, int32>::CreateUFunction(MyActor, "MyFunc");
bool DelegateResult = FuncDelegate.Execute(FString("Hello"), 1024);

方法三:UFunction

//声明一个包含UFUNCTION反射的类
UCLASS()
class XXX_API AMyActor : public AActor
{...
public://此方法不能使用引用类型UFUNCTION()int32 MyFunc(FString InfoStr, int32& Count);
...
}//调用函数
AMyActor* MyActor;          //获取AMyActor指针对象,此处定义省略
UFunction* Func = MyActor->FindFunction(FName("MyFunc"));
if(Func)
{struct{FString InfoStr;int32 Count;} FuncParam;FuncParam.InfoStr = FString("Hello");FuncParam.Count = 1024;MyActor->ProcessEvent(Func, &FuncParam);//获取返回值uint8* RetValPtr = (uint8*)&FuncParam + Func->ReturnValueOffset; //参数地址(指向参数的指针)+返回值位移,获得返回值地址//此处也可以是 void* RetValPtr = (uint8*)&FuncParam + Func->ReturnValueOffset;int32* RetVal = (int32*)RetValPtr;       //RetVal是返回值的指针,其值为*RetVal//int32 RetVal = (int32)*RetValPtr;       //地址取值(即返回值)转换为int32类型
}

七、资源同步、异步加载

资源一般有两种状态,一种是已经加载到内存;另一种就是未加载到内存,即是在磁盘中。
FindObject模板函数可以从内存中查找资源对象,当资源未加载到内存时,将返回nullptr。

UStaticMesh* Mesh = FindObject<UStaticMesh>(NULL, TEXT("SkeletalMesh'/Game/Resource/SCTanks/Meshes/SK_TankPzIV.SK_TankPzIV'"));

LoadObject则可以将资源从磁盘中加载到内存,只要调用函数,即可加载到内存。

LoadObject<UStaticMesh>(NULL, TEXT("SkeletalMesh'/Game/Resource/SCTanks/Meshes/SK_TankPzIV.SK_TankPzIV'"));

如果要加载多个资源对象到内存,如果每个资源都通过LoadObject加载会很繁杂。所以我们可以通过创建一个资源表的方法,在蓝图中设置资源路径,然后在C++中通过资源表加载资源对象。

//创建保存资源路径的结构体
//如果添加BlueprintType标识符,则使用此类型的变量可以声明为BlueprintReadWrite,即可以在蓝图中获取或设置变量
//struct类型如果要在蓝图中使用,声明变量时不要声明为结构体指针
USTRUCT()
struct FWealthNode
{GENERATED_BODY()
public:UPROPERTY(EditAnywhere)FName WealthName;UPROPERTY(EditAnywhere)FStringAssetReference WealthPath; //资源的路径引用,FStringAssetReference是FSoftObjectPath的别名
};//创建继承自DataAsset的类,用于保存资源的数据
//UDataAsset是一种用于保存数据的蓝图,如资源的引用等
UCLASS()
class XXX_API UWealthAssetData : public UDataAsset
{GENERATED_BODY()
public:UPROPERTY(EditAnywhere)TArray<FWealthNode> WealthNode;UPROPERTY(EditAnywhere)TArray<UTexture2D*> WealthTexture;
};//保存资源数据并加载到内存, 我们这个例子将不断循环获取对象并设置为组件显示
UCLASS()
class XXX_API AWealthActor : public AActor
{...
public:void UpdateMesh();public:UPROPERTY(EditAnywhere)UWealthAssetData* WealthAssetData;
private:FTimerHandle UpdateMeshHandle;int32 MeshIndex = 0;
...
}void AWealthActor::BeginPlay()
{Super::BeginPlay();FTimerDelegate TimerDelegate = FTimerDelegate::CreateUObject(this, &AWealthActor::UpdateMesh); //创建计时器委托GetWorld()->GetTimerManager().SetTimer(UpdateMeshHandle, TimerDelegate, 1.f, true); //循环每1秒执行一次函数
}void AWealthActor::UpdateMesh()
{if(WealthAssetData && WealthAssetData->WealthNode.Num()>0){for(int i = 0; i < WealthAssetData->WealthNode.Num(); ++i){//如果LoadObject已经加载到内存,就直接获取内存的资源对象,否则,加载到内存。只加载一次。UStaticMesh* Mesh = LoadObject<UStaticMesh>(NULL, WealthAssetData->WealthNode[MeshIndex].WealthPath.ToString());StaticMeshComp->SetStaticMesh(Mesh);MeshIndex = ++MeshIndex % WealthAssetData->WealthNode.Num();    //循环获取数组下标}}
}

使用UObjectLibrary获取路径下资源数据的方法:

public:void ObjectLibraryOperate();protected:    //private:只允许本类成员访问; protected:允许本类和子类成员访问; public:允许所有成员访问。UObjectLirbary* ObjectLibrary;//FSoftObjectPath有一个别名为FStringAssetReferenceTArray<FSoftObjectPath> TexturePath;void XXX::ObjectLibraryOperate()
{if(!ObjectLibrary)     //如果ObjectLibrary值无效{/*@Param 对象库可以创建的对象类型及其子类@Param 可以创建的对象类型是否包含蓝图类@Param 是否是弱引用类型,true的话将允许被GC*/ObjectLibrary = UObjectLibrary::CreateLibrary(UObject::StaticClass(), false, false);ObjectLibrary->AddToRoot();        //注册到根节点,以免被GC}//搜索并获取路径下的所有资源,这里搜索的是贴图资源ObjectLibrary->LoadAssetDataFromPath(TEXT("/Game/Resource/UI/Texture/MenuTex"));/*声明一个资源数据数组用于存储资源数据FAssetData是一个结构体,用于存储资源寄存器搜索到的资源数据。它一般用于临时使用,不要将其序列化。这里注意一下,有个类叫做UDataAsset,用于在蓝图中保存数据*/TArray<FAssetData> TextureData;       //将刚刚获取到的资源数据保存到TextureDataObjectLibrary->GetAssetDataList(TextureData);//将资源数据转换并保存到TexturePath数组for(int32 i = 0; i < TexturePath.Num(); ++i){TexturePath.AddUnique(TextureData[i].ToSoftObjectPath());}
}

FStreamableManager类提供异步、同步加载函数:RequestAsyncLoad和RequestSyncLoad
通过FStreamableDelegate传入回调函数批量异步加载:RequestAsyncLoad
通过FStreamableDelegate传入回调函数单个异步加载:RequestAsyncLoad
通过TFunction传入回调函数批量异步加载:RequestAsyncLoad
通过TFunction传入回调函数单个异步加载:RequestAsyncLoad
批量同步加载,不需要回调函数:RequestSyncLoad
单个同步加载,不需要回调函数:RequestSyncLoad
其它函数和对象说明:

函数或对象 说明
LoadSynchronous 对RequestSyncLoad封装的函数,同步调用
FStreamableHandle 同步或异步函数调用返回的句柄
bool HasLoadCompleted() const 是否加载完毕
bool IsLoadingInProgress() const 是否正在加载
bool BindCompleteDelegate(FStreamableDelegate NewDelegate) 绑定加载完成后的回调函数
bool BindCancelDelegate(FStreamableDelegate NewDelegate) 绑定取消加载后的回调函数
bool BindUpdateDelegate(FStreamableUpdateDelegate NewDelegate) 绑定更新毁掉函数时的回调函数
void GetRequestedAssets(TArray<FSoftObjectPath>& AssetList) const 获取加载的批量资源
void GetLoadedAssets(TArray<UObject*>& LoadedAssets) cosnt 获取加载的资源
UObject* GetLoadedAsset() const 获取加载的单个资源
void GetLoadedCount(int32& LoadedCount, int32& RequestedCount) const 获取加载的数量
float GetProgress() const 获取加载进度
struct FStreamableManager* GetOwningManager() const 获取对应的FStreamableManager

使用FSoftObjectPath(FStringAssetReference)生成蓝图类对象
上边已经介绍了如何通过资源引用将其加载到内存,并获取其默认对象。
下边将介绍如何通过资源引用获取的默认对象生成一个新的对象。

!!视频中使用此方法打包后的项目会崩溃,我没有测试!!

UPROPERTY(EditAnywhere)
FStringAssetReference ActorPathRef;         //在蓝图中将其引用一个Actor蓝图UObject* ActorObj = LoadObject<UObject>(NULL, *ActorPathRef.GetAssetPathString());    //解疑见下
//这里解释一下Cast和C++的指针类型强转:
//通过Cast方法进行的转换,如果转换成功(一般父类转子类,子类到父类不需要强转),返回指针;如果失败,返回nullptr
//而(Type*)这样的指针类型强转,会直接将指针类型进行转换,但无逻辑的强转会导致成员调用失败
UBlueprint* ActorBlueprint = Cast<UBlueprint>ActorObj;
GetWorld()->SpawnActor<AActor>(ActorBlueprint->GeneratedClass, FVector::ZeroVector, FRotator::ZeroRotator);
=========================================================================================================
UBlueprint* ActorBlueprintObj = LoadObject<UBlueprint >(NULL, *ActorPathRef.GetAssetPathString());

解疑:

  1. 为什么加载蓝图时,不直接生成AActor对象?
    对于除蓝图类外的其他资源加载时,我们可以直接使用其C++类型,但对于蓝图类,我们必须全部使用UObject或UBlueprint类型。其实可以这样理解,通过资源路径加载进来的蓝图类都是同一种类型即继承UObject的UBlueprint类,其具体继承或实现的C++类通过序列化和反射完成。所以我们加载进来的蓝图类就是一种蓝图资源,它的类型是UBlueprint,我们通过它的成员变量GeneratedClass获取其蓝图继承的元数据。通过这个UClass类型的元数据,就可以生成我们需要的对象了。

  2. LoadObject加载进来的是什么?
    通过第一个问题的解释,我们大概已经清楚,LoadObject方法加载进来的对象是一个蓝图类对象。

通过使用LoadObject加载蓝图的代码发现,其中一种蓝图就对应一种C++类型,比如蓝图类对应的就是UBlueprint,静态模型对应的UStaticMesh,数据资源对应的UDataAsset等。而在编辑器通过硬引用的方式设置的对象,都是已经加载到内存的,所以有硬引用的对象在类初始化时,其引用的对象均已加载到内存。

八、异步加载UClass类型

因为使用UBlueprint获取UClass元数据的方法会在打包后崩溃(测试和原因待验证),所以下面介绍其他方法。
这里使用上面的FStreamableManager和TSoftClassPtr及FSoftObjectPath加载多个资源并实例化。

TArray<TSoftClassPtr<UObject>> ObjectClassPtrs;      //在蓝图中设置要加载的资源路径
FStreamableManager StreamableManager;   //资源加载管理器
FStreamableHandle StreamableHandle;     //资源加载句柄//异步加载资源
void XXX::AsyncLoadAsset()
{//将资源路径类型转换TArray<FSoftObjectPath> ObjectPaths;for(int i = 0; i < ObjectClassPtrs.Num(); ++i){ObjectPaths.Push(ObjectClassPtrs[i].ToSoftObjectPath());}//异步加载资源,设置加载完成回调函数,并返回句柄StreamableHandle = StreamableManager.ReuqestAsyncLoad(ObjectPaths, FStreamableDelegate::CreateUObject(this, XXX::LoadCompleted));
}void XXX::LoadCompleted()
{//返回加载的资源对象TArray<UObject*> Objects;StreamableHandle->GetLoadedAssets(Objects);//将其强转为UClass类型,并生成其类型的对象for(int i = 0; i < Objects.Num(); ++i){UClass* NewClass = Cast<UClass>();GetWorld()->SpawnActor<AActor>(NewClass, FVector::ZeroVector, FRotator::ZeroRotator);   //上边我们设置的是AActor子类的类型软引用}
}

九、LatentAction潜在事件

先按照网上给的Demo实现一个继承FPendingLatentAction的潜在事件。

这个结点会在Duration计时到一半时,输出HalfExec;而全部计时结束时,输出CompleteExec。

class FTwiceDelayAction : public FPendingLatentAction
{public:float TotalTime;float TimeRemaining;FName ExecutionFunction;int32 OutputLink;FWeakObjectPtr CallbackTarget;DELAY_EXEC& execRef;          //DELAY_EXEC枚举类的声明定义在下面bool bHalfTriggered = false;
public:FTwiceDelayAction(float Duration, const FLatentActionInfo& LatentInfo, DELAY_EXEC& exec):TotalTime(Duration),TimeRemaining(Duration),ExecutionFunction(LatentInfo.ExecutionFunction),OutputLink(LatentInfo.Linkage),CallbackTarget(LatentInfo.CallbackTarget),execeRef(exec){}virtual void UpdateOperation(FLatentResponse& Response) override{TimeRemaining -= Response.ElapsedTime();//当时间剩余一半时,将execRef设置为HalfExec,并调用回调函数。针脚将从HalfExec输出if(TimeRemaining < TotalTime / 2.0f && !bHalfTriggered){execRef = DELAY_EXEC::HalfExec;Response.TriggerLink(ExecutionFunction, OutputLink, CallbackTarget);   //调用回调函数bHalfTriggered = true;}else if(TimeRemaining < 0.0f){execRef = DELAY_EXEC::CompleteExec;Response.TriggerLink(ExecutionFunction, Outputlink, CallbackTarget);   //调用回调函数Response.DoneIf(TimeRemaining < 0.0f);   //终止Latent}}
}
UENUM(BlueprintType)
enum class DELAY_EXEC :uint8
{HalfExec,CompleteExec
}UCLASS()
class XXX_API ULantentActionLibrary : public UBlueprintFunctionLibrary
{GENERATED_BODY()public:/*标识符解释:HidePin="Param"               隐藏脚针,使用此标识符,每个函数只能隐藏一个针脚DefaultToSelf="Param"      使用结点自身上下文,就是把变量值设置为selfLatent                        指明这个函数时隐式事件,蓝图调用函数时,结点右上角会出现一个时钟LatentInfo="param"         隐式事件会有一个FLatentActionInfo类型的参数,用于指出此参数ExpandEnumAsExecs="param"   将针脚按照枚举值展开,且枚举类型必须有UENUM标识符*/UFUNCTION(BlueprintCallable, meta=(HidePin="WorldContextObject", DefaultToSelf="WorldContextObject", Latent, LatentInfo="LatentInfo", ExpandEnumAsExecs="exec"))static void TwiceDelay(UObject* WorldContextObject, struct FLatentActionInfo LatentInfo, float Duration, DELAY_EXEC& exec);
}void ULantentActionLibrary::TwiceDelay(UObject* WorldContextObject, FLatentActionInfo LatentInfo, float Duration, DELAY_EXEC& exec)
{//从一个包含上下文的对象中获取World,这里的对象就是结点自身。如果获取的World值有效,将通过World获取隐式事件管理器对象if(UWorld* World = GEngine->GetWorldFromContextObjectChecked(WorldContextObject)){FLatentActionManager& LatentActionManager = World->GetLatentActionManager();if(LatentActionManager.FindExistingAction<FTwiceDelayAction>(LatentInfo.CallbackTarget, LatentInfo.UUID) == NULL){//创建一个LatentAction对象,并交给LantentActionManager来管理LatentActionManager.AddNewAction(LatentInfo.CallabckTarget, LatentInfo.UUID, new FTwiceDelayAction(Duration, LatentInfo, exec));}}
}

【UE4全反射松耦合框架笔记】第一章 UE4框架基础(下)相关推荐

  1. Deep learning with python notebooks 笔记 第一章 深度学习基础

    第一章 深度学习基础 好的图表比文字传达的信息量多 图1-1帮助理清了人工智能.机器学习和深度学习之间的关系. 图1-2 清晰的阐述了经典程序设计范式和机器学习一种新的编程范式的不同.两种范式引出了两 ...

  2. 微服务笔记:第一章_微服务简介|Eureka注册中心|Nacos注册中心|Nacos配置管理|Feign|Gateway服务网关

    微服务笔记:第一章_微服务简介|Eureka注册中心|Nacos注册中心|Nacos配置管理|Feign|Gateway服务网关 1. 微服务简介 1.1 服务架构演变 1.2 SpringCloud ...

  3. 框架设计--第一章 Spring的基本应用--习题答案

    摘要:微信搜索[三桥君] 课程介绍:"框架技术"是软件工程专业的核心课程,是本专业限选课,是Java 应用开发课程,是本专业学生就业的主要方向. 说明:框架设计其他章节的习题答案也 ...

  4. 《MAC OS X 技术内幕》读书笔记第一章:MAC OS X的起源

    <MAC OS X 技术内幕>读书笔记第一章:MAC OS X的起源 前言 1 System x.x系列 1.1System 1.0(1984年1月24日) 1.2System 2.x(1 ...

  5. Android群英传神兵利器读书笔记——第一章:程序员小窝——搭建高效的开发环境

    Android群英传神兵利器读书笔记--第一章:程序员小窝--搭建高效的开发环境 目录 1.1 搭建高效的开发环境之操作系统 1.2 搭建开发环境之高效配置 基本环境配置 基本开发工具 1.3 搭建程 ...

  6. PhalAPI学习笔记 ——— 第一章自定义HelloWorld接口

    PhalAPI学习笔记 --- 第一章自定义HelloWorld接口 前言 自定义接口 项目实例 结果 分布解析 结束语 前言 公司业务需要转学PHP,而PHP中一个功能强大且生态链完整的PHP接口框 ...

  7. 深入理解 C 指针阅读笔记 -- 第一章

    上周末,我在图书馆看到了这本很薄的书 -- <深入理解 C 指针>       这本书中写的内容,个人感觉适合一个初学者,内容不是很难.我也读了下,对每一章都做了笔记,笔记都是用代码的形式 ...

  8. 《Go语言圣经》学习笔记 第一章 Go语言入门

    Go语言圣经学习笔记 第一章 Go语言入门 目录 Hello, World 命令行参数 查找重复的行 GIF动画 获取URL 并发获取多个URL Web服务 本章要点 注:学习<Go语言圣经&g ...

  9. 2010计算机知识点总结,2010年全国职称计算机考试:知识点笔记第一章

    第一章 信息技术与计算机文化 1. 信息技术与计算机 1) 信息技术的概念:利用科学的原理.方法及先进的工具和手段,有效地开发和利用信息资源的技术体系.包括微电子技术.计算机技术.软件技术.通信技术等 ...

  10. 【王道考研】操作系统 笔记 第一章

    特此鸣谢王道考研 本文参考王道考研的相关课程 若有侵权请联系,立删 其余笔记链接: [王道考研]操作系统笔记 第一章_才疏学浅743的博客-CSDN博客 [王道考研]操作系统 笔记 第二章上 进程调度 ...

最新文章

  1. 如何才能知道一个导师的人品?
  2. android:catation=quot;90quot;,Android中的AlarmManager的使用.htm
  3. NSArray ----NSMutableArray
  4. android王者调不了界面,王者荣耀登录界面怎么改?登录界面更改教程[多图]
  5. BugkuCTF-WEB题GET和POST
  6. TabError- inconsistent use of tabs and spaces in indentation 查验及解决方法
  7. python第一步怎么写_python第一步
  8. bash:yum:command not found 解决办法
  9. matlab r2016a破解版安装
  10. FCM算法的matlab程序
  11. 2021年T电梯修理考试题及T电梯修理模拟考试题
  12. [目录]-博客笔记导读目录(全部)
  13. can收发器 rx_Microchip工程师社区 - 两组PIC18F25K80+CAN收发器的CAN通讯 - 16位MCU及DSC - 麦田论坛...
  14. 图片批量压缩工具哪个好用?这3个工具可以解决你的压缩烦恼
  15. 国际物流信息管理系统产品简介之CA——ES/1 Supper Logistic
  16. RVB2601开发板试用2——PWM的使用,既三色小灯驱动
  17. web前端 网页智能机器人
  18. 超越图灵测试:判断机器是否在思考的现代方法
  19. 智能硬件的最小可行产品「MVP」
  20. 凛冽寒冬中的一缕阳光

热门文章

  1. 微信公众号无服务器 外部链接,微信公众号文章怎么添加外部链接-给微信公众号文章添加外部链接的方法 - 河东软件园...
  2. 深入理解Magento - 第五章 – Magento资源配置
  3. Java后端进行Base64码加密、解密及MD5加密
  4. python用法查询软件下载_布同自制Python函数帮助查询小工具
  5. SIOCADDRT: File exists
  6. MT6765/MT6762/MT6761芯片平台支持EVS-SWB?
  7. PHP学习日记0_PHP、静态网页、动态网页、静态网站访问流程、动态网站访问流程
  8. dojo调用php,Dojo入门篇
  9. 计算机演示文稿步骤,计算机二级Msoffice演示文稿(解题步骤)总结.doc
  10. JS JavaScript 实现文字上下滚动效果