3.多线程与智能指针

news/2025/11/24 1:06:15/文章来源:https://www.cnblogs.com/xlh626/p/19261976

断言

Check

如:check(表达式)
只要false就崩溃

check(Impls.Num()<=1);

Ensure

Ensure(表达式)
仅首次false会提示,且不崩溃

ensure(StyleSet.IsUnique());

CastChecked

将一个InActor进行格式转换,转换失败就让程序崩溃。

AXGAssertActor* MyAssertActor = CastChecked<AXGAssertActor>(InActor);

注:断言宏会在发行版移除,如果在里面写了数据修改的逻辑,会在运行前后不一样。

DeveloperSetting

DeveloperSetting类会负责生成一个保存键值对的ini文件,所以引擎中的编辑器设置和项目设置都是基于这个DeveloperSetting进行派生的。

  1. 想要使用此功能需要在Buildcs添加一个模块-----DeveloperSettings
  2. 继承自DeveloperSettings
    UCLASS里的Config标识在项目设置中的标识,下面是其属性,构造方法进行初始化这些配置项。

声明

USTRUCT(BlueprintType)  
struct FXGSampleDescriber  
{  GENERATED_USTRUCT_BODY()  
public:  UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="XGprojectInfo")  FString AuthorName = TEXT("XG");  UPROPERTY(VisibleAnywhere,BlueprintReadOnly,Category="Base")  bool bExperiment = false;  
};  /**  * */  
UCLASS(Config = XGSampleSettings, DefaultConfig, meta = (DisplayName = "XG Sample Settings"))  
class UPPLEARN_API UXGSampleSettings : public UDeveloperSettings  
{  GENERATED_BODY()  
public:  %% 构造函数 %%UXGSampleSettings(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get());  virtual ~UXGSampleSettings();  
public:
%% 需要重载的函数,GetContainerName决定放在Project还是Editor里,用FName字符串即可;GetCategoryName大分类名;GetSectionName小标题名。 %%virtual FName GetContainerName() const;  virtual FName GetCategoryName() const;  virtual FName GetSectionName() const;  
public:  %% 单例模式,让UE只保存一份对象 %%static UXGSampleSettings* GetXGXunFeiCoreSettings();  public:  %% 配置项属性 %%UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "XunFeiLinkInfo|Spark")  FString ProjectVersion = TEXT("1.0.0");  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "XunFeiLinkInfo|Spark")  FXGSampleDescriber SampleDescriber;  };

定义

UXGSampleSettings::UXGSampleSettings(const FObjectInitializer& ObjectInitializer)  :Super(ObjectInitializer)  
{  }  
UXGSampleSettings::~UXGSampleSettings() {}  FName UXGSampleSettings::GetContainerName() const  
{  return TEXT("Project");  
}  FName UXGSampleSettings::GetCategoryName() const  
{  return TEXT("XG");  
}  FName UXGSampleSettings::GetSectionName() const  
{  return TEXT("XGSampleSettings");  
}  UXGSampleSettings* UXGSampleSettings::GetXGXunFeiCoreSettings()  
{  return nullptr;  
}

``
../attachment/Pasted image 20251112230305.png

读取与使用

只需在其他类载入Setting类:#include "XGSampleSettings.h"。

bool AXGConfigActor::GetMyAppKey(FString& OutMyAppKey)  
{  UXGSampleSettings* XGSampleSettings = UXGSampleSettings::GetXGXunFeiCoreSettings();  if (XGSampleSettings)  {       OutMyAppKey = XGSampleSettings->ProjectVersion;  return true;  }    OutMyAppKey = TEXT("None");  return false;  
}void AXGConfigActor::SetMyAppKey(const FString& InMyAppKey)  
{  UXGSampleSettings* XGSampleSettings = UXGSampleSettings::GetXGXunFeiCoreSettings();  if (XGSampleSettings)  {       XGSampleSettings->ProjectVersion = InMyAppKey;  XGSampleSettings->Modify();  XGSampleSettings->SaveConfig();  }
}

智能指针

