Chinaunix首页 | 论坛 | 博客
  • 博客访问: 469361
  • 博文数量: 142
  • 博客积分: 4126
  • 博客等级: 上校
  • 技术积分: 1545
  • 用 户 组: 普通用户
  • 注册时间: 2008-02-22 10:03
文章分类

全部博文(142)

文章存档

2011年(8)

2010年(7)

2009年(64)

2008年(63)

我的朋友

分类:

2008-12-01 17:34:25

   很多编程语言让你不得不从一个不爽的低层次来思考问题。当你处理多行文本的时候,你必须得处理好指针;当你处理字符串的时候,你必须得处理好字节。这样的编程语言让你分心。不用沮丧,perl并不是这样的低层次编程语言,多行文本跟字符串在perl里面是非常容易处理的。

   Perl是设计用来可以简单但是强大地操纵文本。事实上,Perl可以使用非常多种的方法来操纵文本,这些方法一章根本讲不完。你可以看一下其他章节里面对文本的处理方法, 特别是第6跟第8章,这2章讲了一些这里没有提到的有趣的技术。

   Perl 里面数据的基本单元是scalar(标量),就是指一个单独的值。标量变量可以是字符串,数字或者reference(引用)。多个标量构成的列表称为数组,多个标量相互结合构成哈希(hash)。reference(引用)经常用来间接地引用数据,这个跟低层次语言里面的指针非常像。perl的数字在计算机上通常是以双精度浮点数的方式存储的。字符串在Perl里面可以是任意长的,取决于你的机器的虚拟内存的大小,你可以随便把什么数据都存在字符串里面,甚至是那些包含NULL字节的二进制数据。

   Perl的字符串并不是字符的数组,也不是简单的字节流。你不可以用数组的下标方法来得到字符串里面某个字符的值,你只能使用函数substr。跟其他类型的数据一样,字符串的长度是按需增长的。Perl的垃圾回收系统会在变量已经不再会被使用到的时候回收变量的空间。典型的例子就是在超出变量的作用域的时候或者在计算一个表达式时用到的临时变量在表达式的值求出来后,Perl会回收这些空间。也就是说,Perl自己会管理好内存,你不需要再管它了。

   一个scalar的值要么是已定义的(defined),要么是未被定义的(undefined),如果是已定义的,它可能是一个字符串,一个数字,或者是一个引用。如果它是未被定义的(undefined)的,那么它的值就是undef。也就是说除了undef的值外,其他所有的值包括数字,空字符串等都是已定义的(defined)。一个变量是否是已被定义的,跟它的布尔值是真还是假不是一样的。你要判断一个变量是不是已被定义的,你要用函数defined。一个变量的布尔值是有特别意义的,用操作符&&或者||来测试变量的布尔值,又或者在if跟while语句中判断条件的真假时来测试变量的布尔值。

    已定义的字符串有2种情况下是布尔假值:空字符串跟一个只包含数字0的字符串("0").除此之外的其他已定义字符串(e.g., "false", 15, and \$x) 都是布尔真值。你可能会对字符串"0"竟然是布尔假值感到惊讶,其实这是因为Perl会在字符串和数字之间进行按需转换。这些值:0., 0.00, 0.0000000, 都是数字, 当它们没有用引号括起来的时候都是布尔假值,因为数字0从字面上看就是假值。但是,当用引号把它们括起来的时候("0.", "0.00", "0.0000000"), 或者这些值是从命令行,环境变量,输入文件读进来的时候这些值就是布尔真值。

    这通常不是什么问题,因为当你把值当成数字用的时候,perl会自动做转换。如果这个值你没有当成数字用过,那么当你仅对它进行真假值测试的时候可能会得到意料外的答案--真假值的测试不会自动进行任何类型的转换。给这个值加上0的话就是明确地告诉Perl把这个字符串转换成数字:

 

print "Gimme a number: ";
#输入:0.00000
chomp($n = <STDIN>); # $n now holds "0.00000";

print "The value $n is ", $n ? "TRUE" : "FALSE", "\n";
# That value 0.00000 is TRUE

