分类:
2008-02-04 14:22:34
package cocoa;
#
# put "require" statements in for all required,imported packages
#
#
# just add code here
#
1; # terminate the package with the required 1;
接下来,我们往包里添加方法使之成为一个类。第一个需添加的方法是new(),它是创建对象时必须被调用的,new()方法是对象的构造函数.
四、构造函数sub new {
my $this = {}; # create an anonymous hash, and #self points to it.
bless $this; # connect the hash to the package cocoa.
return $this; # return the reference to the hash.
}
1;
{}创建一个对不含键/值对的哈希表(即关联数组)的引用,返回值被赋给局域变量$this。函数bless()取出该引用,告诉对象它引用的是cocoa,最后返回该引用。函数的返回值现在指向这个匿名哈希表。
从new()函数返回后,$this引用被销毁,但调用函数保存了对该哈希表的引用,因此该哈希表的引用数不会为零,从而使perl在内存中保存该哈希表。创建对象可如下调用:
$cup = new cocoa;
旅嬗锞湮?褂酶冒?唇ǘ韵蟮睦?樱?
1 #!/usr/bin/perl
2 push (@inc,"pwd");
3 use cocoa;
4 $cup = new cocoa;
第一行指出perl解释器的位置,第二行中,将当前目录加到路径寻找列表@inc中供寻找包时使用。你也可以在不同的目录中创建你的模块并指出该绝对路径。例如,如果在/home/test/scripts/创建包,第二行就应该如下:
push (@inc , "/home/test/scripts");
在第三行中,包含上包cocoa.pm以获取脚本中所需功能。use语句告诉perl在@inc路径寻找文件cocoa.pm并包含到解析的源文件拷贝中。use语句是使用类必须的。第四行调用new函数创建对象,这是perl的妙处,也是其易混淆之处,也是其强大之处。创建对象的方法有多种,可以这样写:
$cup = cocoa->new();
如果你是c程序员,可以用双冒号强制使用cocoa包中的new()函数,如:
$cup = cocoa::new();
可以在构造函数中加入更多的代码,如在cocoa.pm中,可以在每个对象创建时输出一个简单声明,还可以用构造函数初始化变量或设置数组或指针。
注意:
1、一定要在构造函数中初始化变量;
2、一定要用my函数在方法中创建变量;
3、一定不要在方法中使用local,除非真的想把变量传递给其它子程序;
4、一定不要在类模块中使用全局变量。
加上声明的cocoa构造函数如下:
sub new {
my $this = {};
print "
/*
** created by cocoa.pm
** use at own risk";
print "
** did this code even get pass the javac compiler? ";
print "
**/
";
bless $this;
return $this;
}
也可以简单地调用包内或包外的其它函数来做更多的初始化工作,如:
sub new {
my $this = {}
bless $this;
$this->doinitialization();
return $this;
}
创建类时,应该允许它可被继承,应该可以把类名作为第一个参数来调用new函数,那么new函数就象下面的语句:
sub new {
my $class = shift; # get the request class name
my $this = {};
bless $this, $class # use class name to bless() reference
$this->doinitialization(); return $this;
}
此方法使用户可以下列三种方式之一来进行调用:
cocoa::new()
cocoa->new()
new cocoa
可以多次bless一个引用对象,然而,新的将被bless的类必然把对象已被bless的引用去掉,对c和pascal程序员来说,这就象把一个指针赋给分配的一块内存,再把同一指针赋给另一块内存而不释放掉前一块内存。总之,一个perl对象每一时刻只能属于一个类。
对象和引用的真正区别是什么呢?perl对象被bless以属于某类,引用则不然,如果引用被bless,它将属于一个类,也便成了对象。对象知道自己属于哪个类,引用则不属于任何类。
实例变量
作为构造函数的new()函数的参数叫做实例变量。实例变量在创建对象的每个实例时用于初始化,例如可以用new()函数为对象的每个实例起个名字。
可以用匿名哈希表或匿名数组来保存实例变量。
用哈希表的代码如下:
sub new {
my $type = shift;
my %parm = @_;
my $this = {};
$this->{"name"} = $parm{"name"};
$this->{"x"} = $parm{"x"};
$this->{"y"} = $parm{"y"};
bless $this, $type;
}
用数组保存的代码如下:
sub new {
my $type = shift;
my %parm = @_;
my $this = [];
$this->[0] = $parm{"name"};
$this->[1] = $parm{"x"};
$this->[2] = $parm{"y"};
bless $this, $type;
}
构造对象时,可以如下传递参数:
$mug = cocoa::new( "name" => "top","x" => 10,"y" => 20 );
操作符=>与逗号操作服功能相同,但=>可读性好。访问方法如下:
print "name=$mug->{"name"}
";
print "x=$mug->{"x"}
";
print "y=$mug->{"y"}
";
1. sub namelister {
2. my $this = shift;
3. my ($keys ,$value );
4. while (($key, $value) = each (%$this)) {
5. print "t$key is $value.n";
6. }
7. }
六、方法的输出
如果你现在想引用cocoa.pm包,将会得到编译错误说未找到方法,这是因为cocoa.pm的方法还没有输出。输出方法需要exporter模块,在包的开始部分加上下列两行:
require exporter;
@isa = qw (exporter);
这两行包含上exporter.pm模块,并把exporter类名加入@isa数组以供查找。接下来把你自己的类方法列在@export数组中就可以了。例如想输出方法closemain和declaremain,语句如下:
@export = qw (declaremain , closemain);
perl类的继承是通过@isa数组实现的。@isa数组不需要在任何包中定义,然而,一旦它被定义,perl就把它看作目录名的特殊数组。它与@inc数组类似,@inc是包含文件的寻找路径。@isa数组含有类(包)名,当一个方法在当前包中未找到时就到@isa中的包去寻找。@isa中还含有当前类继承的基类名。
类中调用的所有方法必须属于同一个类或@isa数组定义的基类。如果一个方法在@isa数组中未找到,perl就到autoload()子程序中寻找,这个可选的子程序在当前包中用sub定义。若使用autoload子程序,必须用use autoload;语句调用autoload.pm包。autoload子程序尝试从已安装的perl库中装载调用的方法。如果autoload也失败了,perl再到universal类做最后一次尝试,如果仍失败,perl就生成关于该无法解析函数的错误。
七、方法的调用
调用一个对象的方法有两种方法,一是通过该对象的引用(虚方法),一是直接使用类名(静态方法)。当然该方法必须已被输出。现在给cocoa类增加一些方法,代码如下:
package cocoa;
require exporter;
@isa = qw(exporter);
@export = qw(setimports, declaremain, closemain);
#
# this routine creates the references for imports in java functions
#
sub setimports{
my $class = shift @_;
my @names = @_;
foreach (@names) {
print "import " . $_ . ";n";
}
}
#
# this routine declares the main function in a java script
#
sub declaremain{
my $class = shift @_;
my ( $name, $extends, $implements) = @_;
print "n public class $name";
if ($extends) {
print " extends " . $extends;
}
if ($implements) {
print " implements " . $implements;
}
print " { n";
}
#
# this routine declares the main function in a java script
#
sub closemain{
print "} n";
}
#
# this subroutine creates the header for the file.
#
sub new {
my $this = {};
print "n /* n ** created by cocoa.pm n ** use at own risk n */ n";
bless $this;
return $this;
}
1;
现在,我们写一个简单的perl脚本来使用该类的方法,下面是创建一个java applet源代码骨架的脚本代码:
#!/usr/bin/perl
use cocoa;
$cup = new cocoa;
$cup->setimports( "java.io.inputstream", "java.net.*");
$cup->declaremain( "msg" , "java.applet.applet", "runnable");
$cup->closemain();
这段脚本创建了一个叫做msg的java applet,它扩展(extend)了java.applet.applet小应用程序并使之可运行(runnable),其中最后三行也可以写成如下:
cocoa::setimports($cup, "java.io.inputstream", "java.net.*");
cocoa::declaremain($cup, "msg" , "java.applet.applet", "runnable");
cocoa::closemain($cup);
其运行结果如下:
/*
** created by cocoa.pm
** use at own risk
*/
import java.io.inputstream;
import java.net.*;
public class msg extends java.applet.applet implements runnable {
}
注意:如果用->操作符调用方法(也叫间接调用),参数必须用括号括起来,如:$cup->setimports( "java.io.inputstream", "java.net.*");而双冒号调用如:cocoa::setimports($cup, "java.io.inputstream", "java.net.*");也可去掉括号写成:cocoa::setimports $cup, "java.io.inputstream", "java.net.*" ;
八、重载sub destroy {
#
# add code here.
#
}
因为多种目的,perl使用了简单的、基于引用的垃圾回收系统。任何对象的引用数目必须大于零,否则该对象的内存就被释放。当程序退出时,perl的一个彻底的查找并销毁函数进行垃圾回收,进程中的一切被简单地删除。在unix类的系统中,这像是多余的,但在内嵌式系统或多线程环境中这确实很必要。
十、继承
类方法通过@isa数组继承,变量的继承必须明确设定。下例创建两个类bean.pm和coffee.pm,其中coffee.pm继承bean.pm的一些功能。此例演示如何从基类(或称超类)继承实例变量,其方法为调用基类的构造函数并把自己的实例变量加到新对象中。
bean.pm代码如下:
package bean;
require exporter;
@isa = qw(exporter);
@export = qw(setbeantype);
sub new {
my $type = shift;
my $this = {};
$this->{"bean"} = "colombian";
bless $this, $type;
return $this;
}
#
# this subroutine sets the class name
sub setbeantype{
my ($class, $name) = @_;
$class->{"bean"} = $name;
print "set bean to $name n";
}
1;
此类中,用$this变量设置一个匿名哈希表,将"bean"类型设为"colombian"。方法setbeantype()用于改变"bean"类型,它使用$class引用获得对对象哈希表的访问。
coffee.pm代码如下:
1 #
2 # the coffee.pm file to illustrate inheritance.
3 #
4 package coffee;
5 require exporter;
6 require bean;
7 @isa = qw(exporter, bean);
8 @export = qw(setimports, declaremain, closemain);
9 #
10 # set item
11 #
12 sub setcoffeetype{
13 my ($class,$name) = @_;
14 $class->{"coffee"} = $name;
15 print "set coffee type to $name n";
16 }
17 #
18 # constructor
19 #
20 sub new {
21 my $type = shift;
22 my $this = bean->new(); ##### <- look here!!! ####
23 $this->{"coffee"} = "instant"; # unless told otherwise
24 bless $this, $type;
25 return $this;
26 }
27 1;
第6行的require bean;语句包含了bean.pm文件和所有相关函数,方法setcoffeetype()用于设置局域变量$class->{"coffee"}的值。在构造函数new()中,$this指向bean.pm返回的匿名哈希表的指针,而不是在本地创建一个,下面两个语句分别为创建不同的哈希表从而与bean.pm构造函数创建的哈希表无关的情况和继承的情况:
my $this = {}; #非继承
my $this = $thesuperclass->new(); #继承
下面代码演示如何调用继承的方法:
1 #!/usr/bin/perl
2 push (@inc,"pwd");
3 use coffee;
4 $cup = new coffee;
5 print "n -------------------- initial values ------------ n";
6 print "coffee: $cup->{"coffee"} n";
7 print "bean: $cup->{"bean"} n";
8 print "n -------------------- change bean type ---------- n";
9 $cup->setbeantype("mixed");
10 print "bean type is now $cup->{"bean"} n";
11 print "n ------------------ change coffee type ---------- n";
12 $cup->setcoffeetype("instant");
13 print "type of coffee: $cup->{"coffee"} n";
该代码的结果输出如下:
-------------------- initial values ------------
coffee: instant
bean: colombian
-------------------- change bean type ----------
set bean to mixed
bean type is now mixed
------------------ change coffee type ----------
set coffee type to instant
type of coffee: instant
上述代码中,先输出对象创建时哈希表中索引为"bean"和"coffee"的值,然后调用各成员函数改变值后再输出。
方法可以有多个参数,现在向coffee.pm模块增加函数makecup(),代码如下:
sub makecup {
my ($class, $cream, $sugar, $dope) = @_;
print "n================================== n";
print "making a cup n";
print "add cream n" if ($cream);
print "add $sugar sugar cubesn" if ($sugar);
print "making some really addictive coffee ;-) n" if ($dope);
print "================================== n";
}
此函数可有三个参数,不同数目、值的参数产生不同的结果,例如:
1 #!/usr/bin/perl
2 push (@inc,"pwd");
3 use coffee;
4 $cup = new coffee;
5 #
6 # with no parameters
7 #
8 print "n calling with no parameters: n";
9 $cup->makecup;
10 #
11 # with one parameter
12 #
13 print "n calling with one parameter: n";
14 $cup->makecup("1");
15 #
16 # with two parameters
17 #
18 print "n calling with two parameters: n";
19 $cup->makecup(1,"2");
20 #
21 # with all three parameters
22 #
23 print "n calling with three parameters: n";
24 $cup->makecup("1",3,"1");
其结果输出如下:
calling with no parameters:
==================================
making a cup
==================================
calling with one parameter:
==================================
making a cup
add cream
==================================
calling with two parameters:
==================================
making a cup
add cream
add 2 sugar cubes
==================================
calling with three parameters:
==================================
making a cup
add cream
add 3 sugar cubes
making some really addictive coffee ;-)
==================================
在此例中,函数makecup()的参数既可为字符串也可为整数,处理结果相同,你也可以把这两种类型的数据处理区分开。在对参数的处理中,可以设置缺省的值,也可以根据实际输入参数值的个数给予不同处理。
十一、子类方法的重载
继承的好处在于可以获得基类输出的方法的功能,而有时需要对基类的方法重载以获得更具体或不同的功能。下面在bean.pm类中加入方法printtype(),代码如下:
sub printtype {
my $class = shift @_;
print "the type of bean is $class->{"bean"} n";
}
然后更新其@export数组来输出:
@export = qw ( setbeantype , printtype );
现在来调用函数printtype(),有三种调用方法:
$cup->coffee::printtype();
$cup->printtype();
$cup->bean::printtype();
输出分别如下:
the type of bean is mixed
the type of bean is mixed
the type of bean is mixed
为什么都一样呢?因为在子类中没有定义函数printtype(),所以实际均调用了基类中的方法。如果想使子类有其自己的printtype()函数,必须在coffee.pm类中加以定义:
#
# this routine prints the type of $class->{"coffee"}
#
sub printtype {
my $class = shift @_;
print "the type of coffee is $class->{"coffee"} n";
}
然后更新其@export数组:
@export = qw(setimports, declaremain, closemain, printtype);
现在输出结果变成了:
the type of coffee is instant
the type of coffee is instant
the type of bean is mixed
现在只有当给定了bean::时才调用基类的方法,否则直接调用子类的方法。
那么如果不知道基类名该如何调用基类方法呢?方法是使用伪类保留字super::。在类方法内使用语法如:$this->super::function(...argument list...); ,它将从@isa列表中寻找。刚才的语句用super::替换bean::可以写为$cup->super::printtype(); ,其结果输出相同,为:
the type of bean is mixed
十二、perl类和对象的一些注释1、一定要通过方法来访问类变量。
2、一定不要从模块外部直接访问类变量。
当编写包时,应该保证方法所需的条件已具备或通过参数传递给它。在包内部,应保证对全局变量的访问只用通过方法传递的引用来访问。对于方法要使用的静态或全局数据,应该在基类中用local()来定义,子类通过调用基类来获取。有时,子类可能需要改变这种数据,这时,基类可能就不知道怎样去寻找新的数据,因此,这时最好定义对该数据的引用,子类和基类都通过引用来改变该数据。
最后,你将看到如下方式来使用对象和类:
use coffee::bean;
这句语句的含义是“在@inc数组所有目录的coffee子目录来寻找bean.pm”。如果把bean.pm移到./coffee目录,上面的例子将用这一use语句来工作。这样的好处是有条理地组织类的代码。再如,下面的语句:
use another::sub::menu;
意味着如下子目录树:
./another/sub/menu.pm