到底什么是Perl字符串?

11
我找不到有关Perl字符串数据存储方式的基本描述!所有文档都好像默认我已经知道这些。我知道encode()、decode(),我知道可以将原始字节读入Perl“字符串”中并再次输出而不会被Perl弄乱。我知道打开模式。我也知道Perl必须使用某种内部格式来存储字符字符串,并且可以区分字符和二进制数据。请问这个在哪里有记录??
等价问题是:给定Perl代码:
$x = decode($y);

解码成什么和从什么解码?

据我所知,字符串数据结构上必须有一个标志,指示这是二进制异或字符数据(某种内部格式的超集,顺便提一下,它是 Unicode 的超集 - http://perldoc.perl.org/Encode.html#DESCRIPTION)。但我希望文档中能够说明或在此处得到确认或否定。


2
perldoc perlguts会提供一些信息。请注意,字符串通常并不是神奇的;有时候只是看起来像而已。 - Jonathan Leffler
谢谢Jon,这是目前为止最好的答案。perlguts听起来很有趣。相信我,我不相信魔法,特别是当涉及到Perl时。 - spinkus
我也发现这篇文章很有帮助。 - Krishnachandra Sharma
5个回答

18
这是一个很好的问题。为了调查,我们可以使用Devel::Peek来深入了解存储在我们的字符串(或其他变量)中的实际内容。
首先,让我们从ASCII字符串开始。
$ perl -MDevel::Peek -E 'Dump "string"'
SV = PV(0x9688158) at 0x969ac30
  REFCNT = 1
  FLAGS = (POK,READONLY,pPOK)
  PV = 0x969ea20 "string"\0
  CUR = 6
  LEN = 12

然后我们可以打开Unicode IO层并执行相同的操作。
$ perl -MDevel::Peek -CSAD -E 'Dump "string"'
SV = PV(0x9eea178) at 0x9efcce0
  REFCNT = 1
  FLAGS = (POK,READONLY,pPOK)
  PV = 0x9f0faf8 "string"\0
  CUR = 6
  LEN = 12

接下来,让我们尝试手动添加一些宽字符

$ perl -MDevel::Peek -CSAD -e 'Dump "string \x{2665}"'
SV = PV(0x9be1148) at 0x9bf3c08
  REFCNT = 1
  FLAGS = (POK,READONLY,pPOK,UTF8)
  PV = 0x9bf7178 "string \342\231\245"\0 [UTF8 "string \x{2665}"]
  CUR = 10
  LEN = 12

从这个例子中,你可以清楚地看到 Perl 已经正确地解释了 utf8。问题在于,如果我不使用 \x{} 转义序列来指定八进制值,那么它的表示就更像是一个普通的字符串。

$ perl -MDevel::Peek -CSAD -E 'Dump "string ♥"'
SV = PV(0x9143058) at 0x9155cd0
  REFCNT = 1
  FLAGS = (POK,READONLY,pPOK)
  PV = 0x9168af8 "string \342\231\245"\0
  CUR = 10
  LEN = 12

所有Perl看到的都是字节,无法知道你将它们视为Unicode字符,与上面输入转义八进制数不同。现在让我们使用decode并看看会发生什么。
$ perl -MDevel::Peek -CSAD -MEncode=decode -E 'Dump decode "utf8", "string ♥"'
SV = PV(0x8681100) at 0x8683068
  REFCNT = 1
  FLAGS = (TEMP,POK,pPOK,UTF8)
  PV = 0x869dbf0 "string \342\231\245"\0 [UTF8 "string \x{2665}"]
  CUR = 10
  LEN = 12

现在你可以看到,字符串的内部表示与使用 \x{} 转义时输入的内容匹配,这是正确的。实际上,它是从字节解码为字符,但当您看到 Peek 输出时,我认为更有意义。最后,您可以使用 utf8 pragma 来使 Perl 将源代码视为 utf8。
$ perl -MDevel::Peek -CSAD -Mutf8 -E 'Dump "string ♥"'
SV = PV(0x8781170) at 0x8793d00
  REFCNT = 1
  FLAGS = (POK,READONLY,pPOK,UTF8)
  PV = 0x87973b8 "string \342\231\245"\0 [UTF8 "string \x{2665}"]
  CUR = 10
  LEN = 12

6
与其标量变量的流动的字符串/数字状态类似,Perl字符串的内部格式是可变的,并且取决于字符串的内容。
请查看perluniintro,其中提到:
在内部,Perl当前使用平台本地的八位字符集(例如Latin-1),默认为UTF-8,来编码Unicode字符串。具体而言,如果字符串中的所有代码点都小于或等于0xFF,则Perl使用本地的八位字符集。否则,它使用UTF-8。
这意味着像"我有£两个"这样的字符串会被存储为(字节)I have \x{A3} two。(英镑符号是U+00A3。) 现在,如果我附加一个多字节Unicode字符串,如U+263A——一个微笑的脸——Perl将在附加新字符之前将整个字符串转换为UTF-8,产生(字节)I have \xC2\xA3 two\xE2\x98\xBA。再次删除此最后一个字符,字符串将保留为UTF-8编码,即`I have \xC2\xA3 two

但我想知道你为什么需要知道这个。除非您正在使用C编写XS扩展程序,否则内部格式对您来说是透明且不可见的。


1
谢谢。我同意“字符串”和“字符”的编码是透明的。我看到字节序列和字符序列 - Perl对待它们不同,但都存储在相同的数据类型中。我主要想知道Perl是如何做到这一点的,现在我有了答案。从一开始,这似乎对我来说是有问题的,而给出的答案表明确实存在问题,因此值得了解 :) - spinkus
Perl没有一种数据类型是“字节序列”:字符串是字符序列。它在什么方面具有“问题”? - Borodin
基本上,您必须记住您的字符串是否包含字节或字符。- @amon。 - spinkus
这适用于数据的每一次修改。一个数乘以十超过一次,结果就会不同。decodeencode分别用于准备输入和输出数据:你要告诉Perl外部数据是如何编码的,否则它无法知道。对已经处于内部格式的字符串进行解码是没有意义的,如果你无法跟踪它是否已经完成,那么你就是个糟糕的程序员。 - Borodin
我真的认为你没有理解底层编码是不可见和无关紧要的。你需要使用像Devel::Peek这样的东西(或者编写一个Perl的C扩展)才能看到它。唯一需要调用decode的时候是当你读取的外部数据不是字节流时。同样,只有在你的外部设备期望编码数据或者当你有大于\xFF的“宽”字符时,你才需要调用encode来指定使用哪种编码。这几乎不能再简单了。 - Borodin
显示剩余3条评论

2
Perls内部字符串格式是实现相关的,但通常是UTF-8的超集。因为您可以使用decode和encode将字符串从内部格式转换为其他编码格式,反之亦然,所以这并不重要。
Decode将字符串转换为Perls内部格式,而encode则将其从Perls内部格式转换出来。
二进制数据在内部存储方式上与字符0到255相同。
Encode和decode只是在不同格式之间进行转换。例如,UTF8编码意味着每个字符只会使用Perl字符值0到255的八位字节表示,即该字符串由UTF8八位字节组成。

2
简短的回答:这很混乱。
稍微长一点:程序员看不出区别。
基本上,你必须记住字符串中包含字节还是字符,其中字符是Unicode代码点。如果你只遇到ASCII,那么区别是看不见的,这是危险的。
数据本身和表示该数据的方式是不同的,不应混淆。字符串在概念上是一系列代码点,但在内存中表示为字节数组,在编码时表示为某个字节序列。如果你想在字符串中存储二进制数据,你需要将代码点的数量重新解释为字节值,并且限制代码点在0-255之间。
(例如一个文件没有编码。该文件中的信息具有某种编码(无论是字符级别的ASCII、UTF-16还是EBCDIC,还是应用程序级别的Perl、HTML或者.ini))
字符串的确切存储格式是无关紧要的,但是你可以在这样的字符串中存储完整的整数:
# this will work if your perl was compiled with large integers
my $string = chr 2**64; # this is so not unicode
say ord $string; # 18446744073709551615

内部格式会相应地进行调整以容纳这些值;普通字符串不会每个字符占用一个整数。

我认为你的区分并没有太多意义,因为基于这个标准,没有数据具有编码:只有信息具有编码。大多数人用“数据”和“信息”表示同样的意思,而且谈论没有编码的任何内容都没有意义或含义。 - Borodin

-2
Perl 可以处理比 Unicode 更多的内容,因此非常灵活。有时您想要与某些无法处理的东西进行接口,因此可以使用 encode(...) 和 decode(...) 处理这些转换。请参见 http://perldoc.perl.org/utf8.html

1
转换从什么到什么?内部表示是什么?这是我的问题。你说字符的内部表示是Unicode的某个超集?这与我阅读过的一些文档一致 http://perldoc.perl.org/Encode.html#DESCRIPTION。好的。那么二进制数据如何存储在字符串中? - spinkus
这真的没有回答OP所问的任何问题。 - friedo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接