我理解在类的"新"方法中使用Perl中的"bless"关键字:
sub new {
my $self = bless { };
return $self;
}
Run Code Online (Sandbox Code Playgroud)
但究竟什么是"祝福"对哈希引用呢?
Gor*_*son 138
通常,bless将对象与类关联.
package MyClass;
my $object = { };
bless $object, "MyClass";
Run Code Online (Sandbox Code Playgroud)
现在当你调用一个方法时$object,Perl知道要搜索该方法的包.
如果省略第二个参数,如示例中所示,则使用当前包/类.
为清楚起见,您的示例可能写成如下:
sub new {
my $class = shift;
my $self = { };
bless $self, $class;
}
Run Code Online (Sandbox Code Playgroud)
kix*_*ixx 77
bless 将引用与包关联.
引用的内容并不重要,它可以是散列(最常见的情况),数组(不常见),标量(通常这表示内向外对象),正则表达式,子程序或TYPEGLOB(参见" 面向对象的Perl:Damian Conway的概念和编程技术综合指南 "一书中的有用示例)或者甚至是对文件或目录句柄的引用(最不常见的情况).
效果bless是它允许您将特殊语法应用于祝福的引用.
例如,如果存储了一个有福的引用$obj(bless与包"Class" 关联),那么$obj->foo(@args)将调用一个子例程foo并作为第一个参数传递引用,$obj后跟其余的arguments(@args).子程序应在包"Class"中定义.如果foo包"Class"中没有子程序,@ISA则将搜索其他包的列表(从包"Class"中的数组中获取),并且将调用foo找到的第一个子例程.
小智 6
此函数告诉REF引用的实体它现在是CLASSNAME包中的对象,或者如果省略CLASSNAME则是当前包.建议使用两种形式的祝福.
示例:
bless REF, CLASSNAME
bless REF
Run Code Online (Sandbox Code Playgroud)
回报价值
此函数返回对包含在CLASSNAME中的对象的引用.
示例:
以下是显示其基本用法的示例代码,通过祝福对包的类的引用来创建对象引用 -
#!/usr/bin/perl
package Person;
sub new
{
my $class = shift;
my $self = {
_firstName => shift,
_lastName => shift,
_ssn => shift,
};
# Print all the values just for clarification.
print "First Name is $self->{_firstName}\n";
print "Last Name is $self->{_lastName}\n";
print "SSN is $self->{_ssn}\n";
bless $self, $class;
return $self;
}
Run Code Online (Sandbox Code Playgroud)
我将尝试在这里提供一个答案,因为在我最初写这篇文章时,这里的答案并不太适合我(警告,这个答案的结构相当糟糕,请随意跳过对我来说不是特别有用的部分)你)。
Perl 的 bless 函数将指定的引用与一个包名字符串关联起来,并让被祝福的引用的箭头运算符在与该引用关联的包中查找方法,如果没有找到,则继续使用数组查找(如果有@ISA)是一个(这超出了本文的范围)。
为什么我们需要这个?
让我们首先用 JavaScript 表达一个示例:
(() => {
//'use strict'; // uncomment to fix the bug mentioned below.
class Animal {
constructor(args) {
console.log(this);
this.name = args.name;
this.sound = args.sound;
}
}
/* This is left for historical reasons,
* modern JavaScript engines no longer allow you to
* call class constructors without using new.
*
* var animal = Animal({
* 'name': 'Jeff',
* 'sound': 'bark'
* });
* console.log(animal.name + ', ' + animal.sound); // seems good
* console.log(window.name); // my window's name is Jeff?
*/
// as of recently, Animal constructor cannot be called without using the new operator.
var animal = new Animal({
'name': 'Jeff',
'sound': 'bark'
});
console.log(animal.name + ', ' + animal.sound); // still fine.
console.log("window's name: " + window.name); // undefined
})();
Run Code Online (Sandbox Code Playgroud)
现在让我们看看类构造的脱糖版本:
(() => {
// 'use strict'; // uncomment to fix bug.
var Animal = function(args) {
this.name = args.name;
this.sound = args.sound;
return this; // implicit context hashmap
};
/**
* The bug left for historical reasons,
* should still work now in modern web developer consoles.
*
* var animal = Animal({
* 'name': 'Jeff',
* 'sound': 'Bark'
* });
* console.log(animal.name + ', ' + animal.sound); // seems good
* console.log("The window's name is: " + window.name); // my window's name is Jeff?
*/
// the new operator causes the "this" inside methods to refer to the animal
// rather than the global scope, so the bug mentioned above does not occur.
var animal = new Animal({
'name': 'Jeff',
'sound': 'bark'
});
console.log(animal.sound);
console.log(window.name); // the name has not been changed by the constructor.
})();
Run Code Online (Sandbox Code Playgroud)
Animal 的构造函数接受一个属性对象并返回一个具有这些属性的 Animal,或者如果您忘记添加 new 关键字,它将返回整个全局上下文(位于window浏览器开发人员控制台内)。
Perl 没有“this”、“new”或“class”,但它仍然可以有一个行为类似的函数。我们不会有构造函数或原型,但我们将能够创建新的动物并修改它们的个体属性。
# immediatly invoked subroutine execution(IIFE).
(sub {
my $Animal = (sub {
return {
'name' => $_[0]{'name'},
'sound' => $_[0]{'sound'}
};
});
my $animal = $Animal->({
'name' => 'Jeff',
'sound' => 'bark'
});
print $animal->{sound};
})->();
Run Code Online (Sandbox Code Playgroud)
现在,我们有一个问题:如果我们希望动物自己发出声音,而不是直接打印它们的声音,该怎么办?也就是说,我们需要一个函数performSound 来打印动物自己的声音。
实现此目的的一种方法是为 Animal 的每个实例提供其自己的 PerformSound 子例程引用。
# self contained scope
(sub {
my $Animal = (sub {
$name = $_[0]{'name'};
$sound = $_[0]{'sound'};
return {
'name' => $name,
'sound' => $sound,
'performSound' => sub {
print $sound . "\n";
}
};
});
my $animal = $Animal->({
'name' => 'Jeff',
'sound' => 'bark'
});
$animal->{'performSound'}();
})->();
Run Code Online (Sandbox Code Playgroud)
这通常不是我们想要的,因为 PerformSound 被作为构建的每个动物的全新子例程引用。构建 10000 只动物可能会分配 10000 个 PerformSound 子例程。我们希望有一个子例程performSound,供所有查找自己的声音并打印它的Animal 实例使用。
(() => {
'use strict';
/* a function that creates an Animal constructor which can be used to create animals */
var Animal = (() => {
/* function is important, as fat arrow does not have "this" and will not be bound to Animal. */
var InnerAnimal = function(args) {
this.name = args.name;
this.sound = args.sound;
};
/* defined once and all animals use the same single function call */
InnerAnimal.prototype.performSound = function() {
console.log(this.name);
};
return InnerAnimal;
})();
var animal = new Animal({
'sound': 'bark',
'name': 'Jeff'
});
animal.performSound(); // Jeff
})();
Run Code Online (Sandbox Code Playgroud)
与 Perl 的相似之处就到此为止了。
JavaScript 的 new 运算符不是可选的,没有它,对象方法内的“this”会污染全局范围:
(() => {
// uncommenting this prevents unintentional
// contamination of the global scope, and throws a TypeError instead.
// 'use strict';
var Person = function() {
this.name = "Sam";
};
// var wrong = Person(); // oops! we have overwritten window.name or global.main.
// console.log(window.name); // my window's name is Sam?
var correct = new Person; // person's name is actually stored in the person now.
})();
Run Code Online (Sandbox Code Playgroud)
我们希望为每个动物提供一个函数来查找该动物自己的声音,而不是在构造时对其进行硬编码。
Blessing 让我们可以使用包的子例程,而不必将子例程引用附加到每个对象,它还可以引用ref更有意义的包名称(例如Animal)作为对象的名称,而不是无聊的HASH或任何其他引用对象你选择祝福:
package Animal;
sub new {
my $packageRef = $_[0];
my $name = $_[1]->{'name'};
my $sound = $_[1]->{'sound'};
my $this = {
'name' => $name,
'sound' => $sound
};
bless($this, $packageRef);
return $this;
}
# all animals use the same performSound to look up their sound.
sub performSound {
my $this = shift;
my $sound = $this->{'sound'};
print $sound . "\n";
}
package main;
my $animal = Animal->new({
'name' => 'Cat',
'sound' => 'meow'
});
print("The animal's ref is: " . ref($animal) . "\n");
$animal->performSound();
Run Code Online (Sandbox Code Playgroud)
摘要/TL;DR:
Perl 没有“this”,“class”,也没有“new”。
将对象祝福到包中会为该对象提供对该包的引用。
使用箭头运算符来调用受祝福的referent( $blessedObject->method(...arguments))的方法通常与调用相同Package::method($blessedObject, ...arguments),但如果未找到方法,它将继续使用@ISA包的方法进行查找,这超出了本文的范围。
事实上,您可以在运行时创建新类,只要您违反严格的“refs”或使用 eval,这里演示了如何做到这一点:
#!/usr/bin/perl
use warnings;
use strict;
print('Enter the name for the class(eg Greeter): $ ');
my $class_name = <>;
chomp $class_name;
print('Enter the name of the method(eg greet): $ ');
my $method_name = <>;
chomp $method_name;
no strict 'refs';
# The line below violates strict 'refs'.
*{$class_name . '::new'} = sub {
my $self = bless {}, $_[0];
return $self;
};
use strict 'refs';
no strict 'refs';
# The line below violates strict 'refs'
*{$class_name . '::' . $method_name} = sub {
print("Hello, World!\n");
};
use strict 'refs';
my $instance = ($class_name)->new();
$instance->$method_name();
Run Code Online (Sandbox Code Playgroud)
为什么会出现混乱?:
bless 令人困惑的原因之一是,实际上存在三种调用包的方法
A::a(), 作为封装子程序。A->a(), 作为包子例程,但它__PACKAGE__作为第一个参数隐式地在其他参数之前传递。$a->a()有福$a成A。下面的代码说明了这一点:
# | Illustrates catching 3 distinct ways of calling a package's member.
package Test;
sub new {
return bless {}, __PACKAGE__;
}
sub runTest {
if (ref($_[0]) eq __PACKAGE__) {
# | $_[0] is the blessed reference.
# | Despite being called with "->", $_[1] is NOT "Test"(__PACKAGE__).
print("Test::runTest was called through a blessed reference call(\$instance->runTest().\n");
} elsif ($_[0] eq __PACKAGE__) {
# | $_[0] is "Test"(__PACKAGE__), but we can't determine for sure whether it was -> or ::.
print("Test::runTest was called through Test->runTest() or through Test::runTest() with 'Test' as the first argument.\n");
} else {
# | $_[0] is neither a blessed reference nor "Test"(__PACKAGE__), so it can't be an arrow call.
print "Test::runTest was called through Test::runTest()\n";
}
}
package main;
my $test = Test->new();
$test->runTest();
Test->runTest();
Test::runTest();
Test::runTest('Test'); # <- Same as "Test->runTest();"
Test::runTest($test); # <- Same as "$test->runTest();"
Run Code Online (Sandbox Code Playgroud)
另一个原因是,与 JavaScript、Python 不同,它们可以有多个具有不同名称但不同定义/方法/属性的类,Perl 类具有 unique( ref $obj),因为在任何给定时间只能有一个具有特定名称的包,并且 @ ISA 需要一点时间来适应。
我的最后一个原因是,包比其他脚本语言中的类更不具体,在其他脚本语言中,您可以通过赋值运算符将对类本身的引用存储在变量中,而在 Perl 中,您不仅不能存储对类的引用(您只能通过名称字符串引用包),但是如果不违反严格的“refs”或使用 eval,试图通过存储在变量中的名称(例如 String[$method])引用包似乎是不可能的。
无论如何,希望有人会觉得这篇文章有用。
注意:这是一个相当古老的答案尝试,我试图清理大量天真和令人尴尬/毫无意义/分散注意力的陈述,并添加更多有用的示例来帮助理解这个概念,但它仍然远离我想要的喜欢它(重读仍然相当令人沮丧)。我留下它,因为我仍然相信它可能对某人有用。
请谨慎对待,对于因答案结构不佳而引起的任何头痛,我深表歉意。
-ed 引用的具体区别bless在于它获得额外的内部内容:SV 引用(存储在标量中)获取附加值FLAGS( OBJECT),并且有一个STASH携带包名称(还有一些其他差异)
perl -MDevel::Peek -wE'
package Pack { sub func { return { a=>1 } } };
package Class { sub new { return bless { A=>10 } } };
$vp = Pack::func(); print Dump $vp; say"---";
$obj = Class->new; print Dump $obj'
Run Code Online (Sandbox Code Playgroud)
打印,相同(且与此无关)部分被抑制
SV = IV(0x12d5530) at 0x12d5540
REFCNT = 1
FLAGS = (ROK)
RV = 0x12a5a68
SV = PVHV(0x12ab980) at 0x12a5a68
REFCNT = 1
FLAGS = (SHAREKEYS)
...
SV = IV(0x12a5ce0) at 0x12a5cf0
REFCNT = 1
FLAGS = (IOK,pIOK)
IV = 1
---
SV = IV(0x12cb8b8) at 0x12cb8c8
REFCNT = 1
FLAGS = (PADMY,ROK)
RV = 0x12c26b0
SV = PVHV(0x12aba00) at 0x12c26b0
REFCNT = 1
FLAGS = (OBJECT,SHAREKEYS) <--
STASH = 0x12d5300 "Class" <--
...
SV = IV(0x12c26b8) at 0x12c26c8
REFCNT = 1
FLAGS = (IOK,pIOK)
IV = 10
Run Code Online (Sandbox Code Playgroud)
据此,口译员知道
这是一个对象
它属于什么包
这告诉了它的用途。
例如,当取消引用该变量 ( $obj->name) 时,在包(或层次结构)中查找具有该名称的子项,变量(“对象”)将作为第一个参数传递,等等。