跳转到内容

Raku 编程/类和属性

来自 Wikibooks,开放书籍,开放世界

类和对象

[编辑 | 编辑源代码]

到目前为止,我们已经看到了面向过程编程的构建块:表达式、分支、循环和子例程的列表,它们告诉计算机该做什么以及如何做。Raku 很好的支持面向过程编程,但这并不是 Raku 支持的唯一编程风格。Raku 很好的适应的另一个常见范式是面向对象方法。

对象是数据及其操作的组合。对象的“数据”称为其属性,对象的“操作”称为其方法。从这个意义上说,属性定义了对象的“状态”,而方法定义了对象的“行为”。

类是创建对象的模板。当提到特定类的对象时,通常将该对象称为该类的实例。

类使用 class 关键字定义,并被赋予一个名称

class MyClass {

}

在类声明内部,您可以定义属性、方法或子方法。

属性使用 has 关键字定义,并使用特殊的语法指定。例如,考虑以下类

class Point3D {
    has $!x-axis;
    has $!y-axis;
    has $!z-axis;
}

Point2D 类定义了 3D 坐标系中的一个点,它有三个属性,分别名为 x-轴、y-轴和 z-轴。

在 Raku 中,所有属性都是私有的,一种明确表达这一点的方式是使用 ! twigil。使用 ! twigil 声明的属性只能在类内部通过使用 !attribute-name 直接访问。用这种方式声明属性的另一个重要后果是,对象不能使用默认的 new 构造函数进行填充。

如果您使用 . twigil 而不是 ! twigil 声明属性,则会自动生成一个只读访问器[检查拼写] 方法。您可以将 . twigil 视为“属性 + 访问器”。这个访问器,它是一个以其属性命名的“方法”,可以从类外部调用并返回其属性的值。为了允许通过提供的访问器更改属性,必须将 is rw 特性添加到属性中。

之前的 Point3D 类可以声明如下

class Point3D {
    has $.x-axis;
    has $.y-axis;
    has $.z-axis is rw;
}

鉴于 . twigil 声明了一个 ! twigil 和一个访问器[检查拼写] 方法,即使属性使用 . twigil 声明,也可以始终使用 ! twigil 使用它们。

方法的定义与普通子例程相同,只是有一些关键区别

  1. 方法使用 method 关键字而不是 sub
  2. 方法具有特殊的变量 self,它引用正在调用方法的对象。这被称为调用者
  3. 方法可以直接访问对象的内部特性。

在定义方法时,您可以为调用者指定不同的名称,而不是必须使用 self。为此,您将其放在方法签名的开头,并用冒号将其与签名的其余部分隔开

method myMethod($invocant: $x, $y)

在此上下文中,冒号被视为一种特殊的逗号类型,因此您可以用额外的空格来写它,如果这样更容易

method myMethod($invocant  :  $x, $y)

以下是一个示例

class Point3D {
    has $.x-axis;
    has $.y-axis;
    has $.z-axis;
    
    method set-coord($new-x, $new-y, $new-z) {
        $!x-axis = $new-x;
        $!y-axis = $new-y;
        $!z-axis = $new-z;
    }
    
    method print-point {
        say "("~$!x-axis~","~$!y-axis~","~$!z-axis~")";
    }
    
    # method using the self invocant
    method distance-to-center {
        return sqrt(self.x-axis ** 2 + self.y-axis ** 2);
    }
    
    # method using a custom invocant named $rect
    method polar-coordinates($rect:) {
        my $r = $rect.distance-to-center;
        my $theta = atan2($rect.y-axis, $rect.x-axis);
        return "("~$r~","~$theta~","~$rect.z-axis~")";
    }
}

对象是其类型为给定类的“数据项”。对象包含类定义的任何属性,并且还可以访问类中的任何方法。对象使用 new 关键字创建。

使用 Point3D 类

my $point01 = Point3D.new();

类构造函数new() 可以接受命名方法,这些方法用于初始化类的任何属性

# Either syntax would work for object initialization
my $point01 = Point3D.new(:x-axis(3), :y-axis(4), :z-axis(6));
my $point02 = Point3D.new(x-axis => 3, y-axis => 4, z-axis => 6);

类中的方法使用点符号调用。这是对象、一个句点,然后是该方法的名称。

$point01.print-point();
say $point01.polar-coordinates();

当没有为点符号方法调用提供对象时,将使用默认变量 $_ 代替

$_ = Point3D.new(:x-axis(6), :y-axis(8), :z-axis(6));;
.print-point();
say .polar-coordinates();

基本类系统使“数据”及其操作该数据的“代码例程”能够以逻辑的方式捆绑在一起。但是,大多数类系统还有更高级的功能,这些功能也允许继承,这使得类能够相互构建。继承是类形成逻辑层次结构的能力。Raku 支持类的正常继承和子类,但也支持称为mixin角色的特殊高级功能。我们将不得不为后面的章节保留其中一些更难的功能,但我们将在本章中介绍一些基础知识。

基本类型

[编辑 | 编辑源代码]

我们在前面的章节中讨论了 Perl 的一些基本类型。您可能会惊讶地发现,所有 Raku 数据类型都是类,并且所有这些值都具有可以使用的内置方法。在这里,我们将讨论一些可以对到目前为止看到的各种对象调用的方法。

.print.say

[编辑 | 编辑源代码]

我们已经看到了内置函数 printsay。所有内置类都有相同名称的方法,这些方法打印对象的字符串形式。

.perleval

[编辑 | 编辑源代码]

我们将快速讨论一下 eval 函数。eval 使我们能够在运行时编译并执行 Raku 代码字符串。

eval("say 'hello world!';");

所有 Raku 对象都具有一个名为 .perl 的方法,该方法返回表示该对象的 Raku 代码字符串。

my Int $x = 5;
$x.perl.say;          # "5"

my @y = (1, 2, 3);
@y.perl.say;          # "[1, 2, 3]"

my %z = :first(1), :second(2), :third(3);
%z.perl.say;          # "{:first(1), :second(2), :third(3)}"

上下文和强制转换方法

[编辑 | 编辑源代码]

有一些方法可以显式地将给定的数据项更改为不同的形式。这就像一种强制给定的数据项以不同的上下文进行解释的显式方法。以下是一个部分列表

.item
返回标量上下文的项目。
.iterator
返回对象的迭代器。我们将在后面的章节中讨论迭代器。
.hash
返回哈希上下文的对象
.list
返回数组或“列表”上下文的对象
.Bool
返回对象的布尔值
.数组
返回包含对象数据的数组
.哈希
返回包含对象数据的哈希
.迭代器
返回对象的迭代器。我们将在后面的章节中讨论迭代器。
.标量
返回指向对象的标量引用
.字符串
返回对象的字符串表示

内省方法

[编辑 | 编辑源代码]
.WHENCE
返回对象类型自动创建闭包的代码引用。我们稍后将讨论自动创建和闭包。
.WHERE
返回数据对象的内存位置地址
.WHICH
返回对象的标识值,对于大多数对象来说,它就是它的内存位置(即它的 .WHERE)
.HOW
(HOW = 高阶工作方式) 返回处理此对象的元类
.WHAT
返回当前对象的类型对象
下一页: 注释和 POD | 上一页: 代码块和闭包
主页: Raku 编程
华夏公益教科书