数组 array
1. 概述
本章涵盖数组概念、数组创建、增删改查、排序遍历,数组的底层内存扩展机制,以及指向数组相关操作的 API 链接。
本章内容主要面向需要学习 GS 数组基础概念,了解 GS 数组基础操作,了解 GS 数组底层内存拓展机制的 GS 编程初学者。
在阅读本章后应掌握数组概念、数组的使用场景、数组创建、增删改查、排序遍历,数组的底层内存扩展机制,提升数组性能优化理解,并为后续 map 类型的学习奠定基础。
2.数组的概念
数组是计算机中一种基础的数据结构,你可以把它想象成一排连续的、带编号的储物柜。GS中的数组可以用来存储不同类型的数据(比如一堆整数、一堆用户名等)。每个数据称为一个**“元素”**,每个元素在数组中都有一个固定且唯一的位置编号(索引),从0开始计数(第一个位置是0号)。通过这个索引编号,我们可以非常快速准确地找到、访问或修改存放在特定“柜子”里的数据,非常高效有序。
注意 GS 中数组的索引以 0 为起始
3. 数组基础
本段内容介绍数组的创建、操作及遍历方式。包括数组的创建(通过字面量初始化或函数分配),元素操作如新增(push_back/insert)、删除(delete_at/clear)、修改(索引赋值或 set函数)和查询(索引访问、切片或 get_range),以及常用操作(长度获取、元素查找)和遍历方 法(索引循环或逐个访问)。
3.1 创建数组
要使用数组自然从数组的创建开始,常用的创建数组方式有两种:一是用方括号 []直接列出值,比如 [1, 2, 3]。二是使用 array.allocate函数创建指定大小的空数组。如示例 1-1 所示。
// 字面量初始化
array arr = [ 1, 2, nil, "Three", 2, [ "hello", (buffer)1, 3.14 ]]; // 初始化 arr,arr 元素 value 可以为任意不同类型的数值,也可嵌套数组
// 申请指定长度的数组(默认填充nil)
array arr1 = array.allocate(8); // arr1 长度为8的空数组
// 结果:arr1 = [nil, nil, nil, nil, nil, nil, nil, nil]
示例 1-1:创建数组示例
字面量初始化(如 array arr = [1, 2, "Three"];)会在内存中创建一个全新的数组对象
每次字面量初始化初始化都会分配独立的内存空间。因此,即使两个数组的字面量相同,它们也是两个完全独立的对象,互不影响。如示例 1-2 所示:
array a = [1, 2];
array b = [1, 2];
a[0] = 10; // 修改a的第一个元素
// 结果: a = [10,2], b = [1,2]
示例 1-2:字面量初始化创建相互独立
3.2 数组元素新增
GS 的数组为变长数组,允许在运行时确定数组大小,使用变量声明长度,动态拓展数组的存储空间。
若要拓展数组的长度,在数组中存储额外的元素内内容,可以使用 push_back函数 、push_back_n函数 、insert函数、insert_n函数、<< 运算符等,如示例 1-3 所示:
//创建初始数组
array v = [1, 2, "Three", nil, [4.5, "nested"]];
// 尾部追加单个元素
v.push_back(100); //在尾部增加单个值 100。
// 结果:v = [1, 2, "Three", nil, [4.5, "nested"], 100]
// 尾部追加整个数组
v.push_back_n([true, false]); // 函数参数应为数组类型,在尾部增加两个值 true,false。
// 结果:v = [1, 2, "Three", nil, ..., true, false]
// 在指定位置插入元素
v.insert(2, "NEW"); // 在索引2处插入"NEW"
//结果:v = [1, 2, "NEW", "Three", ...]
// 特殊语法:<< 运算符追加元素
v << ["end"]; // 在数组尾部追加 "end"
//结果:v = [1, 2, "NEW", "Three", ..., "end"]
示例 1-3:数组新增元素示例
若当前内存容量不足,底层会自动触发动态内存扩展:重新分配更大的连续内存块,复制原数据并释放旧内存,再执行新增操作。见 数组 部分内容。
3.3 数组元素删除
有多用方法删除array中储存的值:通过arr.delete函数,arr.delete_at函数,clean_up函数,clear函数。如示例 1-4 所示:
// 创建初始数组
array arr = [1, 2, "Three", "delete", nil, "delete"];
// 按值删除(首个匹配项)
int idx = arr.delete("delete"); // 删除索引3处"delete"字符串
// 结果:idx = 3, arr = [1, 2, "Three", nil, "delete"];
// 按位置删除(从索引2删1个)
arr.delete_at(2, 1); // 删除索引2处"Three"字符串
// 结果: arr = [1, 2, nil, "delete"];
// 清除所有空值(nil)
arr.clean_up(); // 索引2处nil值被清除
// 结果: arr = [1, 2, "delete"];
// 清空数组
arr.clear();
// 结果: arr = [];
示例 1-4:数组移除元素示例
3.4 数组元素修改
有多用方法修改array中储存的值:通过索引赋值,set函数。如示例 1-5 所示:
//新建初始数组
array arr = [1,2,3,4];
// 直接索引赋值
arr[0] = 100; // 索引0 处值被修改为100
//结果:arr = [100,2,3,4];
// set函数修改
arr.set(1, "modified"); // 索引1处值被修改为"modified"
//结果:arr = [100,"modified",3,4];
示例 1-5:修改数组元素示例
3.5 数组元素查询
有多用方法查询array中储存的值:通过索引访问,get函数,切片,get_range函数。如示例 1-6 所示:
// 创建初始数组
array arr = [1, 2, "Three", "delete", nil, "last"];
// 索引访问
mixed val = arr[0]; 获取索引为0处数组元素
//结果:val = 1
// 特殊索引语法
mixed last = arr[<1]; // 获取倒数第1个数组元素
//结果:last = "last"
// get函数获取
mixed val2 = arr.get(1); //获取索引为1处数组元素
//结果:val2 = 2
// 切片
array sub = arr[2..<2]; // 获取索引2到倒数第2个元素构成的子数组
//结果:sub = ["Three", "delete", nil]
array sub2 = arr[<3..<1]; // 倒数第3到倒数第1个元素构成的子数组
//结果:sub2 = ["delete", nil, "last"]
// 范围查询
array part = arr.get_range(2, 3); // 获取自索引2处3个元素构成的子数组
//结果:part = ["Three", "delete", nil]
示例 1-6:查询数组中的元素示例
3.6 常用操作
以下是一些其他的常用数组操作:包含数组长度获取,查找元素索引,类型检查等,如示例 1-7 所示:
// 创建示例数组
array arr = []
// 长度获取
int len = arr.length() ; // 获取数组长度
// 结果:len = 0
// 查找元素所在索引
int pos = arr.find("Three"); // 查找"Three"字符串在数组索引的位置
//结果:pos = 2
// 类型检查
bool is_arr = is_array(arr); // 检查 arr 是否为数组
// 结果:is_arr = true
示例 1-7:其他数组操作
3.7 数组遍历
如果想要遍历 array 中的每一个元素,我们可以尝试通过获取数组长度后通过索引逐个访问或修改,如示例 1-8:
array arr = [1,2,3,4];
// 获取数组长度索引遍历 [0, arr.length() - 1]
for(int i = 0; i < arr.length(); i++)
{
write(arr[i]);
if(arr[i] == 2)
arr[i] = 6; // 修改数组元素 2 为 6
}
// 结果:1,2,3,4 逐个输出,arr = [1,6,3,4]
示例 1-8:通过索引逐个访问或修改数组元素
我们也可以通过如下的写法,逐个访问数组中的每一个元素,但无法通过此方式修改数组元素。如示例 1-9:
array arr = [1,2,3,4];
// 获取数组长度索引遍历 [0, arr.length() - 1]
for(mixed val:arr)
{
write(arr[i]);
if(val == 2)
val = 6; // 无法修改数组元素
}
// 结果:1,2,3,4 逐个输出,arr = [1,2,3,4]
示例 1-9:循环逐个访问数组元素
4 数组陷阱与调试
4.1 数组越界
GS 中数组索引是从数字 0 开始的,我们使用索引值 0 来获取第一个元素。当我们使用超出有效范围的索引访问数组元素会发生什么?尝试一下,如示例 1-10
// 创建初始数组
array arr = [1,2,3];
// 索引3超出数组长度范围,尝试输出它
write(arr[3]);
// 结果:Error(-1): Index is out of range
示例 1-10:无效索引访问数组元素
由示例 1-10 可见当使用超出有效范围的索引访问数组元素会触发运行时错误,错误指出索引不在合法的范围内。
作为内存安全语言,GS 会通过内置机制自动管理内存、防止内存的访问或操作错误。
4.2 类型转换
示例 1-6 的索引操作中我们使用 mixed 类型接受了数组元素,但若接收变量类型与取出的数组元素类型不一致会发生什么?我们使用整型接收一个字符串数组元素看看结果,如示例 1-11 所示:
// 创建初始数组
array arr = ["string_element"];
// 以整型变量a接收数组在索引0处的字符串元素并尝试输出
int a = arr[0];
write(a);
// 结果:Error(-1): Invalid type of container[key] result, expected 'int', got 'string'
示例 1-11:接收变量类型与数组元素类型不一致
由示例 1-11 可见当接收变量类型与数组元素类型不一致时会触发运行时错误,错误说明了接收变量期望的类型与从数组中取出元素的的实际类型。
4.3 单步调试
通过代码调试器的单步执行功能(Step-Through),可以直观获取数组内部数据具体细节,以及展示数组的变化情况。如示例 1-12:

示例1-12:单步调试数组内容
在示例示例1-12我们创建了一个简单数组 array arr = [1,2,3,nil];, 之后向数组末尾增加的了新的整型元素4使数组变为arr = [1,2,3,nil,4];,然后调用clear_up