我是 Perl 的新手,目前正在学习 Perl 面向对象编程,并且遇到了编写构造函数的情况。当使用 new
作为子例程名称时,第一个参数将成为包名称。
构造函数必须使用关键字 new
吗?还是因为当我们使用包名称调用 new
子例程时,需要传递的第一个参数是包名称?
packagename->new;
当子程序有其他名称时,第一个参数将是对对象的引用吗?还是因为当通过对象的引用调用子程序时,要传递的第一个参数将是对对象的引用?
$objRef->subroutine;
我是 Perl 的新手,目前正在学习 Perl 面向对象编程,并且遇到了编写构造函数的情况。当使用 new
作为子例程名称时,第一个参数将成为包名称。
构造函数必须使用关键字 new
吗?还是因为当我们使用包名称调用 new
子例程时,需要传递的第一个参数是包名称?
packagename->new;
$objRef->subroutine;
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;
}
然而,这是一项相当高级和微妙的技术,只适用于极少数不常见的情况。对于大多数情况,不建议使用它,因为如果处理不当可能会令人困惑,甚至可能会出现问题。
new
的包名称。 - user2763829X::new("X", 1, 2)
,因此将接收调用者作为其第零个参数。此外,关于间接对象方法调用的简要概述可能不足以让任何人知道它实际上意味着什么。请参阅perlglossary(1)手册页。 - tchristprint STDOUT "$foo\n";
可以理解为“动词间接宾语宾语”,其中STDOUT是打印操作的接收者,而$foo
是被打印的对象。同样,在调用一个方法时,你可能会将调用者放在该方法和其参数之间的与格位置上:$gollum = new Pathetic::Creature "Sméagol";
give $gollum "Fisssssh!";
give $gollum "Precious!";
- tchrist
new
关键字。它是许多类的对象构造器的常用名称,并且被这些类用作这种方式,但这并不是必需的。 - tchrist