我对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";
我已将问题/混淆注入代码作为评论。
答案 0 :(得分:53)
要回答问题的主旨,因为哈希可以初始化为key => value
对列表,您可以将这样的列表发送到函数,然后将@_
分配给哈希。这是在Perl中执行命名参数的标准方法。
例如,
sub foo {
my %stuff = @_;
...
}
foo( beer => 'good', vodka => 'great' );
这将导致子例程%stuff
中的foo
具有包含两个密钥beer
和vodka
的哈希值以及相应的值。
现在,在OO Perl中,还有一些额外的皱纹。每当您使用箭头(->
)运算符调用方法时,箭头左侧的任何内容都会粘贴到@_
数组的开头。
所以,如果你说Foo->new( 1, 2, 3 )
;
然后在您的构造函数中,@_
将如下所示:( 'Foo', 1, 2, 3 )
。
因此,我们使用shift
隐式地对@_
进行操作,以便从@_
中获取第一项,并将其分配给$type
。之后,@_
只剩下我们的名称/值对,为方便起见,我们可以将它直接分配给哈希。
然后,我们将$type
值用于bless
。所有bless
都会引用(在第一个示例中为哈希引用),并说“此引用与特定包相关联”。 Alakazzam,你有一个对象。
请记住$type
包含字符串'Foo',这是我们包的名称。如果您不指定bless
的第二个参数,它将使用当前包的名称,该名称也适用于此示例但不为继承的构造函数工作。
答案 1 :(得分:18)
0.1。在Perl中,构造函数只是一个名为new的子例程。
是的,按惯例,new
是一个构造函数。它也可以执行初始化。如果发生了阻止对象创建的错误,new
应该成功返回一个对象或抛出异常(die
/ croak
)。
您可以根据自己的喜好为构造函数命名,拥有尽可能多的构造函数,甚至可以将bless对象构建到您想要的任何名称空间中(并非这是一个好主意)。
0.2。我没有得到my $type = shift;
所做的一切,但我总是看到它。我需要吗?
shift
会从@_
的头部开出一个参数并将其分配给$type
。 ->
运算符将调用者(左侧)作为子例程的第一个参数传递。所以这一行从参数列表中获取类名。而且,是的,你确实需要它。
0.3。一组输入如何成为%params
哈希? my %params = @_;
在列表上下文中完成对散列的分配,其中列表项对被分组为键/值对。因此,%foo = 1, 2, 3, 4;
会创建一个散列,$foo{1} == 2
和$foo{3} == 4
。通常这样做是为了为子例程创建命名参数。如果sub传递奇数个参数,则在启用警告时将生成警告。
0.4。 '我的$ self = {};`做什么?
此行创建一个匿名哈希引用,并将其分配给词法范围的变量$self
。哈希引用将存储对象的数据。通常,散列中的键具有与对象属性的一对一映射。因此,如果类Foo具有属性'size'和'color',如果检查Foo对象的内容,您将看到类似$foo = { size => 'm', color => 'black' };
的内容。
0.5。给定$self->{'High'} = $params{'High'};
$params{'High'}
来自哪里?
此代码依赖于传递给new
的参数。如果new
被调用为Foo->new( High => 46 )
,那么根据问题3创建的哈希将具有键High
的值(46)。在这种情况下,它相当于说$self->{High} = 46
。但是,如果该方法被称为Foo->new()
,那么没有值可用,我们有$self->{High} = undef
。
0.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;
}
答案 2 :(得分:9)
你的问题不是关于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];
答案 3 :(得分:9)
尚未处理的一些要点:
在Perl中,构造函数只是一个 子程序名为
new
。
不完全。调用构造函数new只是一个约定。你可以随便叫它。从perl的角度来看,这个名字没什么特别的。
bless $self, $type;
你的两个例子都没有明确地回复祝福的结果。我希望你知道他们无论如何都是这样做的。
答案 4 :(得分:8)
如果将数组分配给散列,perl会将数组中的交替元素视为键和值。您的数组看起来像
my @array = (key1, val1, key2, val2, key3, val3, ...);
当你将其分配给%hash时,你得到
my %hash = @array;
# %hash = ( key1 => val1, key2 => val2, key3 => val3, ...);
另一种说法是,在perl list / hash构造语法中,","
和"=>"
意味着同样的事情。
答案 5 :(得分:5)
在Perl中,子例程的所有参数都通过预定义的数组@_
传递。
shift
删除并返回@_
数组中的第一项。在Perl OO中,这是方法调用 - 通常是构造函数的类名和其他方法的对象。
哈希展平并可以按列表初始化。这是模拟子程序的命名参数的常用技巧。 e.g。
Employee->new(name => 'Fred Flintstone', occupation => 'quarry worker');
忽略类名(移出)奇数元素成为散列键,偶数元素成为相应的值。
my $self = {}
创建一个新的哈希引用来保存实例数据。 bless
函数将普通哈希引用$self
转换为对象。它所做的只是添加一些元数据,将参考标识为属于该类。
答案 6 :(得分:1)
是的,我知道我在这里是一个死灵法师,但是......
虽然所有这些答案都非常出色,但我想我会提到Moose。 Moose使构造函数变得容易(package Foo;use Moose;
自动提供一个名为new
的构造函数(尽管如果你愿意,可以覆盖名称“new”)但是doesn't take away any configurability如果你需要它。
一旦我浏览了Moose的文档(总体上非常好,并且如果你正确地使用谷歌,还有更多的教程片段),我从未回头。