原文:知乎 虚荣

  • 1. 绵长的前戏
  • ... 1.1 示例
  • ... 1.2. AHK的对象是字典
  • ... 1.3. 字典里面都是些什么
  • ... 1.4. 数组跟字典是什么关系
  • ... 1.5. AHK的类与对象可以看作字典
  • ... 1.6. 键指向什么内容呐
  • ... 1.7. 复杂的嵌套对象好比是道路
  • ... 1.8. 对象附带的方法
  • ... 1.9. 写在前头: 一个非常非常重要的结论
  • 2. 开始正戏: 对象的声明
  • ... 2.1. 声明空对象
  • ... 2.2. 定义非空对象与键的数据类型
  • ...... 2.2.1. 非空字典(关联数组)定义
  • ...... 2.2.2. 如何用表达式定义键?
  • ...... 2.2.3. 定义非空数组
  • ...... 2.2.4. 重定义对象
  • ...... 2.2.5. 对象的键在该对象中具有唯一性
  • ...... 2.2.6. 定义时写入了重复的键
  • ...... 2.2.7. 特别的 (对象类型的键)
  • ...... 2.2.8. 浮点数键是个什么键
  • ...... 2.2.9. 字符串键不分大小写
  • 3. 增添内容至对象
  • ... 3.1. 给对象中 不存在的键 赋值
  • ... 3.2. 用于 数组 的 插入键值对的方法
  • ...... 3.2.1. .Push() 方法 添加内容到数组的末尾
  • ...... 3.2.2. .InsertAt() 方法 在数组某个位置插入值
  • 4. 对象的查询办法
  • ... 4.1. 取某个键的值
  • ... 4.2. 判断一个变量是否为对象
  • ... 4.3. 对象中是否有某个键
  • ... 4.4. 键值对数量
  • ... 4.5. 数组关于数字键的3个方法
  • 5. for 循环与对象(重要)
  • ... 5.1. 基本格式
  • ... 5.2. for 循环作为循环语句
  • ... 5.3. for 循环之于数组的重要性
  • ... 5.4. 临时存放键值的两个变量
  • ... 5.5. (非对象)或(空对象)引用 for 循环语句
  • ... 5.6. for 循环 对 字符串键 遍历 顺序逻辑
  • 6. 未完待续...

1. 绵长的前戏

1.1 示例

今天,我们要用上很有内容的 对象,以下示例可以粗略看过。

集装箱 := {}
;~ ------ 首先, 表示它是很有内容的(用{}大括号声明一个对象)

集装箱["里面"] := "内容"
;~ ------ 然后, 我们在它 的 "里面" 放上 "内容"

Msgbox, % 集装箱["里面"]
;~ ------ 接着, 我们通过 "里面" 把这个东西拿出来晒一晒

集装箱["别的标签"] := "新来的神秘物体"
;~ ------ 继续, 装点别的东西

s := "别的标签"
Msgbox, % 集装箱[s]
;~ ------ 通过一个变量把新的神秘物体弄出来

a := "别的"
b := "标签"
Msgbox, % 集装箱[a . b]
;~ ------ 通过2个变量拼接把新的神秘物体弄出来

c := "别"
Msgbox, % 集装箱[c . "的标签"]
;~ ------ 再来, 通过1个变量和1个字符串拼接做同样的事

Msgbox, % 集装箱.count() . " 个内容是至今这个集装箱里总共有的"
;~ ------ 我们到底放了多少东西呢, .count()是集装箱的方法之一

for 标签, 内容 in 集装箱
     msgbox, % 标签 "`n" 内容
;~ ------ 用for循环语句分别掏出来瞅一瞅

集装箱 := {"标1": "物1", "标2" : "物2"}
;~ 我们重新定义它

s := ""
for k, v in 集装箱
   s .= k . "=" . v . "`n"
msgbox, % s    
;~ ------ 可以发现最初定义的东西消失了, 
;~ ------ 这是用for循环拼接一个字符串s, 然后一次性显示在信息窗

 

