【发布时间】:2016-04-19 21:03:25
【问题描述】:
我正在努力完成以下工作:
对于任意 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())执行一次。综上所述,上述sn-p所做的唯一动作就是执行printf "%vX\n", $Text;一次。
因此问题:
2) 像示例中所示将其包装到 for 循环中的原因可能是什么?
最后的问题:
3) 如果问题 1) 的答案是“是”,为什么我看到的大多数示例毕竟使用unpack()?
4) 在上面的三行 sn-p 中,unpack() 周围的括号是必需的(离开它们会导致语法错误)。相比之下,在示例中,unpack() 不需要括在括号中(但如果添加它们也无妨)。谁能解释一下原因?
编辑/更新以回复以下 ikegami 的回答:
当然,我知道字符串是整数序列。但是
a) 这些整数有许多不同的编码,并且在某个字符串的内存区域中的字节取决于编码,即如果我有两个包含完全相同的字符串 字符序列,但是我使用不同的编码将它们存储在内存中,字符串内存位置的字节序列是不同的。
b) 我强烈认为(除了 Unicode)还有许多其他系统/标准将字符映射到整数/代码点。例如,Unicode 代码点 0x3B1 是希腊字母 α,但在其他一些系统中,它可能是德语字母 Ö。
在这种情况下,恕我直言,这个问题完全有道理,但我可能应该更准确并改写它:
如果我有一个字符串$Text,它只包含Unicode 代码点的字符,然后如果我执行printf "%vX\n", $Text;,它会为每个字符打印Unicode 十六进制代码点在所有情况下,特别是(但不限于):
- 不考虑 Perl 对字符串的实际内部编码
- 不管字符串的 UTF-8 标志是什么
-
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() 带有 v 标志输出不同的结果,仅取决于字符串的 UTF8 标志,即使字符串的字节保持不变。
【问题讨论】:
-
对于问题 3,
printf "%vX\n", $Text for unpack('C0A*', $Text);中 for 的上下文是语句修饰符语法,其语法为for LIST。其中第 3 行中的 sn-p 是一个控制块。语法不同。LABEL for VAR (LIST) BLOCK您可以在 perl 文档的 perlsyn 部分中的复合语句和语句修饰符下阅读这些内容 -
您对等效的
for循环几乎是正确的,但是unpack返回一个值的列表,因此循环内将是printf "%vX\n", $_。但是unpack的模板非常奇怪。C规则获取下一个字符的代码点并将其作为整数返回,但是如果重复计数为 0,它将完全无效。然后A规则返回一个字符串,并且重复计数为*它只返回对象字符串的所有其余部分。换句话说,unpack 'C0A*', $Text就是$Text。这是损坏的或故意混淆的代码 -
对于您的问题 #1,我在“Programming Perl”第 3 版一书中发现了对“v”标志的引用...“v”标志对于显示字符的序数值很有用任意字符串: printf "version is v%vd\n", $^V; # Perl 的版本 printf "address is %*vX\n", ":", $addr; # IPv6 地址 printf "bits are %*vb\n", " ", $bits; # 随机位串
-
您能否更明确地说一下从
unpack调用中删除括号的意思?我不希望它在这里有任何区别,我的测试证实了这一点 -
@Chris Doyle:感谢您的回答。在阅读 perldoc perlsyn 时我没有注意到这一点。您完全正确,并且在文档中明确提及。
标签: perl for-loop printf pack unpack