Mastering-Perl-Notes

第七章 Symbol Tables and Typeglobs

Symbol tables 组织和存储这 Perl’s package (global) 变量.

可以用 typeglobs 来影响 symbol tables.

package and Lexical Variables (包和词法变量)

Symbol table 会追踪 package variables 而不会追踪 Lexical variables.

如:

1
2
3
my $test = 1;

print "${*test{SCALAR}}";

不会有输出.

而:

1
2
3
$test = 1;

print "${*test{SCALAR}}";

则输出 1.

处理 symbol table 或 typeglobs , 就是处理 package variables. 包变量也是 global variables.

Perl 处理 double use of package variables 的方式, 可以利用 local. 如:

1
2
3
4
5
6
$n = 10;
my $square = square( 15 );

print "n is $n, square is $square\n";

sub square { local $n = shift; $n ** 2; }

Getting the Package Version

可随时用 full package name 来访问包变量.k

$main::global.

our 告诉 Perl 在接下来的 scope 中都使用这个包变量.

The Symbol Table (符号表)

每一个包都有一个 special hash-like data structure 叫做符号表, 其还包括包中的所有的 stashes. stash 即变量表, 一个 stash 就是一个哈希表 (不是传统上的 Perl 哈希, 只是类似), 里面存放包中定义的全部变量. (stash 没有公认的中文翻译)

这个 stash 并不是传统上 Perl 的 hash. 其命名为 package name + ::, 如 %main::.

打印一个符号表中的所有内容:

1
2
3
foreach my $entry ( keys %main:: ) {
print "$entry\n";
}

%main:: 符号表还包含了所有其他的符号表.

也可以对 stash 进行其他哈希操作. 如用 delete 删除表中的元素.

Typeglob

总共有七中变量类型:

  • SCALAR
  • ARRAY
  • HASH
  • CODE - &foo
  • IO - 文件和目录句柄
  • GLOB - *foo
  • FORMAT - 格式名
  • PACKAGE

可以像使用哈希一样使用 GLOB 类型, 如:

1
2
3
4
5
6
7
8
9
10
11
#! /usr/bin/perl
#
use v5.10;

$bar = 'Buster';
@bar = qw(Mini Roscoe);

$foo = *bar{SCALAR}; # 返回引用
$baz = *bar{ARRAY};
say "\$foo is $$foo";
say "\$baz is @$baz";

注意返回的是引用.

即使一个变量从来没使用过, 其 GLOB 返回的始终是一个匿名标量引用:

1
2
3
4
5
6
7
#! /usr/bin/perl
#
use v5.10;

$foo = *bar{SCALAR};

say '$foo is a reference' if ref $foo;

stash 只能作为右值使用, 不能作为左值使用, 不然会报错:

1
*bar{SCALAR} = 5;

但可以将其作为整体赋值. 如:

1
*foo = *bar;

检测一个包变量是否已经在某处被使用的一个方法 :

1
2
3
4
5
6
7
8
9
10
11
#! /usr/bin/perl
#
use v5.10;

foreach my $entry ( sort keys %main:: ) {
say $entry;

say "\tarray is defined" if *{$entry}{ARRAY};
say "\thash is defined" if *{$entry}{HASH};
say "\tsub is defined" if *{$entry}{CODE};
}

也就是说 typeglob 类似与哈希, 其中的值为引用.

别名

通过将一个 typeglob 赋值给另一个 typeglob, 就可以创建变量的别名.

单个类型, 就传递单个类型的引用. 如:

1
*foo = \$scalar;

看这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#! /usr/bin/perl
#
use v5.10;
use strict;

my $scalar = 'foo';
my @array = 1 .. 5;

*foo = \$scalar;
*foo = \@array;

{
no strict 'var';
say "\$foo is $foo";
say "\@foo is @foo";
}

这里的 strict*foo 不起作用, 只对 $foo@foo 管用.

Exporter 也是从包中获取的变量然后将变量名赋值给要导入包的 typeglob.

1
2
3
4
5
6
7
8
9
package Exporter;

sub import {
my $pkg = shift;
my $callpkg = caller($ExporterLevel);

#...
*("$callpkg\::$_") = \&("pkg\::$_") foreach @_;
}

旧代码中的文件句柄参数

在 Perl 5.6 加入对文件句柄的引用之前, 如果需要给一个子程序传递一个文件句柄, 就只能使用 typeglob.

旧代码如:

1
2
3
4
5
6
use CGI;

open FH, $cgi_data_file
or die "Could not open $cgi_data_file: $!";

CGI->new( *FH ); # 不能 new( FH ), 需要 typeglob

新代码如:

1
2
3
4
5
use CGI;

open my $fh, '<', $cgi_data_file
or die "Could not open $cgi_data_file: $!";
CGI->new($fh);

给匿名子程序命名

使用 typeglob 赋值, 可以给匿名子程序赋予一个名字. 这样就可以使用一个已命名的子程序, 而不必使用解引用来得到子程序.

如:

1
*func = sub { ... };

使用则可以如:

1
&func;

因为只是对引用赋值, 它影响的只是 typeglob 中的子程序部分.

简单的方法

使用 Package::Stash 模块, 可以简化对 typeglob 和 stash 的操作.

要修改一个 stash, 只需要创建一个对象, 提供供该 stash 使用的命名空间 (不加冒号), 如:

1
2
3
use Package::Stash;

my $foo_stash = Package::Stash->new( 'Animals' );

添加一个变量:

1
2
3
$foo_stash->add_symbol( '$camel' );
# 并赋值
$foo_stash->add_symbol( '$camel', 'Amelia' );

显示某个特定 stash 中的所有名称:

1
2
3
4
5
6
7
8
9
#! /usr/bin/perl
#
use Package::Stash;

my $main_stash = Package::Stash->new( 'main' )->get_all_symbols;

foreach my $key ( keys %$main_stash ) {
print "$key\n";
}

Mastering-Perl-Notes
http://example.com/2023/01/09/Mastering-Perl-Notes/
作者
Jie
发布于
2023年1月9日
许可协议