1.2. AHK的对象是字典

这样的集装箱,在AHK文档中被称为 Object / 对象, 或细分可称之(关联数组), 在python中有个近似的数据类型, 字典,以下就以字典称呼它(关联数组)好了。

1.3. 字典里面都是些什么

字典的组成单位是 键值对,一个字典中有一些 (索引),分别指向各自的(内容),这样的概念被称作 键值对

对象 := {"键名" : "值"}
Msgbox, % 对象["键名"]

 

1.4. 数组跟字典是什么关系

以字典开篇示例,而不以看似更简单的数组起头,个中原因:

在AHK中,数字像是可以运算的特殊的字符串。

而 数组(Array) 则像是 区分了键形式 (限制键内容为数字) ,拓展了 键 作为数字 可计算 功能 及 有序 性质的 字典,这里下个结论,AHK的 数组 就是 字典 ( 数组 是 关联数组 的一种特殊表现)。

对象 := {2 : 34567}
Msgbox, % 对象[1+1]

 

1.5. AHK的类与对象可以看作字典

如果你在搜索引擎搜过 类 或者 对象,一般会注意两个高频出现的词汇,属性方法,说白了,它们是封装在一个对象里面的 变量 和 函数

贴近AHK一些,对象的 属性/方法 的 索引 就是对象中的键

1.6. 键指向什么内容呐

键 是一个索引,键所指向的事物是所有AHK可定义的数据类型

对象 := {}

;~ 1)可以是一个简单的数值,即它键的值是一个简单的参数

对象["某参1"] := 333
对象["某参2"] := "一行字"

MsgBox, % 对象["某参1"]   ;~ 弹窗 333
MsgBox, % 对象["某参2"]   ;~ 弹窗 一行字

 

;~ 2)是一个方法(功能函数),可以使用它的功能达成一个目的

