对象(object)
1. 概述
本章将系统介绍 GS 语言中的核心概念——对象(Object)。我们将学习并了解对象的本质、创建方式、生命周期管理以及其与原型(Program)的关系。内容包括如何使用 load_static和 new_object来加载和创建对象,理解对象的初始化过程(::entry和 create())和销毁过程(destruct),并掌握一系列用于操作和查询对象的外部函数。
本章面向已经了解 GS 基本语法,并开始接触面向对象部分的同学或需要回顾对象相关内容的同学。
通过阅读本章,您将能够理解 GS 中对象的定义及其与原型(Program)的区别和联系。掌握创建对象的两种主要方式:加载静态原型 (load_static) 和实例化新对象 (new_object)。了解对象的完整生命周期,包括初始化函数(create)和析构函数(destruct)的调用时机。GS 独特的对象管理机制,即对象必须手动销毁的重要性,以避免内存泄漏。尝试使用常用的对象操作函数来查询信息、管理状态和进行日常开发。
2. object 基础
2.1 object 概念
在GS中,对象(object)是一个包含成员变量和方法的模块。在编写.gs文件时,实际上就是在描述一个对象的原型。对象可以从文件中加载(使用load_static加载原型),也可以通过原型创建新的对象副本(使用new_object)。
与其他面向对象语言不同的是,GS对象的变量无法从外部直接访问,开发者需 要在对象中提供接口函数,其他程序通过调用这些函数来操作对象。
特别注意事项:
- GS对象被设计用于表达较大规模、拥有较长生命周期的数据和方法集合
- 由于GC不会频繁检测对象,创建后的对象必须手动调用
destruct_object方法进行回收 - 若不释放对象可能导致内存浪费,严重时可能引发内存耗尽崩溃
- 如果不需要手动释放的对象,或考虑更轻量化的模块结构,可以考虑使用 map 或新版本 GS 的 class_map
2.2 object 创建
2.2.1 object 静态加载
使用load_static可以从文件中加载 object 的原型。使用import import_name;并通过import_name.func_name();调用函数,相应的 object 会在函数调用时静态加载进来。
示例如下(注意多文件实例,需要拆分):
// example.gs
public void hello_world()
{
writeln("Hello world.");
}
// test.gs
import .example;
writeln("Try find example object : ", find_object(example));
example.hello_world(); // 这个操作也将会尝试 load_static example
writeln("Try find example object after function call: ", find_object(example));
object origin_obj_1 = load_static(example, this_domain());
object origin_obj_2 = load_static("./example.gs", this_domain());
writeln("Get static object from string: ", origin_obj_1);
writeln("Get static object from import_name: ", origin_obj_2);
示例2-1:object 创建示例
输出结果如下:
Try find example object : nil
Hello world.
Try find example object after function call: object[2881105:v0]/example.gs<Static>
Get static object from string: object[2881105:v0]/example.gs<Static>
Get static object from import_name: object[2881105:v0]/example.gs<Static>
可以看到example.hello_world()函数调用后example.gs的object被静态加载进来,相应对象实例带有<Static>后缀。此时调用load_static函数,由于example.gs的object已被静态加载,load_static直接返回了已被静态加载的对象实例,即example和"./example.gs"指向相同文件时,origin_obj_1与origin_obj_2是同一个object。总结如下。
- 当参数对应的静态对象实例不存在时
load_static会编译或查找object对象的program原型,并从program原型创建静态object对象实例。 - 当参数对应的静态对象实例存在时
load_static会直接返回该静态对象实例。 import_name.function_name_call()对静态对象的处理与load_static相同。
2.2.2 object 非静态加载
通过调用new_object函数创建非静态加载的对象实例,保持示例2-1的文件结构,修改test.gs的脚本代码为如下所示:
// test.gs
import .example;
object new_obj_1 = new_object(example, this_domain());
object new_obj_2 = new_object("./example.gs", this_domain());
writeln("object1 created by new object: ", new_obj_1);
writeln("object2 created by new object: ", new_obj_2);
示例2-2:object 的非静态实例创建示例
输出结果如下:
object1 created by new object: object[2877009:v0]/example.gs
object2 created by new object: object[2877012:v0]/example.gs
new_object 会根据传入的参数,编译或查找object对象的program原型,并从program原型创建全新的非静态object对象实例。从示例的输出结果也可以看到每个new_object调用都创建了全新的example.gs非静态对象实例,且看到相应object没有<Static>后缀。
2.3 object 生命周期
在对象的创建过程中会按照先后顺序自动的调用脚本中定义的::entry()和create()函数进行对象的初始化操作。可以通过手动声明create函数的方式在object创建时执行一些固定的初始化逻辑。
在对象因为GC或手动关闭而销毁时,会调用destruct函数。
2.3.1 入口函数(::entry)
::entry函数(也称为入口函数)是GS语言中一个特殊的、由编译器自动生成的隐式代码块。它代表了.gs文件脚本顶层的执行上下文环境,包含了文件中所有不在任何函数体内的顶层代码。
核心特性
- 隐式定义:开发者无需(也无法)显式定义
void ::entry() {...}函数。 - 自动生成:编译器会自动将顶层代码组织到
::entry函数中。 - 最先执行:在对象创建过程中,
::entry函数是第一个被调用的代码块。 - 成员初始化:在
::entry中定义的变量会成为对象的成员变量,可以在对象的其他函数中访问。 - 单次执行:每个对象实例的
::entry函数在其生命周期内只执行一次。
示例如下:
// example.gs
// ===== ::entry 开始:文件顶层的所有代码 =====
printf("1. This is the first line of '%s'\n", __FUN__); // 在 ::entry 函数内部
string moduleName = "my module"; // 在 ::entry 函数内部
int count = 0; // 在 ::entry 函数内部
// 函数定义及函数定义内部的代码不属于 ::entry
void create()
{
count++; // 修改的是原型中的 count
printf("5. crate function, total: %d, cur function is '%s'\n", count, __FUN__);
}
// 函数定义及函数定义内部的代码不属于 ::entry
void do_something()
{
printf("4. normal function, cur function is '%s' called by '::entry' \n", __FUN__);
}
printf("2. current function is '%s'\n", __FUN__); //在 ::entry 函数内部
printf("3. object state in ::entry function is '%O'\n", this_object().info().state);
do_something(); //在 ::entry 函数内部,调用do_string() 函数
// ===== ::entry 结束 =====
// 在 ::entry 函数调用结束后,声明的 create 函数被调用。
示例2-3:::entry函数概念示例
示例的输出结果如下:
1. This is the first line of '::entry'
2. current function is '::entry'
3. normal function, cur function is 'do_something' called by '::entry'
4. crate function, total: 1, cur function is 'create'
宏定义__FUN__会被展开为代码所在函数的外部函数名称。从示例及示例的输出结果可以看到,在.gs脚本顶层的不在任何函数内的代码实际是都归属于::entry函数的。而函数create或do_something内的代码__FUN__展开后可以看到代码不归属于::entry函数。在调用::entry函数时,对象处于ObjectState.CREATING(2)状态,这意味着如果::entry中发生错误,对象创建就会失败。
::entry函数在对象创建时立即执行,主要用于初始化对象的成员变量、执行对象启动时需要立即运行的逻辑、建立对象的基础状态、调用其他函数进行初步设置。
重要注意事项
- 在
::entry中定义的变量会成为对象的成员变量,在整个对象生命周期内存在。 ::entry中的代码执行顺序就是其在文件中的书写顺序。- 如果
::entry中发生错误,对象创建过程会中断,create函数将不会被执行。
2.3.2 构造函数(create)
概念定义
create函数是GS对象中一个可选的、由开发者显式定义的构造函数。它在::entry函数执行完毕后被调用,用于完成对象的特定初始化逻辑。
核心特性
- 显式定义:需要开发者主动声明和实现。
- 参数支持:最多可以接收一个构造参数。
- 灵活可选:不是必须实现的函数,根据需要定义。
- 执行时机:在
::entry执行完成后自动调用。 - 单次执行:每个对象实例的
create函数在其生命周期内只执行一次。
代码示例如下:
// example.gs
string config_data = "default";
void create(string msg)
{
// create 函数 *最多* 可以传入 *一个* 构造参数
// 参数在 load_static 或 new_object 时传入
printf("On create: %s. Object is '%O'\n", msg, this_object());
printf("In create function, object state is %O\n", this_object().info().state);
config_data = msg; // 使用构造参数初始化成员变量
}
public string get_config()
{
return config_data;
}
// test.gs
import .example;
// 传入构造函数参数 "init data"
object ob = new_object(example, this_domain(), "init data");
printf("Config data: %s\n", ob.get_config()); // 输出: Config data: init data
示例2-4:构造函数示例
如示例所示,构造函数在::entry函数完全执行完毕后调用,同时只能传入最多一个参数。在create构造函数中对象处于ObjectState.CREATING(2)状态,同样意味着如果create中发生错误,对象创建就会失败。
构造函数主要用于接收并处理构造参数、执行复杂的初始化逻辑、建立对象运行所需的外部依赖、验证对象初始状态的正确、根据参数动态配置对象行为。
参数传递机制
构造参数通过new_object或load_static的第三个参数传递:
// 创建对象并传递构造参数伪代码
object obj1 = new_object(program_name, domain, "construction_parameter");
object obj2 = load_static(program_name, domain, "construction_parameter");