智能指针的优势:

  1. 所有运算均为常量时间
  2. 取消引用多数智能指针的速度和原始C++指针相同
  3. 复制智能指针永远不会分配内存
  4. 现成安全智能指针是无锁的
    智能指针缺陷:
  5. 创建与复制智能指针比原始指针需要更多开销
  6. 保持引用计数增加基本运算周期
  7. 部分智能指针占用的内存比原始C++更多
  8. 引用控制器有两个堆分配,使用MakeShared代替MakeShareable可避免二次分配。

工具

一个被指向的结构体工具

struct FSmartPtrStruct  
{  FSmartPtrStruct()  {       UE_LOG(LogTemp, Warning, TEXT("FSmartPtrStruct构造了"));  };  ~FSmartPtrStruct()  {       UE_LOG(LogTemp,Warning,TEXT("FSmartPtrStruct西沟了"));  };  
};

共享指针和引用

TSharedPtr和TSharedRef(防内存泄漏),共享指针和共享引用的底层用同样的控制模块,这导致同一块内存的共享引用和共享指针共用一个引用计数。

void AXGSmartPointerActor::InitMySpStruct()  
{  TSharedPtr<FSmartPtrStruct> MySmartPtrStruct1 = MakeShareable(new FSmartPtrStruct());   TSharedRef<FSmartPtrStruct> MySmartPtrStruct2 = MakeShareable(new FSmartPtrStruct());  UE_LOG(LogTemp,Warning,TEXT("%d"),MySmartPtrStruct.GetSharedReferenceCount()); UE_LOG(LogTemp,Warning,TEXT("%d"),MySmartPtrStruct.GetSharedReferenceCount());
}

唯一指针

TUniquePtr

TUniquePtr<FSmartPtrStruct> MySmartUniquePtrStruct(new FSmartPtrStruct());

弱指针

TWeakPtr(TWeakPtr不拥有引用的对象的所有权,可以中断引用循环防止悬挂指针)

指针

声明与初始化

%% 只是声明但不初始化 %%
TSharedPtr<FMyObjectType> EmptyPointer;
%% 声明且初始化要用右值来实现初始化 %%
TSharedPtr<FMyObjectType> NewPointer(new FMyObjectType());
TSharedRef<FMyObjectType> NewReference(new FMyObjectType());
%% 拷贝声明初始化,内部引用计数会更改 %%
TSharedPtr<FMyObjectType> PointerFromReference = NewReference;
%% 现成安全版 %%
TSharedPtr<FMyObjectType, ESPMode::ThreadSafe> NewThreadsafePointer = MakeShared<FMyObjectType, ESPMode::ThreadSafe>(MyArgs);

减少计数

%% 针对SharedPtr此处就是减少引用计数,针对UniquePtr此处就是释放内存。 %%
PointerOne.Reset();
PointerTwo = nullptr;

转移指针

转移指针用MoveTemp或者MoveTempIfPossible。

%% 使用左值来转移指针,会同时转移其的共享所有权%%
PointerTwo = MoveTemp(PointerOne);
PointerONe = MoveTempIfPossible(PointerTwo); 

转换到引用

%% 引用到指针 %%
TSharedPtr<FMyObjectType> MySharedPointer = MySharedReference;
%% 指针到引用:因为引用不可能为空,但是共享指针可能为空的指针,所以得判断指针合法性 %%
if (MySharedPointer.IsValid())
{MySharedReference = MySharedPointer.ToSharedRef();
}

SharedThis

某些情况需要将某个对象添加到共享指针中,可以在其方法中使用:

TSharedRef<FMyBaseClass> ThisAsSharedRef = AsShared();
TSharedRef<FMyBaseClass> AsSharedRef = SharedThis(this);

指针的比较

智能指针的==号被重载了,所以直接用此符号就可以进行对比两个指针指向的内存是否是同一块儿。

此外这里还有三种判断指针是否为空的方式。

if (Node.IsValid())
{// ...
}
if (Node)
{// ...
}
if (Node.Get() != nullptr)
{// ...
}

解引用

%% 解引用的首先条件就是指针不为空 %%
if (Node)
{// 以下三行代码中的任意一行都能解引用节点,并且对它的对象调用ListChildren:Node->ListChildren();Node.Get()->ListChildren();(*Node).ListChildren();
}

线程安全

