2.10容器-对象(类)

这部分是入门部分的难点了,但是也是极其重要的一部分。

一、什么是对象

在前文中,系统讲过变量和数组,大家对于一些经典的数据类型已经有了初步的认识,例如:字符串型、整型、浮点型、简单(线性)数组、关联数组,后面两种不再是简单的数据类型,而是一种数据结构。

var := "社区" ;字符串型
var := 123 ;整型
var := 123.123 ;f浮点型
obj := ["张三", "李四", "王五"] ;简单(线性)数组
obj := {一班:"张三", 二班:"李四", 三班:"王五"} ;关联联数组

二、对象和类的关系

开始之前先谈谈对对象和类的认识,建议大家耐得住性子看完,避免到后面云里雾里的,脑壳疼。

类与对象到底是什么关系?这里的混淆的点其实是在对象这里而不是类,其实在ahk中对象的概念非常宽泛,变量也是对象的一种形式,数组、还有我们常规认识的{}的这种形式都是对象,这里混淆的地方就是a:={} ,那么a就是一个对象,class a{一大堆},这样子a是一个类,直观上感觉a对象没有a类高级,其实ahk不是这样的。在c++中对象是类的一个实例,类是对象的一个模板,然而在ahk中对象是一个包含了很多东西的宽泛的概念。在ahk中类成了对象的一种形式,如果理解了这里就理解了类在ahk中的存在了。所以以后不管是new出来的,还是class出来的你都叫类就好了,不要按照c或者c++那样把new出来的实例作为对象,对象就是作为一个宽泛的概念,简单理解就是跟容器相关的都是对象,只是对象的样子不一样而已。

思想上转变过来后就是用法的问题,无非就是继承、调用,还有一些基本概念 方法 属性等等。隐藏的暗线就是方法、属性、变量的作用域的问题,当然还有一些内置的元函数,至于说this指针,一言以蔽之就是指这个类本身,在类内部可以使用this调用自己有点像递归,其中,方法、属性、变量的作用域,这个是绝大部分人用起来头疼的地方,破解这个也简单,认真阅读相关基本概念的定义,以及熟练掌握局部变量、全局变量和静态变量的作用域,this指针也有自己的作用域,就是类构造时产生,执行结束时释放,无法在外部调用。

第 1 节:简介

> 快速摘要
> 简介:对象和键/属性/方法
> 简介:类/实例和 AutoHotkey 的内置类
> Autohotkey 的基本对象
> Autohotkey 对象功能
> 元功能
> A Zoo类和基础对象

> 超类/子类
> 嵌套类
> __CLASS 属性和基础对象:追逐基础
> 基础对象:替换基础/修改 AHK 数组

第 2 节:键/属性/方法
> 类:值属性
> 类:属性
> 类:方法
> 类:键/属性/方法和优先

第 3 节:元函数
> 元函数
> 元函数+:__NEW / __DELETE
> 元函数+:__init
>元函数:__CALL
>元函数:__GET

>元函数:
__SET > __GET/__SET:每次获得/设置密钥时进行监控
> __GET/__SET:添加密钥和子密钥(多维分配)
> __GET/__SET : 同时获取和设置
> 一般类示例:日期和拆分路径

第 4 节:枚举器和杂项
> 枚举器:_NEWENUM 和 NEXT
>单例
> 教程/链接
> 结尾

============================================ =======

[第 1 部分:简介]

> 快速总结

注意:本教程适用于 AHK v1.1,但偶尔会引用 AHK v2。

术语:对象、键/方法。
- 对象结合了数据/变量(键)和函数(方法)的概念。

代码:

;keys:
value := MyObj["MyKey"] ;get a key's value
MyObj["MyKey"] := "NEW VALUE" ;set a key's value

;methods:
output := MyObj.MyMethod(Arg1, Arg2, Arg3) ;call a method

术语:财产。
- 属性可以是类似键的,具有获取/设置的值。在 AHK v2 中,这些被称为“值属性”。
- 属性可以是类似方法的,其值由 getter/setter 函数生成。在 AHK v2 中,这些被称为“动态属性”。
- 两种类型的属性在使用时将具有相同的外观。(虽然在定义时会显得不同。)

代码:

value := MyObj.MyProperty ;get a property's value
MyObj.MyProperty := "NEW VALUE" ;set a property's value

- 在 AHK v1 中,键和属性共享相同的命名空间。obj["key"] 和 obj.property 语法通常可以互换。
- 在 AHK v2 中,键和属性是不同的。obj["key"] 和 obj.property 语法是分开的。

- 在 AHK v1 中,键和值属性是一回事。如果键(/值属性)和动态属性同时存在,且名称相同,则键优先于动态属性。

术语:对象/实例/类。
- 简单来说:
- 一个类是一个蓝图对象。
- 实例是基于类蓝图创建的对象。
- AutoHotkey 有一个 Basic Object 类,以及其他内置类(在下方列出,例如 RegExMatch、SafeArray)。

术语:自定义类。
- 用户可以创建自定义类。即他们自己的班级蓝图。
- 如果您创建一个名为“MyClass”的自定义类,则会创建一个名为“MyClass”的变量。这可以通过使用 ListVars 或“视图、变量及其内容”(在 AHK 的“主窗口”上)来查看。
- (例如,如果您通过 'obj := {}' 创建一个基本对象实例并定义一个名为 'MyClass' 的类,则 IsObject(obj) 和 IsObject(MyClass) 都将报告为 true。)
- 自定义类确定键/properties/methods 实例对象。也可以阻止添加新的键/属性/方法。

6 种方法:__Init/__New/__Get/__Set/__Call/__Delete。
- 创建自定义类时,以下 6 种方法是自定义的关键。
•__Init ['不应被脚本使用',在创建对象实例时
处理] •__New [在创建对象实例时处理]
•__Get / __Set [在获取/设置不存在的键/属性时处理]
•__Call [在调用存在/不存在的方法
时处理] •__Delete [在删除对象实例时处理]

术语:基础。
- 对象的基础对象通常包含确定该对象如何工作的键/属性/方法。
- 在 AHK v1 中,基本对象没有基本对象。
- 在 AHK v1 中,使用 'class' 关键字创建的类对象没有基础对象。(除非您手动添加一个。)
- 通过“新”键盘创建的实例对象是类的实例,实例对象以类对象作为其基础对象。
- 如果尝试访问对象的键/属性/方法,但未找到此类项目,则检查对象的基础以查找该项目。
- ...然后您检查基础的基础,如果它存在,以及基础的基础的基础,如果它存在,等等,直到你到达一个没有基础对象的基础对象。

2 种方法:_NewEnum/Next。
术语:枚举器。
- “for 循环”调用对象的 _NewEnum 方法。
- _NewEnum 方法返回一个称为枚举器对象的对象。
- 枚举器对象将有一个 Next 方法,该方法将返回键/值对,直到没有剩余。
- 枚举器对象可能会返回不基于对象内容但计算出来的键/值对(例如平方数)。它可以无限期地返回键/值对。
- 通常 Next 方法输出 2 个值 ByRef(键名和值),但它可以输出 2 以外的数字。

进一步的概念。
术语:超类、子类、嵌套类。
- 超类/子类。超类可以有一些特性,子类继承这些特性并有自己的特性。
- 对于基于子类的对象,如果超类和子类共享一个特征,则子类优先。
- 嵌套类。在另一个类中定义的类。

====================================================

> 简介:对象和键/属性/方法

- 编程中的两个关键概念是数据和函数。
- 对象结合了这两个概念:键(数据)和方法(函数)。
- 在获取/设置值时,属性的工作方式与键非常相似。
- 但是,值属性包含数据(如键),而动态属性更像函数。
- 注意在 AHK v1 中,键和属性语法是等价的并且指向相同的东西:键(/值属性)或动态属性。

代码:

;e.g. get the value of a key/property:
var := obj["key"]
var := obj.property

;e.g. set the value of a key/property:
obj["key"] := var
obj.property := var

;e.g. use a method:
obj.method(var1, var2)

====================================================

> 介绍:类/实例和 AUTOHOTKEY 的内置

类 - 类是一种对象类型的蓝图,它确定实例对象的键/属性/方法。
- 实例对象是基于类的单个对象。

- AutoHotkey 提供各种内置类(截至 2019 年 12 月):
- 仅限 AHK v1:基本。
- AHK v1/v2:BoundFunc、Enumerator、Exception、File、Func、InputHook、RegExMatch、SafeArray。
- 仅限 AHK v2,或在 Array、Buffer、Class、ClipboardAll、GUI、GuiControl、Map、Menu/MenuBar 中。

- 这里有更多细节:
每个对象类型/属性/方法的列表 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=37&t=44081

=============================== ===================

> AUTOHOTKEY'S BASIC OBJECT

AutoHotkey 将 3 个概念组合成 1 个内置的基本对象类型:
- 线性数组(例如 obj[1] := "value" ) (AKA list/vector)
- 关联数组 (例如 obj["key"] := "value") (AKA dictionary/map)
- 默认类(自定义/内置,键/值属性/动态属性/方法,可以添加/修改/删除,以提供自定义类)

例如创建 AHK 基本对象的一些方法。
注意:AHK 基本对象没有基本对象。

 

;in each case obj is a basic object, and identical:
obj := ["a", "b", "c"]
obj := Array("a", "b", "c")
obj := {1:"a", 2:"b", 3:"c"}
obj := Object(1,"a", 2,"b", 3,"c")
obj := StrSplit("a,b,c", ",")

;here, MyClass is also a basic object:
class MyClass
{
	MyMethod()
	{
	}
}

MsgBox, % IsObject(ObjGetBase(obj)) ;0
MsgBox, % IsObject(ObjGetBase(MyClass)) ;0

;note: an instance of MyClass is *not* a basic object:
obj2 := new MyClass
MsgBox, % IsObject(ObjGetBase(obj2)) ;1

- 注意:在 AutoHotkey 中,线性数组是具有 0 个或多个整数键的数组,从键 1 开始。
- 注意:整数键的内置方法适用于正 * 和 * 负整数(和 0)。

- 创建自定义类时,使用 AHK 基本对象作为模板。
- 可以覆盖/删除内置键/属性/方法。
- 可以添加其他键/属性/方法。
- 注意:默认情况下,可以添加到自定义类的键没有限制(它就像一个关联数组)。这可以通过指定自定义 __Set 方法(稍后讨论)来更改。

- 这是基本对象的内置方法和属性列表。

[基本对象:仅整数键的方法(7):]
•InsertAt / RemoveAt [添加/删除键(任意位置)(并移动后续键)]
•Push / Pop [添加/删除键(在数组末尾)]
•MinIndex / MaxIndex / Length [获取最小/最大键索引]

[基本对象:所有键的方法(8):]
•Delete [删除键]
•Count [获取键计数]
•SetCapacity / GetCapacity [获取/设置对象的键计数容量,或值的字符串大小容量]
•GetAddress [获取值的字符串缓冲区的地址(如果值是字符串)]
•_NewEnum [返回用于循环遍历对象键/值的枚举器对象]
•HasKey [检查对象是否包含键]
•Clone [创建一个对象的浅拷贝]

[基本对象:属性:]
•base [对象实例所基于的对象][如果在实例中找不到键/属性/方法,则检查基础]

[其他方法:]
•__Init ['不应被脚本使用' , 创建对象实例时
处理] •__New [创建对象实例时处理]
•__Get / __Set [获取/设置不存在的键/属性时处理]
•__Call [处理不存在的方法时调用]
•__Delete [删除对象实例时处理]

[其他方法(进一步):]
•Next [用于循环对象键]
•ToString [返回一个字符串,(注意:AHK v2 String 函数检查对象'ToString' 方法)]

[其他属性:]
•__Class [对象实例的类名]

- 有关详细信息,请参阅:
基本对象 - 方法和属性 | AutoHotkey
https://autohotkey.com/docs/objects/Object.htm

一些补充说明

- 键名可以是整数类型(正/0/负)或字符串类型。
- (键名也可以是对象,例如o := {[]:1}.)
- 键名是唯一的。您不能有两个具有相同名称的键。
- 当超过一定大小的整数用作键名时,这些键名存储为字符串(不是整数)。
- 在 AHK v1 中,键名不区分大小写。区分大小写的替代方法是使用 Scripting.Dictionary 对象或具有 __Call 方法的自定义类,该方法可以根据传递给它的键名的大小写来执行操作。
- 值可以是字符串/整数/浮点数/对象引用/函数引用,也可能是其他实体。(AutoHotkey 基本对象非常灵活,值可以是什么类型没有限制。)
- 在对象上执行 for 循环时,整数键名(及其值)按数字顺序返回,然后字符串键名(及其值)按字母顺序返回。(因此,要将整数视为字符串,一种解决方法是对整数和字符串都使用前缀字符,例如“z”。)

- 警告:创建与方法/属性同名的键,可能会阻塞访问该方法/属性。例如,如果您创建一个名为“HasKey”的密钥,这会干扰 HasKey 方法。

- 有关键名的更多信息:
对象 - 定义和用法 | AutoHotkey
https://autohotkey.com/docs/Objects.htm#keys

