我正在尝试完成以下任务:
对于任意的 Perl 字符串(无论它是否以 UTF-8 内部编码,以及它是否设置了 UTF-8 标志),从左到右扫描字符串,对于每个字符,打印 Unicode 代码点对于十六进制格式的那个字符。让我自己绝对清楚:我不想打印 UTF-8 字节序列或其他东西;我只想为字符串中的每个字符打印 Unicode 代码点。
起初,我想出了以下解决方案:
#!/usr/bin/perl -w
use warnings;
use utf8;
use feature 'unicode_strings';
binmode(STDOUT, ':encoding(UTF-8)');
binmode(STDIN, ':encoding(UTF-8)');
binmode(STDERR, ':encoding(UTF-8)');
$Text = "\x{3B1}\x{3C9}";
print $Text."\n";
printf "%vX\n", $Text;
# Prints the following to the console (the console is UTF8):
# αω
# 3B1.3C9
然后我看了一些例子,但是没有合理的解释,这让我怀疑我的解决方案是正确的,现在我对我自己的解决方案以及例子都有疑问。
1) Perl 关于 (...)printf 中 v 标志的文档说:
“这个标志告诉 Perl 将提供的字符串解释为一个整数向量,字符串中的每个字符一个。[...]”
但是,它没有说明“整数向量”的确切含义。在查看我的示例的输出时,这些整数似乎是 Unicode 代码点,但我希望有确切知道的人确认这一点。
因此问题是:
1) 我们能否确定从字符串中提取的每个整数都是相应字符的 Unicode 代码点(而不是其他一些字节序列)?
其次,关于我发现的一个例子(略有修改;我不记得我从哪里得到的,也许来自 Perl 文档):
#!/usr/bin/perl -w
use warnings;
use utf8;
use feature 'unicode_strings';
binmode(STDOUT, ':encoding(UTF-8)');
binmode(STDIN, ':encoding(UTF-8)');
binmode(STDERR, ':encoding(UTF-8)');
$Text = "\x{3B1}\x{3C9}";
print $Text."\n";
printf "%vX\n", $Text for unpack('C0A*', $Text);
# Prints the following to the console (the console is UTF8):
# αω
# 3B1.3C9
作为一个 C 和汇编人员,我只是不明白为什么有人会写
printf
示例中所示的语句。根据我的理解,相应的行在语法上等同于:for $_ (unpack('C0A*', $Text)) {
printf "%vX\n", $Text;
}
据我了解,
unpack()
需要 $Text
, 解压它(无论这意味着什么)并返回一个列表,在这种情况下,该列表具有一个元素,即解压后的字符串。然后 $_ 用一个元素遍历该列表(没有在任何地方使用),因此块(即 printf()
)被执行一次。总而言之,上述代码片段所做的唯一操作是执行 printf "%vX\n", $Text;
一度。因此问题是:
2)将其包装到示例中所示的 for 循环中的原因可能是什么?
最后的问题:
3) 如果问题 1) 的答案是"is",为什么我见过的大多数示例都使用
unpack()
毕竟?4) 在上面的三行代码片段中,围绕
unpack()
的括号是必要的(离开它们会导致语法错误)。相比之下,在示例中,unpack()
不需要括在括号中(但即使添加它们也无害)。有人可以解释原因吗?编辑/更新回复下面池上的回答:
当然,我知道字符串是整数序列。但
a) 这些整数有许多不同的编码,某个字符串的内存区域中的字节取决于编码,即如果我有两个包含完全相同字符序列的字符串,但我使用不同的编码将它们存储在内存中,字符串内存位置的字节序列是不同的。
b) 我强烈认为(除了 Unicode)还有许多其他系统/标准将字符映射到整数/代码点。例如,Unicode 代码点 0x3B1 是希腊字母 α,但在其他一些系统中,它可能是德语字母 Ö。
在这种情况下,恕我直言,这个问题是完全合理的,但我可能应该更准确地改写它:
如果我有一个字符串
$Text
它只包含 Unicode 代码点的字符,如果我然后执行 printf "%vX\n", $Text;
, 是否会在所有情况下以十六进制为每个字符打印 Unicode 代码点,特别是(但不限于):use 'unicode_strings'
活跃 如果答案是肯定的,那么所有使用
unpack()
的示例有何意义? ,特别是上面的例子?顺便说一句,我现在记得我从哪里得到的:原始形式在 Perl 的 pack()
中。文档,在关于 C0 和 U0 模式的部分。因为他们正在使用 unpack()
,这样做一定有充分的理由。编辑/更新第 2 号
我做了进一步的研究。以下证明UTF8标志起着重要作用:
use Encode;
use Devel::Peek;
$Text = "\x{3B1}\x{3C9}";
Dump $Text;
printf("\nSPRINTF: %vX\n", $Text);
print("UTF8 flag: ".((Encode::is_utf8($Text)) ? "TRUE" : "FALSE")."\n\n");
Encode::_utf8_off($Text);
Dump $Text;
printf "\nSPRINTF: %vX\n", $Text;
print("UTF8 flag: ".((Encode::is_utf8($Text)) ? "TRUE" : "FALSE")."\n\n");
# This prints the following lines:
#
# SV = PV(0x1750c20) at 0x1770530
# REFCNT = 1
# FLAGS = (POK,pPOK,UTF8)
# PV = 0x17696b0 "\316\261\317\211"\0 [UTF8 "\x{3b1}\x{3c9}"]
# CUR = 4
# LEN = 16
#
# SPRINTF: 3B1.3C9
# UTF8 flag: TRUE
#
# SV = PV(0x1750c20) at 0x1770530
# REFCNT = 1
# FLAGS = (POK,pPOK)
# PV = 0x17696b0 "\316\261\317\211"\0
# CUR = 4
# LEN = 16
#
# SPRINTF: CE.B1.CF.89
# UTF8 flag: FALSE
我们可以看到
_utf_off
确实删除了 UTF8 标志,但保持字符串的字节不变。 sprintf()
with v 标志输出不同的结果,即使字符串的字节保持不变,也仅取决于字符串的 UTF8 标志。
最佳答案
sprintf '%vX'
不了解代码点或 UTF-8。它只返回字符串字符的字符串表示。换句话说,
sprintf('%vX', $s)
相当于
join('.', map { sprintf('%X', ord($_)) } split(//, $s))
这意味着它输出
s[0]
, s[1]
, s[2]
, ..., s[length(s)-1]
, 十六进制,用点分隔。无论
UTF8
的状态如何,它都会返回字符串的字符(整数)。旗帜。这意味着字符串的存储方式(例如,是否设置了 UTF8
标志)对输出没有影响。use Encopde;
$Text1 = "\xC9ric";
utf8::downgrade($Text2);
printf("Text1 is a string of %1\$d characters (a vector of %1\$d integers)\n",
length($Text1));
print("UTF8 flag: ".((Encode::is_utf8($Text2)) ? "TRUE" : "FALSE")."\n");
printf("SPRINTF: %vX\n\n", $Text1);
$Text2 = $Text1;
utf8::upgrade($Text2);
print($Text1 eq $Text2
? "Text2 is identical to Text1\n\n"
: "Text2 differs from Text1\n\n");
printf("Text2 is a string of %1\$d characters (a vector of %1\$d integers)\n",
length($Text2));
print("UTF8 flag: ".((Encode::is_utf8($Text2)) ? "TRUE" : "FALSE")."\n");
printf "SPRINTF: %vX\n\n", $Text2;
输出:
Text1 is a string of 4 characters (a vector of 4 integers)
UTF8 flag: FALSE
SPRINTF: C9.72.69.63
Text2 is identical to Text1
Text2 is a string of 4 characters (a vector of 4 integers)
UTF8 flag: TRUE
SPRINTF: C9.72.69.63
让我们更改您问题中的代码以显示相关信息:
use Encode;
$Text1 = "\x{3B1}\x{3C9}";
printf("Text1 is a string of %1\$d characters (a vector of %1\$d integers)\n",
length($Text1));
printf("SPRINTF: %vX\n\n", $Text1);
$Text2 = $Text1;
Encode::_utf8_off($Text2);
print($Text1 eq $Text2
? "Text2 is identical to Text1\n\n"
: "Text2 differs from Text1\n\n");
printf("Text2 is a string of %1\$d characters (a vector of %1\$d integers)\n",
length($Text2));
printf "SPRINTF: %vX\n\n", $Text2;
输出:
Text1 is a string of 2 characters (a vector of 2 integers)
SPRINTF: 3B1.3C9
Text2 differs from Text1
Text2 is a string of 4 characters (a vector of 4 integers)
SPRINTF: CE.B1.CF.89
它表明
sprintf '%vX'
不同的字符串会有不同的输出,这并不奇怪,因为 sprintf '%vX'
简单地输出字符串的字符。您可以很容易地使用 uc
而不是 _utf8_off
.sprintf '%vX'
根据 UTF8
更改其输出标志,它将被视为遭受 Unicode 错误。大多数实例都已修复(尽管 sprintf
从未遭受过此错误)。 关于关于 unpack() 和 printf() 中的 v 标志的 Perl 问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36729422/