$n += 0;
print "The value $n is now ", $n ? "TRUE" : "FALSE", "\n";
# That value 0 is now FALSE

 

     把值undef当字符串用的时候它就像是一个空字符串,把它当数字用的时候它就像数字0,把它当引用 用的时候它就像是一个空引用。不管是三种情况中的哪一种,它的布尔值都是假值。如果你把Perl的warning打开的话,当Perl在期待一个已定义值时你给了它一个未定义值,将会在STDERR(标准错误)输出一个运行时的警告消息。如果仅仅是要测试某个值是真值还是假值,并不需要这个值的具体值,所以并不会产生warning消息。有一些对变量的操作就算变量的值是未定义也不会产生warning消息,这些操作包括了自增操作符++,自减操作符--,加等于操作符+=, 点等于操作符.=(dot-equals)。(译者:变量必须处于操作符的左边时才不会产生warning消息)

    在你的程序里面,表示字符串的方式有单引号,双引号,引用操作符q//和qq//,当前文档(here documents)。不管你是用哪种方式,字符串只能是下面2种类型的一种:内插型(interpolated) 跟 非内插型(uninterpolatedSpecify)。内插型字符串管理了是否要扩展字符串引用或者特别的字符串序列。 大多数的字符串默认都是内插型的,比如在模式里面(/regex/),或者运行命令($x = `cmd`)。

    对一些有特别含义的字符,在其前面加上反斜杠\表示这个字符是一个一般的字符。也就是说它这样就变成了一个普通字符(literal)。这就是我们经常提到的转义(escaping),或者叫做反斜杠转义(backslash escaping)。

    得到一个非内插型的字符串的标准方法是使用单引号。在这样的字符串里面有3种特殊的序列是仍然要注意的:'是用来中止字符串的,\'是字符串里面包含的单引号,\\是表示字符串里面的反斜杠\。

 

$string = '\n'; # two characters, \ and an n

$string = 'Jon \'Maddog\' Orwant'; # literal single quotes

 

   双引号里面可以内插变量(不是函数调用,详细见菜谱1.15),会扩展反斜杠转义。包括了 "\n" (换行符), "\033" (8进制值 33), "\cJ" (Ctrl+J), "\x1B" (16进制值0x1B), 等等。完整的转义列表你可以在这里找到:perlop(1) manpage 或者大骆驼书第五章里面的这一节:"Specific Characters"。

 

$string = "\n"; # a "newline" character

$string = "Jon \"Maddog\" Orwant"; # literal double quotes

 

   如果字符串里面没有反斜杠转义跟变量需要扩展,那么你使用哪种类型的字符串(内插或者非内插型)都是一样的。究竟应该采用哪一种类型的字符串呢?有一些Perl程序员比较喜欢用双引号,这样字符串比较显眼(stand out),而且这样避免了一些阅读你代码的人在单引号的字符串里面错认反斜杠的风险。当然不管是哪种字符串,对Perl来说没区别,只是这样能帮助读者更好的阅读你的代码。

    操作符q//跟qq//分别对应于单引号跟双引号引起来的字符串,只是它们允许内插型字符串跟非非内插型字符串有任意的定界符。对于一个包含单引号的非内插型字符串来说,使用q//的话比要在字符串里面对所有的单引号都进行转义要简单多了:

$string = 'Jon \'Maddog\' Orwant'; # embedded single quotes

$string = q/Jon 'Maddog' Orwant/; # same thing, but more legible

 

只要选择相同的字符作为界定符就可以,就像上面用的‘/’符,也可以用一对对称的界定符,如下:

$string = q[Jon 'Maddog' Orwant]; # literal single quotes

$string = q{Jon 'Maddog' Orwant}; # literal single quotes

$string = q(Jon 'Maddog' Orwant); # literal single quotes

$string = q<Jon 'Maddog' Orwant>; # literal single quotes

当前文档(Here documents)这个方法是借用于shell引用大块文本的方法。 文本可以被解释成单引号字符串,双引号字符串,甚至是可以执行的命令,取决于你是怎么样把文本中止符括起来的。非内插型的当前文档不会像单引号字符串那样扩展3种反斜杠转义序列。这里给出一个当前文档的例子(译者:原书的例子不太好理解,这里引用的是大骆驼书中的例子):

 

