perldata

名称

perldata - Perl 数据结构


描述


变量名字

Perl 有三种数据结构: 数值, 数值的数组, 还有数值的关联数组, 即``哈希表''. 普通的数组以数字为索引, 从 0 开始(负值的下标从数组尾部开始计算). 哈希表 中的元素是以字符串索引.

数值变量以 '$' 打头, 当引用数组中的一个元素时也一样. 意思是"这". 举例:

$days # 数值变量 "days" $days[28] # 数组 @days 的第29个元素 $days{'Feb'} # 哈希表 %days 中 'Feb' 代表的数值 $#days # 数组 @days 的最大下标

当表示整个数组或数组的一部分时, 用 '@' 打头, 意思是 "这些" 或 "那些"

@days # ($days[0], $days[1],... $days[n]) @days[3,4,5] # 即 @days[3..5] @days{'a','c'} # 即 ($days{'a'},$days{'c'})

当表示整个哈希表时用 '%' 打头:

%days # (key1, val1, key2, val2 ...)

此外, 子过程用 '&' 打头, 当不致引起混淆的时候可以省略. 符号表表项 用 '*' 打头, 手册后面部分有详细说明.

不同的变量类型有自己的名字空间. 为一个数值变量, 一个数组和一个哈希表(一个 文件句柄, 一个子过程, 一个标号)取相同的名字并不会引起冲突. 也就是说, $foo@foo 是两个不同的变量. $foo[1]@foo 的一部分, 而不是 $foo 的一部分. 看起来也许有点怪, 但要习惯它.

既然变量和数组名都是以 '$', '@', 或 '%' 打头, 那些 ``保留字'' 实际上并非对 变量而言. (它们实际上是对标号和文件句柄而言, 标号和文件句柄是没有特殊的打 头字母. 给一个文件句柄取名 ``log'' 是错误的. 应该用 open(LOG,'logfile') 而非 open(log,'logfile') . 用大写字母来表示文件句柄也增加了可读性, 避免和将来出现的保留字冲突.) 大小写是区分的 -- ``FOO'', ``Foo'' 和 ``foo'' 是完全不同的名字. 以字母或 下划线开头的名字可以包含数字和下划线.

一个以字母数字组成的名字代表的变量可以用一个返回同类型的引用的表达式代替, 详见 perlref .

数字开头的名字只能包含更多的数字. 不是以字母, 数字, 下划线开头的名字 只能有一个字符. 例如: $%$$ . (大部分这些单字符名字是 Perl 的预定义变量, 例如, $$ 是当前进程号.)


上下文

Perl 对操作和变量值的解释有时依赖于上下文. 主要的上下文有两种: 数值和列表. 某些操作, 如果上下文期待的是列表, 就返回列表结果, 对期待数值的上下文就 返回数值(如果某种操作有这种上下文依赖性, 会在有关文档中说明). 换句话说, Perl 会根据期待的结果是单个或多个重载一些操作.

与此相对的, 一个操作会为它的每一个参数确定上下文. 例如

int( <stdin> )

取整数操作为 <STDIN> 提供了一个数值上下文, <STDIN> 在 STDIN 读入一行并 传送给取整数操作, 后者求出这一行代表的整数值并返回. 而下面的例子

sort( <stdin> )

排序操作为 <STDIN> 提供了一个列表上下文, 从 STDIN 中读入所有的行直到 文件结束, 这些行组成的列表被传送到排序操作, 后者对它们排序并返回排序后 的行列表.

赋值操作是用等号左边的参数来确定右边参数的上下文. 为数值变量赋值的操作给 右边参数确定了数值上下文, 给数组或数组的一部分赋值的时候右边参数是处于列表 上下文.

自定义的子过程可以自行确定被调用时的上下文, 但很多场合下不需要这么做, 因为数值可以自动转化成列表. 参看 wantarray .


数值变量

Perl 里的数据要么是数值型, 要么是数值型的数组, 要么是数值型的哈希表. 数值变量可以存放不同类型的单个数据, 比如数字, 字符串, 和引用. 一般情况下, 不同类型之间的转换是透明的. (一个数值型不能存放多个值, 但可以存放对数组或 哈希表的引用.) 由于自动类型转换, 返回数值型的操作和函数不需要担心(实际上是 不能担心)上下文是等待一个数字还是一个字符串.

