27

我对 Perl 构造函数中发生的事情有点困惑。我发现这两个例子perldoc perlbot

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

7 回答 7

53

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

例如,

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

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

这将导致%stuff子例程foo具有带有两个键的散列,beervodka,以及相应的值。

现在,在 OO Perl 中,还有一些额外的问题。每当您使用箭头 ( ->) 运算符调用方法时,箭头左侧的任何内容都会卡在@_数组的开头。

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

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

因此shift,我们使用不带参数@_隐式操作的 ,将第一项从 中取出@_,并将其分配给$type。之后,@_只剩下我们的名称/值对,为了方便,我们可以直接将其分配给哈希。

然后我们将该$type值用于bless。所做的只是获取一个bless引用(在您的第一个示例中是一个哈希引用)并说“这个引用与特定的包相关联”。阿拉卡扎姆,你有一个目标。

请记住,它$type包含字符串“Foo”,这是我们包的名称。如果你没有为 指定第二个参数bless,它将使用当前包的名称,这也适用于本例,但不适用于继承的构造函数。

于 2009-11-09T19:14:35.907 回答
18

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

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

您可以为您的构造函数命名任何您喜欢的名称,拥有任意数量的构造函数,甚至可以将 bless 对象构建到您想要的任何名称空间中(这并不是一个好主意)。

.2. 我根本不明白做什么my $type = shift;,但我总是看到它。我需要吗?

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

.3. 输入数组如何成为%params 哈希?my %params = @_;

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

.4. 'my $self = {};` 有什么作用?

此行创建一个匿名哈希引用并将其分配给词法范围变量$self。哈希引用将存储对象的数据。通常,散列中的键与对象属性具有一对一的映射关系。因此,如果类 Foo 具有属性“大小”和“颜色”,如果您检查 Foo 对象的内容,您将看到类似$foo = { size => 'm', color => 'black' };.

.5. 鉴于$self->{'High'} = $params{'High'};$params{'High'}来自哪里?

此代码依赖于传递给new. 如果new被称为 like 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;
}
于 2009-11-09T20:42:45.540 回答
9

您的问题与 OO Perl 无关。您对数据结构感到困惑。

可以使用列表或数组初始化哈希:

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

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

当您在 ed 引用上调用方法时bless,该引用将附加到该方法接收的参数列表中:

#!/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 = ['高',42,'低',11];
于 2009-11-09T19:07:13.300 回答
9

一些尚未处理的点:

在 Perl 中,构造函数只是一个名为new.

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

bless $self, $type;

您的两个示例都没有明确返回 bless 的结果。我希望你知道他们无论如何都是含蓄地这样做的。

于 2009-11-09T19:47:15.573 回答
8

如果将数组分配给哈希,perl 将数组中的交替元素视为键和值。你的数组看起来像

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

当您将其分配给 %hash 时,您会得到

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

这是在 perl 列表/哈希构造语法中的另一种说法,","并且"=>"意思相同。

于 2009-11-09T19:10:51.670 回答
5

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

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

散列扁平化并可以由列表初始化。模拟子程序的命名参数是一个常见的技巧。例如

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

忽略类名(已移开),奇数元素成为哈希键,偶数元素成为相应的值。

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

于 2009-11-09T19:07:36.373 回答
1

是的,我知道我在这里有点像死灵法师,但是...

虽然所有这些答案都很好,但我想我会提到Moose。Moose 使构造函数变得简单(package Foo;use Moose;自动提供了一个名为的构造函数new(尽管如果您愿意,可以覆盖名称“new”),但如果您需要它,它不会带走任何可配置性。

一旦我浏览了 Moose 的文档(总体上非常好,如果你适当地用谷歌搜索的话,还有更多的教程片段),我再也没有回头。

于 2011-09-21T00:03:03.497 回答