================================== ================

> 自动热键对象功能

- 作为参考,这里列出了以某种方式与对象相关的所有内置 AutoHotkey 函数。
- (该列表旨在完整,但也许其他/未来的功能可能符合条件。)

具有方法等效的功能

- 注意:AutoHotkey 具有内置方法,但可以覆盖这些方法。
- 例如,可以通过定义自定义 HasKey 方法来覆盖 HasKey 方法。
- 例如,如果键(/值属性)调用“HasKey”存在,则 HasKey 方法将失败。
- 许多(但不是全部)ObjXXX 函数的目的是绕过任何自定义方法行为,其中方法已被覆盖,而是使用默认行为。

[基本对象:仅用于整数键的方法(7):]
•ObjInsertAt / ObjRemoveAt
•ObjPush / ObjPop
•ObjMinIndex / ObjMaxIndex / ObjLength

[基本对象:所有键的方法(8):]
•ObjDelete
•ObjCount
•ObjSetCapacity / ObjGetCapacity
•ObjGetAddress
•ObjNewEnum [注意:没有下划线参见。'_NewEnum']
•ObjHasKey
•ObjClone 更多

功能

•Array / Object [创建一个数组/对象,分别相当于 obj := [] 和 obj := {} (注意:在 AHK v1 中,它们都创建了一个基本对象)(注意: AHK v2 使用 []/{}/Map() 分别创建数组/对象/映射)]
•ComObjActive / ComObjArray / ComObjConnect / ComObjCreate / ComObject / ComObjError / ComObjFlags / ComObjGet / ComObjQuery / ComObjType / ComObjValue [COM 对象不是本教程中讨论]
•IsFunc [检查是否存在具有特定名称的函数(仅限AHK v1:可以检查对象是否为函数引用)]
•IsObject [检查变量是否为对象]
•ObjAddRef / ObjRelease [修改对象的引用计数(使用 ObjAddRef,然后使用 ObjRelease 来检索计数,而不更改它)]
•ObjBindMethod [为对象方法创建一个 BoundFunc 对象(即存储一个方法,以及可选的一些初始参数,作为对象变量)(即创建一个变量像函数一样)]
•ObjGetBase / ObjSetBase [获取/设置对象的基础对象]
•ObjRawGet / ObjRawSet [获取/设置键(/值属性)的值,绕过任何自定义类行为]
•String [返回字符串基于一个变量,将检查一个对象的“ToString”方法]
•StrSplit [通过根据分隔符拆分字符串来创建数组]
•Type [获取变量的类型]

============================ =====================

> META-FUNCTIONS

- 以下 3 种方法称为元函数:
•__Get / __Set / __Call

对象 - 定义和用法 | AutoHotkey
https://autohotkey.com/docs/Objects.htm

元函数定义当一个键被请求但在目标对象中找不到时会发生什么。例如,如果 obj.key 没有被赋值,它会调用 __Get 元函数。类似地,obj.key := value 调用 __Set 并且 obj.key() 调用 __Call。这些元函数(或方法)需要在 obj.base、obj.base.base 等中定义。

- 注意:无论方法存在或不存在,都会调用 __Call。

- 以下 3 种方法有时也称为元函数。
•__Init / __New
•__删除

对象- 定义和用法| AutoHotkey
https://autohotkey.com/docs/Objects.htm

要在释放对对象的最后一个引用时运行代码,请实现 __Delete 元函数。

来自 AHK v1.1.28.00 源代码:

LPTSTR Object::sMetaFuncName[] = { _T("__Get"), _T("__Set"), _T("__Call"), _T("__Delete"), _T("__New") };

- 术语“元功能”不是特别重要,在本文档中所有 6 个都应称为方法。

以下是使用中的 5 种方法(6 减去 __Init)的一些示例:
即触发方法的操作:
__New: obj := new MyClass
__Get: var := obj.key ;__Get 仅在 'key' 不被调用时调用存在
__Set: obj["key"] := "value" ;__Set 仅在 'key' 不存在
时调用旨在处理不存在的方法)
__Delete: obj := ""

================================== ================

> 动物园类和基础对象

- 这是一个示例自定义类,具有多个键、方法和属性。这样你就可以看到一个人的样子。
- 定义类后,我们使用 for 循环列出 4 个对象(2 对对象)的内容:
一个实例对象(从类蓝图创建的单个对象)及其基础对象。
类对象(类蓝图存储为类对象)及其基础对象。(在 AHK v1 中,类对象没有基础对象,除非手动添加。)
- 在 AHK v1 中,通常,自定义类的实例具有基础对象。基础对象存储影响对象工作方式的内容。
- 但是 AHK 基本对象和类对象,可以手动给定基本对象。
- (注意:尝试探测 AHK 基本对象的基本对象,列出任何键/属性/方法,不会显示任何内容。因为它没有基本对象。)

代码:

class MyZooClass
{
	;in AHK v1 this is a key(/value property):
	;in AHK v2 this is a value property:
	MyValueProperty := "MyValuePropertyValue"

	;in AHK v1 this is a key(/value property):
	;in AHK v2 this is a value property:
	static MyStaticProperty := "MyStaticPropertyValue"

	MyMethod()
	{
		return "MyMethodValue"
	}

	MyDynamicProperty
	{
		get
		{
			return "MyDynamicPropertyValue"
		}
		set
		{
		}
	}

	__New()
	{
	}
	__Get()
	{
	}
	__Set()
	{
	}
	__Call()
	{
	}
	__Delete()
	{
	}
}

obj := new MyZooClass

vOutput := ""

vOutput .= "instance object:`r`n"
for vKey, vValue in obj
	vOutput .= vKey " " vValue "`r`n"
vOutput .= "`r`n"

vOutput .= "instance object base:`r`n"
for vKey, vValue in obj.base
	vOutput .= vKey " " vValue "`r`n"
vOutput .= "`r`n"

vOutput .= "class object:`r`n"
for vKey, vValue in MyZooClass
	vOutput .= vKey " " vValue "`r`n"
vOutput .= "`r`n"

vOutput .= "class object base:`r`n"
for vKey, vValue in MyZooClass.base
	vOutput .= vKey " " vValue "`r`n"
vOutput .= "`r`n"

Clipboard := vOutput
MsgBox, % vOutput

MsgBox, % IsObject(obj) " " IsObject(obj.base)
MsgBox, % IsObject(MyZooClass) " " IsObject(MyZooClass.base)

;==============================

;some additional tests to retrieve types (Type is AHK v2-only)

MsgBox(Type(MyZooClass.MyValueProperty)) ;String
MsgBox(Type(MyZooClass.MyDynamicProperty)) ;String
MsgBox(Type(MyZooClass.MyMethod)) ;Func

oVProp := ObjRawGet(MyZooClass, "MyValueProperty") ;1 1
oDProp := ObjRawGet(MyZooClass, "MyDynamicProperty") ;1 0
oMtd := ObjRawGet(MyZooClass, "MyMethod")
MsgBox(Type(oVProp)) ;String
MsgBox(Type(oDProp)) ;Property
MsgBox(Type(oMtd)) ;Func

- 以下是列出 4 个对象的内容的结果:

代码:

;instance object:
;MyValueProperty MyValuePropertyValue