print <<EOF; # same as earlier example

The price is $Price.
EOF

print <<"EOF"; # same as above, with explicit quotes

The price is $Price.
EOF

print <<'EOF'; # single-quoted quote

All things (e.g. a camel's journey through
A needle's eye) are possible, it's true.
But picture how the camel feels, squeezed out
In one long bloody thread, from tail to snout.
                                -- C.S. Lewis

EOF

 

注意在最后的结束符后没有分号,关于这文档的更多细节,在菜谱1.16中介绍。

 

通用字符码

 

自从计算机被人关注那时起,所有的数据都只是一连串的没关连的数字,每个字符串都只是一串字节流。文本,字符串都只是数字编码的序列,它们可以被那些程序比如浏览器,邮件收发程序,打印程序,编辑器等解释还原成字符。

    回到那个内存容量非常小,内容价格非常贵的年代,程序员总是千方设法的节省内存。那些策略比如用36 bits来存储6个字符或者用16 bits 来存储3个字符在那时是很普遍的。时至今天,对字符的数字编码通常不会超过7或者8个bits, 而7跟8 bits则分别是ASCII 跟 Latin1 的数字编码长度。

    由于一个字符并没有占用很多的bits,所以字符的总数量也是不多的。举个例子,一个8bit色的图像文件,它的调色版最多就只有256种颜色。同样,由于字符是当成8位字元(octet, 8 bits, 就是1个字节)存储的,一份文档也最多有256种不同的字符,标点符号,其他符号等。

    ASCII, 当时是美国信息交换的标准编码,在美国之外并没有发挥多大的作用,因为它只是覆盖了去掉一些方言字符之后的美国英语字符。因此,很多国家在7 bits的ASCII基础上发明了跟其他国家不兼容的8 bits编码。在这样的一个限制下(8 bits),发生了很多为字符指定数字编码的冲突方案。也就是说,一个数字编码在不同的系统上可能表示了不同的字符,一个字符在不同的系统上可能是不同的数字编码。

   那是因为如果你想生成一份用了Latin, Greek, 跟 Cyrillic字符的文档,你会觉得有困难,因为一样的数字编码在不同的系统下是不同的字符,举个例子,数字编码196在ISO 8859-1 (Latin1)标准里面它是一个上面有分音符号的拉丁大写字母A,而在ISO 8859-7标准下这个数字编码表示的是一个希腊大写字母D ,所以在ISO 8859-1标准下,某个程序会把这个数字编码解释成一个字符,而在另外一个当地标准ISO 8859-7 下,这个程序会把这个数字编码解释成另外一个完全不一样的东西。

    这点导致了要在一个文档里面合并不同的字符集很难。就算你做了一些小修小补把它们勉强合在一起,也很少有程序能正常的操作这个文档的文本。想知道这个是什么字符,必须得先知道你处于一个采用什么编码标准的系统里面,你不能很简单的合并这些系统。如果你猜错了编码标准,那么你的屏幕上显示的可能就是乱码,或者出现错误。

 

PerlUnicode支持

 

Unicode 试图把所有的字符集统一成一个整体,包括那些符号甚至于那些虚构的字符集。在Unicode下面,不同的字符有不同的数字编码,称之为code point。

    混合语言的文档现在是很容易实现的了,而在以前这是不可能实现的。你在一个文档里面不再仅仅可以使用128个或者256个字符了。有了Unicode,在一个文档里面你就可以没有任何混乱的使用好几万个的不同字符了。

    刚才那个混合字符集的问题:一个Ä跟一个D。第一个字符,在Unicode下面正式点的话叫做"LATIN CAPITAL LETTER A WITH DIAERESIS",它的code point是U+00C4。而第2个字符,叫 "GREEK CAPITAL LETTER DELTA", 它的code point现在是U+0394。不同的字符赋予不同的code point,就不会再有冲突了。

    Perl从5.6版本之后开始支持Unicode,但是在5.8版本发行之后对Unicode的支持才算是健壮和易于使用的。其实在unicode编码支持被引入perl的同时引用i/o层次的通信不是一个巧合。更多的细节请看第8章。


    所有Perl对字符串的函数,操作符,包括那些用来模式匹配的,都是作用于单个字符上面的,而不是对一个8元字元进行操作。如果你要得到一个字符串的长度,Perl给你的是这个字符串有多少个字符,而不是它有多少个字节。如果你用substr方法取得某个字符串的前3个字符,你得到的可能是3个字节又或者不是3个字节。你不知道,你也不需要管。不需要关心底层的宽字节表达方式的一个原因是如果你不得不注意这些东西的话,你可能是过于关注了。这些都不应该是什么问题,如果它是一个问题的话,那可能是Perl本身的实现有一些问题,我们正是为此而工作。

    因为支持超过256个使用code points编码的字符,所以函数chr不再局限于传入小于256的参数,同理,ord函数也不再局限于返回一个小于256的数字。举个例子,chr(0x394),结果是返回希腊的大写字母:D。

 

 

$char = chr(0x394);
$code = ord($char);
printf "char %s is code %d, %#04x\n", $char, $code, $code;

#char D is code 916, 0x394

 

    如果你测试一下这个字符串的长度,它会告诉你是1,因为它只包含了一个字符,注意是字符,不是字节。当然在它的内部实现里头这个字符的数字编码肯定超过8bits。但是程序员是在比较抽象的层次上对字符进行处理,而不是物理层次上的8位字元。低层次的实现就交给Perl来就可以了。

    你不应该把字符跟字节等同起来。这些把字节跟字符转来转去的程序员,应该跟那些随便把数字跟指针转来转去的C程序员一样都是有过错的。就算底层的表现形式在一些系统上跟抽象层次上的表现形式碰巧一致,但也只是巧合而已。物理层上的实现跟抽象层上的接口拼合起来的时候往往最后会把你搞晕。

    你有几种方法在Perl的字符串里面嵌入Unicode字符。或许你幸运的话你有一个可以在程序里面直接输入Unicode字符的文本编辑器,你可以通过use utf8 来告诉Perl你已经嵌入了Unicode。还有一种方法就是在内插型字符串里面使用字符的16进制code point 的\x转义,比如在字符串里面嵌入\xC4.如果字符的code point超过了0xFF,那么就需要超过2个16进制的数字来表示了,你得用花括号把这些编码括起来。

 

print "\xC4 and \x{0394} look different\n";

#char Ä and look different\n

 

   菜单1.5 描述了如何使用字符的名字把\N{NAME}这样的转义嵌入到字符串里面,比如\N{GREEK CAPITAL LETTER DELTA}, \N{greek:Delta}, 或者 \N{Delta}都表示了字符 D 。


    现在这些东西已经足够你在Perl里面开始使用Unicode了,但是要Perl能跟其他的一些程序结合到一块还需要其他更多的一些东西。

    使用老的单字节编码比如ASCII 或者 ISO 8859-n,当你写出一个数字编码是NN的字符时,就会出现一个数字编码是NN的单字节。具体出现成什么样子取决于你使用了什么字体,你的本地设置以及其他几种因素。 而在unicode下,这完全重复的逻辑性质的数字到物理字节排放不再适用。相反,他们必须被编码的几个可见输出格式中。

    Perl内部使用了UTF-8的编码方式,同时由于存在其他很多种Unicode的编码方式,Perl也照样可以工作得很好。use encoding 告诉Perl你的脚本是什么编码方式,标准文件句柄应该使用何种编码。use open 可以设置所有句柄的默认编码方式。在open或者binmode的时候传入特定的参数可以为该句柄指定特定的编码格式。命令行里面的-C 标志可以设置所有(或者仅仅是标准)句柄的编码方式,还有命令行本身的参数的编码方式也可以设置。可以从环境变量PERLIO, PERL_ENCODING, 和 PERL_UNICODE里面得到Perl编码的相关信息。

 

阅读(248) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~