数值型不必确定自己的类型. 其实也没有地方去把一个数值型变量声明为``string'', 或 ``number'', 或``filehandle'', 或是其它什么类型. 在 Perl 中, 数值型变量的类型可以是 数字或字符串或引用, 根据上下文确定. 字符串和数字实际上没什么两样, 但引用是 不可转换的指针, 有内建的引用计数和析构过程.

非空的字符串或非 0 (字符串``0'')的数字可以表示布尔类型中的真值. 布尔上下文是一种特殊的数值型上下文.

空数值型实际上有两种情况: 已定义和未定义的. 当没有任何实际的值存在时, 未定义的空数值型 被返回, 比如发生错误的时候, 或者读到文件结束的地方, 或者引用了一个未初始化的变量. 当一个未定义的空数值型首次被使用时, 就变成已定义的, 在此之前可以用 defined() 去检查一个值是否被定义了.

要知道一个字符串是否一个有效的非 0 数字, 一般是确定它不是数值 0 或 字符串 ``0''

if ($str == 0 &amp;&amp; $str ne &quot;0&quot;) { warn &quot;That doesn't look like a number&quot;; }

别的方法是用正则表达式进行检查, 参看 perlre 中对正则表达式的详细介绍.

warn &quot;has nondigits&quot; if /\D/; warn &quot;not a whole number&quot; unless /^\d+$/; warn &quot;not an integer&quot; unless /^[+-]?\d+$/ warn &quot;not a decimal number&quot; unless /^[+-]?\d+\.?\d*$/ warn &quot;not a C float&quot; unless /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/;

数组的长度是一个数值型. 数组 @days 的长度是 $# days, 的值, 和 csh 里一样. (实际上这不是数组的长度, 是最大的下标, 因为有数组里第 0 个元素.) 给 $# days 赋值可以改变数组的长度. 用同样的方法缩短数组的长度会破坏被截去的数值. 重新增加被缩短的数组不再能恢复 这些数值(在 Perl 4 里是可以的, 但为了保证正确的调用析构做了这样的改变). 给超 出最大下标的数组元素赋值可以扩展数组的长度, 给数组赋上空的列表 () 就把数组的 长度减为 0 .

@whatever = (); $#whatever = $[ - 1;

如果在数值型上下文里计算数组的值, 会得到数组的长度. (并不适用于列表, 会返回 最后一个值, 象 C 的逗号操作符) 以下的等式是成立的:

scalar(@whatever) == $#whatever - $[ + 1;

Perl 第 5 版改变了 $[ 的语意: 不设置 $[ 的文件不用担心别的文件会否改变自己的 $[ 的值. (换句话说, 不赞成使用 $[) 所以一般可以认为

scalar(@whatever) == $#whatever + 1;

有些程序员选择显式的转换:

$element_count = scalar(@whatever);

如果在数值型上下文里计算哈希表的值, 如果哈希表里有key/value对, 会得到一字符串, 其中包含了该哈希表占据的空间和已经分配的空间, 用 '/' 分开. 这可以检查一下 Perl 的散列算法是否有效. 比如, 在哈希表里存放了 10,000 个元素, 而在数值型上下文里计算 %HASH 得到 ``1/16'', 那情况就太浪费空间了.


数值型的值的表示

数字用习惯的浮点数或整数表示法:

12345 12345.67 .23E-10 0xffff # 16 进制 0377 # 8 进制 4_294_967_296 # 用下划线容易阅读

字符串一般用单引号或双引号括住. 引号的作用和 shell 里类似: 双引号字符串中可以有 反斜杠和变量替换; 单引号字符串不行(除了 ``\''' 和 ``\\''). 一般的 Unix 反斜杠替换规则同样用来表示换行符, 制表符...等等. 见列表qq.

换行符可以直接嵌入在字符串中. 但如果忘记了结尾的引号, 直到 Perl 找到另外一行有引号的 行前不会报出任何错误. 字符串中的变量替换限制于数值型, 数组和数组的部分. (即以$ @ 打头的标识符, 后跟一个可选的括起来的下标) 下面的代码打印出 "The price is $100."

$Price = '$100'; # not interpreted print &quot;The price is $Price.\n&quot;; # interpreted

和在某些 shell 下一样, 可以用花括号把标识符括住, 以区别于其他后随的字母. 实际上, 花括号中的标识符一定是个字符串, 就象一个哈希表的下标. 早先的例子

$days{'Feb'}

可以写成

$days{Feb}

引号是会被自动加上, 而下标中复杂的部分会被解释为表达式.

要注意, 单引号字符串要和前面的词用空白隔开, 因为单引号本身是可以组成标识符. (参看Packages).

有两个特别的字符串是 __LINE__ 和 __FILE__, 分别代表程序执行点的当前行号和文件名. 它们只能用做分隔记号; 不能被转换成字符串. 此外, __END__ 可以用于在脚本的真正结束 位置前标记逻辑结束位置, 在此后的所有文字都被忽略, 但可以通过 DATA 文件句柄读出. (DATA 文件句柄只能从主脚本读取, 不能从required包含的文件或计算的字符串读取) 控制 字符 ^D 和 ^Z 是 __END__ 的同义字. (在模块里是 __DATA__ 的同义字; 关于 __DATA__ 的详细说明, 参看SelfLoader)

在语法上没有其他解释的词都被看作一个引起来的字符串. 称为``净词''. 和文件句柄和 标号一样, 全是小写字母的净词可能会和将来的保留字冲突, 如果使用 -w 选项, Perl 会对这些词发出警告. 有些人会完全不使用净词. 如果用

use strict 'subs';

那么所有不能被解释为子过程调用的净词都会产生一条编译时刻错误. 严格检查一直到闭合块 的结尾为止. 内部块用以下方法可以撤消严格检查 no strict 'subs' .

如果把数组的所有元素连接到一起形成一个双引号字符串, 以变量 $" ( $LIST_SEPARATOR )指定的字符做为分隔符(默认为空格).

$temp = join($&quot;,@ARGV); system &quot;echo $temp&quot;; system &quot;echo @ARGV&quot;;

在搜索模式(也要做替换)中模糊是很糟糕的: 到底 /$foo[bar]/ 是 解释为 /${foo}[bar]/ ([bar]/ 是正则表达式字符类) 还是解释成 /${foo[bar]}/ ([bar]/ 是数组下标)? 如果 @foo 不存在, 那么明显是个字符类. 如果 @foo 存在, Perl 要猜测 [bar] 是什么, 一般结果是正确的. 但如果猜错了, 或者你可以用花括号来指示正确的解释方法.

面向行的引用是建立在 shell 的 ``here-doc'' 语法上. 在 << 后面指定一个结束引用的字符串, 当前行的所有后随行直到结束串都是引用的内容. 结束字符串可以是一个标识符(一个词), 或引起来的一段文字. 如果是引起来的文字, 引的方式决定了对文字的处理, 象普通的引用. << 和标识符之间不能够有空白. 如果有空白, 引用到第一个空白行 为止. 结束字符串必须单独出现在一行, 前后不能有空白.


        print <<EOF;      # same as above

    The price is $Price.

    EOF

        print <<``EOF'';  # same as above

    The price is $Price.

    EOF

        print <<`EOC`;    # execute commands

    echo hi there

    echo lo there

    EOC

        print <<``foo'', <<``bar''; # you can stack them

    I said foo.

    foo

    I said bar.

    bar

        myfunc(<<``THIS'', 23, <<'THAT'');

    Here's a line

    or two.

    THIS

    and here another.

    THAT



要记得结束的分号, 下面的代码是错误的


        print <<ABC

    179231

    ABC

        + 20;




列表值的表示

列表值用多个用逗号分隔的单独值表示(并用括号括起来):

(LIST)

在非列表的上下文里, 列表的值是最后一个元素的值. 例如,

@foo = ('cc', '-E', $bar);

把整个列表的值赋给数组foo, 而

$foo = ('cc', '-E', $bar);

把变量bar的值赋给变量foo. 注意在数值型的上下文里数组的值是数组的长度; 下面 的赋值把 $foo 的值设为 3:

@foo = ('cc', '-E', $bar); $foo = @foo; # $foo gets 3

列表的结束括号前可以有一个逗号:

@foo = ( 1, 2, 3, );

列表会把子列表自动展开. 当列表被求值时, 其中的每个元素都在列表上下文里被展开, 最后的结果形成一张大的列表. 数组在列表里不再独立存在. 下面的列表

(@foo,@bar,&amp;SomeSub) 包含了 @foo 中的所有元素, 后面是 @bar 的所有元素, 再后面是子过程 SomeSub 在列表上下文里被调用时返回的所有元素. 要使用不展开列表的引用, 参看 perlref

空列表用 () 表示. 在列表中展开一个空列表是不产生任何变化. 因此 ((),(),()) 等同于 (). 类似的, 在列表中展开一个空数组也不产生任何变化.

可以把列表当作数组, 用下标去访问它的值. 但必须用括号括起列表以避免产生混乱. 例如:

# Stat 返回列表值 $time = (stat($file))[8]; # 语法错误 $time = stat($file)[8]; # 漏了括号 # Find a hex digit. $hexdigit = ('a','b','c','d','e','f')[$digit-10]; # A &quot;reverse comma operator&quot;. return (pop(@foo),pop(@foo))[0];

Lists may be assigned to if and only if each element of the list is legal to assign to: 只有当可以对列表中的每个元素赋值, 才可以对列表赋值.

($a, $b, $c) = (1, 2, 3); ($map{'red'}, $map{'blue'}, $map{'green'}) = (0x00f, 0x0f0, 0xf00);

在数值型上下文里, 列表的赋值操作的返回值是等号右边的表达式的值.

$x = (($foo,$bar) = (3,2,1)); # $x = 3, 不是 2 $x = (($foo,$bar) = f()); # $x 等于 f() 的返回值

当在布尔型的上下文里进行列表赋值操作时, 检查返回值是有意义的. 大部分列表函数结束的时候返回空列表, 即数值 0, 布尔值 FALSE.

待赋值列表的最后一个元素可以是哈希表:

($a, $b, @rest) = split; local($a, $b, %rest) = @_;

实际上, 赋值的时候可以把哈希表放在列表的任何位置, 但列表中第一个出现的哈希表会取去 所有的值, 后面的元素都得到一个空值. 这个特点在 local()my() 里可能会有用.

哈希表的元素是一对对的数值, 即键和值的对应.

# same as map assignment above %map = ('red',0x00f,'blue',0x0f0,'green',0xf00);

列表和数组之间通常是可以互相转换的, 但不适用于哈希表. 列表中的元素可以象数组 一样用下标去访问, 但不能用键去访问列表元素.

在哈希表的键和值之间使用 => 操作符会更加明白. => 操作 符是逗号的同意词, 但兼有引起左操作数的功能, 特别适用于哈希表的初始化.

%map = ( red =&gt; 0x00f, blue =&gt; 0x0f0, green =&gt; 0xf00, );

或者是初始化哈希表的引用, 当作记录使用.

$rec = { witch =&gt; 'Mable the Merciless', cat =&gt; 'Fluffy the Ferocious', date =&gt; '10/31/1776', };

或是用于按名调用的复杂函数:

$field = $query-&gt;radio_group( name =&gt; 'group_name', values =&gt; ['eenie','meenie','minie'], default =&gt; 'meenie', linebreak =&gt; 'true', labels =&gt; \%labels );

要记住哈希表的初始化顺序不等于数据的存放顺序. 参看 sort 中有关对输出进行排序的例子


Typeglobs 和 文件句柄

Perl 用一个叫 typeglob 的内部类型来保存一条整份符号表表项. typeglob 的类型前缀是 *, 代表任意的类型. 它原来是给函数传递 数组和哈希表引用的方法, 不过现在有了真正的引用, 就很少用到了.

有一个地方还会用到 typeglobs, 就是传递或存放文件句柄的时候. 要保存一个 文件句柄, 可以这样做:

$fh = *STDOUT;

或者用真正的引用:

$fh = \*STDOUT;

这也是建立局部文件句柄的方法. 例如:

sub newopen { my $path = shift; local *FH; # 不用 my! open (FH, $path) || return undef; return \*FH; } $fh = newopen('/etc/passwd');

欲知更多有关 typeglobs 的信息, 参看 perlref , perlsub , 和 ``Symbols Tables'' . 欲知其他生成文件句柄的方法, 参看 open .