添加第二个参数ESPMode::ThreadSafe即可升级为多线程安全版。

  1. TsharedPtr<T,ESPMode::ThreadSafe>
  2. TsharedRef<T,ESPMode::ThreadSafe>
  3. TWeakPtr<T,ESPMode::ThreadSafe>
  4. TSharedFromThis<T,ESPMode::ThreadSafe>

引用

声明及初始化

%% 因为引用不能为空,所以声明即初始化。必须用=符进行赋值 %%
TSharedRef<FMyObjectType> NewReference = MakeShared<FMyObjectType>();

  1. 直接用new申请内存如TSharedPtr<Foo> NewPointer(new Foo()); 本质上是进行两次内存分配,一次是new来创建字面量数据,另一次是将字面量数据拷贝到这个智能指针容器里。
  2. 使用MakeShared<Foo>()则是只进行了一次内存申请且是连续的内存,是经过UE封装的。也是UE推荐的方式。

转换

TSharedPtr<FMyObjectType> MySharedPointer = MySharedReference;If (MySharedPointer.IsValid())
{MySharedReference = MySharedPointer.ToSharedRef();
}

比较

同指针一样用==符号就行了。

弱指针

初始化和声明

TWeakPtr依赖强指针来进行创建,他需要存在一个左值强指针或者左值强引用

TSharedRef<FMyObjectType> ObjectOwner = MakeShared<FMyObjectType>();
%% 利用强引用的控制块和对象创建一个TWeak的控制块并塞入对象 %%
TWeakPtr<FMyObjectType> ObjectObserver(ObjectOwner);

弱指针所有权

  1. weak指针没有引用计数所以没有共享所有权,所以无法阻碍内存释放,对象销毁。
  2. Pin函数用于根据一个弱指针创建并返回一个强指针,此处是判断Tweak指向的对象是否销毁了(可能弱指针没法直接取查看是否对象销毁了吧)
ObjectOwner.Reset();
if(ObjectObserver.Pin())
{check(false);
}

弱指针销毁

ObjectObserver = nullptr;
AnotherObjectObserver.Reset();

转换

%% 使用Pin函数创建共享指针 %%
if (TSharedPtr<FMyObjectType> LockedObserver = ObjectObserver.Pin())
{LockedObserver->SomeFunction();
}

循环引用

窘境

共享引用会导致循环引用。
添加一个属性持有的指针HoldPtr。并创建两个对象,分别将HoldPtr输入相对立的哪一个对象。

//Struct
TSharedPtr<FSmartPtrStruct> HoldPtr = nullptr;//Actor
TSharedRef<FSmartPtrStruct> ObjectOnwerA = MakeShared<FSmartPtrStruct>();
TSharedRef<FSmartPtrStruct> ObjectOnwerB = MakeShared<FSmartPtrStruct>();
ObjectOwnerA->HoldPtr = ObjectOwnerB;
ObjectOwnerB->HoldPtr = ObjectOwnerA;

正确方式

TWeak不持有所有权。

 //Struct
TWeakPtr<FSmartPtrStruct> HoldPtr = nullptr;//Actor
TSharedRef<FSmartPtrStruct> ObjectOnwerA = MakeShared<FSmartPtrStruct>();
TSharedRef<FSmartPtrStruct> ObjectOnwerB = MakeShared<FSmartPtrStruct>();
ObjectOwnerA->HoldPtr = ObjectOwnerB;
ObjectOwnerB->HoldPtr = ObjectOwnerA;

UE多线程基础

游戏的运行跑在主线程,其次就是渲染线程一般会比主线程慢一到两帧,其他还有音效线程,http线程等等等等。

因为游戏开发中有的电脑主线程较快,就能正常游戏,有的电脑渲染线程快,就可能会崩溃。一旦渲染线程太快改了某个数据后主线程没找到理应的数据就会主线程崩溃。一旦主线程将数据改了,渲染线程对照着找没找到就会渲染线程崩溃。总之问题出在线程竞争上。

解决方法

  1. 让主线程和渲染线程各自用在自己的数据,定期取从主线程存状态到渲染线程。
  2. 游戏线程在每个Tick时间的末尾进行阻塞,直到渲染线程赶上一到两帧的差距。但是因为渲染线程泰国落后,所以让主线程取等待是不合理的,在读取物体和垃圾回收时进行阻塞也不合理,游戏不能像java一样去停止游戏运行然后回收垃圾,需要用异步的方式来处理。
  3. 涉及很多线程通信,相关内容较难。

