跳到主要内容
版本:master

class 类声明

语法

  • parallel? class 标识符 (: 其他Class类型)? { 类成员或函数1 * }

注释:

  1. 类成员或函数的语法:

描述

class 是 GS 在面向对象方面的一个补充,class 用于声明一个 class_map 类型,该类型是一个特殊的 map, 与普通的 map 相比,class_map 具有以下特性:

  • 键只能是字符串类型。
  • 键值对的类型和数量固定,不能删除或新增键值对。编译期能据此进行更严格的类型检查。
  • 支持继承,可以通过继承已有的 class_map 来创建新的 class_map
  • 拥有方法。

class 声明中,可以使用 parallel 关键字来声明并行类(parallel class)。并行类的实例是 parallel 的。

元表常量

每个 class 声明都会隐式地创建一个对应的 class_map 常量,该常量被称为 parent-map元表,该元表包含对应 class 类型的所有成员的默认值,以及所有的成员函数:

class Example
{
int a = 1;
private string b = "Hello";
protected float c = 3.14;

public void foo(Example self)
{
printf("Hello from Example.foo\n");
}
}

writeln(Example); // 输出 Parent-map 常量的信息

类的成员

成员变量

成员变量是在类中定义的变量,成员变量储存在类实例之中(在 1.32.250427 以及之前的版本, 成员变量被分成两部分储存(即实例 + 元表)。之后的版本,成员变量将直接储存在实例之中)。

子类可以覆盖父类的同名 public 和 protected 成员变量(这么做的意义仅仅是提供不同的初始值)。

成员变量的默认访问权限是 public,除非在成员变量前面加上 private 或 protected 关键字。

编译器会为成员变量的索引操作进行类型检查,如果被访问成员的实例有明确类型,编译器能够为 索引表达式推导其成员类型,如果类型没有对应的成员定义,能及时给出诊断。如果被索引的实例 没能明确类型,编译期检查无法执行,运行时如果索引的成员不存在,则结果为 nil(和 map 的 行为类似)

class Example
{
int a = 1; // 成员变量,默认访问权限为 public
private string b = "Hello"; // 成员变量,访问权限为 private
protected float c = 3.14; // 成员变量,访问权限为 protected
}

Example ex = Example.new();
ex.a = 2; // 设置成员变量 a 的值为 2
// ex.b = "World"; // 错误,因为 b 是 private 成员变量,不能在类外访问
// ex.c = 3.14; // 错误,因为 c 是 protected 成员变量,不能在类外访问
// ex.non_existent = 42; // 错误,如果 Example 类没有定义 non_existent 成员变量,编译期会报错

mixed m = ex;
m.non_existent; // 错误,但是此处 m 没有类型信息,因此编译期OK,运行时得到 nil

成员函数

成员函数是在类中定义的函数,成员函数储存在元表中(在 1.32.250427 以及之前的版本中,可 以像访问成员一样访问到成员函数;之后的版本中,只能从元表中取出函数,或者在方法调用和创建 方法闭包中访问成员函数)

class Example
{
void foo(Example self)
{
printf("Hello from Example.foo\n");
}
}
Example ex = Example.new();
ex.foo(); // 调用成员函数, OK

// function f1 = ex.foo; // 1.32.250427 及之前的版本:OK;
// 之后的版本,foo被视为成员变量,找不到foo成员变量;编译期报错或在运行时得到 nil
function f2 = (: ex.foo :); // OK
function f3 = Example.foo; // OK
function f4 = ex.get_class().foo; // OK,等效于 Example.foo

普通的成员函数始终需要定义名为 self 的首参数,首参数的类型应该是类的类型。

使用 static 修饰的成员函数被称为 静态成员函数,静态成员函数不要求声明 self 参数,并且可以通过 元表 直接调用:

class Example
{
static void static_foo(int x)
{
printf("Static foo called with %d\n", x);
}
}

Example.static_foo(42); // 调用静态成员函数

需要注意的是,运行时是根据方法调用的 . 符号前的对象是 实例元表 来决定是否传入自身作为首个参数的。 因此尝试使用实例调用静态成员函数时,仍然会传入自身作为首个参数,这通常会导致问题。

继承

类可以通过 : 符号来继承其他类,继承的类被称为 父类,继承的类被称为 子类。 子类可以覆盖父类公开或保护的成员变量和成员函数。

class Base
{
public int a = 1; // 成员变量
public void foo(Base self) // 成员函数
{
printf("Base foo: %d\n", self.a);
}
}

class Derived : Base // 继承 Base 类
{
public int b = 2; // 新增成员变量
public void foo(Derived self) // 重写成员函数
{
printf("Derived foo: %d, %d\n", self.a, self.b);
}
}

Base base_ = Base.new();
base_.foo(); // 调用 Base 类的 foo 方法,输出 "Base foo: 1"
Derived derived = Derived.new();
derived.foo(); // 调用 Derived 类的 foo 方法,输出 "Derived foo: 1, 2"

