UE学习笔记--干货满满!FString 的 Equals 和 == 源码深度探析

目录

    • 前言
    • FString 的 operator==
    • ESearchCase
    • Stricmp
    • BothAscii
    • LowerAscii
    • Stricmp 结论
    • Stricmp 代码验证
    • 整理思路

前言

最近大概写了如下代码

TArray<FString> TestArray;
FString Z1 = "Z1", z1 = "z1";
TestArray.Emplace(Z1);if(TestArray.Contains(z1))
{UE_LOG(LogTemp, Error, TEXT("Contains z1"));
}
else
{UE_LOG(LogTemp, Error, TEXT("Not Contains z1"));
}

大家可以猜一下输出什么。
正常来说可能会猜 Not Contains,因为 Z1 明显不等于 z1 嘛。
但是结果是 Contains z1。

很多时候我们都会用到 TArray,然后利用它来判断某个键是否存在,但是这里好像忽略了大小写一样。

我们直接进入 Contains 函数看看

/*** Checks if this array contains the element.** @returns	True if found. False otherwise.* @see ContainsByPredicate, FilterByPredicate, FindByPredicate*/
template <typename ComparisonType>
bool Contains(const ComparisonType& Item) const
{for (const ElementType* RESTRICT Data = GetData(), *RESTRICT DataEnd = Data + ArrayNum; Data != DataEnd; ++Data){if (*Data == Item){return true;}}return false;
}

发现是调用了元素的 == 来判断是否存在,这里我们元素的类型是 FString,那就进入 FString 的 == 看看。

FString 的 operator==

我们直接进源码看看

/**
* Lexicographically test whether the left string is == the right string** @param Lhs String to compare against.* @param Rhs String to compare against.* @return true if the left string is lexicographically == the right string, otherwise false* @note case insensitive*/
[[nodiscard]] FORCEINLINE bool operator==(const UE_STRING_CLASS& Rhs) const
{return Equals(Rhs, ESearchCase::IgnoreCase);
}

我们发现 == 其实是调用了 Equals,然后也发现有第二个参数,进去看一下

/** Determines case sensitivity options for string comparisons. */
namespace ESearchCase
{enum Type{/** Case sensitive. Upper/lower casing must match for strings to be considered equal. */CaseSensitive,/** Ignore case. Upper/lower casing does not matter when making a comparison. */IgnoreCase,};
}

ESearchCase

  • CaseSensitive:判断大小写
  • IgnoreCase:忽略大小写

这样就大致明白了,== 调用的是 Equals,传入的第二个参数是IgnoreCase,所以是忽略大小写的。我们再来看看 Equals 源码。

