我是 Perl 的新手,目前正在学习 Perl 面向对象,并且偶然发现了编写构造函数。看起来当使用new
子例程的名称时,第一个参数将是包名称。
构造函数必须使用关键字new
吗?还是因为当我们new
使用包名调用子程序时,传入的第一个参数是包名?
packagename->new;
当子例程有其他名称时,第一个参数将是对对象的引用?还是因为当通过对对象的引用调用子例程时,要传入的第一个参数将是对对象的引用?
$objRef->subroutine;
我是 Perl 的新手,目前正在学习 Perl 面向对象,并且偶然发现了编写构造函数。看起来当使用new
子例程的名称时,第一个参数将是包名称。
构造函数必须使用关键字new
吗?还是因为当我们new
使用包名调用子程序时,传入的第一个参数是包名?
packagename->new;
当子例程有其他名称时,第一个参数将是对对象的引用?还是因为当通过对对象的引用调用子例程时,要传入的第一个参数将是对对象的引用?
$objRef->subroutine;
注意:出于教学目的,以下所有示例均已简化。
是的,你是对的。如果作为方法调用, 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 调试器中并进行了堆栈转储,那么它的调用链中将包含与前一行类似的内容。
由于调用方法总是在参数列表前面加上 invocant,所有将作为方法调用的函数都必须考虑到那个“额外的”第一个参数。这很容易做到:
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. 使用间接方法名称的箭头调用:
如果您不知道方法名称,则可以将其名称放在变量中。仅使用箭头调用尝试此操作,而不是与格调用。
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
方法,一种根据其祈求者是否受到祝福而做不同事情的方法:
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;
}
然而,这是一种相当先进和微妙的技术,仅适用于少数不常见的情况。在大多数情况下不建议这样做,因为如果处理不当,可能会造成混淆——即使处理得当。