;instance object base:
;__Call
;__Class MyZooClass [note: created automatically, we didn't define this explicitly]
;__Delete
;__Get
;__Init [note: created automatically, we didn't define this explicitly]
;__New
;__Set
;MyDynamicProperty
;MyMethod
;MyStaticProperty MyStaticPropertyValue

;[identical to the contents of the instance object base]
;class object:
;__Call
;__Class MyZooClass [note: created automatically, we didn't define this explicitly]
;__Delete
;__Get
;__Init [note: created automatically, we didn't define this explicitly]
;__New
;__Set
;MyDynamicProperty
;MyMethod
;MyStaticProperty MyStaticPropertyValue

;class object base:
;(empty, AHK does not show any base object info for a basic object)

- 我们从检查对象的内容中了解到:
- 当基于类对象创建实例对象时,实例对象的基础对象是类对象的克隆。
- 创建一个“__Class”属性,对应于在类定义开始时指定的类名。

- 回覆。'__Init':
- 虽然我们在类对象中定义了 'MyValueProperty',但我们没有看到它在类对象的内容中列出。('MyValueProperty' 是通过 __Init 方法创建的。)
- 列出了一个 '__Init' 方法,即使我们没有指定一个。__Init 方法创建“MyValueProperty”。如果我们删除该行MyValueProperty := "MyValuePropertyValue"从类定义中,则没有 __Init 方法将出现在内容列表中。
- 所以,'__Init' 方法是由 AHK 为我们创建的,它是创建对象实例的键(/属性值)的方式。

- 在 AHK v1 中,基本对象和自定义类对象没有基本对象。(尽管随后可以给它们一个基础对象。)
- 自定义类的实例对象,具有一个类对象作为其基础对象。
- 编辑基础对象时要小心,它会影响共享该基础对象的现有和未来实例对象。

- 一个小问题:类定义中“base”关键字的可用性:

代码:

obj := new MyClass

obj.prop := 1
MyClass.base := {prop:2}
MyClass.prop := 3
obj.MyMethod()

obj.base.prop := 4
obj.MyMethod()

class MyClass
{
	MyMethod()
	{
		MsgBox, % this.prop ;1
		MsgBox, % base.prop ;2
		MsgBox, % MyClass.base.prop ;2
		MsgBox, % this.base.prop ;3 then 4
		MsgBox, % MyClass.prop ;3 then 4
	}
}

- 注意:我还没有找到明确的答案。AHK 基础对象是否有基础对象。
- 例如,这表明 AHK 基本对象没有基本对象:

代码:

;AHK v1
obj := []
MsgBox, % IsObject(obj.base) ;0 ;reports that the AHK basic object has no base object

- 但也许有人可以说,从概念上讲,AHK 基本对象确实有一个默认的基本对象。如果一个类的实例没有自己的 MyMethod 方法,它会在其基类中检查 MyMethod。同样,如果 AHK 基本对象没有自己的自定义 HasKey 方法,它会回退到默认的 HasKey 方法。
- 注意:AHK 文档讨论了非对象的“默认基础对象”。例如,它定义了当你在 var 不包含对象时执行“mystring”.method() 或 myvar.method() 时会发生什么。

- 这一章几乎暗示了我们将在接下来的章节中看到的所有内容。
- 将提到的其他 3 件事是:超类/子类(父/子类、扩展类)、嵌套类(类中的类)和枚举器对象。
- 超类/子类和嵌套类将在接下来的两章中介绍。
- 之后,我们将更仔细地研究自定义类的特定方面。
- 最后,我们将考虑枚举器对象。这是因为与枚举器有关的问题与一般应用于类的问题有些不同。

====================================================

> SUPERCLASSES/SUBCLASSES

- 创建超类的实例时,任何子类都无关紧要。
- 创建子类的实例时,如果子类中不存在键/属性/方法,则检查超类。
- 例如,在子类的实例中,如果父类和子类各有一个同名方法,则子类方法优先。

代码:

class MySuperClass
{
	Method1()
	{
		return A_ThisFunc
	}
	Method2()
	{
		return A_ThisFunc
	}
}

class MySubClass extends MySuperClass
{
	Method2()
	{
		return A_ThisFunc
	}
	Method3()
	{
		return A_ThisFunc
	}
}

oArray1 := new MySuperClass
oArray2 := new MySubClass
vOutput := oArray1.__Class ;MySuperClass
. "`r`n" oArray1.base.__Class ;MySuperClass
. "`r`n" oArray1.base.base.__Class ;(blank)
. "`r`n" oArray1.Method1() ;MySuperClass.Method1
. "`r`n" oArray1.Method2() ;MySuperClass.Method2
. "`r`n" oArray1.Method3() ;(blank)
. "`r`n" "==============="
. "`r`n" oArray2.__Class ;MySubClass
. "`r`n" oArray2.base.__Class ;MySubClass
. "`r`n" oArray2.base.base.__Class ;MySuperClass
. "`r`n" oArray2.Method1() ;MySuperClass.Method1
. "`r`n" oArray2.Method2() ;MySubClass.Method2
. "`r`n" oArray2.Method3() ;MySubClass.Method3
Clipboard := vOutput
MsgBox, % vOutput

;oArray2 can access both versions of 'Method2'
MsgBox, % oArray2.Method2() ;MySubClass.Method2
MsgBox, % oArray2.base.base.Method2() ;MySuperClass.Method2

;==============================

;output:
;MySuperClass
;MySuperClass
;
;MySuperClass.Method1
;MySuperClass.Method2
;
;===============
;MySubClass
;MySubClass
;MySuperClass [base of base is superclass]
;MySuperClass.Method1
;MySubClass.Method2 [subclass Method2 takes precedence over superclass Method2]
;MySubClass.Method3

- 当子类扩展超类时,超类是子类的基础对象。
- 例如,这里 C.base 是 B,C.base.base 是 A。

代码:

obj := new C
MsgBox, % obj.__Class ;C ;(this points to obj.base.__Class)
MsgBox, % obj.base.__Class ;C
MsgBox, % obj.base.base.__Class ;B
MsgBox, % obj.base.base.base.__Class ;A

MsgBox, % A.base.__Class ;(blank)
MsgBox, % B.base.__Class ;A
MsgBox, % C.base.__Class ;B

class A
{
}
class B extends A
{
}
class C extends B
{
}

====================================================

> 嵌套

类 - 嵌套类可从包含它的类访问,但未在其内容中列出。

代码:

class MyOuterClass
{
	class MyInnerClass
	{
	}
}
obj1 := new MyOuterClass
obj2 := new MyOuterClass.MyInnerClass
;obj3 := new MyInnerClass ;causes error in AHK v2

;MyInnerClass is not listed in obj1's contents
vOutput := ""
for vKey, vValue in obj1
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

vOutput := ""
for vKey, vValue in obj2
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;however, obj1.MyInnerClass is listed as an object
MsgBox, % IsObject(obj1.MyInnerClass)

- 这篇文章对子类化与嵌套进行了巧妙的比较:
关于类的问题 - 寻求帮助 - AutoHotkey 社区
https://autohotkey.com/board/topic/85528-question-about-classes/#entry545206

===== ==============================================

> __类属性,并且BASE OBJECTS: CHASE THE BASE

- 如果我们定义一个空类,它将产生一个与 AHK 基本对象相同的对象,不同之处在于类名。
- 从类对象创建的任何实例对象都将在其基础对象中具有一个名为“__Class”的属性。实例对象的基础对象是类对象。'__Class' 的值是类的名称。
- 在下面的示例中,我们将演示直接修改“base”属性的值,并通过“obj.base :=”和 ObjSetBase 为 base 分配一个对象。
- 我们还将演示通过 for 循环列出对象的键/值对。

代码:

class MyEmptyClass
{
}

MsgBox, % MyEmptyClass.__Class ;MyEmptyClass

obj1 := {} ;basic AHK object
obj2 := new MyEmptyClass
obj3 := new MyEmptyClass() ;equivalent to the line above

MsgBox, % obj1.__Class ;(blank)
MsgBox, % obj2.__Class ;MyEmptyClass
MsgBox, % obj3.__Class ;MyEmptyClass

MsgBox, % obj1.base.__Class ;(blank)
MsgBox, % obj2.base.__Class ;MyEmptyClass
MsgBox, % obj3.base.__Class ;MyEmptyClass

;obj1 does not contain a __Class property:
vOutput := ""
for vKey, vValue in obj1
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;obj1's base does not contain a __Class property:
vOutput := ""
for vKey, vValue in obj1.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;obj2 does not contain a __Class property:
vOutput := ""
for vKey, vValue in obj2
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;obj2's base *does* contain a __Class property:
vOutput := ""
for vKey, vValue in obj2.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;__Class MyEmptyClass

MsgBox, % IsObject(obj1.base) ;0 ;reports that the AHK basic object has no base object
MsgBox, % IsObject(obj2.base) ;1

;change the objects' class names:
obj1.base.__Class := "NewClassName1" ;doesn't work
obj2.base.__Class := "NewClassName2"
MsgBox, % obj1.__Class ;(blank)
MsgBox, % obj2.__Class ;NewClassName2

obj1.base := {__Class:"NewClassName3"}
obj2.base := {__Class:"NewClassName4"}
MsgBox, % obj1.__Class ;NewClassName3
MsgBox, % obj2.__Class ;NewClassName4

ObjSetBase(obj1, {__Class:"NewClassName5"})
ObjSetBase(obj2, {__Class:"NewClassName6"})
MsgBox, % obj1.__Class ;NewClassName5
MsgBox, % obj2.__Class ;NewClassName6

- 从上面的测试中我们看到 obj2.base 有一个名为 '__Class' 的属性,但 obj2 没有这样的属性。
- 'obj2.base.__Class' 按预期返回一个值。
- obj2 没有名为“__Class”的属性,因此它检查它的基数,因此“obj2.__Class”返回“obj2.base.__Class”的值。
- 一般来说,对于对象,如果 obj.prop 不存在,则会检查 obj.base.prop 和 obj.base.base.prop 等。(你可以称之为“追逐基地”。)
-如果找到这样的属性,则返回该属性的值。
- 如果没有找到属性,则返回一个空白字符串。

- 注意:定义类时,会创建一个与该类同名的类对象(变量),其中包含该对象。可通过 ListVars 或“查看、变量及其内容”查看。
- 因此,实例对象和类对象都是对象类型,存储在变量中。

代码:

;this class definition results in a variable being created called 'MyClass'
class MyClass
{
}

;create an instance object
obj := new MyClass

;refer to an instance object
MsgBox, % IsObject(obj) ;1

;refer to the class object
MsgBox, % IsObject(MyClass) ;1

;get the class name
MsgBox, % obj.__Class ;MyClass
MsgBox, % MyClass.__Class ;MyClass
MsgBox, % obj.base.__Class ;MyClass
MsgBox, % MyClass.base.__Class ;(blank)

- 注意:我们在“A Zoo 类和基础对象”一章中了解到,类对象没有基础对象,并且是 AHK 基本对象。

- 一些比较 AHK 基本对象、自定义类对象和自定义类对象实例的测试:
- (注意:在 AHK v1 中,HasKey 检查是否存在键(/值属性)。)

代码:

class MyClass
{
}

;a custom class object:
MsgBox, % MyClass.HasKey("base") ;0
MsgBox, % IsObject(MyClass.base) ;0
MsgBox, % MyClass.base.__Class ;(blank)

;an instance of a custom class object:
obj := new MyClass
MsgBox, % obj.HasKey("base") ;0
MsgBox, % IsObject(obj.base) ;1
MsgBox, % obj.base.__Class ;MyClass

;for comparison, an AHK basic object:
obj := []
MsgBox, % obj.HasKey("base") ;0
MsgBox, % IsObject(obj.base) ;0
MsgBox, % obj.base.__Class ;(blank)

====================================================

> 基础对象:替换基础/修改 AHK 数组

- 这里我们尝试通过 obj.base、ObjSetBase 和 ObjRawSet 设置基础对象:

代码:

class MyClass
{
}

vOutput := ""

obj := new MyClass
vOutput .= obj.HasKey("base") " " obj.__Class " " obj.base.__Class "`r`n"

obj := new MyClass
obj.base := {__Class:"MyClassNew"}
vOutput .= obj.HasKey("base") " " obj.__Class " " obj.base.__Class "`r`n"

obj := new MyClass
ObjSetBase(obj, {__Class:"MyClassNew"})
vOutput .= obj.HasKey("base") " " obj.__Class " " obj.base.__Class "`r`n"

obj := new MyClass
ObjRawSet(obj, "base", {__Class:"MyClassNew"})
vOutput .= obj.HasKey("base") " " obj.__Class " " obj.base.__Class "`r`n"

Clipboard := vOutput
MsgBox, % vOutput

;results:
;0 MyClass MyClass ;original
;0 MyClassNew MyClassNew ;obj.base applied
;0 MyClassNew MyClassNew ;ObjSetBase applied
;1 MyClass MyClassNew ;ObjRawSet applied

- 示例显示的是:
- 如果您创建一个名为“base”的键(/值属性),这将覆盖“base”属性。
- 所以'obj.base.__Class'从名为'base'的属性中获取内容。
- 但是,当'obj.__Class' 找不到名为'__Class' 的属性时,它会检查基础对象,而不检查名为'base' 的键(/值属性)。所以在这方面,创建键“base”并没有覆盖属性“base”。

- 定义/修改基础对象的一些示例,以更改获取/设置值的方式:

代码:

;modify an AHK array:

;edit the base object:
oArray.base := []
oArray.base.__Class := "hello"
vOutput := ""
for vKey, vValue in oArray.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

;==============================

;add a __Get property, and thus change the default return value:
oArray := []
MsgBox, % oArray[1] ;(blank)
oArray.base := {"__Get":"MyFuncReturnHello"}
MsgBox, % oArray[1] ;hello

MyFuncReturnHello()
{
	return "hello"
}

;==============================

;temporarily add a __Set method, and thus prevent new keys being created:
oArray := []
oArray[1] := "a"
oArray.base := {"__Set":"MyFuncPreventObjSet"}
oArray[2] := "b"

oArray.base.Delete("__Set") ;restore the ability to set a key's value

;oArray.Delete("base") ;doesn't work because 'base' is not a key(/value property)

;note: these attempted fixes (to restore setting) did not work,
;because we are tempting to set the value of a property, 'base',
;but MyFuncPreventObjSet is preventing this
;oArray.base := {"__Set":""}
;oArray.base := {}
;oArray.base := ""

oArray[3] := "c"
MsgBox, % oArray[1] "_" oArray[2] "_" oArray[3]

MyFuncPreventObjSet()
{
	MsgBox, % A_ThisFunc
	return
}

;==============================

- 注意:尚未完全解释为什么尝试的修复不起作用。

- 一些点重新。更改基础:
AHK 中的类,解剖(高级) - 第 3 页 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=7&t=6177&p=55441#p55441
AHK 中的类,解剖(高级) ) - 第 3 页 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=7&t=6177&p=55477#p55477

=================== ==============================

第 2 部分:键/属性/方法

> 类别:价值属性

- 这里有一些经典与值属性和类有关的事情,将在下面介绍。
- 注意:在 AHK v1 中,键和值属性是一样的。
- 创建一个预先填充了一些值属性的对象。
- ... 为将来的实例更改这些属性,但保持现有实例不变。
- ...更改未来实例*和*现有实例的这些属性。
- 每次为特定类创建对象时记录。

PRE-POPULATE AN OBJECT WITH VALUE PROPERTIES

- 在类主体中定义值属性的示例。使用/不使用“静态”。
- 当使用“静态”时,属性被放置到基础对象中(到类对象中)。
- 当'static'被省略时,属性的一个本地副本被放置到实例对象中。

代码:

class MyKeyClass
{
	static prop1 := "value1" ;one copy only, in the class object
	prop2 := "value2" ;one local copy for all instance objects
}

obj := new MyKeyClass
MsgBox, % obj.prop1 ;value1 ;value retrieved from base object
MsgBox, % obj.prop2 ;value2
MsgBox, % obj.base.prop1 ;value1
MsgBox, % obj.base.prop2 ;(blank)
obj.Delete("prop1") ;does nothing, 'prop1' exists in the base object, but not the object proper
obj.Delete("prop2") ;prop2 is deleted
MsgBox, % obj.prop1 ;value1
MsgBox, % obj.prop2 ;(blank)

obj.base.Delete("prop1") ;prop1 is deleted
MsgBox, % obj.base.prop1 ;(blank)
vOutput := ""
for vKey, vValue in obj.base
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

;this shows that MyKeyClass.prop1 was also deleted:
vOutput := ""
for vKey, vValue in MyKeyClass
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

- 尝试删除“prop1”,只检查对象本身。
- 尝试检索“prop1”的内容,检查对象本身,没有这样的属性,因此检查基础对象。

每次创建对象时记录

- 这是一个保持实例计数的类的示例。
- 并给每个对象一个问题编号。
- 它使用 __New 方法,将在后面的章节中讨论。

代码:

class MyIncrementClass
{
	static issued := 0
	__New()
	{
		MyIncrementClass.issued++
		this.num := MyIncrementClass.issued
	}
}

;create new instances each with an issue number
obj1 := new MyIncrementClass
obj2 := new MyIncrementClass
obj3 := new MyIncrementClass
MsgBox, % obj1.num ;1
MsgBox, % obj2.num ;2
MsgBox, % obj3.num ;3

;get total number of classes issued
MsgBox, % MyIncrementClass.issued ;3

具有属性的对象(更改现有和未来实例的属性值)

- 在此示例中,更改所有实例的属性内容。

代码:

class MyPropClass
{
	static prop := "MyValue"
}
obj1 := new MyPropClass
obj2 := new MyPropClass
MsgBox, % obj1.prop ;MyValue
MsgBox, % obj2.prop ;MyValue

MyPropClass.prop := "MyValueNEW"
MsgBox, % obj1.prop ;MyValueNEW
MsgBox, % obj2.prop ;MyValueNEW

MsgBox

;note: if we assign to obj1.prop this will create obj1.prop (and will not override MyPropClass.prop aka obj.base.prop)
obj1.prop := "hello"
MsgBox, % obj1.prop ;hello
MsgBox, % obj1.base.prop ;MyValueNEW
MsgBox, % obj2.prop ;MyValueNEW ;reads obj2.base.prop
MsgBox, % MyPropClass.prop ;"MyValueNEW"

- 我们可以使用动态属性来引用全局变量。更改全局变量的值将为我们提供一种影响未来和现有实例的方法。

代码:

global vGblMyValue := "MyValue"
class MyKeyClass
{
	MyProperty
	{
		get
		{
			global vGblMyValue
			return vGblMyValue
		}
		set
		{
		}
	}
}

obj1 := new MyKeyClass
MsgBox, % obj1.MyProperty ;MyValue

vGblMyValue := "MyValueNEW"
MsgBox, % obj1.MyProperty ;MyValueNEW

obj2 := new MyKeyClass
MsgBox, % obj2.MyProperty ;MyValueNEW

- 我们可以在创建时保留所有对象实例的列表,而不是使用静态变量,并更新每个键的值。

代码:

class MyKeepTrackClass
{
	static instances := []
	__New()
	{
		this.prop := "MyValue"
		this.instances.Push(&this)
	}
	UpdateAllProps(vValue)
	{
		this.prop := vValue
		for _, vAddr in this.instances
		{
			obj := Object(vAddr)
			if IsObject(obj)
				obj.prop := vValue
		}
	}
}

;create new instances each with an issue number
obj1 := new MyKeepTrackClass
obj2 := new MyKeepTrackClass
MsgBox, % obj1.prop ;MyValue
MsgBox, % obj2.prop ;MyValue

MyKeepTrackClass.UpdateAllProps("MyValueNEW")
;obj1.UpdateAllProps("MyValueNEW") ;also works
MsgBox, % obj1.prop ;MyValueNEW
MsgBox, % obj2.prop ;MyValueNEW

- 当多个实例派生自一个类时。修改一个实例的基数会影响所有实例。
- 这是一种保留实例特征的方法,但允许对其基础进行任何编辑,以*不*影响其他实例。

代码:

class MyPropClass
{
	static prop := "MyValue"
}
obj1 := new MyPropClass
obj2 := new MyPropClass
MsgBox, % obj1.prop ;MyValue
MsgBox, % obj2.prop ;MyValue

MyPropClass.prop := "MyValueNEW"
MsgBox, % obj1.prop ;MyValueNEW
MsgBox, % obj2.prop ;MyValueNEW

base2 := ObjGetBase(obj2)
ObjSetBase(obj2, ObjClone(base2))

MyPropClass.prop := "MyValueNEWER"
MsgBox, % obj1.prop ;MyValueNEWER"
MsgBox, % obj2.prop ;MyValueNEW" ;this time the value is unaffected

使用键预填充对象(更改未来实例但不更改现有实例)

- 我们可以为未来实例更改键的值,如下所示:
- 每次创建实例时,它都会创建一个键,该键是类键的快照。

代码:

class MyPropClass
{
	static prop := "MyValue"
	__New()
	{
		this.prop := this.base.prop
	}
}
obj1 := new MyPropClass

MyPropClass.prop := "MyValueNEW"
obj2 := new MyPropClass

MyPropClass.prop := "MyValueNEWER"
obj3 := new MyPropClass

MsgBox, % obj1.prop ;MyValue
MsgBox, % obj2.prop ;MyValueNEW
MsgBox, % obj3.prop ;MyValueNEWER

在类与其他地方使用“静态”

- 当在类主体(类定义)中使用“静态”时,它具有特殊含义。这意味着将在类对象内部以及该类的每个实例的基础中创建一个键(/值属性)。
- 可以通过“myclass.prop”或“myinstance.base.prop”访问“静态”键(/值属性),其值可以在以后更改。
- ... 这与函数内部的静态值或动态属性 getter/setter 形成对比,您无法直接访问静态值。
- 注意:一个方法,一个property.get,一个property.set,这些都是函数,它们的函数名可以通过A_ThisFunc来显示。

> 类:属性

- 这是一个简单的类,它具有与键(/值属性)几乎相同的动态属性。
- 有一个区别,创建了一个键(/值属性)来存储属性使用的值。
- 注意:特殊变量“this”在属性 getter/setter 中可用。此外,在属性设置器中,可以使用特殊变量“值”。

代码:

class MyPropertyClass
{
	MyProperty
	{
		get
		{
			return this._MyProperty
		}
		set
		{
			this._MyProperty := value
			return value
		}
	}
}

obj := new MyPropertyClass
MsgBox, % obj["MyKey"] := "value"
MsgBox, % obj["MyKey"]
MsgBox, % obj.MyProperty := "value"
MsgBox, % obj.MyProperty

- 动态属性在功能上类似于键,但在幕后工作类似于方法。
- 它们允许具有动态生成的值的属性,并允许只读属性。
- 只读属性应定义尝试设置其值时发生的情况,否则可能会创建与属性同名的键(/值属性),并且该键将优先于财产。将返回键的值,而不是动态属性的值。
- 注意:在编程中,仅供内部使用的键/属性/方法的名称以下划线“_”开头是很常见的。
- 注意:当在类中定义动态属性时,AHK 将它们定义为名称形式为“class.property.get”和“class.property.set”的函数(可以使用 A_ThisFunc 看到)。

代码:

class MyPropertyClass
{
	;MyProperty works much like a normal key
	MyProperty
	;MyProperty[] ;equivalent to line above (square brackets are optional)
	{
		get
		{
			;MsgBox, % A_ThisFunc ;MyPropertyClass.MyProperty.get
			;a special variable called 'this' is used, which represents an object instance:
			return this._MyProperty
		}
		set
		{
			;MsgBox, % A_ThisFunc ;MyPropertyClass.MyProperty.set
			;a special variable called value is used,
			;e.g. for: 'obj.MyProperty := "hello"', value is equal to 'hello'
			this._MyProperty := value
		}
	}

	MyReadOnlyProperty
	{
		get
		{
			return "ReadOnlyValue"
		}
		set ;this is needed to make it read-only, otherwise you could create a key called 'MyReadOnlyProperty'
		{
		}
	}

	;a write-only property would be highly unusual
	MyWriteOnlyProperty
	{
		get
		{
		}
		set
		{
			_MyWriteOnlyProperty := value
		}
	}

	Now
	{
		get
		{
			return A_Now
		}
		set
		{
		}
	}
}

obj := new MyPropertyClass

;MyProperty works outwardly much like a key:
obj.MyProperty := "MyPropertyValue"
MsgBox, % obj.MyProperty ;MyPropertyValue
obj["MyKey"] := "MyKeyValue"
MsgBox, % obj["MyKey"] ;MyKeyValue

;however, MyProperty also uses a key(/value property) called '_MyProperty' to store its value:
MsgBox, % obj._MyProperty ;MyPropertyValue

;this assignment won't work, because the property is read-only:
obj.MyReadOnlyProperty := "new value"
MsgBox, % obj.MyReadOnlyProperty ;ReadOnlyValue

;get the current date and time:
MsgBox, % obj.Now
Sleep, 1000
MsgBox, % obj.Now

;obj contains various keys(/value properties):
vOutput := ""
for vKey, vValue in obj
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput
;output:
;_MyProperty MyPropertyValue
;MyKey MyKeyValue

;obj's base contains various properties:
vOutput := ""
for vKey, vValue in ObjGetBase(obj)
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput
;output:
;__Class MyPropertyClass
;MyProperty
;MyReadOnlyProperty
;MyWriteOnlyProperty
;Now

- 以下是获取属性信息的示例:

代码:

;AHK v2
;using AHK v2 to use the Type function
class MyPropertyClass
{
	MyProperty
	{
		get
		{
			;MsgBox, % A_ThisFunc
			return "MyPropertyValue"
		}
		set
		{
			;MsgBox, % A_ThisFunc
		}
	}
}

;copy a property from a class to an array:

;some tests:
MsgBox(IsFunc("MyPropertyClass.MyProperty")) ;0
MsgBox(IsObject(MyPropertyClass.MyProperty)) ;0
MsgBox(Type(MyPropertyClass.MyProperty)) ;String ;note: the return value is a string
oProp := ObjRawGet(MyPropertyClass, "MyProperty")

MsgBox(IsObject(oProp)) ;1
MsgBox(Type(oProp)) ;Property

MsgBox(IsFunc("MyPropertyClass.MyProperty.get")) ;2
;MsgBox(IsObject(MyPropertyClass.MyProperty.get)) ;Error:  No object to invoke.
;MsgBox(Type(MyPropertyClass.MyProperty.get)) ;Property

;assign a property to an object:
obj := {}
MsgBox(obj.MyProperty) ;(blank) ;as expected
oProp := ObjRawGet(MyPropertyClass, "MyProperty")
ObjRawSet(obj, "MyProperty", oProp)
MsgBox(obj.MyProperty) ;MyPropertyValue

;assign a property to an object:
obj := {}
MsgBox(obj.MyProperty) ;(blank) ;as expected
for vKey, vValue in MyPropertyClass
{
	if (vKey = "MyProperty")
		ObjRawSet(obj, "MyProperty", vValue)
}
MsgBox(obj.MyProperty) ;MyPropertyValue

====================================================

> 类:方法

- 这是一个带有一些方法的简单类。
- 方法本质上是存储在类中的函数。
- 注意:在 AutoHotkey 中,方法是名称形式为“class.method”的函数。
- 注意:IsObject 和 Type 表明方法是具有“Func”类型的对象。

代码:

class MyMethodClass
{
	MyMethod()
	{
		;MsgBox, % A_ThisFunc ;MyMethodClass.MyMethod
		return "MyMethod"
	}
	Add(num1, num2)
	{
		return num1 + num2
	}
	Concatenate(text1, text2)
	{
		return text1 text2
	}
}

obj := new MyMethodClass
MsgBox, % IsFunc("MyMethodClass.MyMethod") ;2 (a func with 1 parameter)
MsgBox, % IsObject(obj.MyMethod) ;1
;MsgBox(Type(obj.MyMethod)) ;Func ;AHK v2

MsgBox, % obj.MyMethod() ;MyMethod
MsgBox, % obj.Add(1, 2) ;3
MsgBox, % obj.Concatenate("abc", "def") ;abcdef

;AHK v1 only: calling methods with a dynamic method name (computed method name)
var := "MyMethod"
MsgBox, % obj[var]() ;MyMethod
MsgBox, % obj["Add"](1, 2) ;3
MsgBox, % obj["Concatenate"]("abc", "def") ;abcdef

- 功能与方法。
- 在函数中,您明确指定对象(作为第一个参数)。
- 在一个方法中,你没有明确指定对象,它是一个隐式的第一个参数,可以使用'this'访问。
- 这是“这个”的一个例子:

代码:

;an explicit parameter (obj):
MyFunc(obj)
{
	MsgBox, % obj.Length()
	MsgBox, % &obj
}

class MyThisClass
{
	;an implicit 'this' parameter:
	MyMethod()
	{
		MsgBox, % this.Length()
		MsgBox, % &this
	}
}

oArray := new MyThisClass
oArray.Push("a", "b", "c")
MsgBox, % oArray.Length() ;3
MsgBox, % &oArray ;(address)
oArray.MyMethod() ;will give the same values: 3 and (address)

oArray := ["a", "b", "c"]
oArray.base := {MyMethod:Func("MyFunc")}
MsgBox, % oArray.Length() ;3
MsgBox, % &oArray ;(address)
MyFunc(oArray) ;will give the same values: 3 and (address)

- 这是一个获取函数并将其作为方法添加到对象的示例。

代码:

;==============================

class MyMethodClass
{
	MyMethod()
	{
		MsgBox, % this.MyProp
	}
	MyMethod2(var1, var2, var3)
	{
		MsgBox, % this.MyProp " " var1 " " var2 " " var3
	}
}

MyFunc(obj)
{
	MsgBox, % obj.MyProp
}

MyFunc2(obj, var1, var2, var3)
{
	MsgBox, % obj.MyProp " " var1 " " var2 " " var3
}

;==============================

myobj := {}
myobj.MyProp := "value"

;the function expects 1 parameter, but we pass 0 parameters,
;the object is passed implicitly as the 1st parameter
myobj.MyMethod := Func("MyFunc")
myobj.MyMethod() ;value
myobj.MyMethod.Call(myobj) ;value ;equivalent to line above (but with the object passed explicitly)

;the function expects 4 parameters, but we pass 3 parameters,
;the object is passed implicitly as the 1st parameter
myobj.MyMethod2 := Func("MyFunc2")
myobj.MyMethod2(1, 2, 3) ;value 1 2 3
myobj.MyMethod2.Call(myobj, 1, 2, 3) ;value 1 2 3 ;equivalent to line above (but with the object passed explicitly)

;myobj.MyMethod2(1, 2) ;too few parameters, this silently fails in AHK v1 (but gives 'Error:  Missing a required parameter.' in AHK v2)
;myobj.MyMethod2.Call(myobj, 1, 2) ;too few parameters, this silently fails in AHK v1 (but gives 'Error:  Missing a required parameter.' in AHK v2)

;==============================

;using the class:
myobj := new MyMethodClass
myobj.MyProp := "value"

myobj.MyMethod() ;value
myobj.MyMethod.Call(myobj) ;value

myobj.MyMethod2(1, 2, 3) ;value 1 2 3
myobj.MyMethod2.Call(myobj, 1, 2, 3) ;value 1 2 3

- 动态方法调用的一些进一步示例:

代码:

oArray := ["a", "b", "c"]
vMethod := "Length"
MsgBox, % oArray.Length() ;3
;MsgBox, % oArray.%vMethod%() ;AHK v1: Error: Ambiguous or invalid use of "."
MsgBox, % oArray[vMethod]() ;3
;MsgBox, % oArray[vMethod] ;(blank)
;MsgBox, % ObjBindMethod(oArray, vMethod).() ;3 ;deprecated syntax
MsgBox, % ObjBindMethod(oArray, vMethod).Call() ;3
oFunc := ObjBindMethod(oArray, vMethod)
;MsgBox, % oFunc.() ;3 ;deprecated syntax
MsgBox, % oFunc.Call() ;3
MsgBox, % %oFunc%() ;3

====================================================

> 类:键/属性/方法和优先

级 - 类定义中不能有同名的类键(/值属性)、实例键(/值属性)、动态属性或方法。

代码:

;all 4 of these items cause a 'Duplicate declaration' error, if any 2 (or more) are uncommented
class MySameNameClass
{
	;class key(/value property):
	static item := 1

	;instance key(/value property):
	item := 2

	;dynamic property:
	/*
	item
	{
		get
		{
			return 3
		}
		set
		{
		}
	}
	*/

	;method:
	/*
	item()
	{
		return 4
	}
	*/
}

- 你不能有一个类的方法和动态属性共享相同的名称。您收到“重复声明”错误。
- 如果键(/值属性)和方法共享相同的名称,则键优先。
- 如果键(/值属性)和动态属性同名,则键优先。
- 这个例子演示了一些同名问题:

代码:

class MySameNameClass
{
	MyKeyOrProperty
	{
		get
		{
			return "property"
		}
		set
		{
		}
	}
	MyKeyOrMethod()
	{
		return "method"
	}
}

obj := new MySameNameClass

;test what happens when 'keys with the same name as the property/method' don't exist:
MsgBox, % "TEST 0: default behaviour"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method

;create keys with the same names,
;test what happens when keys with the same name do exist:
ObjRawSet(obj, "MyKeyOrProperty", "key")
ObjRawSet(obj, "MyKeyOrMethod", "key")
MsgBox, % "TEST 1A"
MsgBox, % obj.MyKeyOrProperty ;key
MsgBox, % obj.MyKeyOrProperty() ;(blank)
MsgBox, % obj.MyKeyOrMethod ;key
MsgBox, % obj.MyKeyOrMethod() ;(blank)

;delete the keys, and the original behaviour is restored:
obj.Delete("MyKeyOrProperty")
obj.Delete("MyKeyOrMethod")
MsgBox, % "TEST 1B"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method

;creating a key(/value property) called 'base' does not block the methods/properties:
ObjRawSet(obj, "base", "key")
MsgBox, % "TEST 2A"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method

;delete the key, and the original behaviour is restored:
obj.Delete("base")
MsgBox, % "TEST 2B"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method

;creating a key(/value property) called 'base', also blocks the methods/properties:
obj.base := "key"
MsgBox, % "TEST 3A"
MsgBox, % obj.MyKeyOrProperty ;(blank)
MsgBox, % obj.MyKeyOrProperty() ;(blank)
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;(blank)

;attempt to delete the key(/value property), but the original behaviour is not restored:
;this would delete a key called 'base', but not the property 'base'
obj.Delete("base")
MsgBox, % "TEST 3B"
MsgBox, % obj.MyKeyOrProperty ;(blank)
MsgBox, % obj.MyKeyOrProperty() ;(blank)
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;(blank)

;setting the base to the class object restores the original behaviour:
obj.base := MySameNameClass
MsgBox, % "TEST 3C"
MsgBox, % obj.MyKeyOrProperty ;property
MsgBox, % obj.MyKeyOrProperty() ;property
MsgBox, % obj.MyKeyOrMethod ;(blank)
MsgBox, % obj.MyKeyOrMethod() ;method

- 要创建覆盖方法的键,只需分配一个键,例如 obj["key"] := value,或使用 ObjRawSet。
- 要创建一个覆盖动态属性的键,分配一个键(/值属性)将不起作用,例如 obj["key"] := value,它只会为现有属性分配一个值,所以使用 ObjRawSet。

代码:

class MyClass
{
	MyProperty
	{
		get
		{
			return "property"
		}
		set
		{
		}
	}
	MyMethod()
	{
		return "method"
	}
}

obj := new MyClass

;==============================

MsgBox, % obj.MyProperty ;property

;this fails to add a key(/value property),
;since MyProperty is a dynamic property,
;this tries to alter the value of MyProperty
;(but MyProperty is read-only):
obj["MyProperty"] := "new value"
MsgBox, % obj.MyProperty ;property

;ObjRawSet, however, does succeed in adding a key, the key's value is retrieved, not the dynamic property's
ObjRawSet(obj, "MyProperty", "new value")
MsgBox, % obj.MyProperty ;new value

;the key is removed, calling the method works again:
obj.Delete("MyProperty")
MsgBox, % obj.MyProperty ;property

;==============================

MsgBox, % obj.MyMethod() ;method

;this adds a key, calling the method now fails:
obj["MyMethod"] := "new value"
MsgBox, % obj.MyMethod() ;(blank)

;the key is removed, calling the method works again:
obj.Delete("MyMethod")
MsgBox, % obj.MyMethod() ;method

;alternatively, this adds a key, calling the method now fails:
ObjRawSet(obj, "MyMethod", "new value")
MsgBox, % obj.MyMethod() ;(blank)

;again, the key is removed, calling the method works again:
obj.Delete("MyMethod")
MsgBox, % obj.MyMethod() ;method

- 这是“用自身替换方法”的示例。
- 添加与方法同名的键,可以阻止方法工作。
- 但是,如果添加的键是一个 func 对象,它的作用类似于方法,那么即使您添加了一个键,调用该方法也像以前一样工作。

代码:

obj := ["a", "b", "c"]
MsgBox, % obj.Length() ;3
MsgBox, % obj.Length ;(blank)

;if we create a key(/value property) called 'Length', the method fails:
obj.Length := "value"
MsgBox, % obj.Length() ;(blank)
MsgBox, % obj.Length ;value

;if we delete the key(/value property) called 'Length', the method works again:
obj.Delete("Length")
MsgBox, % obj.Length() ;3
MsgBox, % obj.Length ;(blank)

;again, if we create a key(/value property) called 'Length', the method fails:
obj.Length := "value"
MsgBox, % obj.Length() ;(blank)
MsgBox, % obj.Length ;value

;if we create a func object based on ObjLength,
;and assign that to the key(/value property) called 'Length',
;that is another way to get a working method again:
obj.Length := Func("ObjLength")
MsgBox, % obj.Length() ;3
MsgBox, % obj.Length ;(blank)

- 一些将函数/方法存储为对象方法的代码:

代码:

class MyMethodClass
{
	MyMethod(vValue)
	{
		MsgBox, % vValue
	}
}

MyFunc(oObj, vValue)
{
	MsgBox, % vValue
}

MyMethodClass.MyMethod(1)

MyMethodClass.MyMethod2 := MyMethodClass.MyMethod
MyMethodClass.MyMethod2(2)

oMtd := MyMethodClass.MyMethod
MyMethodClass.MyMethod3 := oMtd
MyMethodClass.MyMethod3(3)

MyMethodClass.MyMethod4 := ObjRawGet(MyMethodClass, "MyMethod")
MyMethodClass.MyMethod4(4)

MyMethodClass.MyMethod5 := Func("MyFunc")
MyMethodClass.MyMethod5(5)

MyMethodClass.MyMethod6 := Func("MyMethodClass.MyMethod")
MyMethodClass.MyMethod6(6)
return

- 一些将动态属性存储为对象属性的代码:

代码:

class MyPropertyClass
{
	MyProperty
	{
		get
		{
			;MsgBox, % A_ThisFunc
			return "MyPropertyValue"
		}
		set
		{
		}
	}
}

;copy property from a class to an array

oProp := ObjRawGet(MyPropertyClass, "MyProperty")

MsgBox, % IsObject(oProp) ;1
MsgBox, % IsFunc("MyPropertyClass.MyProperty.get") ;2
MsgBox, % IsFunc("MyPropertyClass.MyProperty.set") ;3

;assign a property to an object:
obj := {}
;MsgBox, % obj.MyProperty ;(blank) ;as expected
oProp := ObjRawGet(MyPropertyClass, "MyProperty")
ObjRawSet(obj, "MyProperty", oProp)
MsgBox, % obj.MyProperty ;MyPropertyValue

;assign a property to an object:
obj := {}
;MsgBox, % obj.MyProperty ;(blank) ;as expected
for vKey, vValue in MyPropertyClass
{
	if (vKey = "MyProperty")
		ObjRawSet(obj, "MyProperty", vValue)
}
MsgBox, % obj.MyProperty ;MyPropertyValue

;==============================

;assign a getter to an object:
obj := {}
;MsgBox, % obj.MyGetter ;(blank) ;as expected
oGetter := Func("MyPropertyClass.MyProperty.get")
;oSetter := Func("MyPropertyClass.MyProperty.set")
obj.MyGetter := oGetter
MsgBox, % obj.MyGetter() ;MyPropertyValue
return

====================================================

第 3 节:元函数

> 元函数

- 根据这个定义,__Call/__Get/__Set 是元函数。
对象- 定义和用法| AutoHotkey
https://autohotkey.com/docs/Objects.htm

元函数定义当一个键被请求但在目标对象中找不到时会发生什么。

- 虽然 __Call 确实是为了处理对不存在的方法的调用。当调用确实存在的方法时,也会调用 __Call 函数。

- 根据其他来源,__Delete/__Init/__New 也是元函数。
- 我们将在章节标题中将这些称为“元函数+”(带有加号)。
每个对象类型/属性/方法的列表 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=37&t=44081

- 这 6 种方法中的哪一个是或不是“元函数”,是'不是特别重要。
- 重要的是要知道它们都是方法,并且它们通常由类内部使用,通常不会在脚本中直接引用。
- 创建称为 __Call/__Get/__Set 或 __Delete/__New 的自定义方法可以让您更好地控制您的类。
- 注意:__Init 未记录,不应使用。

这6种方法前面已经介绍过了,这里还是同样的文字:

6 种方法:__Init/__New/__Get/__Set/__Call/__Delete。
- 创建自定义类时,以下 6 种方法是自定义的关键。
•__Init ['不应被脚本使用',在创建对象实例时
处理] •__New [在创建对象实例时处理]
•__Get / __Set [在获取/设置不存在的键/属性时处理]
•__Call [在调用存在/不存在的方法
时处理] •__Delete [在删除对象实例时处理]

- 注意:__Get/__Set 仅在某些项目不存在时调用。
- 如果您想在每次获取或设置键或属性时记录,有一些变通方法,如下所述。
- 注意:__Call 可用于记录每次调用方法。

- 这是一个示例脚本,其中每次调用方法时都会报告一个 MsgBox。

代码:

class MyNotifyClass
{
	__Init() ;new object created
	{
		MsgBox, % A_ThisFunc
	}
	__New() ;new object created
	{
		MsgBox, % A_ThisFunc
	}
	__Get() ;attempt to retrieve the value of a key that doesn't exist
	{
		MsgBox, % A_ThisFunc
	}
	__Set() ;attempt to set the value of a key that doesn't exist
	{
		MsgBox, % A_ThisFunc
	}
	__Call() ;attempt to use a method
	{
		MsgBox, % A_ThisFunc
	}
	__Delete() ;object is deleted
	{
		MsgBox, % A_ThisFunc
	}
	MyMethod()
	{
	}
}

obj := new MyNotifyClass ;__Init and __New

var := obj["key"] ;__Get (if key doesn't exist)
obj["key"] := "value" ;__Set (if key doesn't exist)
var := obj["key"] ;note: __Get not called, since the key already exists
obj["key"] := "value" ;;note: __Get not called, since the key already exists

obj.NotAMethod() ;__Call ;__Call invoked for non-existent method
obj.MyMethod() ;__Call ;__Call invoked for existent method

obj := "" ;__Delete

- 注意:像 'obj := ""' 这样的行有时会删除一个对象,有时不会。
- 如果在 'obj := ""' 之前,'obj' 指向的对象的引用计数为 1,则删除该对象。__Delete 方法被调用。
- 如果在 'obj := ""' 之前,'obj' 指向的对象的引用计数大于 1,则对象的引用计数减 1。

代码:

obj1 := {}
obj2 := obj1
obj3 := obj1
;ObjAddRef increases the reference count by 1
;ObjRelease decreases the reference count by 1 and returns the current count
MsgBox, % Format("{2}", ObjAddRef(&obj1), ObjRelease(&obj1)) ;3
obj3 := ""
MsgBox, % Format("{2}", ObjAddRef(&obj1), ObjRelease(&obj1)) ;2
obj2 := ""
MsgBox, % Format("{2}", ObjAddRef(&obj1), ObjRelease(&obj1)) ;1
obj1 := "" ;object deleted

- 返回:
- 在 6 种方法之一中使用“返回”的一些微妙之处。
- 以下是 'return' 的 3 种可能用法:
- 过早
结束函数 - 过早结束函数,并防止发生某些看不见的动作
- 输出一些东西
- 对于 6 个“元函数”中的每一个,“return”的可能影响',将在后面的章节中讨论。

- 参数计数(最小值/最大值):
__New() [最小值:0,最大值:无限制][例如 2 个参数:obj := new MyClass(param1, param2)]
__Get() [最小值:0,最大值:无限制][例如 2 个参数:var := obj["key1", "key2"]
__Set() [min: 0, max: unlimited][eg 3 个参数:obj["key1", "key2"] := "value"] [参数:key1,
__Call() [min: 0, max: unlimited][eg 3 个参数 var := obj.MyMethod(param1, param2)][parameters: MyMethod, param1, param2]
__Delete() [min: 0, max: unlimited]

-这个例子演示了定义类的两种方法,函数语法(老派)和方法语法(新派):

代码:

MyFunc(obj, params*)
{
	MsgBox, % A_ThisFunc
}

class MyClass
{
	;'obj' param not needed, use 'this' if needed, an implicit parameter
	__Delete(params*)
	{
		MsgBox, % A_ThisFunc
	}
}

fn := Func("MyFunc")
obj1 := new MyClass
obj2 := {}, obj2.base := {__Delete:fn}
obj1 := ""
obj2 := ""

- 此链接还演示了函数和方法语法:
对象 - 定义和用法 | AutoHotkey
https://autohotkey.com/docs/Objects.htm#Meta_Functions

- 任何作为方法的普通函数都应该有一个必需的参数,而其他参数都应该是可选的。否则调用可能会失败(在 AHK v1 中静默失败)。
- 一个示例,说明用作 __Delete 方法的任何自定义函数都应具有 1 个必需参数,其余参数可选。

代码:

MyFunc1(obj, params*)
{
	MsgBox, % A_ThisFunc
}

MyFunc2(obj, param1, params*)
{
	MsgBox, % A_ThisFunc
}

fn1 := Func("MyFunc1")
fn2 := Func("MyFunc2")
obj1 := {}, obj1.base := {__Delete:fn1}
obj2 := {}, obj2.base := {__Delete:fn2}

;in AHK v1, we do not see a MsgBox for MyFunc2
;in AHK v2, we see an error
obj1 := ""
obj2 := "" ;Error:  Missing a required parameter. ;(AHK v2 error)

====================================================

> META-FUNCTIONS+: __NEW / __DELETE

- __New 方法允许在创建对象时执行操作。
- __Delete 方法允许在删除对象时执行操作。

代码:

global g_CountObjCurrent := 0
global g_CountObjEver := 0
class MyCountClass
{
	__New()
	{
		g_CountObjCurrent++
		g_CountObjEver++
	}
	__Delete()
	{
		g_CountObjCurrent--
	}
}

MsgBox, % g_CountObjCurrent " " g_CountObjEver ;0 0
obj1 := new MyCountClass
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;1 1
obj2 := new MyCountClass
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;2 2
obj3 := new MyCountClass
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;3 3
obj1 := ""
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;2 3
obj2 := ""
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;1 3
obj3 := ""
MsgBox, % g_CountObjCurrent " " g_CountObjEver ;0 3

- __Delete 方法可用作一种 hack,允许您在删除对象时执行某些操作。

代码:

class MyTempSetSCSClass
{
	__New(vMode)
	{
		this.orig := A_StringCaseSense
		StringCaseSense, % vMode
	}
	__Delete()
	{
		StringCaseSense, % this.orig
	}
}

;we write 3 functions that remove capital a's:

;this function sets StringCaseSense to On, but doesn't restore it to its previous state:
Func1(vText)
{
	StringCaseSense, On
	return StrReplace(vText, "A")
}

;this function sets StringCaseSense to On, and then restores it to its previous state:
Func2(vText)
{
	vSCS := A_StringCaseSense
	StringCaseSense, On
	vRet := StrReplace(vText, "A")
	StringCaseSense, % vSCS
	return vRet
}

;like Func2, this function sets StringCaseSense to On, and then restores it to its previous state:
;(note: when return is done, obj is deleted, and the state is reset)
Func3(vText)
{
	obj := new MyTempSetSCSClass("On")
	return StrReplace(vText, "A")
}

vOutput := ""

StringCaseSense, Off
vOutput .= Func1("Aa") " " A_StringCaseSense "`r`n"

StringCaseSense, Off
vOutput .= Func2("Aa") " " A_StringCaseSense "`r`n"

StringCaseSense, Off
vOutput .= Func3("Aa") " " A_StringCaseSense "`r`n"

;we see that each function removed capital a's
;and that Func1 failed to reset A_StringCaseSense
MsgBox, % vOutput

- 注意:如果在 __Delete 方法中使用 'return',对象仍将被删除。
- 注意:如果您在 __New 方法中使用 'return',这将覆盖所创建的对象。你甚至可以返回一个字符串而不是一个对象。

代码:

class MyNewReturnClass1
{
	__New()
	{
		return ["a", "b", "c"]
	}
}
class MyNewReturnClass2
{
	__New()
	{
		return "hello"
	}
}
class MyNewReturnClass3
{
	__New()
	{
		return this
	}
}
class MyNewReturnClass4 ;equivalent to MyNewReturnClass3 (apart from the class name)
{
	__New()
	{
	}
}

obj1 := new MyNewReturnClass1
obj2 := new MyNewReturnClass2
obj3 := new MyNewReturnClass3
obj4 := new MyNewReturnClass4
MsgBox, % obj1.Length() ;3
MsgBox, % obj2 ;hello
MsgBox, % obj3.__Class
MsgBox, % obj4.__Class

- 创建对象时创建属性的两个功能等效的类。
- 一个在类的主体中指定赋值,另一个在 __New 方法中指定。

代码:

class MyClass1
{
	prop := "value"
}

class MyClass2
{
	__New()
	{
		this.prop := "value"
	}
}

obj1 := new MyClass1
obj2 := new MyClass2
MsgBox, % obj1.prop
MsgBox, % obj2.prop

- 创建对象、演示指定参数和演示使用动态名称创建类的示例。

代码:

class MyClass1
{
	__New()
	{
		MsgBox, % A_ThisFunc
	}
}

class MyClass2
{
	__New(vText1, vText2)
	{
		MsgBox, % A_ThisFunc "`r`n" vText1 " " vText2
	}
}

obj := new MyClass1
obj := new MyClass2("a", "b")

class1 := "MyClass1"
class2 := "MyClass2"
obj := new %class1%
obj := new %class2%("a", "b")

====================================================

> META-FUNCTIONS+: __INIT

- __Init 是在脚本启动时调用的未记录方法。
- 这里有一些 __Init 的实验,展示了它的一些特性。

- __Init 将键(/值属性)添加到实例对象:

代码:

;this gives a 'Duplicate declaration' error:
class MyInitClass
{
	var := 1

	__Init()
	{
	}
}

- __Init 不向类对象添加键(/值属性):

代码:

;this does not give a 'Duplicate declaration' error:
class MyInitClass
{
	static var := 1

	__Init()
	{
	}
}

- __Init 在 __New 之前调用:

代码:

;__Init is called before __New:
class MyInitAndNewClass
{
	__Init()
	{
		MsgBox, % A_ThisFunc
	}
	__New()
	{
		MsgBox, % A_ThisFunc
	}
}
obj := new MyInitAndNewClass

- 在类主体中定义变量会导致出现 __Init 方法:

代码:

;defining a variable in the class body
class MyPropNoClass
{
}
class MyPropYesClass
{
	prop := 1
}
class MyPropStaticClass
{
	static prop := 1
}

obj1 := new MyPropNoClass
obj2 := new MyPropYesClass
obj3 := new MyPropStaticClass

;==============================

;test MyPropNoClass:
vOutput := ""
for vKey, vValue in ObjGetBase(obj1)
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

;output:
;__Class MyPropNoClass

;==============================

;test MyPropYesClass:
;a MyPropYesClass instance has an __Init class
vOutput := ""
for vKey, vValue in ObjGetBase(obj2)
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;output:
;__Class MyPropYesClass
;__Init

;==============================

;test MyPropStaticClass:
;a MyPropStaticClass instance does not have an __Init class
vOutput := ""
for vKey, vValue in ObjGetBase(obj3)
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput ;(blank)

;output:
;__Class MyPropStaticClass
;prop 1

;==============================

- 测试重新。__Class 属性:

代码:

;note:
;class MyClass
;{
;}
;effectively does this:
;class MyClass
;{
;	static __Class := "MyClass"
;}

class MyAssignClassClass
{
	;static __Class := "hello" ;error: Duplicate declaration
	;__Class := "hello" ;error: Duplicate declaration
}

- 这个例子说明了 __Init 的作用。
- 注意:使用调用时,必须显式指定对象,对象在各种其他上下文中隐式传递。
- 一个对象被传递给__Init 方法,一个键被分配给那个对象。

代码:

class MyClass
{
	prop := "value"
}

MyClass.__Init.Call(obj:={})
MsgBox, % obj.prop

- 使用 'return' 似乎对 __Init 没有影响。
- 创建了预期的键(/值属性)。

代码:

class MyClass
{
	__Init()
	{
		this.prop := "value"
		return
	}
}

obj := new MyClass
MsgBox, % obj.prop

MyClass.__Init.Call(obj:={})
;MyClass.__Init.(obj:={}) ;deprecated syntax
MsgBox, % obj.prop

====================================================

> META-FUNCTIONS:__CALL

- __Call 是在脚本尝试执行不存在(或存在)的方法时调用的方法。

代码:

class MyCallClass
{
	__Call(vMethod)
	{
		MsgBox, % "attempt to call existent/non-existent method:`r`n" vMethod
	}
	MyMethod()
	{
	}
}

obj := new MyCallClass
obj.MyMethod()
obj.NotAMethod()

- Call 的一种用途是通过将多个方法组合为一个来节省代码行,而不是使用多个单独的方法。

代码:

class MyCaseClass1
{
	Upper(vText)
	{
		return Format("{:U}", vText)
	}
	Title(vText)
	{
		return Format("{:T}", vText)
	}
	Lower(vText)
	{
		return Format("{:L}", vText)
	}
}

class MyCaseClass2
{
	__Call(vMethod, vText)
	{
		if vMethod in % "Upper,Title,Lower"
			return Format("{:" SubStr(vMethod, 1, 1) "}", vText)
		else
			return
	}
}

obj1 := new MyCaseClass1
obj2 := new MyCaseClass2
MsgBox, % obj1.Upper("hello")
MsgBox, % obj2.Upper("hello")

- Call 的另一个用途是具有区分大小写的方法名称。通常在 AutoHotkey 中,方法名不区分大小写。

代码:

class MyCallClass
{
	__Call(vMethod)
	{
		if (vMethod == "HELLO")
			return "HELLO WORLD"
		else if (vMethod == "Hello")
			return "Hello World"
		else if (vMethod == "hello")
			return "hello world"
	}
}
obj := new MyCallClass
MsgBox, % obj.HELLO() ;HELLO WORLD
MsgBox, % obj.Hello() ; Hello World
MsgBox, % obj.hello() ;hello world

;if a key is created with the same name as one of the anticipated values in the __Call method, this prevents the __Call method from being called:
ObjRawSet(obj, "hello", "MyValue")
MsgBox, % obj.HELLO() ;(blank)
MsgBox, % obj.Hello() ;(blank)
MsgBox, % obj.hello() ;(blank)

- 这个例子演示了每次调用看起来像方法的东西(即方法名称和括号,以及可选的参数)时,都会调用__Call 方法。
- 即使存在方法(或属性),也会调用 __Call 方法。
- 如果存在与方法同名的键(/值属性),则不调用 __Call 方法。
- __Call 方法的一个用途是监控每次调用方法或属性。

代码:

class MyCallClass
{
	MyValueProperty := "MyValuePropertyValue"

	__Call(vMethod)
	{
		MsgBox, % A_ThisFunc "`r`n" "method called:`r`n" vMethod
		;return "x"
	}

	MyMethod()
	{
		return "MyMethodValue"
	}

	MyDynamicProperty
	{
		get
		{
			return "MyDynamicPropertyValue"
		}
		set
		{
		}
	}
}

obj := new MyCallClass

MsgBox, % obj.MyMethod() ;MyMethodValue ;__Call invoked
MsgBox, % obj.MyDynamicProperty() ;MyDynamicPropertyValue ;__Call invoked
MsgBox, % obj.MyValueProperty() ;(blank) ;since MyValueProperty is a key(/value property)
MsgBox, % obj.MyNonExistentEntity() ;(blank) ;__Call invoked

;note: since these calls lack parentheses, __Call won't be invoked:
MsgBox, % obj.MyMethod ;(blank) ;since parentheses omitted
MsgBox, % obj.MyDynamicProperty ;MyDynamicPropertyValue
MsgBox, % obj.MyValueProperty ;MyValuePropertyValue
MsgBox, % obj.MyNonExistentEntity ;(blank)

- 如果在 __Call 方法内完成了“返回”,则不会调用任何本应调用的方法/属性。
- 此外,返回由'return'指定的值。

====================================================

> META-FUNCTIONS:__GET

- __Get 是一种在脚本尝试检索不存在的键的值时调用的方法。
- 这里我们将不存在的键返回的默认值从空字符串更改为数字 0。
- __Get 方法中的“return”的效果是为未找到键时指定默认值。
- 默认返回值 0 可用于创建计数(频率计数)。

代码:

class MyGetClass
{
	__Get(vKey)
	{
		return 0
	}
}

obj1 := {}
obj2 := new MyGetClass
MsgBox, % obj1["NonExistentKey"] ;(blank)
MsgBox, % obj2["NonExistentKey"] ;0

obj1["key"]++
obj2["key"]++
MsgBox, % obj1["key"] ;(blank)
MsgBox, % obj2["key"] ;1

;otherwise to increment a key:
obj3 := {}
obj3["key"] := obj3.HasKey("key") ? obj3["key"]+1 : 1
MsgBox, % obj3["key"] ;1

- 这里我们使用 Format 函数将数组的默认值更改为 0。

代码:

;here is a simple 'switch statement' via an object:

;using the HasKey method
obj := {a:"A", b:"B", c:"C"}
MsgBox, % obj.HasKey("a") ? obj["a"] : "ERROR"
MsgBox, % obj.HasKey("d") ? obj["d"] : "ERROR"

;==============================

;using a bound func:
obj := {a:"A", b:"B", c:"C", base:{__Get:Func("Format").Bind("{}", "ERROR")
MsgBox, % obj.a
MsgBox, % obj.d

;for reference:
;MsgBox, % Format("{}", "ERROR") ;ERROR

;the bound func approach is explained in more detail, here:
;jeeswg's object classes tutorial - AutoHotkey Community
;https://autohotkey.com/boards/viewtopic.php?f=7&t=54588&p=285624#p285624

;in short, what is done is:
;__Get is called for obj[key], when 'key' doesn't exist,
;we define __Get to invoke the Format function with 2 default parameters,
;and so the string 'ERROR' is returned

- 这里我们使用 Format 函数将数组的默认值更改为 0。

代码:

obj1 := {}
obj2 := {base:{__Get:Func("Format").Bind("{}", 0)
MsgBox, % obj1.key ;(blank)
MsgBox, % obj2.key ;0
MsgBox, % obj2.key+3 ;3

;for reference:
;MsgBox, % Format("{}", 0) ;0

- 此示例演示了 __Get 方法仅对不存在的键调用:

代码:

class MyGetClass
{
	__Get(vKey)
	{
		MsgBox, % A_ThisFunc
	}
}

obj := new MyGetClass
MsgBox, % obj["key"] ;(blank)
obj["key"] := "value"
MsgBox, % obj["key"] ;value

====================================================

> META-FUNCTIONS:__SET

- __Set 是一种在脚本尝试设置不存在键的值时调用的方法。
- 在这个类中,我们只允许将正整数分配为键。

代码:

class MySetClass
{
	__Set(vKey, vValue)
	{
		if RegExMatch(vValue, "^\d+$")
			ObjRawSet(this, vKey, vValue)
		else
			MsgBox, % "error: invalid value:`r`n" vValue
		return ;without this line the class would assign this[vKey] := vValue
	}
}

obj := new MySetClass
obj["key1"] := 123
obj["key2"] := "abc"
MsgBox, % obj["key1"] ;123
MsgBox, % obj["key2"] ;(blank)

- 注意:如果我们尝试在类中分配这样的键:
这个[vKey] := vValue
而不是这个:
ObjRawSet(this, vKey, vValue)
这将在无限循环中触发 __Set 方法。
- ObjRawSet 绕过 __Set 方法。

====================================================

> __GET/__SET:每次获取/设置密钥时进行监控

- 这里有一些变通方法,实际上,即使密钥已经存在,也可以调用 __Get/__Set。
- 这里有两种方法可以在每次获取/设置完成时进行监控。

- 在这个例子中,我们使用一个全局对象。
- 任何获取/设置都对外部对象进行,并且由于没有在实例对象中创建键,__Get/__Set 元函数将继续被调用。

代码:

global oData := {}
class MyClass
{
	__New()
	{
		;note: 'this' is the instance object
		;note: '&this' is the address of the instance object
		global oData
		oData[&this] := {}
	}
	__Get(vKey)
	{
		global oData
		MsgBox, % A_ThisFunc "`r`n" vKey "=" oData[&this, vKey]
		return oData[&this, vKey]
	}
	__Set(vKey, vValue)
	{
		global oData
		oData[&this, vKey] := vValue
		MsgBox, % A_ThisFunc "`r`n" vKey "=" vValue
		return ;without this line, a key would be automatically assigned to the instance object
	}
	__Delete()
	{
		global oData
		oData.Delete(&this)
	}
}

obj := new MyClass

;the __Set method is invoked each time:
Loop 3
	obj["key"] := "value" A_Index

;the __Get method is invoked each time:
Loop 3
	MsgBox, % obj["key"]

;==============================

;check how many instance objects currently exist:
obj2 := new MyClass
obj3 := new MyClass

MsgBox, % oData.Count() ;3
obj := ""
MsgBox, % oData.Count() ;2
obj2 := ""
obj3 := ""
MsgBox, % oData.Count() ;0

- 在这个例子中,我们使用一个类对象来存储它的所有实例对象的数据。
- 这可能是一种更好的方法,因为通常在编程中不希望使用全局变量。
- (全局变量名称使命名空间变得混乱,并且您冒着使用相同变量的 2 个使用相同全局变量名称的函数的风险。)
- (因此,最好是独立的东西。)

代码:

class MyClass
{
	static Data := {}
	__New()
	{
		ObjRawSet(MyClass.Data, &this, {})
	}
	__Get(vKey)
	{
		MsgBox, % A_ThisFunc "`r`n" vKey "=" MyClass.Data[&this, vKey]
		return MyClass.Data[&this, vKey] ;does work
		;return this.base.Data[&this, vKey] ;doesn't work
	}
	__Set(vKey, vValue)
	{
		MyClass.Data[&this, vKey] := vValue
		MsgBox, % A_ThisFunc "`r`n" vKey "=" vValue
		return ;without this line a key would be automatically assigned to the instance object
	}
	__Delete()
	{
		MyClass.Data.Delete(&this)
	}
}

obj := new MyClass

;the __Set method is invoked each time:
Loop 3
	obj["key"] := "value" A_Index

;the __Get method is invoked each time:
Loop 3
	MsgBox, % obj["key"]

;==============================

;check how many instance objects currently exist:
obj2 := new MyClass
obj3 := new MyClass

MsgBox, % MyClass.Data.Count() ;3
obj := ""
MsgBox, % MyClass.Data.Count() ;2
obj2 := ""
obj3 := ""
MsgBox, % MyClass.Data.Count() ;0

- 也可以使用静态变量将数据存储在属性 getter/setter 和方法中。
- 属性 getter/setter 和方法只是函数。并且这些函数中的任何静态变量都是函数所独有的,但任何使用这些属性获取器/属性设置器/方法的实例对象都可以访问。
- 一个问题是属性 getter 和 setter 是独立的函数,因此除非使用公共变量(例如全局变量),否则它们不能共享数据。
- 另一个问题是您可能想要一种从这些静态变量中删除键的方法,这是您在删除对象实例时可能想要做的事情。
- 下面的脚本存储了一个类的创建时间,它将这个信息存储在一个方法和一个属性获取器中。

代码:

class MyClass
{
	__New()
	{
		;call the method and property when creating the object:
		this.MyMethod()
		var := this.MyProperty
	}
	MyMethod()
	{
		static oData := {}
		if !oData.HasKey(&this)
			oData[&this] := A_Now
		return oData[&this]
	}

	MyProperty
	{
		get
		{
			static oData := {}
			if !oData.HasKey(&this)
				oData[&this] := A_Now
			return oData[&this]
		}
		set
		{
		}
	}
}

;the instance creation date is stored in a method/property:
obj1 := new MyClass
Sleep, 1000
obj2 := new MyClass
vOutput := obj1.MyMethod() "`r`n" obj1.MyProperty "`r`n"
vOutput .= obj2.MyMethod() "`r`n" obj2.MyProperty "`r`n"
MsgBox, % vOutput

====================================================

> __GET/__SET: ADD KEYS AND SUBKEYS (MULTI-DIMENSIONAL ASSIGNMENTS) -

文档提到了这样的分配,如下所示:
自动热键
https://autohotkey.com/docs/Objects.htm#Usage_Arrays_of_Arrays

多维赋值如 table[a, b, c, d] := value

- 以下内容有区别:
var := obj.abc
var := obj["a", "b", "c"]
当某些键(/值属性)尚不存在时。
- 如果 obj.abc 不存在,则立即失败。
- obj["a", "b", "c"] 检查 obj.a,然后是 obj.ab,然后是 obj.abc
- 这个例子演示了不同之处:

代码:

class MyClass
{
	__Get(oParams*)
	{
		vOutput := ""
		for vKey, vValue in oParams
			vOutput .= vKey " " vValue "`r`n"
		MsgBox, % A_ThisFunc "`r`n" vOutput

		if (oParams.1 = "g") ;diagnostic
			return {h:{i:"MyValue" ;diagnostic
	}
	__Set(oParams*)
	{
		vOutput := ""
		for vKey, vValue in oParams
			vOutput .= vKey " " vValue "`r`n"
		MsgBox, % A_ThisFunc "`r`n" vOutput
	}
}

obj := new MyClass

;there is a difference in function between the following 2 syntaxes:
MsgBox, % obj["a", "b", "c"] ;(blank)
;MsgBox, % obj.d.e.f ;Error:  No object to invoke. ;(AHK v2 error)

;an unusual condition was added to the class to handle 'obj.g'
MsgBox, % obj.g.h.i ;MyValue

MsgBox

;again, there is a difference in function between the following 2 syntaxes:
obj["a", "b", "c"] := "KeyABC"
;obj.d.e.f := "KeyDEF" ;Error:  No object to invoke. ;(AHK v2 error)
;obj.d.e := "KeyDE" ;Error:  No object to invoke. ;(AHK v2 error)
obj.d := "KeyD"
MsgBox, % obj.a.b.c ;KeyABC
MsgBox, % obj.d ;KeyD

- 创建键和子键时,必须仔细编写类。
- 需要考虑的一件事是,当对象被创建为对象的子键时。子对象应该与父对象具有相同的类,还是应该是 AHK 基本对象。
- 文档描述了该问题,如下所示:
对象 - 定义和用法 | 自动热键
https://autohotkey.com/docs/Objects.htm#Subclassing_aoa

当像 table[x, y] := content 这样的多参数赋值隐式导致创建新对象时,新对象通常没有基础,因此没有自定义方法或特殊行为。

- 这个例子演示了两个类:一个子对象是基本的 AHK 对象,一个子对象与父对象具有相同的类。

代码:

class MyClass
{
}

class MyClass2
{
	__Set(oParams*)
	{
		;MsgBox, % StrJoin("`r`n", oParams*)
		if (oParams.Length() = 2)
		{
			ObjRawSet(this, oParams.1, oParams.2)
			return oParams.2
		}
		else if (oParams.Length() < 2)
			return
		vValue := oParams.Pop()
		ObjRawSet(this, oParams.1, new MyClass2)
		return this[oParams*] := vValue
	}
}

obj := new MyClass
obj["a", "b", "c", "d", "e"] := "value"

vOutput := ""
vOutput .= obj.__Class "`r`n"
vOutput .= obj.a.__Class "`r`n"
vOutput .= obj.a.b.__Class "`r`n"
vOutput .= obj.a.b.c.__Class "`r`n"
vOutput .= obj.a.b.c.d.__Class "`r`n"
MsgBox, % vOutput

obj := new MyClass2
obj["a", "b", "c", "d", "e"] := "value"

vOutput := ""
vOutput .= obj.__Class "`r`n"
vOutput .= obj.a.__Class "`r`n"
vOutput .= obj.a.b.__Class "`r`n"
vOutput .= obj.a.b.c.__Class "`r`n"
vOutput .= obj.a.b.c.d.__Class "`r`n"
MsgBox, % vOutput

====================================================

> __GET/__SET:同时获取和设置

- '什么时候获取不是获取?当它是一组时。
- 当您这样做时,我们会考虑返回的内容var := obj["key"] := 值
- 我们有一套:obj["key"] := 值
- 得到:var := obj["key"]

代码:

class MyGetSetClass
{
	__Get(k)
	{
		return "key not found"
	}
	__Set(k, v)
	{
		ObjRawSet(this, k, v)
		return "key given initial value"
	}
}

;__Get output v. __Set output
obj := new MyGetSetClass
var1 := obj["key"]
var2 := obj["key"] := "MyValue" ;it appears to get and set at the same time
var3 := obj["key"]
var4 := obj["key"] := "MyValue" ;it appears to get and set at the same time
var5 := obj["key"]
MsgBox, % var1 ;key not found
MsgBox, % var2 ;key given initial value
MsgBox, % var3 ;MyValue
MsgBox, % var4 ;MyValue ;not 'key given initial value' because __Set is only invoked for a key that does not exist
MsgBox, % var5 ;MyValue

====================================================

> 一般类示例:日期和拆分路径

代码:

class MyDateClass
{
	__New(vDate:="")
	{
		if (vDate = "")
		{
			ObjRawSet(this, "date", A_Now)
		}
		else
		{
			;expand truncated dates e.g. 'yyyyMMdd' to 14 characters:
			FormatTime, vDate, % vDate, yyyyMMddHHmmss
			if vDate is not date
				return ;a blank string is returned, no object is returned
			ObjRawSet(this, "date", vDate)
		}
	}
	Format(vFormat)
	{
		FormatTime, vDate, % vDate, yyyyMMddHHmmss
		return vDate
	}
	Add(vNum, vUnit, vFormat="yyyyMMddHHmmss")
	{
		vDate1 := this.date
		EnvAdd, vDate1, % vNum, % vUnit
		if !(vFormat == "yyyyMMddHHmmss")
			FormatTime, vDate1, % vDate1, % vFormat
		return vDate1
	}
	Diff(vDate2, vUnit)
	{
		vDate1 := this.date
		EnvSub, vDate1, % vDate, % vUnit
		return vDate1
	}
	__Get(vKey)
	{
		FormatTime, vDate, % this.date, % vKey
		return vDate
	}
	__Set()
	{
		return
	}
}

;create a date object for the current date and time:
oDate := new MyDateClass
MsgBox, % oDate.date
MsgBox, % oDate.yyyy
MsgBox, % oDate.ddd " " oDate["HH:mm:ss dd/MM/yyyy"]
MsgBox, % oDate.Add(365, "d", "ddd") " " oDate.Add(365, "d", "HH:mm:ss dd/MM/yyyy")

;create a date object for a specific date:
oDate.date := 20030303030303
MsgBox, % oDate.ddd " " oDate["HH:mm:ss dd/MM/yyyy"]
oDate := ""

;create a date object for a specific date:
oDate := new MyDateClass(20060504030201)
MsgBox, % oDate.date
MsgBox, % oDate.ddd " " oDate["HH:mm:ss dd/MM/yyyy"]

;note: if we delete the 'return' line in __Set,
;we can write arbitrary keys:
;oDate["abc"] := "hello"
;MsgBox, % oDate["abc"]

- 另一个例子:
[分割路径类cf。SplitPath 命令]
对象类:编码风格 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=5&t=48938

==================== ==============================

第 4 节:枚举器和杂项

> 枚举器:_NEWENUM 和 NEXT

- 有两个重要的方法与遍历对象中的每个项目有关:_NewEnum 和 Next。
- 当 for 循环应用于对象时,将调用对象的 _NewEnum 方法。该方法创建一个传递给 for 循环的枚举器对象。
- 枚举器对象只需要一个方法,一个 Next 方法。
- Next 方法确定在 for 循环期间输出哪些键/值对,以及输出顺序。它通过指定两个 ByRef 变量来做到这一点:键和值。
- 它可以输出除 2 之外的多个 ByRef 变量。
- 此外,Next 方法确定 for 循环何时结束。它通过指定 0 作为方法(函数的)返回值来结束循环,否则指定一个非零整数(让循环继续进行)。
- 注意:一个枚举器对象类通常有两个方法:一个 __New 方法,用于准备信息,一个 Next 方法,用于输出它。

- 在这个例子中,我们尝试重新创建 AHK 基本对象的功能,关于循环。
- 注意:循环对象的两种方法是:for 循环,以及直接使用 Next 方法。两者都在示例中进行了演示。
- (另一种循环方式,对于线性数组,例如'obj',是检索 obj.Length() 并通过 var := obj[A_Index] 循环。)

代码:

class MyClass
{
	_NewEnum()
	{
		MsgBox, % A_ThisFunc
		return new MyEnumClass(this)
	}
}

class MyEnumClass
{
	__New(obj)
	{
		MsgBox, % A_ThisFunc
		this.data := obj
		this.temp := []

		;we can't do this:
		;because 'for k in obj' would request an enumerator
		;from MyEnumClass, causing an infinite loop
		;for k in obj
		;	this.temp.Push(k)

		;instead we do this:
		oEnum := ObjNewEnum(obj)
		while oEnum.Next(k)
			this.temp.Push(k)

		this.count := this.temp.Length()
		this.index := 1
	}
	Next(ByRef k:="", ByRef v:="")
	{
		if (this.index > this.count)
			MsgBox, % A_ThisFunc
		if (this.index > this.count)
			return 0
		k := this.temp[this.index]
		v := this.data[k]
		this.index++
		MsgBox, % A_ThisFunc "`r`n" k " " v
		return 1
	}
}

oArray := new MyClass
oArray.q := "Q", oArray.w := "W", oArray.e := "E"
for vKey, vValue in oArray
	vOutput .= vKey " " vValue "`r`n"
MsgBox, % vOutput

- 直接使用 Enum 方法,可以同时执行 2 个“for 循环”。
- 同时循环通过 2 个对象。

代码:

oArray1 := {a:1, b:2, c:3}
oArray2 := {d:4, e:5, f:6, g:7, h:8}
oEnum1 := ObjNewEnum(oArray1)
oEnum2 := ObjNewEnum(oArray2)

vOutput := ""
Loop
{
	if vRet1 := oEnum1.Next(vKey, vValue)
		vOutput .= vKey " " vValue "`r`n"
	if vRet2 := oEnum2.Next(vKey, vValue)
		vOutput .= vKey " " vValue "`r`n"
	if !vRet1 && !vRet2
		break
}
MsgBox, % vOutput

- 自定义枚举器可用于创建无限 for 循环。
- 此示例生成平方数。

代码:

class MyClass
{
	_NewEnum()
	{
		return new MyEnumClass
	}
}

class MyEnumClass
{
	__New()
	{
		this.index := 1
	}
	Next(ByRef k:="", ByRef v:="")
	{
		k := this.index
		v := this.index ** 2
		this.index++
		return 1
	}
}

oArray := new MyClass
vOutput := ""
for vKey, vValue in oArray
{
	vOutput .= vKey " " vValue "`r`n"
	if (vKey = 20)
		break
}
MsgBox, % vOutput

;we can use a for loop on an enumerator object class like so:
for vKey, vValue in {_NewEnum:RetNewEnum}
{
	vOutput .= vKey " " vValue "`r`n"
	if (vKey = 20)
		break
}
MsgBox, % vOutput

RetNewEnum(obj)
{
	return new MyEnumClass
}

- 使用 Next 函数允许循环生成超过 2 个输出值。
- 这个例子生成正方形、立方体和四次方。

代码:

class MyEnumClass
{
	__New()
	{
		this.index := 1
	}
	Next(ByRef n1:="", ByRef n2:="", ByRef n3:="", ByRef n4:="")
	{
		n1 := this.index
		n2 := this.index ** 2
		n3 := this.index ** 3
		n4 := this.index ** 4
		this.index++
		return 1
	}
}

oEnum := new MyEnumClass
vOutput := ""
while oEnum.Next(vNum1, vNum2, vNum3, vNum4)
{
	vOutput .= Format("{}`t{}`t{}`t{}", vNum1, vNum2, vNum3, vNum4) "`r`n"
	if (A_Index = 20)
		break
}
MsgBox, % vOutput

- 这里有一些指向更多枚举器示例的链接。
- 在链接中要注意的一件事是枚举器对象所需的类/方法可以出现在不同的位置:例如,作为单独的类,作为嵌套类,在类中(与其他方法一起)。

[循环反向、循环和删除]
对象:枚举器对象查询 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=5&t=43499&p=230778#p230778

[数值示例][类中的枚举器方法]
枚举类型和 while 循环 - 寻求帮助 - AutoHotkey 社区
https://autohotkey.com/board/topic/78869-enum-type-and-while-loop/#entry500848
For 循环问题 - 寻求帮助 - AutoHotkey 社区
https://autohotkey.com/board/topic/66916-for-loop-question/#entry423515

[数值示例][枚举器作为嵌套类]
对象:枚举器对象查询 - AutoHotkey 社区
https://autohotkey.com/boards/ viewtopic.php?f=5&t=43499&p=230779#p230779

[通过创建数组来指定枚举器,即通过老式“函数语法”方法定义的类:{methodname:"funcname"}
]关联数组 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=5&t=5489&p=31741#p31741

[将枚举数传递到 for 循环]
表示法:对象:键名以避免与方法/属性发生冲突- AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=5&t=43595&p=198159#p198159

[用自定义枚举器替换基本对象的枚举器]
如何创建自定义枚举器对象 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=7&t=7199

[COM 对象的下一个示例]
AccViewer Basic - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=6&t=32039

============================= =====================

> SINGLETON

[稍微简单的示例]
对象:将 AHK v2 Gui/Menu 类向后移植到 AHK v1 - AutoHotkey 社区
https://autohotkey.com /boards/viewtopic.php?f=37&t=43530&p=197457#p197457

[稍微复杂的例子]
对象类:临时重新定义__Set() / 通用查询-第3页-AutoHotkey社区
https://autohotkey.com/boards/viewtopic.php?f=5&t=42674&p=198332#p198332

======================= =======================

> 教程 / 链接

[链接的大集合]
对象类:临时重新定义 __Set() / 一般查询 - AutoHotkey 社区
https:// /autohotkey.com/boards/viewtopic.php?f=5&t=42674&p=193931#p193931
对象:枚举器对象查询 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=5&t=43499&p=198571# p198571

[调查 AHK 对象的二进制文件]
对象类:临时重新定义 __Set() / 通用查询 - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=5&t=42674&p=194533#p194533

[字典对象尝试,其中键名与方法名不冲突]
可能的 AHK 错误 - 数组 for 循环中缺少单词“base” - AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f =5&t=24107&p=113927#p113927

[高级基础示例]
调用获取时的括号?- AutoHotkey 社区
https://autohotkey.com/boards/viewtopic.php?f=5&t=10482&p=58116#p58116

======================== ==========================

> THE END通向你漫长而束缚的道路。

===================================================

HotKey估计是最常用的几个AHK命令了,主要功能是在运行过程中操作热键(关闭/开启等),比起静态的注册热键要灵活很多。但是,通过HotKey给"带有参数的函数"注册热键好像并不是那么显而易见,直接使用函数名会导致无法传递参数,其实这个需求可以用"绑定对象函数(BindFunctionObject)"来实现。

绑定函数对象介绍

"绑定函数对象(BindFunctionObject)"是"函数对象"(FunctionObject)的一种。

作为函数对象,它有以下特性,①可以使用%FunctionObject%(ParaMeters)的方法调用,②可以在很多原本只支持Label的地方使用(比如Menu/hotkey/GUI事件)

除了函数对象带来的便利,它的主要作用就是可以给参数固定具体的数值。
这两点结合就可以实现为在"在HotKey命令中给带有参数的函数注册热键"。

绑定函数对象的生成

对于可以获得引用的函数来说,直接用Func对象中的bind()方法即可。

实例

^l:: ;#按下Ctrl+L触发程序

	;#把一个Bind对象放入变量中
	;## 首先我们使用"Func()函数"返回一个"Func函数对象",之后我们调用此对象内的"Bind()方法"返回一个"Bind函数对象"
	P0:=Func("TestFunc").Bind("传参成功")

	;#通过hotkey注册,实现传参
	Hotkey,^p ,%P0%

	;# 用于测试的函数
	TestFunc(Param:="没有传参")
	{
		MsgBox,% Param
		return
	}
return

AHK中的三种复用

AHK中最简陋的复用是"标签",由于不能传递参数,所以对不同的数据进行处理时,非常蹩脚;其次是"函数",在原来"标签"的基础上实现了"传参"和"返回值";然后是"对象",对象把"数据域/方法"封装在了一起。关于各种对象的应用是从11.20.00之后才开始完善的,到现在好像还在继续。
通过三者的递进关系,可以看出AHK变得越来越"面向对象",通过不断的改进,相信AHK在编写相对大型的项目时,会更加得心应手。

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
}

给TA捐赠
共{{data.count}}人
人已捐赠
AHKV1教程

【学习】仿手动获取知乎文章内容

2022-2-5 21:07:45

应用教程游戏

利用windows自带工具定时清理某程序内存占用

2022-2-11 21:51:24

3 条回复 A文章作者 M管理员
  1. ahkjoo

    你好,
    我想调用微信的截图DLL,使用CMD可以完成,vbs也可以。但是在ahk里面调用不成功。
    DllCall("prcscrn.dll prcscrn")不知道什么原因,请教一下

    • 11010010

      DllCall("D:Program FilesWeChatPrScrn.dllPrScrn")

  2. ahkjoo

    你好,
    我想调用微信的截图DLL,使用CMD可以完成,vbs也可以。但是在ahsk里面调用不成功。
    DllCall("prcscrn.dllprcscrn")不知道什么原因,请教一下

个人中心
有新私信 私信列表
搜索