FName 通过一个轻型系统使用字符串。在此系统中,特定字符串即使会被重复使用,在数据表中也只存储一次。FNames 不区分大小写。它们为不可变,无法被操作。FNames 的存储系统和静态特性决定了通过键进行 FNames 的查找和访问速度较快。FName 子系统的另一个功能是使用散列表为 FName 转换提供快速字符串。
FName不区分大小写,作为索引组合存储在唯一字符串和实例编号的表格中。
创建FName
FName TestHUDName = FName(TEXT("ThisIsTestFName"));
FName转FString
FString TestHUDString = TestHUDName.ToString();
FName转FText
FText TestHUDText = FText::FromName(TestHUDName);
FString转FName
TestHUDName = FName(*TestHUDString);
FText到FName的转换不存在,可先转到FString,再转换到FName
FText -> FString -> FName不可靠。因为FName不区分大小写,所以转换存在损耗
执行这些转换时,需注意它们可能包含对创建中的 FName 类型无效的字符。 NameTypes.h 文件中的 INVALID_NAME_CHARACTERS 宏定义 FNames 中无效的字符; FName 中的数个函数(如 IsValidObjectName())将对 FNames 在特定使用情况下的有效性进行检查。
FName之间的比对
==
运算符用于对比两个FName,返回true或false
它并不执行字符串的比对,而是对比索引中的数值,可极大地节约CPU的开销。FName::Compare还可同于对比两个FName,如果
将返回负数, ==Other
返回0,>Other
则返回正数
int32 CompareFloat = TestFName.Compare(OtherFName);
使用FName
在需要FName的地方直接构造即可
FRotator rotPelvis = Mesh->MeshGetInstance(this)->GetBoneRotation(FName(TEXT("pelvis")));
搜索名称表
如需确定FName是否在名称表中(但不希望进行自动添加),可在FName构建函数中补充一个不同的搜索类型:
if (FName(TEXT("pelvis"), FNAME_Find) != NAME_None) { //Do something }
如果名称不在表中,FName的索引将被设定为NAME_None。!注意:将不对指针进行null检查,因为使用的是普通字符串
FText 代表一个显示字符串。为用户显示的文本都需要由 FText 进行处理。FText 类拥有本地化的内置支持,可处理已本地化并存储在查找表中的文本内容,以及在运行时被本地化的文本,如数字、日期、时间和格式化文本。甚至可用 FText 处理无需进行本地化的文本。包括用户输入内容,如玩家名和 Slate 显示的文本。FText 不提供任何变异函数,因为对显示字符串进行修改是非常不安全的操作。
Slate和FText
整体而言,Slate弃用了FString的公开钩,转而支持FText。因为FText可在其接口后处理所有本地化问题,Slate未来支持诸如实时语言勾选之类的功能。使用FString将无法实现。
使用FText还可增强文本渲染的性能。
使用FText时,字符串被假定为不可变,因此字符串的内存地址可用作快速缓存查找的键。与检查字符串更新或对其重新测量进行显示相比较,这种方法的开销较小。最后,在Slate中使用FText以为这本地化支持将与UI系统自然整合,形成本地化最佳方案的坚实框架。创建FText
因为FText的本地化应用,每个FText必须以键和文本进行设置。使用
LOCTEXT
和NSLOCTEXT
宏可对FText进行正确设置。LOCTEXT将键和文本视为参数。所有参数必须为字符串文字。所有文字量将通过本地系统进行传递。
必须首先将全局 LOCTEXT_NAMESPACE 宏设为字符串文字,以指定该本地化键的命名空间。使用
LOCTEXT
宏从字符串创建FText:
/*Top of file*/ #define LOCTEXT_NAMESPACE "Your Namespace" ... FText TestHUDText = LOCTEXT("Your Ket", "Your Text"); ... #undef LOCTEXT_NAMESPACE /*Bottom of file*/
除键和文本外,NSLOCTEXT还将命名空间作为参数。所有参数必须为字符串文字。所有文字量将通过本地化系统进行传递。
FText TestHUDText = NSLOCTEXT("Your Namespace", "Your Key", "Your Text");
也可以使用以下任意一个语法创建空白的FText:
FText TestHUDText = FText::GetEmpty(); FText TestHUDText = FText();
FText转FString
TestHUDString = TestHUDText.ToString(); // FText -> FString 不可靠。它在一些语言的转换中存在潜在损耗
FText转FName
FText到FName的转换不存在,可先转换到FString,在转为FName // FText -> FString -> FName不可靠。因为FName不区分大小写,所在转换存在损耗
FText字符串为不可变,因此必须将其复制,否则无法对返回的字符串进行修改
FName转FText
TestHUDText = FText::FromName(TestHUDName); // FName -> FText在一些情况下有效,但需注意FName内容不会从FText的"自动本地化"中受益
FString转FText
TestHUDText = FText::FromString(TestHUDString); // FString -> FText在一些情况下有效,但需注意FString内容不会从FText的"自动本地化"中受益
从FName字符串到数字变量不存在转换
数字变量转换为FText
可使用
FText::AsNumber()
将浮点数和int转换为FText。此函数生成一个FText,代表当前文化中传递的数字。
FText::AsNumber(CurrentHealth);
额外的数字转换包括:
FText::AsPercent()
FText::AsCurrency()
FText::AsDate()
FText::AsTime()
FText之间的比对
可使用
FText::EqualTo()
或FText::EqualToCaseIgnored()
对两个FText字符串进行对比,接收true或false作为结果。使用FText::ConmpareTo()
或者FText::CompareToCaseIgnored()
对比字符串将返回一个整数值FText当前不对这些对比操作公开运算符,因为这些操作不是FString一类的简单序号比较。我们不使用常规的运算符表达操作的开销
格式化FText
可使用
FText::Format()
函数将不同的FText串联起来。如有需要,将其保存到FText变量
FText::Format(LOCTEXT("HelloMessageFormat", "Hello {0}"), PlayerName)
当前,格式化函数只将FText视为参数
FText::Format支持两种不同类型的格式参数:排序和命名
排序参数
- 参数 = {0}、{1}、{2}、…等
- 大括号标识格式参数的开头和结尾,数值说明已传递的参数
FText::Format( LOCTEXT("HealthFormatStr", "{0} / {1}"), FText::AsNumber(CurrentHealth), FText::AsNumber(MaxHealth) )
命名参数
- 参数 = {Name}、{ID}、{Health}、…等
- 大括号表示格式参数的开头和结尾,大括号之间的文本是在传入的 FFormatNamedArgs 集合中找到的参数名称
FFormatNamedArguments Arguments; Arguments.Add(TEXT("CurrentHealth"), FText::AsNumber(CurrentHealth)); EditorErrors.Error( FTExt::Format( LOCTEXT("ExampleFText", "You currently have {CurrentHealth} health left."), Arguments ) );
HUD中的FText
Canvas
如需使用
Canvas
在HUD中显示FText,必须新建一个FCanvasTextItem
FCanvasTextItem TextItem(FVector2D::ZeroVector, TestHUDText, BigFont, FLinearColor::Black); //设置文本变量即可更改FCanvasTextItem的文本 FFormatNamedArguments Arguments; Arguments.Add(TEXT("CurrentHealth"), FText::AsNumber(CurrentHealth)); TextItem.Text = FText::Format( LOCTEXT("ExampleFText", "You current have {CurrentHealth} health left."), Arguments ); //调用 FCanvas->DrawItem() 函数即可将FText实际显示到屏幕上 Canvas->DrawItem(TextItem, 10.0f, 10.0f); /*必须在HUD类的DrawHUD()函数中调用DrawItem()函数,或在以DrawHUD()开始的函数链中调用*/
蓝图中的FText
蓝图支持文本文字。文本数值的任意输入引脚将支持在节点中被指派一个数值,这些字符串将被收集用作本地化。
FText::Format
在蓝图中也为可用。格式文本可为文字或连接到引脚。作为文本使用时,它将为格式化自动生成参数。因此,输入文字“There are {number} days until {day}.”后,一个 Number 引脚和一个 Day 引脚将被自动添加到节点。这些引脚可作为文字被填充或从变量被填充,输出文本将以钩住引脚的数值替代参数。在 Details 中可对引脚名进行变更、添加和移除
与 FName 和 FText 不同,FString 是唯一允许操作的字符串类。字符串操作的可用方法有许多,如大小写转换、摘录子字符串和逆向。FStrings 可被搜集、修改、并与其他字符串进行对比。然而这些操作可能使 FString 的开销比不可变字符串类更大。
创建FString
FString TestHUDString = FString(TEXT("This is my test FString"));
FString转FName
TestHUDName = FName(*TestHUDString); //FString -> FName不可靠。因为FName不区分大小写,所以转换存在损耗
FString转FText
TestHUDText = FText::FromString(TestHUDString); //FString -> FText在一些情况下有效,但需注意FString内容不会从FText的"自动本地化"中受益
FName转FString
TestHUDString = TestHUDName.ToString();
FText转FString
TestHUDString = TestHUDText.ToString(); //FText -> FString不可靠,它在一些语言的转换中存在损耗
数字和其他变量转FString
//float -> FString FString::SanitizeFloat(FloatVariable); //int -> FString FString::FromInt(IntVariable); //bool -> FString InBool ? TEXT("true") : TEXT("false"); //FVector -> FString VectorVariable.ToString(); //FVector2D -> FString Vector2DVariable.ToString(); //FRotator -> FString RotatorVariable.ToString(); //FLinearColor -> FString LinearColorVariable.ToString(); //UObject -> FString (InObj != NULL) ? InObj->GetName() : FString(TEXT("None"));
对于其他数值转换,可使用带合适参数的
FString::Printf()
函数FString转其他变量
//FString -> bool TestHUDString.ToBool(); //FString -> int FCString::Atoi(*TestHUDString); //FString -> float FCString::Atof(*TestHUDString);
FString之间的比对
重载的==操作符可以用来比较两个FStrings,或者比较一个FString和一个TCHAR *的数组。还有FString :: Equals()方法,该方法需要FString进行测试,ESearchCase枚举是否应该忽略大小写作为参数。如果要比较忽略大小写,请使用ESearchCase :: IgnoreCase,如果不是,则使用ESearchCase :: CaseSensitive。
TestHUDString.Equals(TEXT("Test"), ESearchCase::CaseSensitive);
FString检索
在FString中搜索时有两种搜索类型。第一种是
FString::Contains()
,找到子字符串后返回true,否则返回false。FString::Contains()可搜索FString或TCHAR*s子字符串。ESearchCase枚举可用于指定搜索是否忽略大小写。此外,ESearchDir
枚举可用于指定搜索的方向。默认设置为忽略大小写,从开始执行搜索。
TestHUDString.Contains(TEXT("Test"), ESearch::CaseSensitive, ESearchDir::FromEnd);
第二种是
FString::Find()
,返回找到的第一个子字符串实例的索引。FString::Find()可对FString或TCHAR*s子字符串进行搜索。和FString::Contains()一样,可对大小写敏感和搜索方向进行指定,默认设置为忽略大小写并从字符串开头开始搜索。也可在索引中任选一个索引开始搜索。如果FString::Find()为找到子字符串,它将返回-1。
TestHUDString.Find(TEXT("test"), ESearchCase::CaseSensitive, ESearchDir::FromEnd, 10);
构建FString
有两种方法可以从子字符串或其他变量类型中构造字符串。第一个连接只是将FStrings作为参数。在连接它们之前,需要将其他类型的变量转换为FStrings。其次,Printf可以接受像int和float这样的数字输入,还可以在添加到字符串时设置输入的格式。
串联
有两个运算符用于串联字符串:
+=
将提供的字符串附加到FString对象,StringResult += AddedString;
+
新建一个FString对象并附加提供的字符串Printf
用FString::Printf
构建的FString可被存入FStrings,并与 UE_LOG 调试信息一同显示到屏幕上。格式参数同有和C++ printf函数相同的说明符
FString AShooterHUD::GetTimeString(float TimeSeconds) { //only minutes and seconds are relevant const int32 TotalSeconds = FMath::Max(0, FMath::TruncToInt(TimeSeconds) % 3600); const int32 NumMinutes = TotalSeconds / 60; const int32 NumSeconds = TotalSeconfs % 60; const FString TimeDesc = FString::Printf( TEXT("%02d:%02d"), NumMinutes, NumSeconds )
使用
%s
参数包含FString时,必须使用*
运算符返回 %s 参数所需的 TCHAR *操作字符串
可通过许多函数操作字符串。在
UnralString.h
文件中查阅。复制字符串分段的函数:Left、Right 和 Mid。可在找到的子字符串的位置将一个字符串分为两个字符串。 使用 Split 法即可完成此操作。拆分字符串的另一个方法是 ParseIntoArray,可将一个字符串拆分为字符串阵列,由指定的分隔符隔开。 使用 ToUpper 和 ToLower 即可完成大小写转换,将字符串转换为大写或小写。
HUD中的FString
Canvas
如需要在HUD中显示FString,使用 Canvas 调用FCancas::DrawText()
函数
Canvas->DrawText(BigFont, TestHUDString, 110.0f, 110.0f); /*必须在HUD类的DrawHUD()函数中调用DrawItem()函数,或在以DrawHUD()开始的函数链中调用*/
调试信息
打印到视口
FString可被打印到 视口 和 输出日志,以便进行调试
使用UEngine::AddOnScreenDebugMessage()
将调试信息打印到视口。
/**** * @param Key int 防止相同信息多次添加的唯一键。使用-1作为键,使调试信息短时出现 * @param TimeToDisplay float 信息显示时长,按秒计算 * @param DisplayColor FColor 文本显示的颜色 * @param DebugMessage FString 显示的信息(FString) * ****/ GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Blue, TEXT("Test debug message."));
打印到输出日志
UE_LOG 使用printf标记进行参数化
/**** * @param LogClass 日志类别。 * 可使用现有的类别(在 `OutputDevices.h` 中用 `DECLARE_LOG_CATEGORY_EXTERN` 宏设置) * 也可以使用DEFINE_LOG_CATEGORY_STATIC定义自己的类别 * @param Log 日志级别。在 `ELogVerbosity` 枚举中被定义 * 有效值包含 `Fatal` `Error` `Warning` `Display` `Log` `Verbose` `VeryVerbose` * 下一个参数即为输出的文本,包括对参数的标记 ****/ UE_LOG(LogClass, Log, TEXT("This is a testing statement. %s"), *TestHUDString); //此范围使用一个 %s 参数,因此 * 运算符用于返回 %s 参数所需的 TCHAR*
转换宏编码
FString类构建在TCHARs的TArray之上。有多个宏可同于将应用字符串(TCHAR*)转换至ANSI或UNICODE字符集,反之亦然。宏定义的存储路径为
Engine\Source\Runtime\Core\Public\Containers\StringConv.h
如果字符串相对较小,则在堆栈中作为转换器类的一部分进行分配; 否则堆被用来分配一个临时缓冲区。使用堆之前的大小是一个模板参数,所以你可以调整它到你的应用程序。这在循环内是安全的,因为类的范围从堆栈分配中弹出。
常见的转换宏有:
TCHAR_TO_ANSI
将引擎字符串(TCHAR*)转换为ANSI字符串
ANSI_TO_TCHAR
将ANSO字符串转换为引擎字符串(TCHAR*)
这些宏声明的对象具有非常短的生命周期。它们被用来作为参数来使用。您无法为转换的字符串的内容分配变量,因为对象将超出生命周期范围,并且字符串将被释放。
传入的参数必须是适当的字符串,因为参数是指向一个指针的类型。如果你传递一个TCHAR而不是一个TCHAR *,它会编译,然后在运行时崩溃。
用法:SomeApi(TCHAR_TO_ANSI(SomeUnicodeString));
TCHARs 用于存储不受正在使用的字符集约束的字符。平台不同,它们也可能存在不同。UE4 字符串在后台使用 TCHAR 数组将数据保存在 UTF-16 编码中。使用返回 TCHAR 的重载解引用运算符可以访问原始数据
文本格式
ASCII
在32和126之间的字符(32和126包括在内)以及0、9、10和13。(P4型文本)(它在迁入的时候通过P4触发器才有效)
ANSI
ASCII及当前编码页面。(例如,Western European high ASCII)(在P4服务器上需要作为二进制来存储)
UTF-8
一个由可以使用特殊字符序列获取非ANSI字符的单独字节组成的字符串(P4型Unicode)
UTF-16
二进制
优点
没有定义内部格式,可以下载每个文件,无论是什么格式缺点
不可以进行合并,需要将所有此类型的文件独占迁出
没有定义内部格式,每个文件可以是不同的格式
P4可以存储所有版本的完整内容,这样可能会没必要地使库大小膨胀得很大文本
优点
可合并,不需要独占迁出
缺点
非常有限,只允许ASCII字符UTF-8
优点
只会访问我们将会需要的所有字符
使用更少的内存
是一个ASCII的超级组合,一个纯ASCII字符是一个完全有效的UTF-8字符串
在游戏检测到字符串时ASCII并且就这样将它输出时仍然可以正常工作
如果没有一个启用Unicode的服务器,文件可以合并,并且不会要求独占迁出
可以通过解析检测字符串是否是UTF-8(有或没有BOM)
缺点
对于亚洲语言而言有不同的内存分析
在Perforce服务器上没有启用P4类型的Unicode
字符串操作符更复杂
MSDev在亚洲区域无法正常处理除ASCII之外的字符编码,这就是为什么Unreal在迁入的过程中将文本审核为ASCIIUTF-16
优点
只会访问我们将会需要的所有字符
字符串操作可以进行分割/合并,不需要解析这个字符串
与游戏中使用的格式相同,不需要转换、解析或内存操作
可合并,不需要独占迁出
C# 会在内部使用 UTF-16
缺点
如果这个格式没有 BOM,那么很难检测到它
当游戏检测到字符串是 ASCII 而且就这样将其输出的时候不能工作(现在是在迁入的时候通过 UTF-16 验证器进行检测
MSDev 在亚洲区域无法正常处理除 ASCII 之外的字符编码UE4内部字符串表示
虚幻引擎4中的所有字符串都以 UTF-16 的格式作为 FStrings 或 TCHAR 字符串存储在内存中。大多数代码假设两个字节是一个 codepoint(码点),所以仅支持Basic Multilingual Plane (BMP),可以将 Unreal 的内部编码更精确地描述为 UCS-2。字符串存储按照适于当前平台的字节序进行存储
当把包 到/从 磁盘或网络上进行序列化时,具有所有 TCHAR 字符 < 0xff 的字符串都存储为一连串 8 位字节,其它的字符串存储为 2 位的 UTF-16 字符串。序列化代码可以根据需要处理任何字节序转换
由UE4载入的文本文件
当Unreal加载外部文本文件(比如在运行时读取一个.INI文件)时,这些操作基本上都是通过UnMisc.cpp中的
appLoadFileToString()
函数来完成。主要的工作发生在appBufferToString()
函数中这个函数识别UTF-16文件中的Unicode字节顺序标记(BOM),如果存在的话,将以UTF-16的形式加载文件
BOM 不存在时所发生的现象取决于平台
如果在非Windows平台转换失败,它将仅读取每个字节,并把它填充为16位来构成一组TCHAR
没有代码可以检测或解码使用appLoadFileToString()
函数加载的UTF-8格式编码的贴图文件
Unreal保存的文本文件
大多数引擎产生的文本文件都是使用
appSaveStringToFile()
函数来进行保存通过一个字节可以展现所有TCHAR字符的字符串将被存储为一连串的8位字节;除了
bAlwaysSaveAsAnsi
标志传入的值为true外,其他的字符串都将会存储为UTF-16,在这种情况下,它将首先会被转换为默认的 Window 编码。目前,这个操作仅在着色器文件上执行,用于解决着色器编译器上的 UTF-16 文件的问题Unreal推荐使用的文件编码
INT和INI文件
任何字节排序的 UTF-16 格式。虽然亚洲语言(例如CP932)的默认MBCS编码可以在Windows上运行,但这些文件需要在PS3和Xbox360上加载,并且转换代码只能在Windows上运行
源代码
一般,不推荐 C++ 源码文件中存在的字符串文字,推荐把这些数据放到 INT 文件中
C++源代码
UTF-8或默认的Windows编码。Xbox360 的编译器 MSVC 和 gcc 应该都很容易处理 UTF-8 编码的源文件。Latin-1 编码的文件的字符具有高位集,比如应该避免在源码文件中出现版权、商标或者度数的符号,因为在具有不同编码的系统上,编码将会中断。某些这样的实例在第三方软件中是不可避免的(比如,版权标志),所以,我们仅用了 MSVC 的警告 4819,否则当我们在 Asian Windows 上进行编译时将会出现这个警告
在Perforce中存储UTF-16文本文件
- 请不要使用
文本
格式
- 如果迁入了一个UTF-x文件并把它保存为文本文件,那么在同步后将会发生崩溃
- 如果使用
二进制
格式,那么请把文件标记为独占迁出
- 人们可以迁如ASCII、UTF-8、UTF-16格式的文件,它们是可以在引擎中正常工作的
- 但是,二进制文件不能被融合,所以如果没有把文件标记为独占迁出,那么修改将会重叠到一起
- 如果使用了
UTF-16
格式,请确保其他人没有迁入不是UTF-16的文件
- Unreal有个
Perforce
触发器,它不允许迁入非UTF-16格式的文件作为UTF-16文件
- //depot/UnrealEngine3/Development/Tools/P4Utils/CheckUTF16/
Unicode
类型是UTF-8,当然这里没有什么用途转换机制
我们有许多宏来把字符串转换为各种编码以及从各种编码转换回字符串。这些宏使用在本地范围内声明的类实例并且在栈上分配空间,所以不要保留指向它们的指针是很重要的! 它们的作用仅仅是将字符串传递给调用函数
- TCHAR_TO_ANSI(str)
- TCHAR_TO_OEM(str)
- ANSI_TO_TCHAR(str)
- TCHAR_TO_UTF8(str)
- UTF8_TO_TCHAR(str)
这些使用
UnStringConv.h
中的以下辅助类:
- typedef TStringConversion FANSIToTCHAR;
- typedef TStringConversion FTCHARToANSI;
- typedef TStringConversion FTCHARToOEM;
- typedef TStringConversion FTCHARToUTF8;
- typedef TStringConversion FUTF8ToTCAHR;
当使用
TCHARTOANSI
时,不要假设字节的数量和TCHAR字符串的长度一样也是很重要的。多字节的字符集的每个TCHAR字符可以对应多个字节。如果需要知道最终字符串的字节长度,可以使用辅助类而不是使用宏。比如:
FString String; ... FTCHARToANSI Convert(*String); Ar->Serialize((ANSICHAR*)Convert, Convert.Length()); //FTCHARToANSI::Length()函数返回编码字符串的字节长度,包括null文字
Unicodevs的ToUpper()和ToLower() Non-Trivial
UE4目前只会处理ANSI
针对East Asian编码的C++源码的注意事项
UTF-8和默认的Windows编码都可能导致C++编译器出现问题
默认Windows编码
如果源代码中有EastAsian双字节编码,比如CP932(日文)、CP936(简体中文) 或 CP950(繁体中文),那么当在运行单字节字符代码页(比如CP437 United States)的 Windows 上编译的 C++ 源码时要格外小心
这些 East Asian 字符编码系统为第一字节使用 0x81-0xFE ,为第二个字节使用 0x40-0xFE。在第二个字节中的 0x5C 的值在 ASCII/latin-1 中将会被作为反斜线符号解释,并且它对 C++ 来说有特殊的意义。(如果在一行的末位使用它,将会决定一个字符串中的文字换码顺序及行连续性)。 当在单字节代码页的 Windows 上编译源码时,编译器不关心 East Asian 的双字节编码,这将可能会导致编译错误或者更糟糕的情况,在 EXE 中产生 bug
单行注释:
如果在East Asian编码的注释尾部有0x5c
,这可能会造成代码行的丢失,而导致难以发现的bug或错误
//East Asian编码以0x5c结尾(0x5c会被解释为反斜线,从而连接下一行代码)0x5c import_function(); //这行代码会被上一行的注释连接而丢失
在一个字符串文字中:
使用一个可识别的 0x5c 换码顺序,这可能会导致断裂的字符串或者其它错误
printf("EastAsianCharacterThatContains0x5c'\'AndIfContains0x5cInTheEndOfString0x5c'\'"); function(); printf("Compiler recognizes left double quotation mark in this line as the end of string literal that continued from first line, and expected this message is C++ code.(编译器将会把左侧的双引号作为从第一行连续的字符串文字的尾部,并且编辑器将会把这个消息作为C++代码。)");
在上面的例子中,字符串的尾部有 0x5c 反斜杠,并且下一个字符是双引号,所以换码顺序\”被转换为字符数据中的双引号,并且在下一个双引号或文件结尾之前,编译器将继续把它们当做字符串数据,从而导致了错误
无BOM的UTF-8(某些文本编辑器将 BOM 作为识别标志)
如果源码中有存储为UTF-8的East Asian字符,那么在 East Asian 代码页面 CP949(韩语)、CP932(日语)、CP936(简体中文)或 CP950(繁体中文)的 Windows 上编译 C++ 源码时一定要格外小心
UTF-8 字符编码为 East Asian 字符使用 3 个字节: 为第一个字节使用 0xE0-0xEF 、第二个字节使用 0x80-0xBF 、第三个字节使用 0x80-0xBF。如果没有 BOM,East Asian 的 Windows 的默认编码将把 3 个 UTF-8 编码字节和接下来的字节作为 2 个字节的 East Asian 编码字符,第一个和第二个字节成对组成了第一个 East Asian 字符,第三个字节和接下来的字节成对组成第二个 East Asian 字符。 如果遵循 UTF-8 编码的三个字节在字符串字符或注释中有特殊的意义时,可能会出现一些问题
比如,在一行的注释中:
如果注释文本包含了奇数个 East Asian 字符,并且下一个字符作为注释的结束标志时,丢失的代码将会导致很难发现的 bug 或错误
/*OddNumberOfEastAsianCharacterComment*/ important_function(); /*normal comment*/
在 East Asian 编码页面的 Windows 上,编译器会将 UTF-8 的最后一个字节解码的 East Asian 字符注释和星号‘*’作为一个单独的 East Asian 字符,并且它接下来的字符将仍然作为注释的一部分对待。在上面的例子中,编译器删除了 important_function(),因为它似乎是注释的一部分。 这种行为是非常危险的,而且很难找到丢失的代码
在字符串字符内部:
当在一个字符串文字中有奇数个 UTF-8 编码的 East Asian 字符并且接下来的字符有特殊意义时,这将会导致断开的字符串、错误或警告
printf("OddNumberOfEastAsiaCharacterString"); printf("OddNumberOfEastAsiaCharacterString%d",0); printf("OddNumberOfEastAsiaCharacterString\n");
在 East Asian 字符编码的 Windows 上,编译器会将 UTF-8 最后一字节的解码的 East Asian 字符串和一个字符作为一个单独的 East Asian 字符。如果您幸运,编译器将会出现 “C4819” 警告(如果没有禁用)或者一个错误来提示您该问题。如果您不走运,那么字符串将会被破坏
总结
可以为CV++源码使用UTF-8或默认的WIndows编码,但是知道这些可能出现问题。再次说明,不推荐在C++源码中加入字符串字符,如果必须在C++源码中使用East Asian字符编码,请一定要使用East Asian作为默认字符编码,另一个很好的方法是,使用具有 BOM 的 UTF-8(某些文本编辑器将 BOM 作为 Unicode 识别标志)