我正在努力完成以下任务:
对于任意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)的答案为“是”,为什么我见过的大多数例子都使用unpack()
?
4)在上面的三行代码段中,围绕unpack()
的括号是必要的(将它们留下会导致语法错误)。相反,在该示例中,unpack()
不需要括在括号中(但是如果它们被添加则不会有害)。有人能解释一下原因吗?
编辑/更新以回复ikegami的回答:
当然,我知道字符串是整数序列。但
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标志,但保持字符串的字节不变。带有v标志的sprintf()
输出不同的结果,仅依赖于字符串的UTF8标志,即使字符串的字节保持不变。
答案 0 :(得分:6)
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 Bug 的影响。这些问题的大部分实例都已修复(尽管sprintf
从未遇到此错误)。