对比PHP理解LPC语言的类与对象

前言

关于LPC语言的面向对象编程,有很多人还是有一些误区,在这里对比另一门语言php来理解。明明LPC是一门类C语言,为什么选PHP而不是C++?因为C++懂的人没有PHP多,被调侃为全世界最好的编程语言的PHP学习容易,懂的人也非常多,用这个对比更容易理解,而且一方面LPC和PHP都是解释型的面向对象编程语言,另一方面在特性上也有很多类似之处,比如:都有mixed这个少见的伪类型(php是到2020年底推出php8.0才增加这个类型),整型只有int,浮点型只有float,但数据范围都相当于long和double。

面向对象简介

PHP

PHP 具有完整的对象模型。特性包括:访问控制,抽象类(abstract)和 final 类与方法,附加的魔术方法,接口(interface),深层对象复制。

PHP对待对象的方式与引用和句柄相同,即每个变量都持有对象的引用,而不是整个对象的拷贝。

LPC

和PHP类似,LPC也具有完整的对象模型,特性上支持访问控制,但没有抽象类(abstract),也没有final类与方法,只有和final类似的nomask方法,有类似PHP魔术方法的apply方法,没有接口(interface),不支持直接的深层对象复制。

LPC对待对象的方式和PHP一样,也是引用,变量存的是对象的地址(指针),而不是整个对象的拷贝。

基本概念

class

PHP

在PHP中每个类的定义都以关键字 class 开头,后面跟着类名,后面跟着一对花括号,里面包含有类的属性与方法的定义。

一个类可以包含有属于自己的常量,变量(称为“属性”)以及函数(称为“方法”)。

示例:

<?php
class SimpleClass
{
    // 声明属性
    public $var = 'a default value';

    // 声明方法
    public function displayVar() {
        echo $this->var;
    }
}
?>

LPC

在LPC中class只是结构体的别名,使用class关键字并不是定义类,而是定义一个结构体数据类型,实际上:LPC语言中每个.c源文件就是一个类,类名就是文件名。这种特殊的方式也让LPC中的类不会有抽象类和接口等特别的类,因为你没法像abstract class AbstractClass{}这样的定义类。

示例,新建文件test.c:

// test.c

protected nosave int A;

int setA(int x)
{
    return A = x;
}

int getA()
{
    return A;
}

这个文件就是类,类名是test,类中有自己的属性A和方法setA()getA()

因为每个文件就是一个类,所以一个没有任何代码的文件就是一个空类,也可以正常的实例化载入内存

new

PHP

在PHP中要创建一个类的实例,必须使用 new 关键字。

示例:

<?php
$instance = new SimpleClass();

// 也可以这样做:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
?>

LPC

在LPC中使用new()clone_object()函数创建类的实例(对象),实例化也是载入新对象到内存中。

示例:

int main(object me, string arg)
{
    // 实例化一个test对象
    object ob = new(__DIR__"test");
}

在LPC中每个对象都有一个编号,这个编号在内存中唯一。

属性和方法

PHP

PHP在类中访问属性和调用方法使用同样的操作符,具体是访问一个属性还是调用一个方法,取决于你的上下文,即用法是变量访问还是函数调用。

<?php
class Foo
{
    public $bar = 'property';

    public function bar() {
        return 'method';
    }
}

$obj = new Foo();
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL;

LPC

在LPC中使用->操作符只能访问方法,不能访问类的属性,因为->在LPC中本质上是函数call_other(),只能访问对象的public方法,不过在访问方法上更强大,调用的对象可以是对象数组。

int main(object me, string arg)
{
    // 实例化对象test
    object a = new(__DIR__"test");
    // 调用方法
    a->setA(20);
    // 通过方法访问属性
    printf("%O %O\n", a->getA(), fetch_variable("A", a));
    // 对象数组访问方法
    users()->save();

    return 1;
}

