class 类声明
语法
- parallel? class 标识符 (: 其他Class类型)? { 类成员或函数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.new 和 class_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.new 或 class_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,
// }