特殊函数

一些特定函数名被class视为特殊函数,这些函数拥有特别的功能,所有的特殊函数都应该是公开的。

构造函数(__new)

在 class 内定义名为 __new 的成员函数会被视为构造函数,构造函数应该是一个非静态的成员函数,可以从 class_map.newclass_map.new_by_map 中得到额外的构造参数。

构造函数只有在手动调用和 class_map.new / class_map.new_by_map 创建实例时才会被调用。

创建实例时,如果实例本身有定义构造函数,则不会自动调用父类的构造函数,因此需要手动调用父类的构造函数。

class Base
{
public void __new(Base self)
{
printf("Base constructor called\n");
}
}
class Derived : Base
{
public void __new(Derived self, string msg)
{
Base.__new(self); // 手动调用父类的构造函数
printf("Derived constructor called with: %s\n", msg);
}
}
Derived instance = Derived.new("Helloworld"); // 构造参数被传递给 Derived 的构造函数

析构函数(__destruct)

析构函数是在类中定义名为 __destruct 的成员函数,析构函数会在实例被GC机制释放,或者手动调用 class_map.close() 调用。

析构函数应该是一个非静态的成员函数,析构函数应该只有 self 参数。

只有使用 class_map.newclass_map.new_by_map 创建的实例才会触发析构函数。

class Example
{
public void __destruct(Example self)
{
printf("Example instance is being destroyed\n");
}
}
Example ex = Example.new();
Example ex2 = ex.dup(); // 复制实例

ex.close(); // 手动调用析构函数,输出:"Example instance is being destroyed\n"
ex2.close(); // ex2 不是被 new/new_by_map 创建的实例,因此不会触发析构函数

序列化函数(__serialize)

在类中定义名为 __serialize 的成员函数会被视为序列化函数,序列化函数应该是一个非静态成员函数, 返回值类型可以是任何可被序列化的类型,仅接收 self 参数。

在 json 和 msgpack 对class实例执行序列化时,会调用此函数,序列化函数的返回值将代替class实例被 序列化。

class Example
{
int a = 1;
public mixed __serialize(Example self)
{
return {"a" : self.a * 2}; // 序列化时将 a 的值乘以 2
}
}
Example ex = Example.new();
writeln(json.save(ex)); // 输出 {"a": 2}

如果class 有定义反序列化函数 __deserialize,则序列化结果将被包装成一个带有class元数据(类名, 脚本名或序列化ID)的map,这将被用于之后反序列化:

class Example
{
int a = 1;
public mixed __serialize(Example self)
{
return {"a" : self.a * 2}; // 序列化时将 a 的值乘以 2
}
public static Example __deserialize(map data)
{
Example ex = Example.new();
ex.a = data["a"] / 2; // 反序列化时将 a 的值除以 2
return ex;
}
}
Example ex = Example.new();
writeln(json.save(ex));
// 输出: {"__class__":"Example","__program__":"/test.gs","__value__":{"a":2}}

反序列化函数(__deserialize)

在类中定义名为 __deserialize 的成员函数会被视为反序列化函数,反序列化函数应该是一个静态成员函数, 接收一个参数,这个参数用于接收从序列化数据中读取出的原始类型,并通过反序列化函数将其转化成最终类型。 反序列化函数的返回值类型通常应该是类的类型。

class Example
{
int a = 1;
public static Example __deserialize(map data)
{
Example ex = Example.new();
ex.a = data["a"]; // 从序列化数据中读取 a 的值
return ex;
}
}
Example ex = Example.new();
string dat = json.save(ex);

Example ex2 = json.load(dat); // 反序列化时调用 Example.__deserialize
writeln(ex2);
// 输出:
// class Example{ /* sizeof() == 1 */
// "a" : 1,
// }

示例

class A 
{
int a = 1;
public int foo(A self)
{
return self.a;
}
};

class B : A
{
string b = "b";
public void __new(B self, int a, string b)
{
self.a = a;
self.b = b;
};
public int foo(B self)
{
return self.a * self.a;
}
};

class C : B
{
float c = 3.0f;
A inner_cls = nil;

public void __new(C self, int a, string b, float c)
{
B.__new(self, a, b);
self.c = c;
};

public int foo(C self)
{
return B.foo(self) + A.foo(self) + (int)self.c;
};

public int foo2(C self)
{
return self.inner_cls.foo();
};

public static int static_foo(int a)
{
return a * a;
};
};

void test_class()
{
assert(A.a == 1);
A a = A.new();
assert(a.a == 1);
assert(a.get_class() == A);
assert(B.b == "b");
B b = B.new(2, "bb");
assert(b.get_class() == B);
assert(b.a == 2);
assert(b.b == "bb");
C c = C.new(3, "bb", 12);
assert(c.foo() == 24);
assert(C.static_foo(10) == 100);
c.inner_cls = b;
assert(c.foo2() == 4);
}

test_class();