在LPC中除了通过自定义的方法访问对象的属性外,还可以通过fetch_variable()函数来访问指定对象的属性。

继承

PHP

在PHP中一个类可以在声明中用 extends 关键字继承另一个类的方法和属性。PHP 不支持多重继承,一个类只能继承一个基类。

被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法或者属性时使用了 final,则不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。

<?php
class ExtendClass extends SimpleClass
{
    // 同样名称的方法,将会覆盖父类的方法
    function displayVar()
    {
        echo "Extending class\n";
        parent::displayVar();
    }
}

$extended = new ExtendClass();
$extended->displayVar();
?>

LPC

在LPC中一个类可以使用inherit关键字继承另一个类的方法和属性,LPC支持多重继承

被继承的方法和属性可以通过同样的名字重新声明被覆盖,但是如果父类定义的方法或者属性使用了nomask,则不可被覆盖。可以通过::来访问被覆盖的方法,如果是多重继承,可以通过父类名::来访问被覆盖的方法。

我们分别建三个类文件A.cB.cC.c

// A.c
void display()
{
    write("AAA\n");
}
// B.c
void display()
{
    write("BBB\n");
}
// C.c
inherit __DIR__"A";
inherit __DIR__"B";

void display()
{
    write("CCC\n");
}

int main(object me, string arg)
{
    A::display();
    B::display();
    ::display();
    display();
    return 1;
}

运行结果输出:

AAA
BBB
AAA
CCC

注意,多重继承时,对同名方法如果不指定类名只使用::调用父类的方法,会调用第一个继承的,而如果子类不覆盖父类的方法,不使用::直接调用父类方法会调用最后一个继承的,如上删除类C.c中的display()方法,编译运行结果是:

AAA
BBB
AAA
BBB

但是,对这种多重继承同名方法日志会报重复继承的警告,项目开发时请避免这样做,确实遇到重复继承了,那就先覆盖这个方法,在调用时再指定到底调用哪个。

特别提示:因为类名为文件名,如果你文件名不符合变量声明的规范,如使用纯数字或中文命名,那在被继承时使用类名::调用方法会报错,所以,如果你一个类可能被继承调用,请用英文开头的名称做文件名。

需要注意的是如果属性被覆盖会报重复声明警告:

/cmds/test/test_ob.c line 4: Warning: Redeclaration of global variable 'A'. before the end of line

对属性,不推荐覆盖使用,而是直接继承调用。

属性

类的变量成员叫做属性,属性可以增加访问控制修饰符(public, private 或者 protected),没有声明访问控制(可见性)修饰符的属性将默认声明为 public,在这一点上,LPC和PHP一致。

提示:在PHP中有静态关键字static,声明类属性或方法为静态,就可以不实例化类而直接访问。但LPC中static的意义和PHP完全不同,声明属性代表这个属性不会被存档,现在已被弃用,改用nosave关键字修饰,声明方法代表protected,现在统一用protected关键字修饰。

类常量

可以把在类中始终保持不变的值定义为常量。类常量的默认可见性是 public 。在这一点上LPC和PHP一致。

但是PHP中常量使用关键字const定义,LPC中只能使用宏定义。

构造函数

具有构造函数的类会在每次创建新对象时先调用此方法,所以非常适合在使用对象之前做一些初始化工作。

PHP

PHP中的构造函数是:

function __construct(mixed ...$values = ""): void
{

}

LPC

LPC中的构造函数是:

void create(mixed *values...)
{

}

PHP 有析构函数的概念__destruct(): void,这类似于其它面向对象的语言,如 C++。析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。

LPC语言中没有析构函数,LPC的内存不会被自动回收,但是存在系统函数destruct()用来主动销毁对象。

其它

在LPC语言中除了通过new()实例化对象外,还可以通过load_object()直接加载蓝图对象,这个其实类似其它语言中的静态类,也就是LPC语言中的类即可以实例化成多个对象,也可以直接静态调用。

京ICP备13031296号-4