消息() {
  static 引用次数
  引用次数 += 1
  Msgbox, 弹出一个消息`n引用次数为%引用次数%
}

对象["弹窗"] := func("消息")
对象["弹窗"]()

 

;~ 3)是另一个对象(对象的键索引至有其他关联键值对的对象)

即将被扔进另一个对象 := {"键": "别的键的值"}
对象["小天地"] := 即将被扔进另一个对象
Msgbox, % 对象["小天地"]["键"]

 

1.7. 复杂的嵌套对象好比是道路

对象可以是一个复杂的集体,你想象是一个坐标原点。

从这个对象的首都(对象本身)出发,从主干道分成一条条支路

键 是每条道路的唯一的指示牌,而对象的键的值可以指向另一个对象,即这条支路又分出了其他道路,也同样以路牌指示。

做一个不正经的示例:

换个例子可以是我们日常接触的文件路径

假如有个天大的对象C盘

C := {"国学" : {"儒学" : {"荀子": {"温碧霞":"香魂.rmvb", "舒淇":"玉蒲团.avi", "叶玉卿":"卿本佳人.flv","快播":func("播放"), "高清写真" : {....})}}}}

 

我们可以看出在

C:\国学\儒学\荀子\

这个路径下有很多姿势,包括3大著名女星的视频文件,包括一个可执行的快播程序,还有一个名为高清写真的文件夹

假如我们要运行纯洁的快播呢

就酱:

好地方 := C["国学"]["儒学"]["荀子"]
好地方["快播"](好地方["舒淇"])

 

由于藏得比较深,所以这个路径有点长,当然,正经的我们知道这只是一个字典索引的示例。

1.8. 对象附带的方法

在创建字典时,同时附带它作为字典的方法

而索引向这些方法的键,是被隐藏的,无法被for 循环取得。

;~ 譬如 .count() 方法,获取字典中键值对数量
字典 := {"键1":"", "键2":""}

;~ 正常我们使用方法
Msgbox, % 字典.count()

;~ 用键名取方法也是被允许的
Msgbox, % 字典["count"]()

;~ 取键的值也可以用. 英文句号
Msgbox, % 字典.键1

;~ 但是这样内置的方法可以被重写(覆盖)
字典["count"] := 233

;~ 如此,这个字典永远失去了它的方法
Msgbox, % 字典["count"] . ": 这是count的新内容"
Msgbox, % 字典["count"]() . ": 到底经历了什么, 左边空掉了"

 

1.9. 写在前头: 一个非常非常重要的结论

AHK 关于 简单对象, 所有所有的内容, 都可以归结为对 (键) 与 (值) 的操作

而对 ( 键 ) 的性质了解 直接 影响 对象操作 影响 AHK 编码性能

2. 开始正戏: 对象的声明

它只有是一个对象,才能执行作为对象的所有操作

2.1. 声明空对象

空对象是对象的最简实现

声明空对象,以下4种方式是等效的

对象 := object()
对象 := {}

对象 := Array()
对象 := []

 

2.2. 定义非空对象

在定义对象时候顺带给对象 赋予 自定义数量的 键值对 单位

2.2.1. 非空字典(关联数组)定义

/* 感谢热心网友 bootloader 指出 文章本段含有2处符号错误, 现已改正 */

以下3个定义方式是等效的

;~ 由object() 函数创建, 传入参数为一般为2的倍数, 单数位为键, 双数位为值
o1 := object("键1","1内容", "键2","2内容")

;~ 由{ } 中 的 (键 : 值) 以 (,) 分隔 方式
o2 := {"键1" : "1内容", "键2": "2内容"}

;~ 同上等效
o3 := {键1 : "1内容", 键2: "2内容"} ;键名有无双引号都视作字符串

 

o2 和 o3 的区别

双引号中定义的键键名中可以包含" "空格与"."句点符号 等特殊符号

包含空格与句点符号时,则不适用 对象.键名 形式的取值取值

o := {"左.右": 123}
Msgbox, % ".点取值试验结果: "  o.左.右   ;~ 空弹窗
Msgbox, % "[ ]取值试验结果: "    o["左.右"] ;~ 123

 

2.2.2. 非空对象定义时怎么用变量(或表达式的结果)作为键?

加上括号即可。

变量 := "键"
a := {变量 : 11}
Msgbox, % a[变量] ;~ 空弹窗
Msgbox, % a["键"] ;~ 空弹窗
Msgbox, % a["变量"] ;~ 弹窗11

b := {(变量) : 11}
Msgbox, % b["键"] ;~ 弹窗11

c := {( "ab" . "cd") : 123 }
Msgbox, % c["abcd"]

 

2.2.3. 定义非空数组

以下3种非空数组定义是等效的,

;~ a1和a2两种定义方法,解释器默默地将内容本体从左至右写上出现次序的键
;~ 是以1为首序号,步进为1的递增

a1 := Array("1内容","2内容","3内容")
a2 := ["1内容", "2内容", "3内容"]


;~ 若你心大(闲得蛋疼)定义这样的数组可以如下办法, 也是等效的
a3 := {1 : "1内容", 2 : "2内容", 3 : "3内容"}

 

2.2.4. 重定义对象

并非增加内容至原来对象,将删除原来的对象包括名下所有键值对

o := {"标1":123}
msgbox, % o["标1"] ;~ 弹窗123

o := {"标2":556}
msgbox, % o["标1"] ;~ 弹窗空白

 

2.2.5. 对象的键在该对象中具有唯一性

任意对象中不会存在两个相同的键

相同的键多次赋值即修改同一个键的内容

2.2.6. 定义时写入了重复的键

只有最后一个(最右侧)键值对生效

a := {"xxx" : 123, "xxx": 456, "xxx": 789}

Msgbox, % a["xxx"] ;~ 弹窗789

 

2.2.7. 特别的 (对象类型的键)

a := {}
b := {}

c := {}
c[a] := 123
msgbox, % c[a] ;~ 取得123
msgbox, % c[b] ;~ 空弹窗

 

此处可见,包括对象类型可以作为键索引

这里我要卖个萌,应该有特殊用途。。但我暂时想不出来。。

2.2.8. 浮点数键是个什么键

经过吃撑了的简单考察

直接说结果吧,浮点数键 不算 数字键,它只能算是 字符串键,被排除在数组规则之外

a := {}
a[1.1] := 11
a[5] := 22
a[9.9] := 33
Msgbox, % a.maxindex()
Msgbox, % a.minindex()

;~ 两个弹窗皆返回5

Msgbox, % a[9.9] ;~ 取值正常弹窗33

 

2.2.9. 字符串键中的字母不分大小写

感谢 梦醒(feiyue) 大佬 补充... 不过这里我并未做过测试, 原谅我偷懒...


3. 增添内容至对象

3.1. 给对象中 不存在的键 赋值

即视作添加键值对

对象 := {"键1" : 123, "键2" :123}

;~如此行为添加内容(键值对)
对象["键3"] := 556  

for 键, 值 in 对象
  msgbox, % 键 "=" 值

/* 依次弹窗
键1=123
键2=123
键3=556
*/

a:= [123, 123]
a[3] := 556

for 键, 值 in a
  msgbox, % 键 "=" 值

 

3.2. 用于 数组 的 插入键值对的方法

上头说道数组是特殊的对象
从插入 键值对 的方法也可见端疑

3.2.1. .Push() 方法 添加内容到数组的末尾

数组.Push(值)

该方法比较适用于数组

存在纯数字键的对象中,Push()方法 增加一个键在末尾。

A := [123, 123]
A.push(556)
for 键, 值 in A
  msgbox, % 键 "=" 值   

/* 依次弹出
1=123
2=123
3=556
*/

 

.Push()实则是给 一个不存在的 数值为(最大数字键+1) 的键 赋值

A := {-6: 123, -5: 456}
A.push(789)
for 键, 值 in A
  msgbox, % 键 "=" 值

/* 依次弹窗:
-6=123
-5=456
-4=789
*/


;~ 等效方法:

A := {7: 123, 8: 123}
最大键 := A.maxindex()
msgbox, % 最大键       ;~弹窗8

A[最大键 + 1] := 556
for 键, 值 in A
  msgbox, % 键 "=" 值

 

.push()方法加入多个值

对象.push(值1, 值2, ..., 值n)

从第一个 值1 赋值给 .Maxindex() + 1键开始

次位 值2 赋值给 Maxindex() + 2,依次类推,步进始终为1的赋值方法

A := [ ]
A.Push("abc")
A.Push("def")
A.Push("ghi")
for i, v in A
     MsgBox, % i "=" v

/* 依次弹窗
1=abc
2=def
3=ghi
*/

 

但是.push()方法 对 不存在数字键的 字典 也生效

这种情况:

a := {"我不是数字键的哦" : 123}
a.push("随便放个东西")
for key in a
   Msgbox,% key


依次弹窗:
> 1
> 我不是数字键哦

3.2.2. .InsertAt() 方法 在数组某个位置插入值

数组.InsertAt(索引, 插入的值)

a := ["x1", "x2", "x3"]

/*
譬如有个数组a, 键值分别为
1=x1
2=x2
3=x3
*/


a.InsertAt(2, "x9")

/* 此时数组内容为
1=x1
2=x9
3=x2
4=x3

*/

 

可以看出,插入位置之前的键不变,从插入位置启,其后的所有大于插入位置键的 键值对 的 键,都被推后1个位(原有的键+1)

也许这样不严谨,所以虚荣又做了另一个操蛋的试验

同.push()方法一样,.InsertAt() 方法也是可变参数,它可以一次插入多个值

;~数字键分别为1 6 10
A := {1: 111, 6: 222, 10: 333}

;~ 从第1位开始插入3个字符
A.InsertAt(1, "a", "b", "c")

for i, v in A
   Msgbox, % i "=" v

/* 分别弹窗:
1=a
2=b
3=c
4=111
9=222
13=333
*/

 

可以看出,从插入位置启,之后原有的 键都会累加,

键 = 键 + 插入元素个数

也能看出,这种方法会改变数组原有结构


4. 对象的查询办法

4.1. 取某个键的值

对象[ 表达式 ]

假设我们有一个对象

需要取出对应键索引的值时,要在对象名称右侧加上[ ] 小括号

对象 := {"abc": 123}
msgbox, % 对象["abc"]   ;~ 弹窗123

 

这里要注意: 命令式中的参数无法用传统式方式 对 对象 进行 取值!

如以下两种方式都是错误

Msgbox, %对象%["abc"]
Msgbox, %对象["abc"]%

 

( 对象["abc"] ) 这样的形式在表达式中视为一个整体,可以作一个变量食用,无论 赋值 还是 取值

[ ]小括号中一般是一个字符串也可以是计算结果为 字符串 或 数字的表达式

假设我们有一个混乱的对象

* 此处再次致谢 热心的 bootloader 同志..键名为"22"时 [11 * 2]的取值办法无效..改为无双引号的 22 可解

obj := {22      : 100
    , "灵能100%": 200
    , "funcRet" : 300}

;~ 取键 22 的值
msgbox, % obj[11 * 2] 

;~ 取键 "灵能100%"的值
msgbox, % obj["灵能" . (20* 5) . "%"]

;~ 取键 "funcRet"的值
函数() {
   return "funcRet"
}
msgbox, % obj[函数()]

 

4.2. 判断一个变量是否为对象

用IsObject()函数

a := 123
b := {}
c := [123]
msgbox, % IsObject(a) ;~ 返回 0
msgbox, % IsObject(b) ;~ 返回 1
msgbox, % IsObject(c) ;~ 返回 1

 

4.3. 对象中是否有某个键

用 .haskey(键) 方法

;~ 数组A的3个键分别为,1, 2, 3
A := ["内容", "内容", "内容"]
msgbox, % A.haskey(3)  ;~ 返回1 (true)
msgbox, % A.haskey("内容") ;~ 返回0 (false)

;~ a的2个键分别为 "abc" "def"
A := {"abc": 111, "def": 222}

msgbox, % A.haskey("def") ;~ 返回1(true)
msgbox, % A.haskey(111) ;~ 返回0 (false)

 

4.4. 键值对数量

用 .count() 方

该方法返回该字典的键值对数量,此命令较通用

就是有多少组数值, 不做示例

4.5. 数组关于 数字键 的3个方法

.MaxIndex()/.MinIndex() 方法

该方法返回字典中 最大/最小的 整数键

在很长一段时间里,Maxindex()方法被笔者用作查询数组长度(键值对数量)的方法,但是这种方法并不严谨

在查询数组内容数量时,建议用.count()方法

这两种方法一般用于纯数组

.MaxIndex()方法在数组首位以1开始计算,且正常1位步进时,可代替.count() 获取键值对数量的方法

数组的键可以为负数如:

数组 := {-1:"",-3:"", -5:""}
msgbox, % "最大键为" . 数组.maxindex()  ;~ 弹窗-1
msgbox, % "最小键为" . 数组.minindex()  ;~弹窗-5

 

.Length() 方法

文档中描述,数组.Length() 方法与.Maxindex()类似,笔者也做过调查,返回的也是最大整数键

区别在不存在数字键时 .Length() 方法 返回0

而.Maxindex() 方法 返回空值

5. for 循环与对象(重要)

5.1. 基本格式

for 键 in 对象
{
   ;~ 代码块
}

for 键, 值 in 对象
{
   ;~ 代码块
}

 

可以看出此类循环格式:

由关键词 for 开头

键, 值 分别为两个变量名称,保存遍历对象时当前循环的 键 和 值,其中(值)可省

in 后接 对象, 或者是 计算结果 为 对象 的 表达式。

for i in 数组
for i, var in 数组

for key in 字典
for key, var in 字典

推荐这两种写法,从变量名做文章在某种程度上从感官上区别数组和字典

;~ 数组:
for i, v in ["abc", "def", "ghi"]
{
   Msgbox, 
   (
       当前循环次数: %A_Index%
       当前取得下标: %i%
       当前取得值: %v%
    )
}

;~ 字典:
for key, v in {"k1": 333, "k2":666, "k3":999}
{
   Msgbox, 
   (
       当前循环次数: %A_Index%
       当前取得键名: %key%
       当前取得值: %v%
    )
}

 

循环 for语句 其他惯用形式

;~ for 遍历 数组 A
A := [333, 666, 999]
for i, v in A
    msgbox, % i "=" v


;~ for 遍历 函数的返回值
函数() { 
   return {"a":33, "b":66, "c":99}
}
for k, v in 函数()
    Msgbox, % k "=" v



;~ for 遍历 二维嵌套对象


a:= {}

a["abc"] := [11, 22, 33]
a["def"] := [44, 55, 66]
a["ghi"] := [77, 88, 99]

/* 等效定义方法
a := {"abc": [11, 22, 33]
    , "def": [44, 55, 66]
    , "ghi": [77, 88, 99]}
*/

计次 := 0

for key, List in a {
   for i, v in List {
      计次 += 1
      Msgbox, 
      (
         当前取得对象a中列表: %key%
         取得该列表下标: %i%
         下标对应值: %v%
         总计次: %计次%
      )

   }
}

 

5.2 for 循环作为循环语句

for ( ) in ( ) 语句是一个有限的循环,循环次数与对象内键值对数量相等。

即以下两个循环语句循环次数是一样的

loop, % 对象.count()
{
}

for i in 对象
{
}

 

同样的,拥有循环语句的特性:

break: 命令, 在任意位置结束循环
continue: 命令, 在任意位置跳过本次循环直接进行下个循环
A_Index: 内置变量, AHK特有的记录循环次数的变量, 正常情况下与正常使用的数组下标一致

5.3 for 循环之于数组的重要性

数组的有序性的特质在for循环中呈现,for 循环取出数组内容的是依键值从小到大的顺序。

键的数值越小,越先在for循环中出现,其最大的键值会出现在for循环中最后一个循环段中。

这是数组有序性质的最重要体现

for i in {11:"", 9:"", 5:"", 1:""}
  msgbox, % i

 

5.4 临时存放键值的两个变量

除非这两个变量可能被赋予了对象中的对象

否则对两个变量的赋值更改 不会影响 到 对象本身 的 内容

且在下一次循环开头被刷新

且在循环结束 或 循环中断以后,两个变量依旧存放 最后一次循环 所 被赋予的值

5.5. (非对象)或(空对象)引用 for 循环语句

这个请放心,不会执行任何动作,一次循环都不会有

包括空对象也不会执行

5.6. for 循环 对 字符串键 遍历 顺序逻辑

笔者是个没文化土包子,偏偏喜欢计算机相关的东西,喜欢看那些高大上的算法的名词(仅限于名词),听过哈希表什么的。

常年跟AHK打交道,也觉着有迹象表明,AHK的字符串键也是有排序规律的。

本着探索精神。。还是做个不知道对不对的试验。

;~ 取得"启"字utf-16编码
code := asc("启")
MsgBox, % "启字编码为: " code

;~ 将"启"开始 编码步进为1 的所有编码对应字符依序拼接 200 个, 赋值给s1
s1 := ""
Loop, 200
  s1 .= chr(code + A_Index - 1)
MsgBox, % "初次执行结果:`n" . s1

;~ 声明一个空对象
obj := {}

;~ 解析s1 每个字符作为对象 obj 的键
Loop, parse, s1
{
  obj[A_LoopField] := ""
}

;~ 用 for 循环依次取出obj的键, 拼接赋值 给s2
s2 := ""
for key in obj
{
  s2 .= key
}
MsgBox, % "第二次执行结果:`n" . s2

if (s1 = s2)
{
  MsgBox, 这两个字符串是等值的
}
return

 

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。