关于 unpack() 和 printf() 中的 v 标志的 Perl 问题

Perl questions regarding unpack() and the v flag in printf()

我正在努力完成以下任务:

对于任意 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 标志的文档说:

"This flag tells Perl to interpret the supplied string as a vector of integers, one for each character in the string. [...]"

不过,它并没有说明 "a vector of integers" 的确切含义。在查看我的示例的输出时,似乎那些整数是 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,将其解包(无论详细意味着什么)和 returns 一个列表,在这种情况下有一个元素,即解压缩的字符串。然后 $_ 使用一个元素遍历该列表(没有在任何地方使用),因此块(即 printf())被执行一次。总而言之,上述代码片段执行的唯一操作是执行 printf "%vX\n", $Text; 一次。

因此问题:

2) 将其包装到示例中所示的 for 循环中的原因是什么?

最后的问题:

3) 如果问题 1) 的答案是 "yes",为什么我看到的大多数示例毕竟使用 unpack()

4) 在上面的三行代码片段中,unpack() 两边的括号是必需的(不加括号会导致语法错误)。相反,在示例中,unpack() 不需要括在括号中(但添加括号也无妨)。谁能解释一下原因?

编辑/更新回复以下池上的回答:

当然,我知道字符串是整数序列。但是

a) 这些整数有许多不同的 encodings,并且某个字符串的内存区域中的字节取决于编码,即如果我有两个字符串包含完全相同的字符序列,但我使用不同的编码将它们存储在内存中,字符串内存位置的字节序列不同。

b) 我强烈认为(除了 Unicode 之外)还有许多其他系统/标准可以将字符映射到整数/代码点。例如,Unicode码位0x3B1是希腊字母α,但在其他一些系统中,它可能是德文字母Ö。

在这种情况下,恕我直言,这个问题很有意义,但我可能应该更准确地重新措辞:

如果我有一个字符串 $Text 只包含 Unicode 代码点的字符,如果我然后执行 printf "%vX\n", $Text;,它会打印 Unicode 在所有情况下 每个字符的十六进制代码点 ,特别是(但不限于):

如果答案是肯定的,那么所有使用 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 "6171"[=13=] [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 "6171"[=13=]
#   CUR = 4
#   LEN = 16
#
# SPRINTF: CE.B1.CF.89
# UTF8 flag: FALSE

我们可以看到 _utf_off 确实删除了 UTF8 标志,但保留了字符串的字节不变。 sprintf() 使用 v 标志输出不同的结果,完全依赖于字符串的 UTF8 标志,即使字符串的字节保持不变。

sprintf '%vX' 不了解代码点或 UTF-8。它只是 returns 字符串表示字符的字符串。也就是说,

sprintf('%vX', $s)

等同于

join('.', map { sprintf('%X', ord($_)) } split(//, $s))

这意味着它输出 s[0], s[1], s[2], ..., s[length(s)-1],十六进制,用点分隔。

它 returns 字符串的字符(整数)与 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.


  1. 如果对于两个相同的字符串,sprintf '%vX' 根据 UTF8 标志更改其输出,将被认为存在 Unicode 错误 。这些问题的大多数实例已得到修复(尽管 sprintf 从未遇到过此错误)。