/*** Lexicographically tests whether this string is equivalent to the Other given string* * @param Other 	The string test against* @param SearchCase 	Whether or not the comparison should ignore case* @return true if this string is lexicographically equivalent to the other, otherwise false*/
[[nodiscard]] FORCEINLINE bool Equals(const UE_STRING_CLASS& Other, ESearchCase::Type SearchCase = ESearchCase::CaseSensitive) const
{int32 Num = Data.Num();int32 OtherNum = Other.Data.Num();if (Num != OtherNum){// Handle special case where FString() == FString("")return Num + OtherNum == 1;}else if (Num > 1){if (SearchCase == ESearchCase::CaseSensitive){return TCString<ElementType>::Strcmp(Data.GetData(), Other.Data.GetData()) == 0; }else{return TCString<ElementType>::Stricmp(Data.GetData(), Other.Data.GetData()) == 0;}}return true;
}

我们发现有一个 if 分支,不同点是第一个分支调用的:Strcmp,第二个调用的 Stricmp。第一个比较常见,就是比较字符串。

Stricmp

我们进去看看第二个是干什么的,一直点进去发现如下代码

template<typename CharType1, typename CharType2>
int32 StricmpImpl(const CharType1* String1, const CharType2* String2)
{while (true){CharType1 C1 = *String1++;CharType2 C2 = *String2++;uint32 U1 = TChar<CharType1>::ToUnsigned(C1);uint32 U2 = TChar<CharType2>::ToUnsigned(C2);// Quickly move on if characters are identical but// return equals if we found two null terminatorsif (U1 == U2){if (U1){continue;}return 0;}else if (BothAscii(U1, U2)){if (int32 Diff = LowerAscii[U1] - LowerAscii[U2]){return Diff;}}else{return U1 - U2;}}
}

大致逻辑就是,一个一个字符作比较,如果发现不同的,那么立马返回。如果相同那么就一直执行,直到走到空字符(ASCLL = 0),会走到 return 0; 语句,最后返回给 Equals 函数,由于 0 = 0,所以返回 true,也就是两个数相等。

uint32 U1 = TChar<CharType1>::ToUnsigned(C1);
uint32 U2 = TChar<CharType2>::ToUnsigned(C2);

上面的代码其实就是把 C1、C2 两个 TCHAR 转换成了无符号整型,内部是转换成了 ASCLL 码。

比如C1 = ‘Z’, C2 = ‘z’,我们查询 ASCLL 码,U1 和 U2 应该等于 90 和 122。

ASCLL 码表

下一行代码

if (U1 == U2)

明显不符合,那么就进入下一个 else if

else if (BothAscii(U1, U2))

BothAscii

我们来看看这个函数

FORCEINLINE bool BothAscii(uint32 C1, uint32 C2)
{return ((C1 | C2) & 0xffffff80) == 0;
}

转换一下二进制
0xffffff80 = 1111 1111 1111 1111 1111 1111 1000 0000

因为 ASCLL 最大是 255,如果 C1 | C2 大于 255,也就是从第8位二进制开始,有一个1出现,那么就能说明 C1 和 C2 至少有一个不是 ASCLL 字符。所以BothAscii就是判断 C1 和 C2 是不是 ASCLL 表中存在的数字。

BothAscii 结束了我们在进入下一个 if 看看

if (int32 Diff = LowerAscii[U1] - LowerAscii[U2])
{return Diff;
}

LowerAscii

它是一个数组,我们来看下声明

static constexpr uint8 LowerAscii[128] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,0x40, 'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o','p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  0x5B, 0x5C, 0x5D, 0x5E, 0x5F,0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F
};

很明显,这就是一个 ASCLL 表,一行 16 个元素。

比如传进来的字符是’Z’ 和 ‘z’,那么它们的 ASCLL 分别是 90,122。
参数 U1 = 90, U2 = 122。根据 LowerAscii 我们就知道

LowerAscii['Z'] = 'z' = 122;
LowerAscii['z'] = 0x5A = 122;

所以 LowerAscii[‘Z’] == LowerAscii[‘z’];

Stricmp 结论

进一步得出结论,StricmpImpl 本身就是忽略大小写来进行字符串的判断的。

Stricmp 代码验证

uint32 U1 = TChar<TCHAR>::ToUnsigned('Z');
uint32 U2 = TChar<TCHAR>::ToUnsigned('z');static constexpr uint8 LowerAscii[128] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,0x40, 'a',  'b',  'c',  'd',  'e',  'f',  'g',  'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o','p',  'q',  'r',  's',  't',  'u',  'v',  'w',  'x',  'y',  'z',  0x5B, 0x5C, 0x5D, 0x5E, 0x5F,0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F
};int32 LowerU1 = LowerAscii[U1], LowerU2 = LowerAscii[U2];
int32 Diff = LowerU1 - LowerU2;int32 U1U2 = (U1 | U2), U1U2And = (U1 | U2) & 0xffffff80;UE_LOG(LogTemp, Error, TEXT("U1U2: %d, U1U2And: %d, Diff: %d"), U1U2, U1U2And, Diff);if(U1 == U2)
{UE_LOG(LogTemp, Error, TEXT("U1 == U2"));
}
else
{UE_LOG(LogTemp, Error, TEXT("U1 != U2"));
}FString Z1 = "Z1", z1 = "z1";
if(Z1 == z1)
{UE_LOG(LogTemp, Error, TEXT("Z1 == z1"));
}

大家可以先思考一下会输出什么,后面再给出答案


输出如下

LogTemp: Error: U1U2: 122, U1U2And: 0, Diff: 0
LogTemp: Error: U1 != U2
LogTemp: Error: Z1 == z1

整理思路

好了,现在我们回到最初的问题,然后整理下思路,TArray 的 Contains 方法实际是调用元素的 ==,而 FString 的 == 内部是调用了忽略大小写的 Equals,才会导致 Contains(“z1”); 返回 true。
所以如果逻辑本身不能忽略大小写,那么就自己 Foreach 一个一个 Equals 去判断。
或者利用 UE 提供的另外一个函数:ContainsByPredicate

