对象(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");
重要注意事项
create函数最多只能有一个参数,如果需要传递多个参数,可以使用数组、映射等复合数据类型。- 如果定义了带参数的
create函数,但在创建对象时没有传递参数,将会触发运行时错误。 create函数中发生的异常会中断对象创建过程,对象将会创建失败。- 静态加载的对象(
load_static)只在第一次加载时执行create函数,后续调用复用已创建的对象。
2.3.3 析构函数(destruct)
概念定义
destruct函数是GS对象中用于清理资源的析构函数。在对象被销毁时自动调用,确保资源得到正确释放。
核心特性
- 资源清理:专门用于释放对象占用的资源。
- 自动调用:在对象销毁时由系统自动触发。
- 单次执行:每个对象实例的
destruct函数在其生命周期内只 执行一次。 - 最后机会:是对象生命周期中最后一个被调用的函数。
触发时机
destruct函数在以下情况下会被调用:
- 手动销毁:调用
destruct_object(obj)函数。 - 关闭操作:调用
obj.close()方法。 - 垃圾回收:当对象不再被引用且被GC检测到时。
一个简单的示例如下:
// example.gs
void destruct()
{
// 做一些资源清理工作
printf("On destruct of '%O'.\n", this_object());
printf("In destruct funtion, handle state is %O\n", this_object().info().handle.state);
}
// test.gs
import .example;
object ob = new_object(example, this_domain(), "new_object()");
printlnf(HIG"destruct_object '%O' is called"NOR, ob);
destruct_object(ob); // 手动触发destruct函数
示例2-5:析构函数示例
如示例所示,析构函数destruct在手动调用destruct_object(object_instance)、调用object_instance.close()、或垃圾回收(GC)机制回收对象时执行,且析构函数仅执行一次。在析构函数中对象处于ObjectState.DESTRUCTING(-1)状态。
析构函数destruct主要用于处理必要的资源释放、各句柄handle的关闭、未持久化数据的保持等工作。
重要注意事项
destruct函数中应避免调用可能失败或阻塞的操作。- 在
destruct函数执行期间,对象已处于"正在关闭"状态,理论上不应再被其他代码使用。 - 如果
destruct函数中发生异常,异常中断destruct函数,但不会中断销毁过程。 - 对于持有重要资源的对象,建议显式调用
destruct_object而不是依赖GC,以确保资源及时释放。 - 静态对象(通过
load_static加载)在程序运行期间通常不会自动销毁,需要手动管理其生命周期。
2.3.4 完整的生命周期流程
正常生命周期序列
- 对象创建 →
new_object/load_static - 入口初始化 →
::entry函数执行 - 构造过程 →
create函数执行 - 对象使用 → 正常的业务逻辑处理
- 析构过程 →
destruct函数执行(手动或GC触发) - 对象销毁 → 资源完全释放
::entry、create、destruct函数按顺序先后调用的示例如下:
// example.gs
void create(string msg)
{
// create 函数 *最多* 可以传入 *一个* 构造参数
// 参数在 load_static 或 new_object 时传入。
printf("On create: %s. Object is '%O'\n", msg, this_object());
}
void destruct()
{
printf("On destruct of '%O'.\n", this_object());
}
printf("On ::entry of '%O'.\n", this_object());
//test.gs
import .example;
object ob = new_object(example, this_domain(), "new_object()");
printlnf(HIG"destruct_object '%O' is called"NOR, ob);
destruct_object(ob);
示例2-6:对象生命周期示例
示例的输出结果如下:
On ::entry of 'object[2881105:v0]/example.gs'.
On create: new_object(). Object is 'object[2881105:v0]/example.gs'
destruct_object 'object[2881105:v0]/example.gs' is called
On destruct of 'object[2881105:v0]/example.gs<Destructing>'.
从输出结果可以看到,在创建的object的声明周期内特殊函数的调用顺序如下:
::entry函数:object创建时首先调用::entry函数。create函数:::entry函数调用结束后调用create函数。destruct函数:GC或手动关闭或销毁时调用destruct函数。
2.4 object 函数调用
在之前的程序结构/函数的章节 我们已经简单聊过函数的调用类型等内容。通过持有object实例我们可以很方便的进行.、.?、=>、=>?型等各种调用,此处不在赘述。一个简单的代码示例如下:
// example.gs
array arr = [];
public void push_one()
{
arr.push_back(1);
}
public int get_arr_len()
{
return arr.length();
}
//test.gs
import .example;
object ob = new_object(example, this_domain(), "new_object()");
printf("Init array len in ob is '%d'\n", ob.get_arr_len());
ob.push_one();
ob.?push_one();
ob=>push_one();
ob=>?push_one();
printf("After 4 push one arr len is '%d'\n", ob.get_arr_len());
示例2-7:object函数调用示例
输出结果如下:
Init array len in ob is '0'
After 4 push one arr len is '4'
从示例代码及其输出可以看到,给出的.、.?、=>、=>?型的函数均被成功的调用了,其他调用类型、一些应当调用失败、一些调用相当于没有调用的情况请修改代码自行尝试。
2.5 object 域
在创建object过程中诸如示例2-1或示例2-2中调用的new_object或load_static函数,我们可以看到我们均传入了一个this_domain()的参数,用于指定在当前的域中创建object。在之前的程序结构/函数的章节介绍=>跨域调用时我们简单的将域描述为了一个大锁,现在让我们进一步思考。
假如当多个并行的执行流(线程)同时**持有同一个object句柄,并调用函数操作同一个对象数据时,会发生什么?**比如如下示例:
// bank_account.gs
int balance = 1000;
public void withdraw(int amount)
{
if (balance >= amount) {
// 模拟一些处理时间
coroutine.sleep(10);
balance = balance - amount;
printf("withdraw %d, balance: %d\n", amount, balance);
}
}
public int get_balance() {
return balance;
}
示例2-8:object 域思考示例
现在考虑两个不同的执行流同时调用 ob=>withdraw(800);,**你认为最终余额会是多少?**这个简单的例子让我们触及了GS编程的一个核心概念或问题:GS如何处理或解决并行程序的资源竞争问题?
- 🤔 这个对象可以被多个执行流同时访问吗?
- 🤔 对象的方法是否需要考虑并行安全问题?
- 🤔 如何设计对象才能既保证安全又保持性能?
- 🤔 GS提供了哪些机制来帮助处理并行竞争?
详细的解答会在下一章节的域(Domain)中给出,现在暂时搁置我们的疑问与好奇,让我们继续Object生命周期学习。
3. 拓展阅读
更多 object 相关内容,object 的外部函数如获取 object 状态,判断 object 是否被销毁,获取 obejct 的组件或域信息。以及更多的 object 具体细节,请参照手册文档,链接如下:
4. 总结
本章详细介绍了 GS 中对象(object)的核心概念与基础操作。我们深入理解了对象的本质——既是包含数据和方法的模块,也是需要通过 load_static和 new_object创建、并必须手动调用 destruct_object销毁的独立实体。我们学习了对象的完整生命周期,从 ::entry初始化、create构造到 destruct析构的整个过程,并掌握了通过不同操作符进行函数调用的方法。
我们从一个关键问题出发,初步探讨了对象的并发访问挑战:当多个执行流同时操作同一对象时,如何保证数据的一致性与线程安全?这个问题让我们触及了 GS 对象模型中一个更深层的核心机制——域(Domain)。在下一章中,我们将正式揭开**域(Domain)**的神秘面纱,让我们继续这场学习之旅。