FRunnable

FRunnable是一个线程的包装器,是一个线程的管理器,所以,FRunnable的使用,需要先创建一个FRunnable的派生类,并重载Init函数,Run函数,Exit函数,Stop函数。

  1. Init函数负责初始化资源并返回是否初始化完成的状态。
  2. Run函数负责线程的主逻辑,常用来跑循环之类的东西
  3. Stop负责从包装器外部获取堆此线程的控制
  4. 在Run函数运行并完成return后调用一次。

派生

  1. 派生自FRunnable并且需要导入
  2. #include "HAL/Runnable.h"
//头文件
%% 派生自FRunnable %%
#pragma once  #include "CoreMinimal.h"  
#include "HAL/Runnable.h"  /**  * */  
class UPPLEARN_API FXGSimpleRunnable : public FRunnable  
{  
public:  FXGSimpleRunnable(FString InThreadName)  :XGSimpleRunnableName(InThreadName)  {  };  virtual ~FXGSimpleRunnable();  virtual bool Init() override;  virtual uint32 Run() override;  virtual void Exit() override;  virtual void Stop() override;  FString GetThreadName();  
protected:  FString XGSimpleRunnableName = TEXT("None");  bool bRunning;
};//源文件
#include "XGSimpleRunnable.h"  
FXGSimpleRunnable::~FXGSimpleRunnable() {}  bool FXGSimpleRunnable::Init()  
{  bRunning = true;  return FRunnable::Init();  
}  uint32 FXGSimpleRunnable::Run()  
{  while (bRunning)  {       UE_LOG(LogTemp,Warning,TEXT("新线程正在工作"));  FPlatformProcess::Sleep(0.1);  }    return uint32(); }  void FXGSimpleRunnable::Exit()  
{  FRunnable::Exit();  
}  void FXGSimpleRunnable::Stop()  
{  bRunning = false;  // FRunnable::Stop();  
}  FString FXGSimpleRunnable::GetThreadName()  
{  return XGSimpleRunnableName;  
}}

使用

FRunnableThread::Create()可以启动一个线程并调用其Init函数,Run函数

XGSimpleRunnable = MakeShared<FXGSimpleRunnable>(TEXT("XGThread000"));  
FRunnableThread::Create(XGSimpleRunnable.Get(),*XGSimpleRunnable->GetThreadName());

想要停止一个线程

XGSimpleRunnable->Stop();

加锁

//头文件
%% 需要在头文件声明一个锁 %%
FCriticalSection CriticalSection;%% 调用时给一个线程的所在作用域加一个锁,所有带有这个锁标记的线程的作用域同时只能有一个执行 %%
FScopeLock Lock(&CriticalSection);

一般Run函数里是新线程,其他的函数里是主线程或者其他线程中运行的,所以可以通过加锁,让后面的此作用域中此数据仅仅在此线程中可被改

线程安全数据

普通数据的读写不是原子操作,所以在多线程环境下会有半读半写,而加锁虽然安全了但是开销大。所以UE提供一个线程安全的数据类型

  1. 导入方式: #include "HAL/ThreadSafeBool.h
  2. 使用方式:
FThreadSafeBool bRunning = true;

一般开关用布尔,线程间共享数据就得加锁,或者线程安全的类。

异步AsyncTask

需要指定一个线程去异步执行一个Lambda。AnyncTask会将lambda当成一个独立的job扔到线程的队列中,等线程空闲就会去执行job。注意:如果使用GameThread/RHIThread/RenderThread/Any,会导致job投递到引擎线程中,会卡游戏主线程。
AsyncTask(ENamedThreads: :Type,Lambda表达式)

AsyncTask(ENamedThreads::GameThread,[InStr](){UE_LOG(LogTemp,Warning,TEXT("ThreadLog:[%s]"),*InStr)
});

FGraphEvent

通过FFunctionGraphTask::CreateAndDispatchWhenReady(lambda表达式)可以构建起一个任务A。而这个A可以作为一个参数参与构建另一个任务B,这标识B任务对A任务有依赖关系。所以FGraphEvent可以将一批task连成一个图结构的pipeline,会交给引擎来按照这个图结构来进行执行。