/*** Checks if this array contains an element for which the predicate is true.** @param Predicate to use* @returns	True if found. False otherwise.* @see Contains, Find*/
template <typename Predicate>
FORCEINLINE bool ContainsByPredicate(Predicate Pred) const
{return FindByPredicate(Pred) != nullptr;
}

用法大致如下

TArray<FString> TestArray;
FString Z1 = "Z1", z1 = "z1";
TestArray.Emplace(Z1);if(TestArray.ContainsByPredicate([z1](const FString& Item){ return Item.Equals(z1, ESearchCase::CaseSensitive); }))
{UE_LOG(LogTemp, Error, TEXT("ByPredicate Contains z1"));
}
else
{UE_LOG(LogTemp, Error, TEXT("ByPredicate Not Contains z1"));
}

这里会输出:ByPredicate Not Contains z1,说明会判断大小写。

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

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

相关文章

代码随想录第十七天|动态规划(1)

目录 LeetCode 509. 斐波那契数列 LeetCode 70. 爬楼梯 LeetCode 746. 使用最小花费爬楼梯 LeetCode 62. 不同路径 LeetCode 63. 不同路径 II 总结 动态规划在算法课上学习过&#xff0c;看过了之后有一些熟悉感。&#xff08;虽然贪心算法也学过&#xff0c;但是不如动态…

样式迁移及代码

一、定义 1、使用卷积神经网络&#xff0c;自动将一个图像中的风格应用在另一图像之上&#xff0c;即风格迁移&#xff1b;两张输入图像&#xff1a;一张是内容图像&#xff0c;另一张是风格图像。 2、训练一些样本使得样本在一些cnn的特征上跟样式图片很相近&#xff0c;在一…

Java字符串与Unicode编码(码点、代码单元、基本多语言平面BMP、辅助平面、代理对)

Java字符串与Unicode编码 1. Unicode编码简介 Unicode是一个为世界上所有书写系统设计的字符编码标准。它旨在解决不同编码标准之间不兼容的问题&#xff0c;使得计算机能够处理和显示世界上几乎所有的字符。Unicode为每个字符分配了一个唯一的数字&#xff0c;称为“码点”&…

字典集合案例

1.统计字符 统计字符串中每个字符出现的次数 s l like summer very much #去掉空格 s s.replace(" ","") d dict() for i in s:if i in d:d[i] 1else:d[i] 1 for i in d:print(i,d[i]) 2.求不重复的随机数 #导入随机数 import random a int(input(&q…

自动化测试的艺术:Xcode中GUI测试的全面指南

自动化测试的艺术&#xff1a;Xcode中GUI测试的全面指南 在软件开发过程中&#xff0c;图形用户界面&#xff08;GUI&#xff09;测试是确保应用质量和用户体验的关键环节。Xcode&#xff0c;作为苹果的官方集成开发环境&#xff08;IDE&#xff09;&#xff0c;提供了一套强大…

智能疲劳驾驶检测:基于YOLO和深度学习的全流程实现

引言 疲劳驾驶是导致交通事故的重要原因之一。为了提高道路安全&#xff0c;及时检测和预警驾驶员的疲劳状态显得尤为重要。本文介绍了一种基于深度学习的疲劳驾驶检测系统。该系统利用YOLO模型&#xff08;YOLOv8/v7/v6/v5&#xff09;进行疲劳驾驶检测&#xff0c;并提供了详…

OD C卷 - 密码输入检测

密码输入检测 &#xff08;100&#xff09; 给定一个密码&#xff0c;‘<’ 表示删除前一个字符&#xff0c;输出最终得到的密码&#xff0c;并判断是否满足密码安全要求&#xff1a; 密码长度>8;至少包含一个大写字母&#xff1b;至少包含一个小写字母&#xff1b;至少…

探索若依(Ruoyi):开源的企业级后台管理系统解决方案

探索若依&#xff08;Ruoyi&#xff09;&#xff1a;开源的企业级后台管理系统解决方案 在现代企业管理中&#xff0c;拥有一个高效、稳定的后台管理系统是至关重要的。若依&#xff08;Ruoyi&#xff09;作为一款开源的企业级后台管理系统&#xff0c;为企业提供了丰富的功能…

SpringBoot中JSR303校验

JSR是 Java EE 的一种标准&#xff0c;用于基于注解的对象数据验证。在Spring Boot应用中&#xff0c;你可以通过添加注解直接在POJO类中声明验证规则。这样可以确保在使用这些对象进行操作之前&#xff0c;它们满足业务规则。个人认为非常有用的&#xff0c;因为它减少了代码中…

