2009-11-09 71 views
27

我对Perl构造函数中发生了什么感到困惑。我发现这两个例子perldoc perlbot面向对象的Perl构造函数语法

package Foo; 

#In Perl, the constructor is just a subroutine called new. 
sub new { 
    #I don't get what this line does at all, but I always see it. Do I need it? 
    my $type = shift; 

    #I'm turning the array of inputs into a hash, called parameters. 
    my %params = @_; 

    #I'm making a new hash called $self to store my instance variables? 
    my $self = {}; 

    #I'm adding two values to the instance variables called "High" and "Low". 
    #But I'm not sure how $params{'High'} has any meaning, since it was an 
    #array, and I turned it into a hash. 
    $self->{'High'} = $params{'High'}; 
    $self->{'Low'} = $params{'Low'}; 

    #Even though I read the page on [bless][2], I still don't get what it does. 
    bless $self, $type; 
} 

而另一个例子是:

package Bar; 

sub new { 
    my $type = shift; 

    #I still don't see how I can just turn an array into a hash and expect things 
    #to work out for me. 
    my %params = @_; 
    my $self = []; 

    #Exactly where did params{'Left'} and params{'Right'} come from? 
    $self->[0] = $params{'Left'}; 
    $self->[1] = $params{'Right'}; 

    #and again with the bless. 
    bless $self, $type; 
} 

这里是使用这些对象的脚本:我一直有

package main; 

$a = Foo->new('High' => 42, 'Low' => 11); 
print "High=$a->{'High'}\n"; 
print "Low=$a->{'Low'}\n"; 

$b = Bar->new('Left' => 78, 'Right' => 40); 
print "Left=$b->[0]\n"; 
print "Right=$b->[1]\n"; 

我已经注射了问题/混乱作为评论进入代码。

+4

这是基本的Perl将列表变成散列。它交替出现,以第一个为关键,第二个为价值。 DWIM(做我的意思)是Perl努力的目标 - 并且通常会命中。 – Axeman 2009-11-09 19:12:03

回答

52

要回答你的问题的主要问题,由于散列可以初始化为key => value对列表,所以可以将这样的列表发送给函数,然后将@_分配给散列。这是在Perl中执行命名参数的标准方式。

例如,

sub foo { 
    my %stuff = @_; 
    ... 
} 

foo(beer => 'good', vodka => 'great'); 

这将导致在%stuff子程序具有两个键,beervodka,和相应的值的哈希foo

现在,在OO Perl中,还有一些额外的皱纹。无论何时使用箭头(->)运算符调用某个方法,箭头左侧的任何内容都会卡在@_阵列的开头。

所以,如果你说Foo->new(1, 2, 3);

然后在您的构造函数中,@_将如下所示:('Foo', 1, 2, 3)

因此我们使用shift,它不带参数的@_工作含蓄,得到的是第一个项目出来的@_,并将其分配给$type。之后,@_只剩下我们的名称/值对,为方便起见,我们可以直接将其分配给哈希。

然后,我们使用bless$type值。所有bless确实是采取一个参考(在你的第一个例子中一个哈希ref),并说“这个引用与一个特定的包相关联。” Alakazzam,你有一个对象。

请记住,$type包含字符串'Foo',它是我们包的名称。如果不是指定bless的第二个参数,它将使用当前包的名称,该名称也适用于此示例,但而不是适用于继承的构造函数。

+1

+1一个体面的清晰(!)解释 – 2009-11-09 19:18:09

+8

你可以做'我的($自己,%的东西)= @_;'而不是转移。 – 2009-11-10 00:36:52

+0

简短并正确 - 真棒解释 – Bohdan 2012-12-17 22:41:37

8

你的问题不是OO Perl。你对数据结构感到困惑。

my @x = ('High' => 42, 'Low' => 11); 
my %h = @x; 

use Data::Dumper; 
print Dumper \%h; 
 
$VAR1 = { 
      'Low' => 11, 
      'High' => 42 
     }; 

当调用一个方法上的bless ED参考,参考被预置到该方法接收参数列表:

散列可以使用列表或数组进行初始化

#!/usr/bin/perl 

package My::Mod; 

use strict; 
use warnings; 

use Data::Dumper; 
$Data::Dumper::Indent = 0; 

sub new { bless [] => shift } 

sub frobnicate { Dumper(\@_) } 

package main; 

use strict; 
use warnings; 

my $x = My::Mod->new; 

# invoke instance method 
print $x->frobnicate('High' => 42, 'Low' => 11); 

# invoke class method 
print My::Mod->frobnicate('High' => 42, 'Low' => 11); 

# call sub frobnicate in package My::Mod 
print My::Mod::frobnicate('High' => 42, 'Low' => 11); 

输出:

 
$VAR1 = [bless([], 'My::Mod'),'High',42,'Low',11]; 
$VAR1 = ['My::Mod','High',42,'Low',11]; 
$VAR1 = ['High',42,'Low',11]; 
4

在Perl中,子例程的所有参数都通过预定义数组@_传递。

shift删除并返回@_数组中的第一项。在Perl OO中,这是调用方法 - 通常是构造函数的类名称和其他方法的对象。

哈希平顶并且可以通过列表进行初始化。将命名参数模拟到子例程是一种常见的技巧。例如

Employee->new(name => 'Fred Flintstone', occupation => 'quarry worker'); 

忽略类名(将其移除)奇数元素变成散列键,偶数元素变成相应的值。