FGraphEventRef TaskA = FFunctionGraphTask::CreateAndDispatchWhenReady(Lambda表达式);FGraphEventRef TaskB = FFunctionGraphTask::CreateAndDispatchWhenReady(Lambda表达式,TStatId{},TaskA);

例:

FGraphEventArray SimpleEventArray;
for(Size_t index=0;index<20;index++)
{SimpleEventArray.Add(FFunctionGraphTask::CreateAndDispatchWhenReady([](){PrintWarning(FString::Printf(TEXT("我是SimpleEvent开始执行--Index:%d"),index));FPlatformProcess::Sleep(index/3);PrintWarning(FString::Printf(TEXT("我是SimpleEvent执行完成了--Index:%d"),index));}));
}FTaskGraphInterface::Get().WaitUntilTaskComplete();

ParallelFor

ParalleFor需要指定一个数值N,这就是要创建的线程数N,然后将后面lambda表达式是要被调度起来的任务,所以是要调度起来N个相同的任务。如果涉及到全局变量,则一定会出现线程竞争,所以需要加锁。最后的参数是调度策略。

无竞争版

int32 MaxNum =0;
ParallelFor(10,[&MaxNum](int32 Index){UE_LOG(LogTemp,Display,TEXT("this is ParallelFor:%d"),Inex);FPlatformProcess::Sleep(0.2);
},EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced);

存在竞争版1:加锁

FCriticalSection NumCriticalSection;
int32 MaxNum=0;
ParallelFor(10,[&MaxNum](int32 Index){FPlatformProcess::Sleep(Index %3);FScopeLock Lock(&NumCriticalSection);MaxNum+=Index;UE_LOG(LogTemp,Display,TEXT("this is ParalleFor:%d"),Index);
},EParallelForFlags::BackgroundPriority | EParallelForFlags::Unbalanced);

存在竞争2:用线程安全的数据结构,原子性。

ControlFlow

需要再Buildcs文件导入模块ControlFlows并开启UE自带的ControlFlows插件,之后重新构建项目。不知道为什么我用Rider直接从uproject文件打开编辑器就读取不到ControlFlow这个模块。但是先打开UEEditor,再从UEEditor打开Rider项目就不会有这个问题。

头文件

  1. "ControlFlowNode.h":提供了FControlFlowNodeRef这个数据类型,作为每个节点(我尝试在头文件写前向声明爆红且找不到原因,最后只好包含到头文件中)
  2. "ControlFlow.h":FControlFlowNodeRef的容器
  3. “ControlFlowManager.h":不知道是啥我通过找到ControlFlow.h文件定位到FControlFlowStatics类的前向声明后可以所引导这个文件(确实有FControlFlowStatics的实现,懒得看)

使用

  1. FControlFlow通过FControlFlowStatic::Create()来创建,将自己这个实例的地址注册到控制流中
  2. FControlFlow类的方法QueueStep将方法添加到控制流的队列中
  3. FControlFlow的ExecuteFlow方法负责执行构建好的控制流
  4. FControlFlowNodeRef下一个要执行的节点。
void UXGControlFlowSubsystem::InitLevel()  
{  if (bIniting)  {       UE_LOG(LogTemp,Warning,TEXT("正在初始化过程中"));  return;  }    bIniting = true;  FControlFlow& Flow = FControlFlowStatics::Create(this,TEXT("XGControlFlowInitLevel"));  Flow.QueueStep(TEXT("InitLocalAsset"),this,&UXGControlFlowSubsystem::InitLocalAsset);  Flow.QueueStep(TEXT("InitNetInfo"),this,&UXGControlFlowSubsystem::InitNetInfo);  Flow.QueueStep(TEXT("InitUserInfo"),this,&UXGControlFlowSubsystem::InitUserInfo);  Flow.QueueStep(TEXT("NotifyMainUI"),this,&UXGControlFlowSubsystem::NotifyMainUI);  Flow.QueueStep(TEXT("FinishControlFLow"),this,&UXGControlFlowSubsystem::Finish);  
UE_LOG(LogTemp,Warning,TEXT("[%s]--ExecuteFlow"),*FString(__FUNCTION__));  
Flow.ExecuteFlow();  
}
void UXGControlFlowSubsystem::InitLocalAsset(FControlFlowNodeRef SubFlow)  
{  UE_LOG(LogTemp,Warning,TEXT("InitLocalAsset"));  SubFlow->ContinueFlow();  
}  void UXGControlFlowSubsystem::InitNetInfo(FControlFlowNodeRef SubFlow)  
{  UE_LOG(LogTemp,Warning,TEXT("InitNetInfo"));  SubFlow->ContinueFlow();  
}  void UXGControlFlowSubsystem::InitUserInfo(FControlFlowNodeRef SubFlow)  
{  UE_LOG(LogTemp,Warning,TEXT("InitUserInfo"));  SubFlow->ContinueFlow();  
}  void UXGControlFlowSubsystem::NotifyMainUI(FControlFlowNodeRef SubFlow)  
{  UE_LOG(LogTemp,Warning,TEXT("NotifyMainUI"));  SubFlow->ContinueFlow();  
}  
void UXGControlFlowSubsystem::Finish(FControlFlowNodeRef SubFlow)  
{  UE_LOG(LogTemp,Warning,TEXT("Finish"));  SubFlow->ContinueFlow();  bIniting = false;  
}