2.6基本算法之动态规划2989:糖果

描述 由于在维护世界和平的事务中做出巨大贡献&#xff0c;Dzx被赠予糖果公司2010年5月23日当天无限量糖果免费优惠券。在这一天&#xff0c;Dzx可以从糖果公司的N件产品中任意选择若干件带回家享用。糖果公司的N件产品每件都包含数量不同的糖果。Dzx希望他选择的产品包含的糖…

被问到MQ消息已丢失,该如何处理?

在分布式系统中&#xff0c;消息中间件&#xff08;如 RabbitMQ、RocketMQ、Kafka、Pulsar 等&#xff09;扮演着关键角色&#xff0c;用于解耦生产者和消费者&#xff0c;并确保数据传输的可靠性和顺序性。尽管我们通常会采取多种措施来防止消息丢失&#xff0c;如消息持久化、…

【Vue实战教程】之 Vue Router 路由详解

Vue Router路由 1 路由基础 1.1 什么是路由 用Vue.js创建的项目是单页面应用&#xff0c;如果想要在项目中模拟出来类似于页面跳转的效果&#xff0c;就要使用路由。其实&#xff0c;我们不能只从字面的意思来理解路由&#xff0c;从字面上来看&#xff0c;很容易把路由联想…

HTML(五)——HTML区块,布局

HTML区块 HTML可以通过 <div> 和 <span>将元素组合起来&#xff0c;可以来布局&#xff0c;就是盒子&#xff0c;div是块级盒子&#xff0c;里面 可以放任何东西&#xff0c;span里面装的是文本 HTML 区块元素 大多数 HTML 元素被定义为块级元素或内联元素。 实…

Java 面试 | Redis

目录 1. 在项目中缓存是如何使用的&#xff1f;2. 为啥在项目中要用缓存&#xff1f;3. 缓存如果使用不当会造成什么后果&#xff1f;4. redis 和 memcached 有什么区别&#xff1f;5. redis 的线程模型是什么&#xff1f;6. 为什么单线程的 redis 比多线程的 memcached 效率要…

dns逆向解析,主从服务,多域名访问(穿插ntp服务器)

复习 域名解析&#xff1a; 正向解析&#xff1a;将域名解析为ip 反向解析&#xff1a;将ip解析为域名 逆向解析 关闭防火墙和selinux&#xff0c;配置静态ip [rootdns ~]# vim /etc/named.rfc1912.zones [rootdns ~]# vim /etc/named.conf [rootdns ~]# cd /var/named/ [rootd…

【电子数据取证】了解数据库

文章关键词&#xff1a;电子数据取证、数据库取证、手机取证 一、前言 数据库是信息系统中不可或缺的部分。无论是取证收集&#xff0c;网站重建又或是开发程序都离不开数据库这个角色。 可能你已经或多或少看到过像是MySQL、Redis、MongoDB之类的程序&#xff0c;这些程序统…

前端:Vue学习-4

前端&#xff1a;Vue学习-4 1. 组件缓存 keep-alive2. 状态管理工具 - Vuex2.1 vuex 提供数据&使用数据 - mapState2.2 mutations 修改数据 - mapMutations2.3 actions - 异步操作 - mapActions2.4 getters - 计算属性 - mapGetters 3. Vuex 模块 modules - state,mutation…

day07:用户下单、订单支付

文章目录 地址薄相关相关代码需求分析和设计代码书写 用户下单需求分析和设计代码开发 订单支付微信支付介绍微信支付准备工作如何保证数据安全&#xff1f;如何调用到商户系统 地址薄相关相关代码 需求分析和设计 产品原型接口设计数据库设计 代码书写 地址薄相关代码都是单…

Objects类

Objects类 Objects类&#xff1a;常用方法&#xff1a;equals()&#xff1a; Objects类&#xff1a; Objects是一个工具类&#xff0c;提供了很多操作对象的静态方法给我们使用。 常用方法&#xff1a; 方法名说明public static boolean equals(Object a, Object b)先做非空判…

电商大型活动行动清单样例

背景 为保证在大型活动/节日&#xff08;双十一、黑色星期五&#xff09;时服务稳定&#xff0c;提出各个角色必要的行动清单 涉及到的角色与职能范围&#xff08;包括但不限于&#xff09; 产品&#xff1a;确定核心功能链路&#xff0c;制定服务降级默认行为&#xff0c;提…