perlsub-Perl子例程(用户定义的函数)
要声明子例程:
子名称;#“转发”声明。子名称(PROTO);#同上,但使用原型子名称:ATTRS;#具有属性子名称(PROTO):ATTRS;#具有属性和原型sub NAME BLOCK#声明和定义。子名称(PROTO)块#同上,但带有原型子名称:带属性的ATTRS BLOCK#子名称(PROTO):带原型和属性的ATTRS BLOCK#使用特征“签名”;带签名的子名称(SIG)块#子名称:带签名的ATTRS(SIG)BLOCK#,属性子名称:原型(PROTO)(SIG)BLOCK#,带签名,原型
要在运行时定义匿名子例程:
$subref=子块;#无原型$subref=sub(PROTO)块;#使用proto$subref=sub:ATTRS BLOCK;#具有属性$subref=sub(PROTO):ATTRS块;#带有proto和属性使用特征“签名”;$subref=sub(SIG)块;#带有签名$subref=sub:ATTRS(SIG)BLOCK;#带有签名,属性
要导入子例程,请执行以下操作:
使用MODULE qw(NAME1 NAME2 NAME3);
调用子例程:
名称(列表);#常规子程序调用。姓名列表;#如果预先声明/导入,则括号可选。&名称(列表);#规避原型。&名称;#使当前@_对调用的子例程可见。
与许多语言一样,Perl提供了用户定义的子例程。它们可以位于主程序中的任何位置,通过做
,要求
,或使用
关键字,或使用动态生成评估
或匿名子例程。您甚至可以使用包含函数名或CODE引用的变量间接调用函数。
函数调用和返回值的Perl模型很简单:所有函数都作为参数传递一个简单的标量平面列表,所有函数同样向调用方返回一个标量平面表。这些调用和返回列表中的任何数组或散列都将崩溃,丢失其标识——但您可以始终使用pass-by-reference来避免这种情况。调用和返回列表可以包含任意数量的标量元素。(通常,没有显式返回语句的函数被称为子例程,但从Perl的角度来看,实际上没有什么区别。)
在使用签名的子例程中(请参见“签名”下面),参数被分配到签名引入的词汇变量中。在Perl的当前实现中,也可以在@_
数组的方式与非签名子程序相同,但现在不鼓励在这种签名子程序中以这种方式访问它们。
在不使用签名的子例程中,传入的任何参数都会显示在数组中@_
。因此,如果调用带有两个参数的函数,这些参数将存储在$_[0]
和$_[1]
。数组@_
是本地数组,但其元素是实际标量参数的别名。特别是,如果元素$_[0]
更新时,相应的参数也会更新(如果不可更新,则会发生错误)。如果参数是调用函数时不存在的数组或散列元素,则只有在修改或引用该元素时才会创建该元素。(一些早期版本的Perl创建了元素,无论元素是否被赋值。)赋值给整个数组@_
删除该别名,并且不更新任何参数。
在不使用签名时,Perl不提供创建命名形式参数的方法。实际上,您所做的只是分配给我()
这些列表。未声明为私有的变量是全局变量。有关创建私有变量的详细信息,请参阅“通过my()的私有变量”和“通过local()的临时值”。要在单独的包(可能还有单独的文件)中为一组函数创建受保护的环境,请参阅perlmod中的“包”.
A类返回
语句可用于退出子例程,可以选择指定返回值,该值将根据子例程调用的上下文在适当的上下文(列表、标量或void)中进行计算。如果未指定返回值,则子例程在列表上下文中返回空列表,在标量上下文中返回未定义的值,或在void上下文中不返回任何值。如果返回一个或多个聚合(数组和散列),这些聚合将被平铺到一个不可区分的大列表中。
如果没有返回
如果最后一条语句是表达式,则返回其值。如果最后一条语句是循环控制结构,如foreach公司
或a虽然
,返回的值未指定。空子函数返回空列表。
例子:
次最大值{我的$max=移位(@_);每个$foo(@_){如果$max<$foo;,$max=$foo;}返回$max;}$bestday=最大值($mon,$tue,$wed,$thu,$fri);
例子:
#获取一行,合并续行#以空白开头子get_line{$thisline=$lookahead;#全局变量!行:while(已定义($lookahead=<STDIN>)){if($lookahead=~/^[\t]/){$thisline.=美元$展望未来;}其他{最后一行;}}返回$thisline;}$lookahead=<STDIN>;#获得第一行while(已定义($line=get_line())){...}
为私有变量列表赋值以命名参数:
子可能被包围{my($key,$value)=@_;$Foo{$key}=$value,除非$Foo{$key};}
因为赋值复制了值,所以这也具有将按引用调用转换为按值调用的效果。否则,函数可以自由地对@_
并更改其调用方的值。
upcase_in($v1,$v2);#这将更改$v1和$v2子upcase_in{对于(@_){tr/a-z/a-z/}}
当然,您不允许以这种方式修改常量。如果一个参数实际上是字面的,并且您试图更改它,那么您会遇到一个(可能是致命的)异常。例如,这行不通:
upcase_in(“弗雷德里克”);
如果upcase_in()
编写函数是为了返回其参数的副本,而不是就地更改参数:
($v3,$v4)=大写($v1,$v2);#这不会改变$v1和$v2子大写{返回,除非定义wantarray;#上下文无效,不执行任何操作我的@parms=@_;对于(@parms){tr/a-z/a-z/}要回来吗@参数:$parms[0];}
注意,这个(非类型化的)函数不关心它是传递给实数标量还是数组。Perl将所有参数视为@_
。这是Perl简单的参数传递风格的一个亮点。这个upcase()
如果不更改upcase()
定义,即使我们给它这样的东西:
@newlist=大写(@list1,@list2);@newlist=upcase(拆分/:/,$var);
然而,不要试图这样做:
(@x,@y)=大写(@list1,@list2);
与扁平化的传入参数列表一样,返回列表在返回时也是扁平化的。所以你在这里所做的就是把所有东西都存储在@x个
并制成@年
为空。请参见“通过引用”替代品。
可以使用显式调用子例程&
前缀。这个&
在现代Perl中是可选的,如果子例程已预先声明,括号也是可选的。这个&
是不当仅命名子例程时是可选的,例如当它用作defined()或undef()的参数时。当您想使用&$subref()
或&{$subref}()
构造,尽管$subref->()
符号解决了这个问题。请参见perlref公司了解更多相关信息。
可以递归调用子例程。如果使用&
形式,参数列表是可选的,如果省略,则否@_
为子例程设置数组:@_
调用时的数组对子例程可见。这是一种新用户可能希望避免的高效机制。
&foo(1,2,3);#传递三个参数foo(1,2,3);#相同的foo();#传递空参数列表&foo();#相同的&foo;编号foo()获取当前参数,如foo(@_)!使用严格的“subs”;foo;编号like foo()iff子foo预先声明,else#编译时错误没有严格的“subs”;foo;编号类似于foo()iff子foo预定义,否则#文字字符串“foo”
不仅如此&
表单使参数列表成为可选的,它还禁止对您提供的参数进行任何原型检查。这部分是由于历史原因,部分是因为如果你知道自己在做什么,那么你可以用一种方便的方式作弊。请参见“原型”如下所示。
自Perl 5.16.0以来__潜艇__
代币可用于使用功能“current_sub”
和使用v5.16
。它将计算为对当前运行子例程的引用,允许在不知道子例程名称的情况下进行递归调用。
使用v5.16;my$阶乘=子{我的($x)=@_;如果$x==1,则返回1;返回($x*__SUB__->($x-1));};
的行为__SUB公司__
在regex代码块中(例如/(?{...})/
)可能会发生变化。
名称全部大写的子例程和名称全部小写的模块都保留给Perl内核。所有大写字母的子例程都是一个松散的约定,这意味着它将由运行时系统本身间接调用,通常是由于触发的事件。名称以左括号开头的子例程也以同样的方式保留。下面是一些当前执行特殊预定义操作的子例程的列表。
- 本文件后面记录
-
自动加载
- 记录在中珀尔莫德
-
克隆
,克隆_跳过
- 记录在中珀罗布吉
-
毁灭
,做
- 记录在中珍珠岩
-
BINMODE公司
,清除
,CLOSE(关闭)
,删除
,毁灭
,电动势
,存在
,EXTEND(扩展)
,获取
,FETCHSIZE(取尺寸)
,文件编号
,FIRSTKEY公司
,GETC公司
,NEXTKEY公司
,打开
,爆裂
,打印
,打印
,推
,阅读
,READLINE(阅读线路)
,标尺
,SEEK(搜索)
,SHIFT(轮班)
,剪接
,商店
,存储
,电话
,TIEARRAY公司
,TIEHANDLE公司
,TIEHASH公司
,TIESCALAR公司
,取消吊装
,解开
,写入
- 记录在中PerlIO::通过
-
BINMODE公司
,CLEARERR(清除错误)
,CLOSE(关闭)
,电动势
,错误
,FDOPEN(FDOPEN)
,文件编号
,填充
,冲洗
,打开
,被逮捕的
,推
,阅读
,SEEK(搜索)
,SETLINEBUF公司
,SYSOPEN(系统打开)
,电话
,未读取
,UTF8标准
,写入
- 记录在中珀尔func
-
进口
,不适合
,集成数字控制
- 记录在中通用
-
版本
- 记录在中珍珠肠
-
数据库::数据库
,数据库::sub
,数据库::lsub
,数据库::转到
,DB::延期
- 未记录,由内部使用超载特征
-
任何以开头的(
这个开始
,UNITCHECK公司
,检查
,初始
和结束
子程序与其说是子程序,不如说是命名的特殊代码块,在一个包中可以有多个子程序,也可以不显式调用。请参见perlmod中的“BEGIN、UNITCHECK、CHECK,INIT和END”
Perl有一种功能,允许子程序的形式参数通过特殊语法声明,与子程序体的过程代码分开。形式参数列表称为签名.
必须先启用此设施,然后才能使用它。它由使用v5.36
(或更高)声明,或更直接地由使用功能“签名”
,在当前范围内。
签名是子例程主体的一部分。通常情况下,子例程的主体只是一个有支撑的代码块,但当使用签名时,签名是一个带括号的列表,位于块之前、任何名称或属性之后。
例如,
子foo:lvalue($x,$y=1,@z){…}
签名声明了块范围内的词法变量。调用子例程时,签名首先控制。它从传递的参数列表中填充签名变量。如果参数列表不满足签名的要求,那么它将抛出异常。签名处理完成后,控制权传递给块。
位置参数通过简单地在签名中命名标量变量来处理。例如,
子foo($left,$right){返回$left+$right;}
采用两个位置参数,必须在运行时由两个参数填充。默认情况下,参数是必需的,不允许传递比预期更多的参数。因此,上述内容相当于
子foo{die“子程序参数太多”,除非@_<=2;die“子程序参数太少”,除非@_>=2;我的$left=$_[0];我的$right=$_[1];返回$left+$right;}
通过从参数声明中省略名称的主要部分,可以忽略参数,只留下一个空的$
符号。例如,
sub-foo($first,$,$third){return“first=$first,third=$third”;}
尽管被忽略的参数没有进入变量,但调用方仍必须传递它。
通过提供默认值,位置参数成为可选参数,默认值与参数名之间用=
:
子foo($left,$right=0){返回$left+$right;}
可以使用一个或两个参数调用上述子例程。默认值表达式在调用子例程时进行求值,因此它可能会为不同的调用提供不同的默认值。只有在调用中实际省略了参数时,才会对其进行求值。例如,
我的$auto_id=0;子foo($thing,$id=$auto_id++){打印“$thing有ID$ID”;}
自动为调用者没有提供ID的对象分配不同的顺序ID。默认值表达式还可以引用签名中前面的参数,使一个参数的默认值根据前面的参数而变化。例如,
sub-foo($first_name,$surname,$昵称=$first-name){打印“$first_name$surname is known as \”$昵称\“”;}
默认值表达式也可以使用//=
运算符,如果调用方省略了值或提供的值是未定义
.
子foo($name//=“世界”){打印“你好,$name”;}foo(未定义);#将打印“你好,世界”
类似地||=
运算符可用于提供默认表达式,以便在调用方提供假值时使用(请记住,缺少或未定义
值也为false)。
子foo($x||=10){返回5+$x;}
可选参数可以是无名称的,就像必需参数一样。例如,
子foo($thing,$=1){打印$thing;}
如果没有提供相应的参数,即使值不会存储在任何地方,参数的默认值仍将被计算。这是为了防止评估它有重要的副作用。然而,它将在void上下文中进行评估,因此,如果它没有副作用并且不是微不足道的,那么如果启用“void”警告类别,它将生成警告。如果无名可选参数的默认值不重要,则可以像参数的名称一样省略它:
sub-foo($thing,$=){打印$thing;}
可选位置参数必须位于所有必需位置参数之后。(如果没有强制位置参数,则签名中的第一个参数可以是可选位置参数。)如果有多个可选位置参数,但没有提供足够的参数来填充所有参数,则将从左到右填充这些参数。
在位置参数之后,可以在slurpy参数中捕获其他参数。最简单的形式是数组变量:
子foo($filter,@inputs){打印$filter->($_)foreach@inputs;}
签名中有slurpy参数时,传递的参数数量没有上限。slurpy数组参数可能与位置参数一样是无名称的,在这种情况下,它的唯一作用是关闭否则会应用的参数限制:
sub-foo($thing,@){打印$thing;}
slurpy参数可以是散列,在这种情况下,它可用的参数被解释为交替键和值。键必须和值一样多:如果有一个奇怪的参数,那么会抛出一个异常。键将被字符串化,如果存在重复项,则后面的实例优先于前面的实例,就像标准散列构造一样。
子foo($filter,%输入){打印$filter->($_,$inputs{$_})foreach排序键%inputs;}
与其他类型的参数一样,slurpy散列参数可能没有名称。它仍然坚持认为它可用的参数数量是均匀的,即使它们没有被放入变量中。
子foo($thing,%){打印$thing;}
slurpy参数(数组或散列)必须是签名中的最后一个参数。它可以遵循强制性和可选的位置参数;它也可能是签名中唯一的东西。Slurpy参数不能有默认值:如果没有为它们提供参数,则会得到一个空数组或空哈希。
签名可能完全为空,在这种情况下,它所做的只是检查调用方是否未传递参数:
子foo(){返回123;}
在Perl 5.36之前,这些被认为是实验性的,并在实验::签名
类别。从Perl 5.36开始,这种情况不再发生,尽管警告类别仍然存在,用于与试图使用以下语句禁用它的代码进行反向兼容:
无警告“实验::签名”;
在当前的Perl实现中,当使用签名时,参数在特殊数组变量中仍然可用@_
但是,现在不鼓励通过此数组访问它们,并且不应在新编写的代码中依赖它们,因为此功能在未来版本中可能会发生更改。尝试访问@_
数组将在实验::args_array_with_signatures
编译时的类别:
子f($x){#该行发出如下警告打印“参数为@_”;}
在带有签名子程序的联接或字符串中使用@_是在……进行实验。。。
访问参数的两种方式不同:@_
别名参数,但签名变量副本的参数。因此,写入签名变量只会更改该变量,而不会影响调用方的变量,而是写入@_
修改调用方用来提供该参数的任何内容。
签名和原型之间存在潜在的语法歧义(请参阅“原型”),因为两者都以左括号开头,并且可以出现在某些相同的位置,例如子例程声明中名称的后面。由于历史原因,当签名未启用时,这种上下文中的任何左括号都将触发非常宽容的原型解析。在这种情况下,大多数签名将被解释为原型,但不是有效的原型。(有效的原型不能包含任何字母字符。)这会导致错误消息有些混乱。
为了避免歧义,当启用签名时,将禁用原型的特殊语法。没有人试图猜测括号中的组是原型还是签名。要在这些情况下为子例程提供原型,请使用原型属性例如,
子foo:原型($){$_[0]}
子程序完全可以同时具有原型和签名。它们执行不同的任务:原型影响子程序调用的编译,签名在运行时将参数值放入词汇变量。因此,你可以写
sub-foo:原型($$)($left,$right){返回$left+$right;}
原型属性和任何其他属性必须位于签名之前。签名总是紧跟在子程序体的块之前。
简介:
我的$foo;#声明$foo词汇为本地我的(@wid,%get);#声明局部变量列表my$foo=“flurp”;#声明$foo词法并初始化它my@oof=@bar;#声明@oof词法并初始化它我的$x:Foo=$y;#类似,应用了一个属性
警告:属性列表的使用我的
宣言仍在发展。当前的语义和接口可能会更改。请参见属性和属性::处理程序.
这个我的
运算符声明所列变量在词汇上限制在封闭块中,条件(如果
/除非
/埃尔西夫
/其他的
),循环(对于
/foreach公司
/虽然
/直到
/持续
),子程序,评估
,或做
/要求
/使用
'd文件。如果列出了多个值,则必须将列表放在括号中。所有列出的元素必须是合法的左值。只有字母数字标识符可以在词汇上确定范围——像$/
当前必须是地方的
使用地方的
而是动态地限制其范围。
与由地方的
运算符,用声明的词法变量我的
完全隐藏在外部世界中,包括任何调用的子例程。如果它是从自身或其他地方调用的同一个子例程,则是这样的——每个调用都有自己的副本。
这并不意味着我的
在静态封闭词法范围中声明的变量将是不可见的。只有动态范围被切断。例如bumpx()
下面的函数可以访问词法$x变量,因为我的
和附属的
发生在同一范围,可能是文件范围。
我的$x=10;sub-bumpx{$x++}
安评估()
但是,只要名称不被评估()
自身。请参见perlref公司.
如果需要,可以将my()的参数列表分配给,这样可以初始化变量。(如果没有为特定变量指定初始值设定项,则使用未定义的值创建它。)通常,这用于命名子例程的输入参数。示例:
$arg=“fred”;#“全局”变量$n=cube_root(27);打印“$arg认为根是$n\n”;#输出:fred认为根是3sub-cube_root{my$arg=移位;#名字无关紧要$arg**=1/3;返回$arg;}
这个我的
是可能赋值对象的简单修饰符。因此,当您在其参数列表中赋值给变量时,我的
不会更改这些变量是被视为标量还是数组。所以
my($foo)=<STDIN>;#错了吗?我的@FOO=<STDIN>;
两者都为右侧提供了一个列表上下文,而
我的$foo=<STDIN>;
提供标量上下文。但以下仅声明一个变量:
我的$foo,$bar=1;#错误
这与
我的$foo;$bar=1;
声明的变量在当前语句之后才引入(不可见)。因此,
我的$x=$x;
可以使用旧$x的值初始化新$x
我的$x=123和$x==123
为false,除非旧$x碰巧有值123
.
控制结构的词汇范围并不是由分隔其控制块的大括号精确限定的;控件表达式也是该范围的一部分。因此在循环中
while(我的$line=<>){$line=lc$line;}继续{打印$line;}
$line的范围从其声明扩展到循环构造的其余部分(包括持续
子句),但不能超出它。同样,在条件
if((我的$答案=<STDIN>)=~/^yes$/i){user_agrees();}elsif($answer=~/^no$/i){user_disagrees();}其他{chomp$answer;die“$answer”既不是“yes”也不是“no”;}
$answer的范围从其声明扩展到该条件的其余部分,包括任何埃尔西夫
和其他的
子句,但不能超出它。请参见perlsyn中的“简单语句”有关带修饰符的语句中变量范围的信息。
这个foreach公司
循环默认以以下方式动态确定其索引变量的范围地方的
。但是,如果索引变量前缀为关键字我的
,或者如果作用域中已经存在该名称的词法,则会创建一个新的词法。因此在循环中
为我的$i(1,2,3){some_function();}
$i的范围扩展到了循环的末尾,但没有超出它,使得$i的值在中不可访问some_function()
.
一些用户可能希望鼓励使用词汇范围的变量。作为捕获包变量的隐式用法的辅助工具,如果您说
使用严格的“vars”;
那么从那里到封闭块末尾提到的任何变量都必须引用词法变量,并通过预先声明我们的
或使用vars
,否则必须使用包名称完全限定。否则会导致编译错误。内部块可以用没有严格的“vars”
.
A类我的
具有编译时和运行时效果。在编译时,编译器会注意到它使用严格的“vars”
,但它对于生成闭包也是至关重要的,如perlref公司。但实际的初始化会延迟到运行时,因此它会在适当的时间执行,例如每次通过循环执行。
用声明的变量我的
不是任何程序包的一部分,因此永远不会完全限定程序包名称。特别是,您不允许尝试使包变量(或其他全局变量)具有词法:
我的$pack::var;#错误!非法语法
事实上,仍然可以使用完全限定的::
即使相同名称的词法也可见:
包装总管;我们的x美元=10;我的$x=20;打印“$x和$::x\n”;
会打印出来的20
和10
.
你可以申报我的
变量在文件的最外层范围内,以向该文件外部世界隐藏任何此类标识符。这与C在文件级使用的静态变量在本质上类似。要使用子例程实现这一点,需要使用闭包(访问封闭词典的匿名函数)。如果要创建无法从该块外部调用的私有子例程,它可以声明包含匿名子引用的词法变量:
my$secret_version=“1.001-beta”;我的$secret_sub=sub{print$secret_version};$secret_sub->();
只要模块内的任何函数都没有返回引用,外部模块就无法看到子例程,因为它的名称不在任何包的符号表中。记住这不是真正地打电话$some_pack::secret_version
或任何东西;它只是$secretversion,不合格且不合格。
然而,这不适用于对象方法;所有对象方法都必须位于要找到的某个包的符号表中。请参见perlref中的“函数模板”为了解决这个问题。
在Perl5.10中,有两种方法可以构建持久的私有变量。首先,您可以简单地使用状态
功能。或者,如果希望与早于5.10的版本保持兼容,可以使用闭包。
从Perl 5.10.0开始,可以使用状态
关键字替代我的
。但要使其生效,您必须事先启用该功能,或者使用特征
pragma,或使用-E类
在一个衬垫上(参见特征). 从Perl 5.16开始核心::状态
表单不需要特征
杂注。
这个状态
关键字创建词法变量(遵循与我的
)从一个子例程调用到下一个子例程时持续存在。如果状态变量驻留在匿名子例程中,则子例程的每个副本都有其自己的状态变量副本。然而,在调用匿名子例程的同一副本之间,状态变量的值仍将保持不变。(别忘了子{…}
每次执行时都会创建一个新的子例程。)
例如,以下代码维护一个私有计数器,每次调用gimme_another()函数时都会递增:
使用功能“state”;子gimme_another{state$x;return++$x}
本例使用匿名子例程创建单独的计数器:
使用功能“state”;子create_counter{return子{state$x;return++$x}}
此外,因为x美元
是词法的,外部的任何Perl代码都无法访问或修改它。
与变量声明结合时,将简单赋值给状态
变量(如状态$x=42
)仅在第一次执行。当随后对此类语句进行求值时,将忽略赋值。分配给的行为状态
赋值左侧包含任何括号的声明目前尚未定义。
仅仅因为词汇变量在词汇上(也称为静态)作用于其封闭块,评估
,或做
FILE,这并不意味着在函数中它的工作方式类似于C静态。它通常更像C auto,但使用隐式垃圾收集。
与C或C++中的局部变量不同,Perl的词法变量不一定会因为其作用域已退出而被回收。如果某个更持久的东西仍然能够识别词汇,它就会一直存在。只要其他东西引用了一个词法,这个词法就不会被释放——这是应该的。在你用完它之前,你不会希望内存是空闲的,或者在你用完之后,内存就一直保留着。自动垃圾收集为您解决了这一问题。
这意味着您可以传回或保存对词法变量的引用,而返回指向C auto的指针是一个严重错误。它还为我们提供了一种模拟C函数静力学的方法。这里有一种机制,用于为函数提供具有词法作用域和静态生存期的私有变量。如果您确实想创建类似C语言的静态变量,只需将整个函数封装在一个额外的块中,并将静态变量放在函数外部的块中。
{我的$secret_val=0;给我另一个{return++$secret_val;}}#外部无法访问$secret_val#世界,但在调用“给我另一个”之间保留其值
如果此函数是从单独的文件中通过要求
或使用
,那么这可能很好。如果都在主程序中,您需要安排我的
提前执行,可以将整个块放在主程序之上,或者更可能的是,只放置一个开始
围绕它的代码块,以确保在程序开始运行之前执行它:
开始{我的$secret_val=0;给我另一个{return++$secret_val;}}
请参见perlmod中的“BEGIN、UNITCHECK、CHECK,INIT和END”关于特殊触发代码块,开始
,UNITCHECK公司
,检查
,初始
和结束
.
如果在最外面的作用域(文件作用域)声明,那么字典的工作方式有点像C的文件静态。它们可用于在其下面声明的同一文件中的所有函数,但从该文件外部无法访问。有时在模块中使用此策略来创建整个模块都可以看到的私有变量。
警告:通常,您应该使用我的
而不是地方的
因为它更快更安全。例外情况包括全局标点变量、全局文件句柄和格式,以及对Perl符号表本身的直接操作。地方的
通常在变量的当前值必须对调用的子例程可见时使用。
简介:
#价值定位本地$foo;#将$foo动态设置为本地本地(@wid,%get);#将变量列表设为局部变量local$foo=“flurp”;#使$foo成为动态的,并初始化它local@oof=@bar;#使@oof成为动态的,并初始化它本地$hash{key}=“val”;#设置此哈希项的本地值删除本地$hash{key};#删除当前块的此条目本地($cond?$v1:$v2);#几种左值支持#本地化#符号的本地化本地*FH;#本地化$FH、@FH、%FH和FH。。。本地*merlyn=*randal;#现在$merlyn真的是$randal,加上#@merlyn真的是@randal等local*merlyn='randal';#同样的事情:将“randal”推广到*randal本地*merlyn=\$randal;#只是别名$merlyn,而不是@merlyn等
A类地方的
将其列出的变量修改为封闭块的“局部”变量,评估
,或执行FILE
--和至从该块中调用的任何子例程.A型地方的
只为全局(即包)变量提供临时值。确实如此不创建局部变量。这称为动态作用域。词汇范围界定是通过我的
,其工作方式更像C的自动声明。
一些类型的左值也可以本地化:散列和数组元素和片、条件(前提是它们的结果总是可本地化的)以及符号引用。对于简单变量,这会创建新的动态范围值。
如果给定多个变量或表达式地方的
,它们必须放在括号中。此运算符的工作方式是将参数列表中这些变量的当前值保存在隐藏堆栈上,并在退出块、子例程或eval时恢复它们。这意味着被调用的子例程也可以引用局部变量,但不能引用全局变量。如果需要,可以将参数列表分配给,这样可以初始化局部变量。(如果没有为特定变量指定初始值设定项,则使用未定义的值创建它。)
因为地方的
是一个运行时操作符,每次都通过循环执行。因此,将变量定位在循环之外更有效。
A类地方的
只是一个左值表达式的修饰符。当您分配给地方的
ized变量地方的
不会更改其列表是作为标量还是数组查看。所以
本地($foo)=<STDIN>;local@FOO=<STDIN>;
两者都为右侧提供了一个列表上下文,而
本地$foo=<STDIN>;
提供标量上下文。
如果你本地化一个特殊的变量,你会给它一个新的值,但它的魔力不会消失。这意味着与此魔术相关的所有副作用仍然使用本地化值。
此功能允许这样的代码工作:
#在$slurp中读取FILE的全部内容{local$/=undef;$slurp=<FILE>;}
然而,请注意,这限制了某些值的本地化;例如,从Perl 5.10.0开始,以下语句终止,并出现错误试图修改只读值,因为$1变量是神奇的只读变量:
本地$1=2;
一个例外是默认的标量变量:从Perl 5.14开始本地($_)
将始终去除$的所有魔力,以便在子例程中安全地重用$。
警告:绑定数组和散列的本地化目前无法按所述进行。这将在Perl的未来版本中修复;同时,避免使用依赖于本地化绑定数组或散列的任何特定行为的代码(本地化单个元素仍然可以)。请参见perl58delta中的“定位绑定数组和散列被破坏”了解更多详细信息。
构造
本地*名称;
为glob创建一个全新的符号表条目名称
在当前包中。这意味着它的glob槽中的所有变量($name、@name、%name、&name和名称
filehandle)被动态重置。
这意味着,除其他外,这些变量最终承载的任何魔法都会在局部丢失。换句话说,就是说本地*/
不会对输入记录分隔符的内部值产生任何影响。
花点时间解释一下当你地方的
对复合类型(即数组或散列元素)的成员进行ize。在这种情况下,元素是地方的
1.adj.使…化按名称这意味着当本地()
结束时,保存的值将还原为哈希元素,该元素的键在本地()
,或其索引在本地()
。如果在本地()
已生效(例如,由删除()
从散列或移位()
),它将恢复存在,可能会扩展数组并用填充跳过的元素未定义
例如,如果你说
%hash=(‘This’=>‘is’,‘a’=>’test’);@ary=(0..5);{本地($ary[5])=6;local($hash{'a'})='drill';while(my$e=pop(@ary)){打印“$e…\n”;最后,除非$e>3;}if(@ary){$hash{'onlya'}='test';删除$hash{'a'};}}打印连接('',映射{“$_$hash{$_}”}排序键%hash),“.\n”;打印“The array has”,标量(@ary),“elements:”,join(',',map{defined$_?$_:'undef'}@ary),“\n”;
Perl将打印
6 . . .4 . . .3 . . .这只是一个测试。数组有6个元素:0,1,2,undef,undef5
local()在复合类型不存在的成员上的行为将来可能会发生更改。local()在使用负索引指定的数组元素上的行为特别令人惊讶,并且很可能会发生变化。
您可以使用删除本地$array[$idx]
和删除本地$hash{key}
构造以删除当前块的复合类型条目并在其结束时将其恢复。它们在本地化之前返回数组/散列值,这意味着它们分别等价于
做{我的$val=$array[$idx];本地$array[$idx];删除$array[$idx];$val美元}
和
做{我的$val=$hash{key};本地$hash{key};删除$hash{key};$val美元}
除了那些地方的
作用域为做
块。也接受切片。
我的%hash=(a=>[7、8、9],b=>1,){my$x=删除本地$hash{a};#x美元是[7,8,9]#%散列为(b=>1){my@nums=删除本地@$x[0,2]#@nums是(7,9)#$x是[undef,8]$x[0]=999;#范围结束时将被擦除}#$x回到[7,8,9]}#%hash返回到其原始状态
自Perl v5.12以来支持此构造。
可以从子例程返回可修改的值。为此,必须声明子例程以返回左值。
我的$val;子canmod:左值{$val;#或:返回$val;}子名词{$val;}canmod()=5;#分配给$valnomod()=5;#错误
子程序和赋值右侧的标量/列表上下文被确定,就好像子程序调用被标量替换一样。例如,考虑:
数据(2,3)=获取数据(3,4);
此处的两个子程序都是在标量上下文中调用的,而在:
(数据(2,3))=获取数据(3,4);
和in:
(数据(2),数据(3))=获取数据(3,4);
所有子例程都在列表上下文中调用。
Lvalue子例程很方便,但您必须记住,当与对象一起使用时,它们可能会违反封装。正常的赋值函数可以在设置它所保护的属性之前检查所提供的参数,而左值子例程则不能。如果在存储和检索值时需要任何特殊处理,请考虑使用CPAN模块Sentinel或类似的东西。
从Perl 5.18开始,可以用我的
或状态
。与状态变量一样状态
关键字仅在下可用使用功能“状态”
或使用v5.10
或更高。
在Perl 5.26之前,词汇子程序被认为是实验性的,并且只能在使用功能'lexical_subs'
杂注。除非禁用“experimental::lexical_subs”警告类别,否则它们还会生成警告。
这些子程序仅在声明它们的块中可见,并且仅在声明之后可见:
#如果您的代码打算在Perl下运行,请包括这两行#5.26之前的版本。没有警告“experimental::lexical_subs”;使用功能'lexical_subs';foo();#调用包/全局子例程状态子foo{foo();#还调用程序包子程序}foo();#调用“状态”子我的$ref=\&foo;#引用“state”sub我的子栏{…}bar();#呼叫“我的”潜艇
您不能(直接)编写递归词法子程序:
#错误我的替补队员{baz();}
此示例失败的原因是baz()
指包/全局子例程巴兹
,而不是当前正在定义的词法子例程。
解决方案是使用__SUB公司__
:
我的替补队员{__SUB__->();#调用自身}
可以预先声明词法子例程。这个子foo{…}
子例程定义语法遵循任何先前的我的潜艇;
或状态子;
宣言。然而,用它来定义递归子程序是个坏主意:
我的子baz;#宣布前sub-baz{定义“我的”subbaz();#错误:调用自身,但泄漏内存}
就像这样我的$f$f=子{$f->()}
,此示例会泄漏内存。姓名巴兹
是子程序的引用,子程序使用名称巴兹
; 他们让彼此活着(参见perlref中的“循环引用”).
“状态”潜艇和“我的”潜艇之间有什么区别?每次当声明“my”sub时,执行进入一个块时,都会创建每个sub的新副本。“状态”子例程从包含块的一次执行持续到下一次执行。
因此,一般来说,“状态”子程序更快。但是,如果要创建闭包,则必须使用“my”sub:
sub无论什么{my$x=班次;我的内心深处{…用$x做点什么。。。}inner();}
在这个示例中,一个新的x美元
创建时间无论什么
被称为,也是一个新的内部的
,可以看到新的x美元
。“state”sub只会看到x美元
从第一次呼叫到无论什么
.
喜欢我们的$变量
,我们的潜艇
为同名的包子例程创建词汇别名。
其两个主要用途是切换回使用内部作用域内的包子:
子foo{…}子工具栏{我的子foo{…}{#需要在此处使用外部foo我们的子foo;foo();}}
以及使子例程对同一范围内的其他包可见:
包MySneakyModule;我们的子任务{…}与调用方一起执行sub do something{包DB;()=呼叫者1;#设置@DB::argsdo_something(@args);#使用MySneakyModule::do_something}
警告:本节中描述的机制最初是在旧版本的Perl中模拟pass-by-reference的唯一方法。虽然它在现代版本中仍然工作良好,但新的引用机制通常更易于使用。请参见下文。
有时,您不想将数组的值传递给子例程,而是传递它的名称,以便子例程可以修改它的全局副本,而不是使用本地副本。在Perl中,您可以通过在名称前面加一个星号来引用特定名称的所有对象:*foo公司
这通常被称为“typeglob”,因为前面的星号可以被认为是变量和子例程等上所有有趣前缀字符的通配符匹配。
求值时,typeglob生成一个标量值,该值表示该名称的所有对象,包括任何文件句柄、格式或子例程。当分配给时,它会使所提到的名称表示任何内容*
值。例如:
亚二重态{local(*someary)=@_;foreach$elem(@someary){$elem*=2;}}双重(*foo);双重(*bar);
标量已经通过引用传递,因此可以通过显式引用来修改标量参数,而无需使用此机制$_[0]
等。您可以通过将所有元素作为标量传递来修改数组的所有元素,但必须使用*
机制(或等效参考机制)推
,流行音乐
,或更改数组的大小。传递typeglob(或reference)肯定会更快。
即使您不想修改数组,此机制对于在单个LIST中传递多个数组也很有用,因为通常LIST机制会合并所有数组值,因此您无法提取单个数组。有关typeglobs的更多信息,请参阅perldata中的“Typeglobs和Filehandles”.
尽管存在我的
,仍有三个地方地方的
操作员仍然闪耀着光芒。事实上,在这三个地方,你必须使用地方的
而不是我的
.
您需要给全局变量一个临时值,尤其是$_。
全局变量,如@ARGV公司
或标点符号变量,必须是地方的
使用本地()
。此块读入/等/其他,并将其拆分为由等号线分隔的块,这些等号线放置在@字段
.
{local@ARGV=(“/etc/motd”);局部$/=undef;本地$=<>;@字段=拆分/^\s*=+\s*$/;}
尤其重要的是地方的
在任何分配给它的例程中使用ize$_。注意虽然
条件句。
您需要创建本地文件或目录句柄或本地函数。
需要自己的文件句柄的函数必须使用本地()
在一个完整的typeglob上。这可用于创建新的符号表条目:
子队列{本地(*READER,*WRITER);#不是我的!pipe(READER,WRITER)或die“pipe:$!”;return(*READER,*WRITER);}($head,$tail)=ioqueue();
有关创建匿名符号表条目的方法,请参阅符号模块。
因为对typeglob的引用的赋值会创建别名,所以可以使用它来创建有效的本地函数,或者至少是本地别名。
{本地*grow=\&shrink;#直到这个街区退出增长();#实际调用shrink()移动();#如果move()增长()s,它也会收缩()s}增长();#再次获得真正的增长()
请参见perlref中的“函数模板”有关以这种方式按名称操作函数的更多信息。
您只想暂时更改数组或散列的一个元素。
你可以地方的
只计算聚合中的一个元素。通常这是在动力学上完成的:
{local$SIG{INT}='IGNORE';func();#不间断的}#此处自动恢复可中断性
但它也适用于词汇声明的聚合。
如果要将多个数组或散列传递到函数中,或者从函数中返回它们,并使它们保持完整性,那么必须使用显式的pass-by-reference。在此之前,您需要了解参考资料,如perlref公司。否则,本节可能对您没有多大意义。
这里有几个简单的例子。首先,让我们将几个数组传递给一个函数并使其具有流行音乐
然后,返回所有以前最后一个元素的新列表:
@尾矿=popmany(\@w,\@x,\@y,\@z);次流行音乐{我的$aref;我的@retlist;每个$aref(@_){推送@retlist,弹出@$aref;}return@转发列表;}
下面是如何编写一个函数,该函数返回传递给它的所有散列中出现的键列表:
@common=内部(\%foo,\%bar,\%joe);子inter{my($k,$href,%已查看);#当地人foreach$href(@_){while($k=每个%$href){$已看到{$k}++;}}返回grep{$seen{$_}==@_}键%seen;}
到目前为止,我们只使用了正常的列表返回机制。如果要传递或返回散列,会发生什么?好吧,如果您只使用其中一个,或者不介意将它们串联起来,那么正常的调用约定是可以的,尽管有点昂贵。
人们遇到麻烦的地方是:
(@w,@x)=函数(@y,@z);或(%w,%x)=函数(%y,%z);
这种语法根本不起作用@w个
或%w个
并清除@x个
或%x个
。此外,该函数没有被传递到两个单独的数组或散列中:它在中有一个长列表@_
一如既往。
如果您可以安排每个人通过引用来处理此问题,那么代码会更简洁,尽管看起来不太好。下面是一个函数,它接受两个数组引用作为参数,并按照它们包含的元素数量的顺序返回两个数组元素:
($wref,$xref)=函数(\@y,\@z);打印“@$wref有超过@$xref\n”;子函数{my($yref,$zref)=@_;if(@$yref>@$zref){返回($yref,$zref);}其他{返回($zref,$yref);}}
事实证明,您实际上也可以这样做:
(*w,*x)=函数(\@y,\@z);print“@w的数量超过@x\n”;子函数{局部(*y,*z)=@_;如果(@y>@z){返回(\@y,\@z);}其他{返回(\@z,\@y);}}
这里我们使用typeglobs来进行符号表别名。不过,这有点微妙,如果你正在使用,也不会起作用我的
变量,因为只有全局变量(即使伪装成地方的
s) 在符号表中。
如果要传递文件句柄,通常可以只使用裸类型glob,如*STDOUT公司
,但typeglobs引用也有效。例如:
溅射(\*STDOUT);亚溅射{my$fh=班次;打印$fh“她嗯……嗯……”;}$rec=get_rec(\*STDIN);子get_rec{my$fh=班次;返回标量<$fh>;}
如果您计划生成新的文件句柄,您可以这样做。请注意,只传回空的*FH,而不是其引用。
子openit{my$path=移位;本地*FH;返回打开(FH,$path)*FH:未定义;}
Perl支持使用函数原型进行非常有限的编译时参数检查。这可以在PROTO部分中声明,也可以使用原型属性。如果您声明
sub-mypush(\@@)sub-mypush:原型(\@@)
然后mypush()
接受的参数与push()
做。
如果启用了子程序签名(请参见“签名”),则较短的PROTO语法不可用,因为它会与签名冲突。在这种情况下,原型只能以属性的形式声明。
函数声明必须在编译时可见。原型只影响对函数的常规调用的解释,其中常规被定义为不使用&
符号。换句话说,如果像内置函数一样调用它,那么它的行为就像内置函数一样。如果您像老式(perl4)子例程那样调用它,那么它的行为就像老式子例程。从这个规则中自然可以看出,原型对子程序引用没有影响,比如\&足球
或间接子程序调用,如&{$subref}()
或$subref->()
.
方法调用也不受原型的影响,因为要调用的函数在编译时是不确定的,因为调用的确切代码取决于继承。
因为此功能的目的主要是让您定义像内置函数一样工作的子例程,所以这里是一些其他函数的原型,这些函数的解析几乎与相应的内置函数完全相同。
声明为调用为子mylink($$)mylink$old,$newsub-myvec($$$)myvec$var,$offset,1sub-myindex($$;$)myindex getstring(),“substr”sub-mysyswrite($$$;$)mysyswite$buf,0,length($buf)-$off,$offsub-myreverse(@)myreverse$x,$y,$zsub-myjoin($@)myjoin“:”,$x,$y,$zsub-mypop(\@)mypop@数组sub-mysplice(\@$$@)mysplice@array,0,2,@pushme子mykeys(\[%@])mykeys$hashref->%*sub-myopen(*;$)myopen HANDLE,$namesub mypipe(**)mypipe READHANDLE,WRITEHANDLEsub-mygrep(&@)mygrep{/foo/}$x,$y,$zsub-myrand(;美元)myrand 42次mytime()mytime
任何反斜杠原型字符都表示必须以该字符开头的实际参数(可以选择以我的
,我们的
或地方的
),除了$
,它将接受任何标量左值表达式,例如$foo=7
或my_function()->[0]
。作为的一部分传递的值@_
将是对子程序调用中给定的实际参数的引用,通过应用\
对于那个论点。
您可以使用\[]
反斜杠组表示法指定多个允许的参数类型。例如:
sub myref(\[$@%&*])
将允许将myref()调用为
myref$varmyref@数组myref%散列myref和submyref*球
myref()的第一个参数将是对标量、数组、哈希、子例程或glob的引用。
未反斜杠的原型字符具有特殊含义。任何未反斜线@
或%
吃掉所有剩余的参数,并强制列表上下文。由表示的参数$
强制标量上下文。安&
需要一个匿名子例程,如果作为第一个参数传递,它可能看起来像一个裸块:它不需要附属的
关键字或其后的逗号。
A类*
允许子例程接受一个bareword、常量、标量表达式、typeglob或对该槽中的typeglop的引用。子程序可以使用该值作为简单标量,或者(在后两种情况下)作为对类型glob的引用。如果希望始终将此类参数转换为类型glob引用,请按如下方式使用Symbol::qualify_to_ref():
使用符号“qualify_to_ref”;子foo(*){my$fh=qualify_to_ref(移位,调用者);...}
这个+
原型是$
那就好像\[@%]
当给定一个文本数组或散列变量时,否则将强制参数上的标量上下文。这对于应接受文字数组或数组引用作为参数的函数很有用:
sub-mypush(+@){my$aref=班次;die“Not an array or arrayref”,除非ref$aref eq'array';按@$aref,@_;}
使用时+
原型中,函数必须检查参数的类型是否可接受。
分号(;
)将强制参数与可选参数分开。以前是多余的@
或%
它吞噬了所有其他东西。
作为原型的最后一个字符,或者就在分号之前@
或a%
,您可以使用_
代替$
:如果未提供此参数,$_
将被使用。
请注意上表中的最后三个示例是如何由解析器专门处理的。mygrep()
被解析为true列表运算符,米兰德(myrand)
被解析为一元运算符,一元优先级与兰特()
、和我的时间()
真的没有争论,就像time()
。也就是说,如果你说
我的时间+2;
你会得到mytime()+2
,不是我的时间(2)
,这就是在没有原型的情况下如何解析它。如果要强制一元函数与列表运算符具有相同的优先级,请添加;
到原型的末尾:
子mygetprotobynumber($;);mygetprotobynumber$x>$y;#解析为mygetprotobynumber($x>$y)
有趣的是&
如果它位于初始位置,则可以使用它生成新语法:
子尝试(&@){my($try,$catch)=@_;评估{&$try};如果($@){本地$_=$@;&$catch;}}子捕获(&){$[0]}尝试{死“phooey”;}捕获{/phooey/并打印“unshooey”;};
那是指纹“取消选择”
(是的,仍然存在与可见性有关的未决问题@_
.我暂时忽略了这个问题。(但请注意,如果我们@_
在词汇范围内,这些匿名子例程可以像闭包一样工作。。。(哎呀,这听起来有点像莉斯吗?(没关系。))
下面是Perl的重新实现格雷普
操作员:
次mygrep(&@){my$code=移位;我的@result;每$_(@_){如果&$code;,则推送(@result,$_);}@结果;}
有些人更喜欢完整的字母数字原型。为了在未来的某一天添加命名的形式参数,字母数字被故意排除在原型之外。当前机制的主要目标是让模块编写器为模块用户提供更好的诊断。Larry认为这个符号对Perl程序员来说是可以理解的,并且它不会严重影响模块的核心,也不会使其更难阅读。线条噪音被直观地封装在一个易于吞咽的小药丸中。
如果您尝试在原型中使用字母数字序列,则会生成可选警告-“原型中存在非法字符…”。不幸的是,只要前缀是有效的原型,早期版本的Perl就允许使用原型。一旦大多数有问题的代码得到修复,该警告可能会在未来版本的Perl中升级为致命错误。
最好是建立新功能的原型,而不是将原型改造为旧功能。这是因为您必须特别小心不同列表和标量上下文的无声强制。例如,如果你决定一个函数只需要一个参数,如下所示:
子函数($){my$n=班次;打印“您给了我$n\n”;}
有人用返回列表的数组或表达式调用它:
函数(@foo);函数($text=~/\w+/g);
那么你刚刚提供了一个自动标量
在他们的争论面前,这可能有点令人惊讶。老人@foo公司
用来装一件东西的东西不会被传进去。相反,func()
现在通过了1
; 即,中的元素数@foo公司
.以及米/克
在标量上下文中调用,因此它返回布尔结果并前进位置($text)
.哎哟!
如果子接头同时具有PROTO和BLOCK,则在BLOCK完全定义之前,不会应用原型。这意味着必须预先声明具有原型的递归函数,以便原型生效,如下所示:
子foo($$);亚富($$){foo 1、2;}
当然,这一切都非常强大,应该适度使用,以使世界变得更美好。
原型为的函数()
是内联的潜在候选。如果优化和常量折叠后的结果是常量或没有其他引用的字典范围的标量,则将使用它来代替在没有&
。使用拨打的电话&
从不内联。(请参见常数以便于声明大多数常量。)
以下函数都将内联:
sub-pi(){3.14159}#不精确,但接近。sub PI(){4*atan21,1}#尽可能好,#它也是内联的!子ST_DEV(){0}子ST_INO(){1}子FLAG_FOO(){1<<8}子FLAG_BAR(){1<<9}子FLAG_MASK(){FLAG_FOO|FLAG_BAR}子OPT_BAZ(){not(0x1B58&FLAG_MASK)}子N(){int(OPT_BAZ)/3}sub FOO_SET(){1 if FLAG_MASK&FLAG_FOO}子FOO_SET2(){if(FLAG_MASK&FLAG_FOO){1}}
(请注意,上一个示例在Perl 5.20和更早版本中并不总是内联的,这与包含内部作用域的子例程的行为不一致。)您可以通过使用显式的返回
:
sub baz_val(){如果(OPT_BAZ){返回23;}其他{返回42;}}sub-bonk_val(){return 12345}
如前所述,如果内联sub的主体由没有其他引用的字典范围的标量组成,则还可以在BEGIN时动态声明内联subs。只有这里的第一个示例将被内联:
开始{我的$var=1;没有严格的“refs”;*INLINED=sub(){$var};}开始{我的$var=1;我的$ref=\$var;没有严格的“参考”;*NOT_INLINED=子(){$var};}
关于这一点,一个不太明显的警告(参见[RT#79908])是,如果变量可能是可修改的,会发生什么。例如:
开始{我的$x=10;*FOO=子(){$x};$x++;}打印FOO();#5.32.0之前打印10
从Perl 5.22开始,它发出了一个弃用警告,从Perl 5.32开始,它变成了一个运行时错误。以前,变量被立即内联,不再像正常的词法变量那样运行;所以它打印出来了10
,不是11
.
如果您仍然希望这样的子例程被内联(没有警告),请确保该变量不在可以在声明它的位置之外进行修改的上下文中使用。
#很好,没有警告开始{我的x美元=54321;*INLINED=sub(){$x};}#错误开始{我的$x;x美元=54321;*ALSO_INLINED=子(){$x};}
Perl 5.22还引入了“const”属性作为替代。它最初是实验性的,但在Perl 5.40中变得稳定。当应用于匿名子例程时,它强制在附属的
计算表达式。捕获返回值并将其转换为常量子例程:
我的x美元=54321;*INLINED=sub:const{$x};$x++;
的返回值内嵌式
在这个示例中,无论以后对$x进行什么修改,都将始终为54321。您也可以将任意代码放入sub中,它将立即执行,其返回值将以相同的方式捕获。
如果你真的想要一个带有()
返回词法变量的原型可以通过添加显式返回
:
开始{我的$x=10;*FOO=sub(){return$x};$x++;}打印FOO();#打印11
判断子例程是否内联的最简单方法是使用B: :去除。考虑以下两个子例程返回1
,一个带有()
原型导致它被内联,一个没有(为了清晰起见,deparse输出被截断):
$perl-MO=Deparse-e'sub ONE{1}if(ONE){print ONE if ONE}'次一{1;}如果(一){如果是ONE,则打印ONE();}$perl-MO=Deparse-e'sub ONE(){1}if(ONE){print ONE if ONE}'子ONE(){1}做{打印1};
如果重新定义一个符合内联条件的子例程,默认情况下会收到警告。您可以使用此警告来判断特定子例程是否被视为可内联的,因为它不同于覆盖非内联子例程的警告:
$perl-e“子一(){1}子一(”{2}“常量子程序一在-e第1行重新定义。$perl-we“子一{1}子一{2}”子程序1在-e行重新定义。
警告被认为足够严重,不会受到-w个切换(或不切换),因为之前编译的函数调用仍将使用函数的旧值。如果需要能够重新定义子例程,则需要通过删除()
原型(它会改变调用语义,所以要小心)或以其他方式阻止内联机制,例如通过添加显式返回
,如上所述:
sub not_inlined(){return 23}
许多内置函数可能会被覆盖,尽管这应该只是偶尔尝试,而且有充分的理由。通常,这可能是由尝试在非Unix系统上模拟缺少的内置功能的包来完成的。
重写只能通过在编译时从模块导入名称来完成——普通的预声明还不够好。然而使用sub
实际上,pragma允许您通过导入语法预先声明sub,然后这些名称可能会覆盖内置名称:
使用sub“chdir”、“chroot”、“chmod”、和“chown”;chdir$某处;子chdir{…}
要明确地引用内置表单,请在内置名称之前加上特殊的包限定符核心:
例如,说核心::open()
始终指内置打开()
,即使当前程序包已导入其他调用的子例程&打开()
来自其他地方。尽管它看起来像是一个常规函数调用,但它不是:核心:
在这种情况下,前缀是Perl语法的一部分,适用于任何关键字,无论核心
包裹。引用它,即,\&核心::打开
,仅适用于某些关键字。请参见核心.
库模块通常不应导出内置名称,如打开
或chdir公司
作为其违约的一部分@出口
列表,因为这些可能会潜入其他人的名称空间并意外更改语义。相反,如果模块将该名称添加到@导出_确定
则用户可以显式而非隐式地导入名称。也就是说,他们可以说
使用模块“打开”;
它将导入打开
覆盖。但如果他们说
使用模块;
他们将获得不带覆盖的默认导入。
上述重写内置的机制被严格限制为请求导入的包。第二种方法有时适用于希望覆盖任何位置的内置,而不考虑命名空间边界的情况。这是通过将子项导入特殊的命名空间来实现的核心::全球::
这是一个厚颜无耻地取代全球
运算符,具有理解正则表达式的功能。
REGlob包;要求出口商;@ISA=“出口商”;@EXPORT_OK='glob';次级进口{my$pkg=班次;除非@_;否则返回;my$sym=班次;my$where=($sym=~s/^GLOBAL_//?'核心::GLOBAL':调用方(0));$pkg->导出($where,$sym,@_);}子全局变量{我的$pat=班次;我的@got;if(opendir my$d,'.'){@get=grep/$pat/,readdir$d;关闭目录$d;}return@got;}1;
以下是如何使用(ab):
#使用REGlob'GLOBAL_glob';#覆盖所有命名空间中的glob()包装Foo;使用REGlob'glob';#仅在Foo::中重写glob()打印<^[a-z_]+\.pm\$>;#显示所有实用模块
最初的评论显示了一个人为的、甚至是危险的例子。通过重写全球
在全球范围内,您将迫使全球
操作员每一个命名空间,而不需要拥有这些命名空间的模块的完全识别或合作。当然,如果必须这样做的话,这应该非常谨慎。
这个REGlob公司
上面的示例没有实现彻底覆盖Perl所需的所有支持全球
操作员。内置全球
根据它是出现在标量上下文还是列表上下文中,有不同的行为,但我们的REGlob公司
事实上,许多Perl内置程序都有这样的上下文敏感行为,这些行为必须由正确编写的覆盖来充分支持。对于覆盖的完整功能示例全球
,研究实施文件::DosGlob
在标准库中。
重写内置时,替换内容应与内置本机语法保持一致(如果可能)。您可以通过使用合适的原型来实现这一点。要获取可重写内置的原型,请使用原型
参数为的函数“核心::builtin_name”
(请参见perlfunc中的“原型”).
然而请注意,一些内置程序不能用原型来表达其语法(例如系统
或咀嚼
). 如果覆盖它们,则无法完全模仿它们的原始语法。
内置组件做
,要求
和全球
也可以被重写,但由于特殊的魔力,它们的原始语法被保留了下来,并且您不必为它们的替换定义原型。(您不能覆盖做BLOCK
语法)。
要求
有特殊的额外黑暗魔法:如果你调用你的要求
替换为需要Foo::Bar
,它将实际接收参数“Foo/Bar.pm”
在@_。请参见perlfunc中的“require”.
并且,正如您在前面的示例中注意到的那样,如果您覆盖全球
,的<*>
glob运算符也被覆盖。
以类似的方式,覆盖读行
函数还覆盖等效的I/O运算符<FILEHANDLE>
。此外,重写读管道
也覆盖运算符``
和qx(质量x)//
.
最后,一些内置组件(例如。存在
或格雷普
)无法重写。
如果调用未定义的子例程,通常会立即出现致命错误,抱怨该子例程不存在。(当方法不存在于类包的任何基类中时,作为方法使用的子例程也是如此。)但是,如果自动加载
子例程定义在用于定位原始子例程的一个或多个包中,然后自动加载
使用传递给原始子例程的参数调用子例程。原始子例程的完全限定名神奇地出现在与自动加载
例行程序。这个名字并不是作为一个普通的参数传递的,因为,呃,好吧,只是因为,这就是为什么。(作为例外,对不存在的进口
或不适合
方法被跳过。此外,如果AUTOLOAD子例程是XSUB,还有其他方法可以检索子例程名称。请参见perlguts中的“使用XSUB自动加载”详细信息。)
许多自动加载
例程使用eval()加载所请求子例程的定义,然后使用特殊形式的goto()执行该子例程,该形式的goco()擦除自动加载
没有跟踪的例程。(请参阅中记录的标准模块的来源自动加载器例如。)但是一个自动加载
例程也可以只模拟例程而从不定义它。例如,让我们假设一个未定义的函数应该只调用系统
用那些论点。你所要做的就是:
sub AUTOLOAD(自动加载){我们的$AUTOLOAD;#让“严格使用”快乐我的$program=$AUTOLOAD;$program=~s/.*:://;系统($program,@_);}日期();who();ls('-l');
事实上,如果您预先声明了要以这种方式调用的函数,则甚至不需要括号:
使用subs-qw(日期who ls);日期;谁;ls“-l”;
一个更完整的例子是CPAN上的Shell模块,它可以将未定义的子程序调用视为对外部程序的调用。
有一些机制可以帮助模块编写器将模块拆分为可自动加载的文件。请参阅中描述的标准AutoLoader模块自动加载器和中自动拆分中的标准SelfLoader模块自动加载器,以及中关于将C函数添加到Perl代码的文档珍珠色.
子程序声明或定义可能有一个与之关联的属性列表。如果存在这样的属性列表,则在空格或冒号边界处将其分解,并将其视为使用属性
已经被看到了。请参见属性有关当前支持哪些属性的详细信息。与过时的限制不同使用属性
,的主题:ATTRLIST
语法的作用是将属性与预先声明相关联,而不仅仅是与子例程定义相关联。
属性必须作为简单标识符名称有效(除了“_”字符之外,没有任何标点符号)。它们可能附加了一个参数列表,仅检查其括号(“(”,“)”)是否嵌套正确。
有效语法示例(即使属性未知):
sub-fnord(&\%):开关(10,foo(7,3)):昂贵;sub-plugh():丑陋('\(“):糟糕;亚氧气:_5x5{…}
无效语法示例:
sub-fnord:开关(10,foo();#()-字符串不平衡sub-snoid:丑陋('(');#()-字符串不平衡亚氧气:5x5;#“5x5”不是有效的标识符子插头:Y2::北;#“Y2::north”不是简单标识符子snurt:foo+bar;#“+”不是冒号或空格
属性列表作为常量字符串列表传递给将其与子例程关联的代码。特别是,上述有效语法的第二个示例目前在如何解析和调用方面看起来像这样:
使用属性__PACKAGE__,\&plugh,q[Ugly('\(“)],'Bad';
有关属性列表及其操作的更多详细信息,请参见属性和属性::处理程序.
请参见perlref中的“函数模板”有关引用和闭包的更多信息。请参见珍珠色如果您想了解如何从Perl调用C子例程。请参见被玷污的如果您想了解如何从C调用Perl子例程,请参阅珀尔莫德了解如何将函数捆绑在单独的文件中。请参见perlmodlib了解哪些库模块是您系统上的标准模块。请参见珍珠岩学习如何进行对象方法调用。