整体来说,有那么点代理的功能(代理时执行容器中所有方法,这个时执行一个节点的方法,不过都由跨类的能力),相当于用FControlFlowStatics::Create创建一个控制流容器,之后细致的逻辑交给控制流,但是可以跨类将不同的方法塞入这个容器。只需要每个节点发出ContinueFlow的信号就可以将任务交接到下一个节点。

换句话说ControlFlow是运行在GameThread上,而正常情况下ControlFlow是串行执行的,执行到ContinueFlow的时候会进入一个等待状态。为什么要等待呢?因为ControlFlow是串行的所以如果在某个节点执行异步操作,也会启动一个异步操作就立刻进行下一个节点的执行。要是某个node刚好存在对上一个节点运算结果的依赖,而上一个节点刚好又是用AsyncTask在线程池里跑的异步运算,就会使得ControlFlow不知道上一步啥时候算完了,而这个ContinueFlow就是用在这个异步任务中的,就是为了告诉下一个节点自己执行完成了的。

精髓:下一个节点的执行条件:(当前节点执行完成 AND 当前节点的ContinueFlow()被执行)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/974384.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

TypedSql:在 C# 类型系统上实现一个 SQL 查询引擎

前言 在 .NET 里写查询的时候,很多场景下数据其实早就都在内存里了:不是数据库连接,也不是某个远程服务的结果,而就是一个数组或者 List<T>。我只是想过滤一下、投影一下。这时候,通常有几种选择:写一个 f…

C#/.NET/.NET Core技术前沿周刊 | 第 62 期(2025年11.17-11.23)

前言 C#/.NET/.NET Core技术前沿周刊,你的每周技术指南针!记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿,助力技术成长与…

KEYDIY MLB26 434 Mhz 3-Button Universal Smart Remote PCB Board - Non-OEM Solution

When Your OEM Smart Remote Fails: A Costly Headache for Shops and Owners For automotive repair professionals and car owners across Europe and America, a malfunctioning smart remote can grind operations…

KEYDIY MLB08 434MHz OEM Smart Key PCB: Audi-Style 3-Button Universal for EU/US Cars

The Smart Key Replacement Solution Your Garage (and Wallet) Has Been Waiting For In today’s automotive landscape, smart keys are more than a convenience—they’re a necessity. But when they fail, get …

完整教程:Python pip instsll报错 Can‘t connect to HTTPS URL because the SSL module is not available.

完整教程:Python pip instsll报错 Can‘t connect to HTTPS URL because the SSL module is not available.pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; displa…

Semgrep体验

检查硬编码 规则 rules:- id: java-jwt-hardcoded-secretlanguages:- javaseverity: ERRORmessage: hardcodepatterns:- pattern: $SENVAR="$VALUE"# 这里可以去掉一些比如 xxxKey="appid"- patte…

102302133陈佳昕作业3

