Perl构造函数关键字'new'

9

我是 Perl 的新手,目前正在学习 Perl 面向对象编程,并且遇到了编写构造函数的情况。当使用 new 作为子例程名称时,第一个参数将成为包名称。

构造函数必须使用关键字 new 吗?还是因为当我们使用包名称调用 new 子例程时,需要传递的第一个参数是包名称?

packagename->new;

当子程序有其他名称时,第一个参数将是对对象的引用吗?还是因为当通过对象的引用调用子程序时,要传递的第一个参数将是对对象的引用?
$objRef->subroutine;

3
如果你还没有阅读过《现代Perl》,那么阅读它可能会对你有所裨益。http://modernperlbooks.com/books/modern_perl_2014/ - Neil H Watson
3
在Perl中没有new关键字。它是许多类的对象构造器的常用名称,并且被这些类用作这种方式,但这并不是必需的。 - tchrist
1
是的,你是对的:在方法调用下,调用者总是第一个参数(或零), 无论调用方法如何。 - tchrist
2个回答

14

NB: 以下所有示例都是简化的,只用于教学目的。

关于方法

是的,你说得对。如果作为方法调用,new函数的第一个参数将是你调用它的对象。

有两种“风格”可以调用方法,但无论哪种方式,结果都是相同的。一种风格依赖于运算符,即二进制的 -> 运算符。另一种风格则依赖于参数的顺序,就像英语中双及物动词的工作方式一样。大多数人只在内置函数和也许构造函数中使用与其他东西不同的这种数据/双及物风格。

在大多数情况下(但并非全部情况),以下两个示例是等效的:

1. 数据/双及物式 调用方法

这是位置风格,它使用单词顺序来确定正在发生的事情。

use Some::Package;
my $obj1 = new Some::Package NAME => "fred"; 

注意我们没有使用箭头方法:->。这就是Perl自己在许多自己的函数中使用的方式,比如

 printf STDERR "%-20s: %5d\n", $name, $number;

几乎每个人都喜欢这种方式,而不是等效的方式:

 STDERR->printf("%-20s: %5d\n", $name, $number);

然而,现在这种数据调用方式几乎只用于内置函数,因为人们经常混淆。

2. 箭头 调用方法

箭头调用方式大多更清晰、更简洁,而且不太可能让你陷入 Perl 解析怪异性的困扰中。注意我说的是“不太可能”,我并没有说它完全摆脱了所有的问题。但就本回答的目的而言,我们假装如此。

use Some::Package;
my $obj2 = Some::Package->new(NAME => "fred");

在运行时,除非有奇特的花哨特性或继承问题,否则实际的函数调用会是:

 Some::Package::new("Some::Package", "NAME", "fred");
例如,如果您在Perl调试器中并执行堆栈转储,它将在其调用链中有类似于前一行的内容。
由于调用方法始终以调用者为前缀,所有将作为方法调用的函数都必须考虑到该“额外”的第一个参数。这非常容易实现:
package Some::Package;
sub new {
   my($classname, @arguments) = @_;
   my $obj = { @arguments };
   bless $obj, $classname;
   return $obj;
}

这只是一个极其简化的示例,介绍了构造函数的最常用方式以及内部发生的情况。在实际的生产代码中,构造函数会更加小心谨慎。

方法和间接引用

有时您不知道类名称或方法名称,因此需要使用变量来存储其中之一或两者皆有。编程中的间接引用与自然语言中的间接对象不同。间接引用只是意味着您有一个包含其他内容的变量,因此您可以使用该变量来访问其内容。

print 3.14;    # print a number directly

$var = 3.14;   # or indirectly
print $var;

除了方法的参数,我们可以使用变量来保存方法调用中涉及的其他内容。

3. 带有间接“方法名称”的箭头调用:

如果您不知道方法的名称,那么可以将其名称放入变量中。仅在箭头调用中尝试此操作,而不是在dative调用中。

use Some::Package;
my $action = (rand(2) < 1) ? "new" : "old";
my $obj    = Some::Package->$action(NAME => "fido");

在这里,直到运行时才知道方法名称。

4. 通过间接的名调用箭头:

这里我们使用一个变量来包含我们想要使用的类的名称。

my $class = (rand(2) < 1) 
              ? "Fancy::Class" 
              : "Simple::Class";
my $obj3 = $class->new(NAME => "fred");

现在我们随机选择一个类或另一个类。

你也可以这样使用[数据执行]:

my $obj3 = new $class NAME => "fred";

但这通常不会在用户方法中进行。不过内置方法有时会发生这种情况。

my $fh = ($error_count == 0) ? *STDOUT : *STDERR;
printf $fh "Error count: %d.\n", $error_count;

这是因为在赋予与格时尝试使用表达式通常不会起作用,除非周围有一个块;否则它只能是一个简单的标量变量,甚至不能是数组或哈希中的单个元素。

printf { ($error_count == 0) ? *STDOUT : *STDERR } "Error count: %d.\n", $error_count;
更简单地说:
print { $fh{$filename} } "Some data.\n";

这相当丑陋。

让调用者小心

请注意,这并不完美。在数据对象插槽中的文字和变量有所不同。例如,使用文字文件句柄:

print STDERR;
意味着
print STDERR $_;

但是如果您使用间接文件句柄,就像这样:

print $fh;

这实际上意味着

print STDOUT $fh;

这很可能不是您想要的意思,您想要的可能是这个:

print $fh $_;

$fh->print($_);

高级用法:双重性方法

方法调用箭头->并不关心其左侧操作数是表示类名的字符串还是表示对象实例的引用。

当然,$class不一定必须包含一个包名。它可以是任意一个,如果是这样,那么就要由方法本身来处理正确的事情。

use Some::Class;

my $class = "Some::Class";
my $obj   = $class->new(NAME => "Orlando"); 

my $invocant = (rand(2) < 1) ? $class : $obj;
$invocant->any_debug(1);

这需要一个相当高级的any_debug方法,根据它的调用者是否被赋予了祝福,该方法会执行不同的操作:

那需要一个相当高级的any_debug方法,具体取决于其调用者是否已被赋予祝福:

package Some::Class;
use Scalar::Util qw(blessed);

sub new {
   my($classname, @arguments) = @_; 
   my $obj = { @arguments };
   bless $obj, $classname;
   return $obj;
}   

sub any_debug {
    my($invocant, $value) = @_;
    if (blessed($invocant)) {
        $invocant->obj_debug($value);
    } else {
        $invocant->class_debug($value);
    }
}

sub obj_debug {
    my($self, $value) = @_;
    $self->{DEBUG} = $value;
}

my $Global_Debug;
sub class_debug {
    my($classname, $value) = @_;
    $Global_Debug = $value;
}

然而,这是一项相当高级和微妙的技术,只适用于极少数不常见的情况。对于大多数情况,不建议使用它,因为如果处理不当可能会令人困惑,甚至可能会出现问题。


7
这句话的意思是,“用new关键字创建对象时,第一个参数不是间接对象语法。”
perl -MO=Deparse -e 'my $o = new X 1, 2'

这会被解析为

my $o = 'X'->new(1, 2);

根据perldoc中的描述,Perl 支持一种称为“间接对象”符号的方法调用语法。这种语法被称为“间接”,因为方法位于被调用对象之前。

也就是说,new 并不是一个专为构造函数调用保留的关键字,而是方法/构造函数本身的名称,在 Perl 中并没有强制执行(例如,DBIconnect 构造函数)。


因为使用“X”调用了新的子程序,所以第一个参数将是“X”,它应该是子程序new的包名称。 - user2763829
@user2763829 是的,但请注意只有1和2是参数。 - mpapec
这里调用的函数是除了继承之外的模块X::new("X", 1, 2),因此将接收调用者作为其第零个参数。此外,关于间接对象方法调用的简要概述可能不足以让任何人知道它实际上意味着什么。请参阅perlglossary(1)手册页。 - tchrist
1
这是关于英语中的与格宾语:间接宾语: 在英语语法中,指动词和其直接宾语之间的简短名词短语,表示行动的受益者或接收者。在Perl中,print STDOUT "$foo\n";可以理解为“动词间接宾语宾语”,其中STDOUT是打印操作的接收者,而$foo是被打印的对象。同样,在调用一个方法时,你可能会将调用者放在该方法和其参数之间的与格位置上:$gollum = new Pathetic::Creature "Sméagol"; give $gollum "Fisssssh!"; give $gollum "Precious!"; - tchrist
间接对象槽:在使用间接对象调用语法时,位于方法调用和其参数之间的句法位置。(该插槽的特点是它与下一个参数之间没有逗号。)这里STDERR位于间接对象插槽中: “print STDERR” 唤醒!唤醒!恐惧、火焰、敌人!唤醒!\n”; - tchrist
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接