my $self = {}创建一个新的散列引用来保存实例数据。 bless函数是将正常哈希引用$self转换为对象的东西。它所做的只是添加一些元数据,以将引用标识为属于该类。

7

如果将一个数组分配给一个散列,perl会将该数组中的交替元素视为键和值。你的阵列是看像

my @array = (key1, val1, key2, val2, key3, val3, ...); 

如果您分配到%的哈希,你

my %hash = @array; 
# %hash = (key1 => val1, key2 => val2, key3 => val3, ...); 

这是说,在Perl列表/哈希建设的语法,",""=>"意味着另一种方式一样。

+1

+1,但请注意,如果LHS仅由[A-Za-z0-9_]组成,则逗号('=>')会自动引用LHS。 – 2009-11-09 19:20:40

8

那些尚未处理的几点:

在Perl中,构造函数只是一个子程序 叫new

不完全。调用新的构造函数只是一个约定。你可以称它为任何你喜欢的。从perl的角度来看,这个名字没有什么特别之处。

bless $self, $type;

的你的例子都没有返回保佑明确的结果。无论如何,我希望你知道他们这么做是隐含的。

+0

是的,我知道在子例程中执行的最后一条语句是返回的语句,如果没有显式的返回语句。 – 2009-11-09 20:00:27

+0

好。我只是想确定。 – innaM 2009-11-09 20:11:06

17

.1。在Perl中,构造函数只是一个名为new的子例程。

是的,按照惯例new是一个构造函数。它也可以执行初始化或不执行。new如果发生阻止对象创建的错误,则应成功返回对象或抛出异常(die/croak)。

可以命名你的构造你喜欢的东西,有许多构造函数,只要你喜欢,甚至构建祝福对象到你想要的(不,这是一个好主意)的任何名称空间。

.2。我根本没有得到什么my $type = shift;,但我总是看到它。我需要它吗?

shift不带参数从@_的头部取出一个参数并将其分配给$type->运算符将调用者(左侧)作为第一个参数传递给子例程。所以这一行从参数列表中获取类名称。而且,是的,你确实需要它。

.3。一系列输入如何成为%params散列? my %params = @_;

分配到一个哈希列表上下文中完成,与对列表中的项目被分为以键/值对。所以%foo = 1, 2, 3, 4;,创建一个散列,使得$foo{1} == 2$foo{3} == 4。这通常是为子例程创建命名参数。如果子传递了奇数个参数,那么如果启用警告,则会生成警告。

.4。 'my $ self = {};'是做什么的?

这行创建一个匿名散列引用,并将其分配给词法作用域变量$self。哈希引用将存储该对象的数据。通常,散列中的键具有对对象属性的一对一映射。所以如果Foo类有属性'size'和'color',如果你检查Foo对象的内容,你会看到类似$foo = { size => 'm', color => 'black' };的东西。

.5。鉴于 $params{'High'}从哪里来?

该代码依赖于传递给new的参数。如果new被称为Foo->new(High => 46),则根据问题3创建的散列将具有关键字High(46)的值。在这种情况下,它相当于说$self->{High} = 46。但是,如果方法被称为Foo->new()那么没有价值可用,我们有$self->{High} = undef

.6。 bless是做什么的?

bless需要参考和联营公司与一个特定的包,这样就可以用它来进行方法调用。有一个论点,该参考文献与当前包相关。使用两个参数,第二个参数指定将引用与之关联的软件包。最好始终使用这两个参数形式,以便您的构造函数可以被子类继承并仍然正常工作。

最后,我将重写基于哈希的对象访问器,因为我会使用经典的OO Perl编写它。

package Foo; 

use strict; 
use warnings; 
use Carp qw(croak); 

sub new { 
    my $class = shift; 

    croak "Illegal parameter list has odd number of values" 
     if @_ % 2; 

    my %params = @_; 

    my $self = {}; 
    bless $self, $class; 

    # This could be abstracted out into a method call if you 
    # expect to need to override this check. 
    for my $required (qw{ name rank serial_number }); 
     croak "Required parameter '$required' not passed to '$class' constructor" 
      unless exists $params{$required}; 
    } 

    # initialize all attributes by passing arguments to accessor methods. 
    for my $attrib (keys %params) { 

     croak "Invalid parameter '$attrib' passed to '$class' constructor" 
      unless $self->can($attrib); 

     $self->$attrib($params{$attrib}); 
    } 

    return $self; 
} 
+0

+1恒星的答案,非常彻底! – Ether 2009-11-09 21:52:49

+1

Re:#3,实际上散列元素的奇数会导致警告,而不是致命错误。 – friedo 2009-11-10 18:22:06

+2

你说新的应该返回undef,如果它不能创建一个对象。这不是一条硬性规定。有一个很好的观点认为构造函数应该返回一个对象或死亡,如果这是不可能的。这样任何人调用构造函数都需要处理可能的异常。 – 2011-09-21 09:23:18

1

是的,我知道我是一个有点方士这儿,但是......

虽然所有这些答案都是优秀的,我想我会提Moose。Moose使构造函数变得简单(package Foo;use Moose;自动提供了一个名为new的构造函数(尽管如果你愿意,可以覆盖名称“new”),但如果你需要的话可以使用doesn't take away any configurability

一旦我浏览了Moose的文档(这是非常好的整体,如果你适当的谷歌有更多的教程片段),我从来没有回头。

+0

此答案中的第一个链接已损坏。你能替换它吗? – 2015-10-08 03:57:34