作业①: 要求:指定一个网站,爬取这个网站中的所有的所有图片,例如:中国气象网(http://www.weather.com.cn)。实现单线程和多线程的方式爬取。 –务必控制总页数(学号尾数2位)、总下载的图片数量(尾数后3位)等…

CSAPP 处理器体系结构

处理器体系结构 CISC 与 RISC 指令集 CISC:复杂指令计算机 (如x86-64) RISC:精简指令计算机 (如RISC-V) RISC 相较于CISC指令数量少得多,编码长度固定,寻址没有变址寄存器和伸缩因子,对机器级程序实现细节可见...…

AI协助 一周打造「七巧板益智小游戏」:从零高效开发教学工具

AI协助 一周打造「七巧板益智小游戏」:从零高效开发教学工具 一个“教学痛点”引发的开发冲动。本文讲述如何利用Copilot在一周内快速开发一个面向小学数学教学的七巧板益智小游戏,涵盖需求分析、技术选型、核心算法…

【MCP系列】用 MCP 扩展 AI 编辑器:从零开发一个自己的MCP服务

【MCP系列】用 MCP 扩展 AI 编辑器:从零开发一个自己的MCP服务本文MCP协议,手把手教你构建一个自己的MCP Server,在AI编辑器实现通过自然语言指令调用自己写的工具脚本。随着 AI 编辑器(如 Copilot、通义灵码、Tra…

VB6版MP3文件信息编辑器 - 开源研究系列文章 - 个人小作品

VB6版MP3文件信息编辑器 - 开源研究系列文章 - 个人小作品Posted on 2025-11-24 00:00 lzhdim 阅读(0) 评论(0) 收藏 举报 这次整理VB6编写的MP3文件的ID3v1信息编辑器。该应用比较简单,主要是对于ID3v1信…

手把手教你用 React + Zustand 打造 Windows 风格可拖拽,缩放,多窗口 Modal 组件

手把手教你用 React + Zustand 打造 Windows 风格可拖拽,缩放,多窗口 Modal 组件记录仿Windows风格的可拖拽、缩放、多窗口 Modal 组件的实现还在为每个弹窗写重复的拖拽、缩放、Z轴代码而烦恼吗?还在复制粘贴 onMo…

ImGui Learn Data Day 1

ImGui Learn data Day 1ImGui::Begin("Hello Gui");static float u = 0;static bool an = 0;if (ImGui::CollapsingHeader("Settings"))//展开条{//如果点击就展开或者关闭ImGui::SliderFloat(&qu…

OI 笑传 #34

夜の東側今天是 bct Day4,赛时 \(75+30+40+0=115\),rk 54。 T1 挂分原因仍未知,直接原因是没有大样例,然后是用数据结构维护的贪心,比较恶心。 赛时比较爆炸,T1 连想带调用了 3h,导致比较简单的 T2,T3,T4 没有…

【MCP系列】介绍一个我自己开发的MCP工具:MCP Shipit

【MCP系列】介绍一个我自己开发的MCP工具:MCP Shipit介绍一个我自己开发的MCP工具:MCP Shipit,欢迎来github提issue,star!在AI编辑器日益普及的今天,我们可以通过自定义工具来扩展AI的能力。本文将介绍如何从零开…

第34天(简单题中等题 数据结构)

打卡第三十四天 1道简单题+两道中等题题目:思路:哈希表+遍历 代码: class Solution{ public:int maxSum(vector<int>& nums){unordered_map<int,int> hash;int ans = -1;for(int x: nums){int maxd…

3. Gin RESTful API 开发

3. Gin RESTful API 开发 3.1 RESTful API简介 3.1.1 RESTful API 定义REST(Representational State Transfer,表现层状态转换)是一种软件架风格、设计风格,而不是一种标准。它提供了一组设计原则和约束条件,主要用…

说课逐字稿2

尊敬的各位评委老师: 大家好!今天我说课的题目是《健康数据小哨兵——循环选择嵌套》。 面对海量健康数据,如何从“人工低效核对”跨越到“智能精准监测”?这是本课要解决的核心问题。我将从分析策略、教学过程、教…

Codeforces Round 1066 (Div. 1 + Div. 2) 做题记录

Dashboard - Codeforces Round 1066 (Div. 1 + Div. 2) - Codeforces Problem - A - Codeforces 题意: 平衡数组定义为:若 \(x\) 存在,则存在 \(x\) 个 \(x\),求给定数组至少删去多少数变成平衡数组。 题解: 若 \…