UE_C++ —— Container TSet
目录
一,TSet
二,Creating and Filling a Set
Editing UPROPERTY TSets
三,Iteration
四,Queries
五,Removal
六,Sorting
七,Operators
八,Slack
九,DefaultKeyFuncs
十,Miscellaneous
类似于 TMap 和 TMultiMap,但 TSet 是使用数据值本身作为键,而不是将数据值与独立的键相关联;TSet 可以非常快速地添加、查找和删除元素(恒定时间);默认情况下,TSet 不支持重复的键,但使用模板参数可激活此行为;
一,TSet
TSet是一种快速容器类,通常用于在排序不重要的情况下存储唯一元素;在大多数情况下,只需要一种参数——元素类型;但,TSet 可配置各种模板参数来改变其行为,使其更全面;除了可指定从 DefaultKeyFuncs 的派生结构体来提供散列功能,还可允许多个键拥有相同的值;和其它容器类一样,可设置自定义内存分配器来存储数据;
和 TArray 一样,TSet 是同质容器,即其所有元素均完全为相同类型;TSet 也是值类型,支持常规复制、赋值和析构函数操作,以及其元素较强的所有权;TSet 被销毁时,其元素也将被销毁;键类型也必须是值类型;
TSet 使用散列hash,即如给出了 KeyFuncs 模板参数,该参数会告知set如何从某个元素确定键,如何比较两个键是否相等,如何对键进行散列,以及是否允许重复键;它们默认只返回对键的引用,使用 operator== 对比相等性,使用非成员函数 GetTypeHash 进行散列;默认,set中不允许有重复的键;如键类型支持这些函数,则可以将其用作集合键,无需提供自定义 KeyFuncs;要写入自定义 KeyFuncs,可扩展 DefaultKeyFuncs 结构体;
最后,TSet 可通过任选分配器控制内存分配行为;UE4分配器(如 FHeapAllocator 和 TInlineAllocator)不能用作 TSet 的分配器;实际上,TSet 使用set分配器,该分配器可定义set中使用的散列桶数量以及用于存储元素的标准UE分配器;
与 TArray 不同,内存中 TSet 元素的相对排序既不可靠也不稳定,对这些元素进行迭代很可能会使它们返回的顺序和它们添加的顺序有所不同;这些元素也不太可能在内存中连续排列;set中的后台数据结构是稀疏数组,即在数组中有空位;从set中移除元素时,稀疏数组中会出现空位;添加新的元素,可填补这些空位;但,即便 TSet 不会打乱元素来填补空位,指向元素的指针仍然可能失效,因为如存储器被填满,又添加了新的元素,整个存储可能会重新分配;
二,Creating and Filling a Set
//创建一个空 TSet,用于存储 FString 数据;
TSet<FString> FruitSet;
TSet 会直接使用 运算符== 比较元素,使用 GetTypeHash 对其进行散列,然后使用标准的堆分配器;当前尚未分配内存;
填充set的标准方法是使用 Add 函数并提供键(元素);
FruitSet.Add(TEXT("Banana"));
FruitSet.Add(TEXT("Grapefruit"));
FruitSet.Add(TEXT("Pineapple"));
// FruitSet == [ "Banana", "Grapefruit", "Pineapple" ]
此处的元素按插入顺序排列,但不保证这些元素在内存中实际保留此排序;如是新集合,可能会保留插入排序,但插入和删除的次数越多,新元素不出现在末尾的可能性越大;
由于此集合使用了默认分配器,可以确保键是唯一的。如果尝试添加重复键,新元素会替代旧的元素;
FruitSet.Add(TEXT("Pear"));
FruitSet.Add(TEXT("Banana"));
// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear" ]
// Note:Only one banana entry.
和 TArray 一样,还可使用 Emplace 代替 Add,避免插入集合时创建临时文件;
FruitSet.Emplace(TEXT("Orange"));
// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange" ]
参数直接传递给键类型的构造函数;可避免为该值创建临时 FString;与 TArray 不同的是,只能使用单一参数构造函数;
也可使用 Append 函数进行合并来插入另一个集合中的所有元素;和使用 Add 或 Emplace 进行单个添加是相同的;源set中的重复键将会替代目标set中相应的键;
TSet<FString> FruitSet2;
FruitSet2.Emplace(TEXT("Kiwi"));
FruitSet2.Emplace(TEXT("Melon"));
FruitSet2.Emplace(TEXT("Mango"));
FruitSet2.Emplace(TEXT("Orange"));
FruitSet.Append(FruitSet2);
// FruitSet == [ "Banana", "Grapefruit", "Pineapple", "Pear", "Orange", "Kiwi", "Melon", "Mango" ]
Editing UPROPERTY TSets
用 UPROPERTY 宏和一个可编辑的关键词(EditAnywhere、EditDefaultsOnly 或 EditInstanceOnly)标记 TSet,则可在编辑器中添加并编辑元素;
UPROPERTY(Category = SetExample, EditAnywhere)
TSet<FString> FruitSet;
三,Iteration
TSet 的迭代类似于 TArray,可使用C++的范围for;
for (auto& Elem : FruitSet)
{FPlatformMisc::LocalPrint(*FString::Printf(TEXT(" \"%s\"\n"),*Elem));
}
// Output:
// "Banana"
// "Grapefruit"
// "Pineapple"
// "Pear"
// "Orange"
// "Kiwi"
// "Melon"
// "Mango"
也可用 CreateIterator 和 CreateConstIterators 函数来创建迭代器;CreateIterator 返回拥有读写访问权限的迭代器,而 CreateConstIterator 返回拥有只读访问权限的迭代器;无论哪种情况,均可用这些迭代器的 Key 和 Value 来检查元素;
for (auto It = FruitSet.CreateConstIterator(); It; ++It)
{FPlatformMisc::LocalPrint(*FString::Printf(TEXT("(%s)\n"),*It));
}
四,Queries
调用 Num 函数可查询set中保存的元素数量;
int32 Count = FruitSet.Num();
// Count == 8
要确定set是否包含特定元素,可调用 Contains 函数;
bool bHasBanana = FruitSet.Contains(TEXT("Banana"));
bool bHasLemon = FruitSet.Contains(TEXT("Lemon"));
// bHasBanana == true
// bHasLemon == false
使用 FSetElementId 结构体可查找集合中某个键的索引;然后,就可使用该索引与 operator[] 查找元素;在非常量set上调用 operator[] 将返回非常量引用,而在常量set上调用将返回常量引用;
FSetElementId BananaIndex = FruitSet.Index(TEXT("Banana"));
// BananaIndex is a value between 0 and (FruitSet.Num() - 1)
FPlatformMisc::LocalPrint(*FString::Printf(TEXT(" \"%s\"\n"),*FruitSet[BananaIndex])
);
// Prints "Banana"FSetElementId LemonIndex = FruitSet.Index(TEXT("Lemon"));
// LemonIndex is INDEX_NONE (-1)
FPlatformMisc::LocalPrint(*FString::Printf(TEXT(" \"%s\"\n"),*FruitSet[LemonIndex])
); // Assert!
如不确定set中是否包含某个键,可使用 Contains 函数和 operator[] 进行检查;但这并非理想的方法,因为同一键需要进行两次查找才能获取成功;使用 Find 函数查找一次即可完成这些行为;如集合中包含该键,Find 将返回指向元素数值的指针;如映射不包含该键,则返回null;对常量set调用Find,返回的指针也将为常量;
FString* PtrBanana = FruitSet.Find(TEXT("Banana"));
FString* PtrLemon = FruitSet.Find(TEXT("Lemon"));
// *PtrBanana == "Banana"
// PtrLemon == nullptr
Array 函数会返回一个 TArray,其中填充了 TSet 中每个元素的一份副本;被传递的数组在填入前会被清空,因此元素的生成数量将始终等于set中的元素数量;
TArray<FString> FruitArray = FruitSet.Array();
// FruitArray == [ "Banana","Grapefruit","Pineapple","Pear","Orange","Kiwi","Melon","Mango" ] (order may vary)
五,Removal
通过 Remove 函数可按索引移除元素,但仅建议在通过元素迭代时使用;Remove函数会返回已删除元素的数量。如果给定的键未包含,则会返回0;如 TSet 支持重复的键,Remove 将移除所有匹配元素;
FruitSet.Remove(0);
// FruitSet == [ "Grapefruit","Pineapple","Pear","Orange","Kiwi","Melon","Mango" ]
移除元素将在数据结构中留下空位,但为保证清晰度,此处省略。
int32 RemovedAmountPineapple = FruitSet.Remove(TEXT("Pineapple"));
// RemovedAmountPineapple == 1
// FruitSet == [ "Grapefruit","Pear","Orange","Kiwi","Melon","Mango" ]
FString RemovedAmountLemon = FruitSet.Remove(TEXT("Lemon"));
// RemovedAmountLemon == 0
使用 Empty 或 Reset 函数可将set中的所有元素移除;Empty 和 Reset 相似,但 Empty 可采用参数指示保留的slack量,而 Reset 则是尽可能多地留出slack量;
TSet<FString> FruitSetCopy = FruitSet;
// FruitSetCopy == [ "Grapefruit","Pear","Orange","Kiwi","Melon","Mango" ]FruitSetCopy.Empty();
// FruitSetCopy == []
六,Sorting
TSet 可被排序,排序后,会以排序的顺序显示元素,但下次修改set时,排序可能会发生变化;由于排序不稳定,可能按任何顺序显示重复键;
FruitSet.Sort([](const FString& A, const FString& B) {return A > B; // sort by reverse-alphabetical order});
// FruitSet == [ "Pear", "Orange", "Melon", "Mango", "Kiwi", "Grapefruit" ] (order is temporarily guaranteed)FruitSet.Sort([](const FString& A, const FString& B) {return A.Len() < B.Len(); // sort strings by length, shortest to longest});
// FruitSet == [ "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" ] (order is temporarily guaranteed)
七,Operators
和 TArray 一样,TSet 是常规值类型,可通过标准复制构造函数或赋值运算符进行复制;因为set严格拥有其元素,是深拷贝,所以新set将拥有其自身的元素副本;
TSet<int32, FString> NewSet = FruitSet;
NewSet.Add(TEXT("Apple"));
NewSet.Remove(TEXT("Pear"));
// FruitSet == [ "Pear", "Kiwi", "Melon", "Mango", "Orange", "Grapefruit" ]
// NewSet == [ "Kiwi", "Melon", "Mango", "Orange", "Grapefruit", "Apple" ]
八,Slack
Slack是不包含元素的已分配内存,调用 Reserve 可分配内存,无添加元素;通过非零slack参数调用 Reset 或 Empty 可移除元素,无需将其使用的内存取消分配;Slack优化了将新元素添加到set的过程,因为可以使用预先分配的内存,而不必分配新内存;它在移除元素时也十分实用,因为系统不需要取消内存分配;在清空并希望用相同或更少的元素立即重新填充的set时,此方法尤其有效;
TSet 不像 TArray 中的 Max 函数那样可检查预分配元素的数量;
//不取消任何内存的情况下移除所有元素,从而产生slack;
FruitSet.Reset();
// FruitSet == [ <invalid>, <invalid>, <invalid>, <invalid>, <invalid>, <invalid> ]
使用 Reserve 函数可直接创建slack,如在添加元素之前预分配内存;
FruitSet.Reserve(10);
for (int32 i = 0; i < 10; ++i)
{FruitSet.Add(FString::Printf(TEXT("Fruit%d"), i));
}
// FruitSet == [ "Fruit9", "Fruit8", "Fruit7" ... "Fruit2", "Fruit1", "Fruit0" ]
预先分配slack会导致以倒序添加新元素;与数组不同,set不维护元素排序,不能指望元素排序稳定或可预测;
使用 Collapse 和 Shrink 函数可移除 TSet 中的全部slack;Shrink 将从容器的末端移除所有slack,但这会在中间或开始处留下空元素;
// Remove every other element from the set.
for (int32 i = 0; i < 10; i += 2)
{FruitSet.Remove(FSetElementId::FromInteger(i));
}
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0", <invalid> ]FruitSet.Shrink();
// FruitSet == ["Fruit8", <invalid>, "Fruit6", <invalid>, "Fruit4", <invalid>, "Fruit2", <invalid>, "Fruit0" ]
以上 Shrink 只删除了一个无效元素,因为末端只有一个空元素;要移除所有slack,首先应调用 Compact 或 CompactStable 函数,将空白空间组合在一起,为调用 Shrink 做好准备;顾名思义,CompactStable 可在合并空元素时保持元素的排序;
FruitSet.CompactStable();
// FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0", <invalid>, <invalid>, <invalid>, <invalid> ]
FruitSet.Shrink();
// FruitSet == ["Fruit8", "Fruit6", "Fruit4", "Fruit2", "Fruit0" ]
九,DefaultKeyFuncs
只要类型具有 operator== 和非成员 GetTypeHash 重载,就可为TSet所用,因为此类型既是元素又是键;然而,不便于重载这些函数时可将类型作为键使用;在这些情况下,可对 DefaultKeyFuncs 进行自定义;为键类型创建 KeyFuncs,必须定义两个typedef和三个静态函数;
typedef
KeyInitType—— 用于传递键的类型,通常抽取自ElementType模板参数;ElementInitType—— 用于传递元素的类型,同样通常抽取自ElementType模板参数,因此与KeyInitType相同;
static function
KeyInitType GetSetKey(ElementInitType Element)——返回元素的键,通常是元素本身;bool Matches(KeyInitType A, KeyInitType B)—— 如A和B等值将返回true,否则返回false;uint32 GetKeyHash(KeyInitType Key)—— 返回Key的散列值;
KeyInitType 和 ElementInitType 是键/元素类型普通传递惯例的typedef;它们通常为浅显类型的一个值和非浅显类型的一个常量引用;请注意,set的元素类型也是键类型,因此 DefaultKeyFuncs 仅使用一种模板参数 ElementType 定义两者;
TSet 假定在 DefaultKeyFuncs 中使用 Matches 进行对比结果为相等的两个项,也将在 KeyFuncs 的 GetKeyHash 中返回相同的值;
切勿在更改现有元素时改变来自这两个函数中任一个的结果,因为这会使set的内部散列失效;使用 DefaultKeyFuncs 的默认实现时,此规则也适用于 operator== 和 GetKeyHash 的重载;
十,Miscellaneous
CountBytes 和 GetAllocatedSize 函数用于估计内部数组的当前内存使用情况;CountBytes 接受 FArchive 参数,而 GetAllocatedSize 则不接受;这些函数常用于统计报告;
Dump 函数接受 FOutputDevice 并写出关于set内容的实现信息;还有一个名为 DumpHashElements 的函数,可列出来自所有散列条目的所有元素;这些函数常用于调试;
相关文章:
UE_C++ —— Container TSet
目录 一,TSet 二,Creating and Filling a Set Editing UPROPERTY TSets 三,Iteration 四,Queries 五,Removal 六,Sorting 七,Operators 八,Slack 九,DefaultKe…...
多线程和并发篇
多线程和并发篇 创建一个对象时底层汇编指令实现步骤(cpu可能会进行指令重排序):一、二、三级缓存的实现:并发编程三要素:线程的五大状态:创建线程的三种方式:线程的特征和状态:Thre…...
【3.5JavaScript】JavaScript字符串对象
文章目录 1.获取字符串长度2.大小写转换3.获取某一个字符4.截取字符串5.替换字符串6.分割字符串7.检索字符串位置8.例题:统计某一个字符的个数 在 JavaScript 中,对象是非常重要的知识点。对象分为两种:一种是 ”自定义对象“,另…...
Apache Flink架构深度解析:任务调度、算子数据同步与TaskSlot资源管理机制
Apache Flink是一个分布式流处理框架,其核心架构设计围绕有界与无界数据流的统一处理能力展开。以下从任务分配、算子数据同步、TaskManager与JobManager的TaskSlot机制三个维度展开详细分析: 一、任务分配机制 Flink的任务分配基于并行度(P…...
路由基本配置
学习目标 • 根据拓扑图进行网络布线。 • 清除启动配置并将路由器重新加载为默认状态。 • 在路由器上执行基本配置任务。 • 配置并激活以太网接口。 • 测试并检验配置。 • 思考网络实施方案并整理成文档。 任务 1:网络布线 使用适当的电缆类型连接网络设备。…...
windows上vscode cmake工程搭建
安装vscode插件: 1.按装fastc(主要是安装MinGW\mingw64比较方便) 2.安装C,cmake,cmake tools插件 3.准备工作完成之后,按F1,选择cmake:Quick Start就可以创建一个cmake工程。 4.设置Cmake: G…...
VUE3+TS+element-plus项目从0开始入门 - 创建项目、认识基本结构
文章目录 写在前面1、创建vue3项目npm create vuelatestnpm i 2、项目结构.vscodevue3结构a、项目树结构b、package.jsonc、tsconfig.jsond、index.htmld、srce、main.tsf、App.vue 写在前面 开前请自行下载vs code、node.js, 在vs code里面安装Vue - Official插件。本文使用的…...
shared_ptr 不析构的问题记录
片段1: 片段2: 你们猜 哪个有问题 ?...
DeepSeek模型量化
技术背景 大语言模型(Large Language Model,LLM),可以通过量化(Quantization)操作来节约内存/显存的使用,并且降低了通讯开销,进而达到加速模型推理的效果。常见的就是把Float16的浮…...
原生稀疏注意力机制(NSA):硬件对齐且可原生训练的稀疏注意力机制-论文阅读
摘要 长上下文建模对于下一代语言模型至关重要,但标准注意力机制的高计算成本带来了巨大的计算挑战。稀疏注意力提供了一种在保持模型能力的同时提高效率的有前途的方向。本文提出了一种名为 NSA(原生可训练稀疏注意力机制) 的方法ÿ…...
从0到1:固件分析
固件分析 0x01 固件提取 1、从厂商官网下载 例如D-link的固件: https://support.dlink.com/resource/products/ 2、代理或镜像设备更新时的流量 发起中间人攻击MITM #启用IP转发功能 echo 1 > /proc/sys/net/ipv4/ip_forward#配置iptables,将目…...
Zookeeper(58)如何在Zookeeper中实现分布式锁?
在 Zookeeper 中实现分布式锁是一种常见的用例。Zookeeper 提供了强一致性、高可用性的分布式协调服务,使得它非常适合用来实现分布式锁。以下是详细的步骤和代码示例,展示如何在 Zookeeper 中实现分布式锁。 1. Zookeeper 分布式锁的基本原理 Zookeep…...
23种设计模式 - 观察者模式
模式定义 观察者模式(Observer Pattern)是一种行为型设计模式,定义了一对多的依赖关系:当一个对象(称为主题)状态变化时,所有依赖它的对象(称为观察者)会自动收到通知并…...
conda、anaconda、pip、pytorch、tensorflow有什么区别?
先画一张图,可以大致看出它们的区别和关联: pytorch、tensorflow都是Python的第三方库,相当于封装的代码工具集库,通过import导入使用。这两个都是深度学习框架,用来搭建AI模型什么的,使用范围非常之广&…...
项目设置内网 IP 访问实现方案
在我们平常的开发工作中,项目开发、测试完成后进行部署上线。比如电商网站、新闻网站、社交网站等,通常对访问不会进行限制。但是像企业内部网站、内部管理系统等,这种系统一般都需要限制访问,比如内网才能访问等。那么一个网站应…...
Vue面试2
1.跨域问题以及如何解决跨域 跨域问题(Cross-Origin Resource Sharing, CORS)是指在浏览器中,当一个资源试图从一个不同的源请求另一个资源时所遇到的限制。这种限制是浏览器为了保护用户安全而实施的一种同源策略(Same-origin p…...
合合信息2025届春季校园招聘全面启动!
世界因你而AI,合合信息2025届春季校园招聘启动! 我们是谁? 我们是一家行业领先的人工智能及大数据科技企业 18年深耕AI领域,C端产品与B端服务布局矩阵完善 9.4亿全球累计用户首次下载量💥 来到这里你能得到什么&a…...
如何利用 Vue 的生命周期钩子进行初始化和清理操作?
一、初始化操作的核心钩子 1. created(选项式API) export default {data() {return { user: null };},created() {// 适合初始化数据、发起非DOM操作请求this.fetchUser();},methods: {async fetchUser() {const response await fetch(/api/user);thi…...
Excell 代码处理
文章目录 Excell 代码处理cvc格式xlsl格式小结 Excell 代码处理 有时候要对excell进行分析,或者数据的导入导出,这个时候如果可以用代码读写分析操作那么会方便很多 cvc格式 CSV(Comma-Separated Values,逗号分隔值)是…...
KMP的next数组构建详解
KMP的next数组构建详解 1. next数组的作用 核心功能:在KMP算法中,当模式串与主串发生不匹配时,next数组决定模式串指针回退的位置,避免无效匹配。 定义:next[i]表示子串s[0...i]的最长公共前后缀长度。例如ÿ…...
Docker 的安全配置与优化(二)
Docker 安全优化策略 (一)多阶段构建优化镜像大小 多阶段构建是 Docker 17.05 版本引入的强大功能,它允许在一个 Dockerfile 中定义多个构建阶段,每个阶段都可以使用不同的基础镜像和依赖项,最终只将必要的文件和依赖…...
shiro代码层面追踪
文章目录 环境漏洞分析硬编码 反序列化Gadget构造 环境 环境搭建:https://blog.csdn.net/qq_44769520/article/details/123476443 漏洞分析 硬编码 shiro是对rememberMe这个cookie进⾏反序列化的时候出现了问题。 相应代码 // // Source code recreated from …...
通信系统中物理层与网络层联系与区别
在通信系统中,物理层和网络层是OSI(开放系统互连)模型中的两个重要层次,分别位于协议栈的最底层和第三层。它们在功能、职责和实现方式上有显著的区别,但同时也在某些方面存在联系。以下是物理层与网络层的联系与区别的…...
虚拟机网络ssh连接失败,没有网络
vscode进行ssh时连接失败,发现是虚拟机没有网络。 虚拟机ping不通www.baidu.com但可以ping通内网 ping 8.8.8.8ping不通。 sudo dhclient -r ens33 sudo dhclient ens33 ip route show可以了。 20250221记录:不知道是不是重启了虚拟机还是咋了&#…...
已知点矩阵的三个顶点坐标、行列数和行列的间距,计算得出剩余所有点的坐标
已知点矩阵的三个顶点坐标、行列数和行列的间距,计算得出剩余所有点的坐标 计算矩阵中每个点的坐标代码实现案例图调用验证 计算矩阵中每个点的坐标 给定左上角、左下角和右上角三个点的坐标,以及矩阵的行数、列数、行间距和列间距,我们可以…...
Python Cookbook-2.4 从文件中读取指定的行
任务 根据给出的行号,从文本文件中读取一行数据。 解决方案 Python标准库linecache模块非常适合这个任务: import linecache theline linecache.getline(thefilepath, desired_line_number)讨论 对这个任务而言,标准的 linecache 模块是 Python 能…...
go 并发 gorouting chan channel select Mutex sync.One
goroutine // head: 前缀 index:是一个int的指针 func print(head string, index *int) {for i : 0; i < 5; i {// 指针对应的int *indexfmt.Println(*index, head, i)// 暂停1stime.Sleep(1 * time.Second)} }/* Go 允许使用 go 语句开启一个新的运…...
Unity游戏制作中的C#基础(3)加减乘除算术操作符,比较运算符,逻辑与,或运算符
1. 基本算术运算符 算术运算符主要用于对数值类型(整型和浮点型)进行基本的数学运算。以下是常见的算术运算符及其说明: 运算符描述示例结果加法运算符,用于两个数相加,也可用于字符串连接int a 5 3; string str &…...
深度学习入门--python入门2
以前学的全忘了,现在算是才开始学,有错误,恳请指正。 目录 1.4 Python脚本文件 1.4.1保存为文件 1.4.2 类 1.5 Numpy 1.5.1 导入Numpy 1.5.2 生成Numpy数组 1.5.3 Numpy的算术运算 1.5.4 Numpy的N维数组 1.5.5 广播 1.5.6 访问元素…...
题海拾贝:【枚举】P2010 [NOIP 2016 普及组] 回文日期
Hello大家好!很高兴我们又见面啦!给生活添点passion,开始今天的编程之路! 我的博客:<但凡. 我的专栏:《编程之路》、《数据结构与算法之美》、《题海拾贝》 欢迎点赞,关注! 1、题…...
