处理符号和代码页
由于字符串是由字符组成的,有时候需要在整数代码层级操纵字符串中的单个字符或字符组,或者说这样更方便。例如,需要一次一个地读取或替换一个字符,或者将它们转换为整数代码数组,以便通过通信协议进行传输,或者转换为 动态库 DLL 的第三方编程接口。在所有这些情况下,将字符串作为文本传递可能面临各种困难:
- 确保正确编码(有很多种编码,具体的选择取决于操作系统区域设置、程序设置、与之进行通信的服务器的配置等因素)
- 本地文本编码与 Unicode 的国家/地区语言字符之间的相互转换
- 以统一方式进行内存分配和解除分配
使用包含整数代码的数组(此类使用实际上生成了字符串的二进制表示而不是文本表示)会让这些问题变得简单。
MQL5 API 提供了对单个字符或字符组进行运算的一系列函数,这些函数已考虑到了编码特性。
MQL5 中的字符串包含两字节 Unicode 编码的字符。以单个(但非常大)字符表为所有各个国家的字母表提供了通用支持。两个字节即允许 65535 个元素的编码。
默认字符类型为 ushort。但在必要时,可将字符串以特定语言编码转换为单字节 uchar 字符系列。这种转换可能伴随某些信息的丢失(尤其是不在本地化字符表中的字母可能“丢失”变音符或甚至“转变”为某种替代字符:取决于上下文,其可能以不同方式显示,但通常显示为 ' ?' 或方块字符)。
为避免可能包含任意字符文本的问题,建议始终使用 Unicode。如果应与 MQL 程序集成的某些外部服务或程序不支持 Unicode,或者如果该文本从一开始就打算用于存储有限的字符集(例如,仅数字和拉丁字母),则可以例外。
与单字节字符相互转换时,MQL5 API 默认使用 ANSI 编码,具体取决于当前 Windows 设置。然而,开发人员可指定不同的码表(参见函数 CharArrayToString、StringToCharArray)。
这些函数的使用示例在 StringSymbols.mq5 文件中提供。
bool StringSetCharacter(string &variable, int position, ushort character)
该函数将传递的 variable 字符串中 position 处的字符更改为 character 值。数字必须在 0 到字符串长度 (StringLen) 减 1 之间。
如果要写入的字符为 0,则其指定一个新行结束(作为终止符零),即行长度变为等于 position。为该行分配的缓冲区大小不会改变。
如果 position 参数等于字符串长度,并且要写入的字符不等于 0,则该字符被添加到字符串,其长度增加 1。这等效于表达式:variable += ShortToString(character)。
如果成功完成,该函数返回 true,如果出错,则返回 false。
void OnStart()
|
ushort StringGetCharacter(string value, int position)
该函数返回位于字符串中指定位置的字符的代码。位置编号必须在 0 到字符串长度 (StringLen) 减 1 之间。如果出错,该函数将返回 0。
该函数等效于使用运算符 '[]’ 写入:value[position]。
string numbers = "0123456789";
|
string CharToString(uchar code)
该函数将字符的 ANSI 码转换为单字符字符串。根据设置的 Windows 代码页,代码的上半部分(大于 127)可能生成不同的字母(字符样式不同,而代码保持相同)。例如,代码为 0xB8(十进制数 184)的符号在西欧语言中表示变音符,而在俄语中,此位置是字母 'ё'。再举一个例子:
PRT(CharToString(0xA9)); // "©"
|
string ShortToString(ushort code)
该函数将字符的 Unicode 码转换为单字符字符串。对于 code 参数,可以使用字面量或整数。例如,希腊大写字母 "sigma"(数学公式中的求和符号)可指定为 0x3A3 或 'Σ'。
PRT(ShortToString(0x3A3)); // "Σ"
|
int StringToShortArray(const string text, ushort &array[], int start = 0, int count = -1)
该函数将字符串转换为 ushort 字符码系列并拷贝到数组中的指定位置:从编号为 start(默认为 0,即数组开头)的元素开始,并且数量为 count。
请注意,start 参数指数组中的位置,而不是字符串中的位置。如果想转换字符串的某一部分,必须首先使用 StringSubstr 函数来提取这一部分。
如果 count 参数等于 -1(或者 WHOLE_ARRAY),则将拷贝截至字符串末尾的所有字符(包括终止符 NULL)或者根据数组大小确定的字符数量(如果为固定大小)。
对于动态数组,如果必要,其大小将自动增加。如果动态数组的大小大于字符串的长度,则该数组的大小不减少。
要拷贝没有终止 null 的字符,必须将 StringLen 作为 count 自变量进行显式调用。否则,数组的长度将比字符串的长度大 1(而在最后一个元素为 0)。
该函数返回拷贝的字符数。
...
|
请注意,如果拷贝位置大于数组大小,则中间元素将被分配,但不会初始化。因此,它们可能包含随机数据(上面以黄色高亮显示的部分)。
string ShortArrayToString(const ushort &array[], int start = 0, int count = -1)
该函数将数组带字符码的部分转换为字符串。数组元素的范围分别由 start(表示起始位置)和 count(表示数量)参数设置。start 参数值必须在 0 到数组元素数量减 1 之间。如果 count 等于 -1(或 WHOLE_ARRAY),则将拷贝截至数组末尾或截至第一个 null 的所有元素。
使用来自 StringSymbols.mq5 的相同示例,我们尝试将一个数组转换为大小为 30 的 array2 字符串。
...
|
由于在 array2 数组中,字符串 "ABCDEABCD" 被拷贝两次,具体为,第一次拷贝到开头位置,第二次拷贝到偏移 20 的位置,因此中间字符将为随机,能够形成一个比之前更长的字符串。
int StringToCharArray(const string text, uchar &array[], int start = 0, int count = -1, uint codepage = CP_ACP)
该函数将 text 字符串转换为一个单字节字符系列并拷贝到数组中的指定位置:从编号为 start(默认为 0,即数组开头)的元素开始,并且数量为 count。拷贝过程将字符从 Unicode 转换为选定代码页 codepage 默认为 CP_ACP,即 Windows 操作系统语言(下文会详细介绍)。
如果 count 参数等于 -1(或 WHOLE_ARRAY),则将拷贝截至字符串末尾的所有字符(包括终止符 NULL)或者根据数组大小确定的字符数量(如果为固定大小)。
对于动态数组,如果必要,其大小将自动增加。如果动态数组的大小大于字符串的长度,则该数组的大小不减少。
要复制没有终止 null 的字符,必须将 StringLen 作为 count 自变量进行显式调用。
该函数返回拷贝的字符数。
参见文档中 codepage 参数的有效代码页列表。下面是一些广泛使用的 ANSI 代码页:
语言 |
代码 |
---|---|
中欧拉丁语 |
1250 |
西里尔语 |
1251 |
西欧拉丁语 |
1252 |
希腊语 |
1253 |
土耳其语 |
1254 |
希伯来语 |
1255 |
阿拉伯语 |
1256 |
波罗的海语 |
1257 |
因此,在采用西欧语言的计算机上,CP_ACP 为 1252,而在采用俄语的计算机上,则为 1251。
在转换过程中,某些字符在转换后可能损失信息,因为 Unicode 表比 ANSI 表大得多(每个 ANSI 码表有 256 个字符)。
在这方面,CP_UTF8 在所有 CP_*** 常量中特别重要。它可以通过可变长度编码确保国家字符正确保留:生成的数组仍然存储字节,但每个国家字符可以跨多个字节,以特殊格式写入。因此,数组长度可能比字符串长度大得多。UTF-8 编码广泛用于互联网及各种软件。顺便说一下,UTF 代表 Unicode Transformation Format(统一码转换格式),还有其它修改格式,尤其是 UTF-16 和 UTF-32。
在我们熟悉“逆”函数 CharArrayToString 之后,我们将探讨 StringToCharArray 的示例:它们的工作原理必须共同演示。
string CharArrayToString(const uchar &array[], int start = 0, int count = -1, uint codepage = CP_ACP)
该函数将字节数组或其一部分转换为字符串。数组必须包含特定编码的字符。数组元素的范围分别由 start(表示起始位置)和 count(表示数量)参数设置。start 参数值必须在 0 和数组元素数量之间。当 count 等于 -1(或 WHOLE_ARRAY),则截至数组末尾或截至第一个 null 的所有元素被复制。
我们来了解函数 StringToCharArray 和 CharArrayToString 如何处理具有不同代码页面设置的不同国家字符。为此准备了测试脚本 StringCodepages.mq5。
将使用两行作为测试对象,分别是俄语和德语:
void OnStart()
|
我们将其复制到数组 bytes1 和 bytes2,然后将其还原为字符串。
首先,我们使用欧洲代码页 1252 转换德语文本。
...
|
在欧洲版本的 Windows 上,这等效于使用默认参数的较简单函数调用,因为其中 CP_ACP = 1252:
StringToCharArray(german, bytes1); |
然后我们使用以下调用将文本从数组还原,确保一切与原始内容匹配:
...
|
现在我们尝试以相同的欧洲编码转换俄语文本(或者可以在 CP_ACP 设置为 1252 作为默认代码页的 Windows 环境中调用 StringToCharArray(english, bytes2)):
...
|
你会看到转换期间出现问题,因为 1252 没有西里尔语。从数组还原字符串可清晰看到本质:
...
|
我们在条件性俄语环境中重复试验,即我们使用西里尔语代码页 1251 双向转换字符串。
...
|
这样,单字节编码的脆弱性就显而易见了。
最后,我们为两个测试字符串均启用 CP_UTF8 编码。不论 Windows 设置如何,示例的这一部分将能够稳定工作。
...
|
请注意,两个 UTF-8 编码的字符串需要的数组大小均超过 ANSI 编码字符串需要的数组。此外,俄语文本的数组实际上长度已变为 2 倍,因为所有字母现在占用 2 个字节。有兴趣者可在开源代码中找到有关 UTF-8 编码具体如何工作的详细信息。在本书中,重要的是 MQL5 API 提供了现成可用的函数。