表( table )是 aardio 中唯一的复合数据类型。除了非复合的基础数据类型以外,aardio 中几乎所有的复合对象都是表,即使是变量的命名空间也是表。
表的本质是一个集合(collection),可以用于容纳其他的数据成员,并且表也可以嵌套的包含其他的表(table),在aardio里表几乎可以容纳一切其他对象。
如果我们把字符串、buffer、数值、函数、指针.....这些基础数据类型比喻为盘子中的菜,那么表(table)这样的复合数据类型就是装菜的盘子,如果没有了盘子,所以就没有办法愉快的吃菜了。
aardio 中的表可以包含不定个数的成员,每个成员都由一个键值对组成(键用来指定成员的名称,值用来指定成员的值)。“键”可以是任何除 null 以外的数据类型。“值”也可以是任意数据类型,当值为 null 时表示删除该成员。table 也允许嵌套,可以在 table 元素中包含 table 。
通常把“键”放在索引操作符[]
中来索引一个元素的值,这时候键又称为“下标”或“索引”。例如 tab["键"]
或 tab[1],[]
则被称为下标操作符。也可以把一个符合标识符命名规则的键名放在成员操作符.
后面,例如 tab.key
, tab.key2
。
表包含的成员类型主要分为哈希表与有序数组,表可以同时包含这两种类型的成员。
哈希表(无序集合)
哈希表用于包含不定个数的键值对成员,使用哈希算法建议键与值之间的映射以实现高效的元素查询。
哈希表描述的数据结构通常也可以称为字典(dictionaries)、映射(map)、关联数组(associative arrays)、对象(object)...... 等等。当然这些数据结构在不同语言中存在不同实现,也可能不会用到哈希算法,但描述的数据结构相似。
哈希表使用哈希算法存取,不会按其他特定顺序排序(在代码中添加成员的顺序将被忽略)。
在遍历哈希表时的顺序并不一定会保持代码中书写、添加表成员的顺序,因此我们把这种集合称为无序集合。哈希表的优势是查找速度非常快,即使表包含的成员非常多,仍然可以快速地访问指定的成员。
下面是一个创建哈希表的例子:
var tab = {
a = 123;
str = "字符串";
[123] = "不符合变量命名规则的键名应放在下标内。";
["键 名"] = "不符合变量命名规则的键名应放在下标内。";
键名 = {
test = "表也可以包含表";
}
}
用 {}
表示创建一个表,表成员写在{}
内部, 使用
aardio 允许用 ,
号代替 ;
号分隔表成员 ,并且允许用 :
替代 =
分隔键值对,也允许在表中用引号(双引号、单引号、反引号 )包含的字符串表示键名。因此可以使用类 JSON 的语法定义表对象,例如:
var tag ={"name1":123,"name2":456}
数组(有序集合) #
如果表中不定个数的成员的“键”是从1开始、有序、连续的数值,那么这些成员构成一个有序数组。
创建有序数组的示例:
var array = {
[1] = 123;
[2] = "数组的值可以是任何其他对象";
[3] = { "也可以嵌套包含其他表或数组"}
}
上面我们将数值键放在下标[]
中,用分号;
分隔数组成员(也可以使用逗号,
分隔数组成员),用等号=
分隔键值对。
在创建数组时,数组的键可以省略不写。有序数组的键可以省略,示例:
var array = {
123,456,789,"其他数组值"
}
有序数组可以使用 for 循环( 或者 table.eachIndex )有序地遍历成员,示例:
import console;
//在表中创建数组
var array = {
[1] = 123;
[2] = "数组的值可以是任何其他对象";
[3] = { "也可以嵌套包含其他表或数组"}
}
//数组的键可以省略,下面这样写也可以(并且建议省略)
var array = {
123;
"数组的值可以是任何其他对象";
{ "也可以嵌套包含其他表或数组"}
}
//遍历数组成员, 用 #array 取数组长度
for(i=1;#array;1){
console.dump(i,array[ i ]);
}
//使用 table.eachIndex 也可以遍历数组
for i,v in table.eachIndex(array){
console.dump(i,v);
}
console.pause();
表总是可以同时包含数组、以及非数组成员,即使不包含数组成员,我们也可以将表作为空数组处理。
稀疏数组可以理解为无序集合(哈希表)的一个特例。
如果表中包含的成员使用了数值作为键,但是多个成员的键并不是从1开始的连续数值 - 则构成稀疏数组。
如果表中包含了稀疏数组 - 也就是说成员的数值键(索引)包含不连续的、中断的数值,那么不应当作为有序数组使用。
在 aardio 一般我们提到的数组 - 如果未加特别说明则是特指有序连续数组(不包含稀疏数组)。aardio 中几乎所有针对数组操作的函数或操作符 - 如果未加特别说明都要求参数是有序数组。
至于稀疏数组我们将其作为一般的哈希表操作就可以了,不要用数组函数去操作稀疏数组。
下面的数组就包含了 null 值,属于数值键(索引)不连续的稀疏数组:
var sparseArray = { "值:1", null, "值:2", "值:4", null, null }
null 值就是没有值,所以在表的尾部写 null 值是无意义的,正确写法如下:
var sparseArray = { "值:1", null, "值:2", "值:4" }
表中间的 null 值仅仅是占位作用,因为 null 仍然是表示不存在的值,以上的表等价写法如下:
var sparseArray = {
[1] = "值:1";
[2] = "值:2";
[4] = "值:4";
}
这种稀疏数组不应当作为有序数组使用,也不应当使用 #
操作符去获取稀疏数组的长度。 s
稀疏数组可以用 table.range() 获取最小索引、最大索引。
也可以使用 table.eachArgs() 遍历稀疏数组。
示例: #
import console.int;
var sparseArray = { "值:1", null, "值:2", "值:4" }
//获取稀疏数组长度
var min,max = table.range(sparseArray);
//遍历稀疏数组(可用于遍历函数的不定参数)
for i,v in table.eachArgs(sparseArray) {
console.log(i,v)
}
表的构造器是用一对大括号 {}
包含零个或多个用分号;
分隔的元素(键值对或数组成员),使用等号=
分隔键值对。
import console;
//空表
var tab = {}
//哈希表、对象
var object = {
key1 = "字符串";
key2 = 123;
[123] = "不符合变量命名规则的键名应放在下标内。";
["键 名"] = "不符合变量命名规则的键名应放在下标内。";
}
//数组
var array = {
123;456;789;"其他值"
}
console.pause(true);
表可以包含不同类型的数据,可以同时包含键值对与数组成员。
如果表的键名不符合标识符命名规则,则必须写在下标内,例如:
var object = {
[123] = "不符合变量命名规则的键名应放在下标内。";
["键 名"] = "不符合变量命名规则的键名应放在下标内。";
}
```a
aardio 中的表允许用 `,` 号代替 `;` 号分隔表成员 ,并且允许用 `:` 替代 `=` 分隔键值对,也允许在表中用引号(双引号、单引号、反引号 )包含的字符串表示键名。因此可以使用类 JSON 的语法定义表对象,例如:
```aardio
var tag ={"name1":123,"name2":456}
如果表的成员是一个函数,且函数体是一个语法块,则可以省略表分隔符,例如:
var object = {
func1 = function(){}
func2 = function(){}
}
不能在表构造器后直接使用成员操作符或下标,例如 {}.member
这样写是错的。
当在函数内部有只有一个使用 {}
构造器构建的 table 参数(并且表不是结构体)时,并且第一个出现的成员是用 =
分隔的键值对(不能用冒号替代),就可以省略外层的 {}
。
例如以下 aardio 代码:
func( { k = 123, k2=456, 123 } )
可以省略外层的 {}
写成如下格式:
func( k = 123, k2 = 456, 123 )
注意上面的参数是一个表对象,虽然这样看起来很像其他编程语言里的"命名参数",但实际上 aardio 里没有"命名参数"。
但 aardio 确实可以用这种语法模拟其他语言的命名参数,例如 aardio 调用 Python 的示例:
import py3;
var requests = py3.import("requests");
var ses = requests.Session();
var res = ses.$get(verify=false,"https://www.aardio.com");
上面的代码 ses.$get(verify=false,"https://www.aardio.com")
里面就用 verify=fals
指定了命名参数。
aardio 实际上是执行了 ses.$get( { verify=false,"https://www.aardio.com" } )
,传过去的调用参数实际是一个表对象,然后由 aardio 的 py3 扩展库内部转换为了 Python 的命名参数。
在访问表中的元素时,可以用元素的键作为下标查询元素的值,例如:
//创建表
var tab = {};
//下标为字符串"x",键名为字符串"x",值为数值200。
tab["x"] = 200;
/下标为数值1,值为字符串 "Hello, World!"
tab[1] = "Hello, World!";
如果键名是合法的标志符,则可以用"."成员符访问。例如 tab["x"] = 200
也可以写为 tab.x = 200
。
也可以使用直接下标 [[]]
读写表的成员,直接下标使用两对中括号包含要查询的索引表达式。对于一般的表直接下标与下标的作用是一样的,区别在于普通下标( []
)或成员操作符( .
)会触发表的元方法(如果定义了元方法),而直接下标( [[]]
)不会触发表的元方法。
示例:
//创建表
var tab = {
//定义元表
@{
_get = function(k){
return k + "的值"
}
}
};
import console
//输出了 "xyz的值"
console.log( tab["xyz"] );
//无输出值,直接下标不触发元方法
console.log( tab[["xyz"]] )
console.pause()
当我们将表元素赋值为 null 会删除这个元素。例如:
tab.x = null; //删除tab.x
表对象( table )的成员名称如果首字符为下划线,并且长度大于 1 个字节并小于 256 个字节,则是一个只读成员( readonly member )。
示例:
var tab = {
_readonlyMember = "值不可修改";
member = "值可以修改";
}
//没有修改值,被忽略
tab._readonlyMember = "值不可修改";
//不能修改只读成员,报错。
tab._readonlyMember = "新的值";
要特别注意,表的只读成员并不要求是合法标识符,例如:
tab["_ (不是合法标识符)"] = 1;
tab["_ (不是合法标识符)"] = 1234;//报错,不能修改常量
在对象的元表中指定元属性 _readonly = false
可禁用读成员保护,这样就可以自由修改名字以下划线开头的表成员。如果在表对象的元表中不设置 _readonly
则默认启用只读成员保护(所有名字以下划线开头的成员禁止修改非 null 值)。
如果元表中设置 _readonly
为任何非 null 值都会被强制转换为 false, 该值一旦设置以后即不可修改,如果希望启用只读成员保护,唯一的方法就是不设置该属性。
全局表( global )将元属性 _readonly
设置为任何值都会被忽略,global 的只读成员保护总是启用状态。
请参考:
遍历表中的全部元素
import console;
var tab = { a111="字符串"; b2=123; c="字符串2"; d=23; e=56; 78; 99; 123; 0 }
for k,v in tab {
/*
k为键,v 是匹配的值,
在这里键值并不会按上面创建表的键值顺序输出
*/
console.log(k,v);
};
console.pause(true);
如果表只包含有序数组(“键”是从1开始、有序、连续的数值),则 for in 语句会按数组顺序循环。
如果表还包含非数组成员,则需要改用计数循环才能顺序遍历数组成员。示例:
import console;
var array = {
a="字符串";b=123;c="字符串2";d=23;e=56;
78; 99; 123; 0
}
//使用 #array 取数组长度
for( i=1; #array;1){
//i为当前数值索引,tab[ i ]为当前值
console.log( i ,array[ i ] );
}
//也可以用 while var 语句模拟 for 循环语句遍历数组
while( var i = 0; i++ ; i <= #array ) {
console.log( i ,array[ i ] );
}
console.pause(true);
遍历稀疏数组的方法请参考: table.eachArgs() 示例 。
使用 table.sort 可以重新排序有序数组(不适用于稀疏数组)。
函数原型:
table.sort(array[, compareProc])
说明:
排序 array 参数指定的数组,此函数无返回值。
默认较小的值排在前面,可选用 compareProc 参数自定义比较函数。
比较函数应使用 <
或 >
操作符比较元素的大小。两元素相等时不能返回 true,因此应避免使用 <=
或 >=
操作符比较大小。
compareProc 需要比较的是 owner 参数与第一个参数,返回 true 表示 owner 应排序在前。
示例:
var array = {1,2,3}
//排序
table.sort(array,function(next){
//较大的值应排序在前
return owner > next
})
上面的自定义排序结果为 {3,2,1}
, 较大的值排序在前。
哈希表的用途是快速查找值,无序正是他的最大优点(索引快如闪电)。
数组的排序功能与表的快速查找值的优势是可以相互结合的。
示例:
import console;
//无序的哈希表
var tab = {
c = "3";
b = "2";
a = "1";
}
//有序的数组
var keys = {
"c","b","a"
}
//用数组排序键名
table.sort(keys)
//顺序遍历键名数组
for(i,k in keys){
//再到哈希表中去查找值
console.log(k,tab[k])
}
console.pause(true);
aardio 提供了一个原理类似的 table.eachName 函数可以对表的键名排序然后遍历表的所有元素。
示例:
import console;
//无序的哈希表
var tab = {
c = "3";
b = "2";
a = "1";
}
//排序哈希表
for k,v in table.eachName(tab){
console.log(k,v)
}
console.pause(true);
table.eachName 函数可以指定排序函数与键名数组,具体请参考该函数参考文档。
我们可以把多个变量用一对花括号包含起来以转换为数组,也可以使用 table.unpack 函数将数组转换为多个变量。
示例:
import console;
var func = function(a,b,...){
//将多个变量与不定参数合并为数组,返回多个值的 ... 必须放在最后面
var args = {a,b,...}
//展开数组
var a,b,c,d = table.unpack(args);
//合并返回值,返回多个值的 table.unpack(args) 必须放在最后面
var args2 = {a,b,table.unpack(args)}
//返回多个值
return table.unpack(args2)
}
//合并函数的多个返回值到数组
var arr = { func(1,2,3,4,5) }
//输出数组
console.dumpTable(arr);
console.pause();