概念和约定

本文档涵盖了 AutoHotkey 所使用的一些通用概念和约定, 重点放在解释而不是代码上. 不会假定读者之前具备任何脚本或编程的知识, 但应该做好学习新术语的准备.

有关语法的更多具体细节, 请参阅脚本语言.

目录

只是程序中的一条信息. 例如, 要发送的键名或要运行的程序名, 热键被按下的次数, 要激活的窗口的标题, 或在程序或脚本中具有某种意义的其他任何内容.

AutoHotkey 支持这些类型的值:

Type 函数可用于确定值的类型.

其他一些相关的概念:

字符串

字符串 只是文本. 每个字符串实际上是一个由字符组成的序列或 字符串, 但可以视为单个实体. 字符串的 长度 是序列中字符的数量, 而字符串中字符的 位置 只是该字符的序号. 按 AutoHotkey 中的约定, 第一个字符的位置为 1.

数字字符串: 当数学运算或比较需要时, 数字字符串(或任何其他受支持的数字格式) 自动解释为数字.

脚本中如何书写原义文本, 取决于上下文. 例如, 在表达式中, 字符串必须括在双引号中. 在指令(除了 #HotIf) 和自动替换热字串中, 不需要双引号.

有关字符串如何工作的更详细说明, 请参阅字符编码.

数字

AutoHotkey 支持这些数字格式:

除非本文档注明, 十六进制数字必须使用 0x0X 前缀. 这个前缀必须写在 +- 号之后(如果存在), 并且在前导零之前. 例如, 0x001 有效, 但 000x1 无效.

带小数点的数字总是被认为是浮点数, 即使小数部分是零. 例如, 4242.0 通常可以互换, 但并非总是如此. 科学记数法也是认可的(例如 1.0e4-2.1E-4), 但总是产生浮点数(即使没有小数点).

小数点分隔符总是一个点, 即使用户的区域设置指定了一个逗号.

当数字被转换为字符串时, 它被格式化为十进制. 浮点数以全精度进行格式化(但是丢弃尾部多余的零), 这在某些情况下可能会暴露它们的不准确性. 使用 Format 函数生成不同格式的数字字符串. 浮点数也可以使用 Round 函数进行格式化.

有关数字值的范围和精度的详细信息, 请参阅纯数字.

布尔值

布尔值 可以是 truefalse. 布尔值用于表示具有两种可能状态的任何东西, 例如表达式的 真假. 例如, 当 x 小于或等于 y 的值时, 表达式 (x <= y)true. 布尔值也可以表示 yesno, onoff, downup(如 GetKeyState) 等等.

AutoHotkey 没有特定的布尔类型, 因此它使用整数值 0 表示 false, 而 1 表示 true. 当要求判断一个值的真假时, 空白或零值被认为是假, 而所有其他值被认为是真. (对象总是被视为真.)

单词 truefalse 是值分别为 1 和 0 的内置变量. 使用它们可以增加脚本的可读性.

空值

AutoHotkey 中没有像其他语言那样有一个独特的表示 nothing, null, nilundefined 的值.

任何读取没有值的变量, 属性, 数组元素或映射项的尝试都会导致抛出 UnsetError, 而不是产生 "null" 或 "undefined" 值. 这比隐式地允许空值在代码中传播更容易识别错误. 另请参阅: 未初始化变量.

函数的可选参数可以在调用时 省略, 在这种情况下, 函数可能会改变其行为或使用默认值. 参数通常通过从代码中文本省略来省略, 但也可以通过使用 unset 关键字显式或有条件地省略. 这个特殊的信号只能通过 maybe 操作符(var?) 显式地传播. 未设置的参数在函数执行前自动接收其默认值(如果有的话).

主要是由于历史原因, 空字符串有时会在其他语言中使用 null 或未定义值的地方使用, 例如没有显式返回值的函数.

如果一个变量参数被称为 "空" 或 "空白", 这通常意味着一个空字符串(长度为零的字符串). 这与省略参数不同, 尽管在某些情况下可能会产生相同的效果.

对象

对象 是 AutoHotkey 的复合或抽象数据类型. 对象可以由任意数量的 属性(可以检索或设置) 和 方法(可以调用) 组成. 每个属性或方法的名称和效果取决于特定的对象或对象类型.

注意: 所有从对象派生的对象都具有额外的共享行为, 属性和方法.

对象的一些使用方法包括:

适当的使用对象(特别是) 能产生 模块化可重复使用 的代码. 模块化代码通常更容易测试, 理解和维护. 例如, 人们可以改进或修改一段代码, 而不需要知道其他段的细节, 也不必对这些段做相应的修改. 可重复使用的代码节省了时间, 避免了一遍又一遍地为相同或相似的任务编写和测试代码.

对象协议

本节以这些概念为基础, 这些概念将在后面的章节中介绍: 变量, 函数

对象通过 消息传递 的原理工作. 您不知道对象的代码或变量实际驻留在哪里, 因此您必须将消息传递给对象, 如 "give me foo" 或 "go do bar", 并依赖对象来响应消息. AutoHotkey 中的对象支持以下基本消息:

属性 只是可以设置和/或检索的对象的某个方面. 例如, 数组具有 Length 属性, 它对应于数组中元素的数量. 如果您定义了一个属性, 它可以具有您想要的任何含义. 通常属性的作用类似于一个变量, 但是它的值可以根据需要计算, 而不是实际存储在任何地方.

每条消息包含以下内容, 通常在属性或调用方法的地方编写:

例如:

myObj.methodName(arg1)
value := myObj.propertyName[arg1]

对象还可以具有 默认 属性, 当使用方括号而没有属性名时, 将调用该属性. 例如:

value := myObj[arg1]

通常, Set 与赋值有相同的含义, 所以它使用相同的运算符:

myObj.name := value
myObj.name[arg1, arg2, ..., argN] := value
myObj[arg1, arg2, ..., argN] := value

变量

一个变量允许您使用一个名称作为一个值的占位符. 在脚本运行期间, 这个值可能会重复更改. 例如, 一个热键可以使用一个变量 press_count 来计算它被按下的次数, 并且当 press_count 是 3 的倍数(每第三次按下) 时, 发送一个不同的键. 即使只有一次赋值的变量也是有用的. 例如, WebBrowserTitle 变量可用于在更改首选 Web 浏览器时, 或者由于软件更新而导致标题窗口类发生更改时, 更容易更新代码.

在 AutoHotkey 中, 创建变量仅需要使用它们即可. 每个变量都 不是 永久性地限制在一个数据类型中, 而是可以保存任何类型的值: 字符串, 数字或对象. 试图读取一个没有分配值的变量会被视为一个错误, 所以初始化变量是很重要的.

一个变量有三个主要方面:

某些适用于变量名称的限制 - 请参阅名称了解详细信息. 简而言之, 坚持使用由 ASCII 字母(不区分大小写), 数字和下划线组成的名称是最安全的, 并避免使用以数字开头的名称.

变量名称具有 作用域(范围), 该范围定义了代码中的哪些位置可使用变量名称来引用该特定变量; 换句话说, 变量在哪些位置是 可见的. 如果一个变量在一个给定的范围内是不可见的, 那么在该给定的范围内相同的名称可以引用不同的变量. 两个变量可能同时存在, 但只有一个对脚本的每个部分是可见的. 全局变量在 "全局范围"(即函数之外) 中是可见的, 并可被函数默认读取, 但如果要在函数中给它们赋值, 则必须声明. 局部变量只在创建它们的函数内部可见.

一个变量可以被认为是一个值的容器或存储位置, 所以你会经常发现文档引用变量的值作为 变量的内容. 对于变量 x := 42, 我们也可以说变量 x 的值是 42, 或者 x 的值是 42.

值得注意的是, 一个变量和它的值是不一样的. 例如, 我们可以说 "myArray 是一个数组", 但是我们真正的意思是 myArray 是一个包含对数组的引用的变量. 我们通过使用变量的名称来引用其值, 但是 "myArray" 实际上只是变量的名称; 数组对象不知道它有一个名称, 并且可以被许多不同的变量引用(因此有很多的名称).

未初始化变量

初始化 变量就是给它分配一个起始值. 一个尚未分配值的变量是 未初始化变量(或简写为 unset). 试图读取一个未初始化的变量被视为一个错误. 这有助于检测错误, 例如拼写错误的名称和忘记的赋值.

IsSet 可用于确定一个变量是否已经初始化, 例如在首次使用时初始化一个全局或静态变量.

通过将直接赋值(:=) 与 unset 关键字或 maybe(var?) 运算符联合使用, 可以 un-set(取消设置) 变量. 例如: Var := unset, Var1 := (Var2?).

当变量缺少值时, or-maybe 运算符(??) 可用于提供默认值. 例如, MyVar ?? "Default" 等同于 IsSet(MyVar) ? MyVar : "Default".

内置变量

程序中内置了许多有用的变量, 可以通过任何脚本来引用. 除特殊说明外, 这些变量是只读的; 也就是说, 他们的内容不能被脚本直接改变. 按照惯例, 这些变量中的大部分以前缀 A_ 开头, 所以最好避免使用这个前缀作为你自己的变量.

某些变量如 A_KeyDelayA_TitleMatchMode 代表控制脚本行为的设置, 并为每个线程保留不同的值. 这允许由新线程(如热键, 菜单, 计时器等) 启动的子例程在不影响其他线程的情况下更改设置.

一些特殊的变量不是周期性地更新, 而是脚本引用变量时检索或计算它们的值. 例如, A_Clipboard 以文本形式检索剪贴板的当前内容, A_TimeSinceThisHotkey 计算自热键被按下以来经过的毫秒数.

相关: 内置变量列表.

环境变量

环境变量由操作系统维护. 在命令提示符中输入 SET 并回车后, 您可以看到环境变量列表.

脚本中可以使用 EnvSet 创建新的环境变量或改变现有环境变量的内容. 系统的其他部分不会看到这种添加和改变. 但是, 通过调用 RunRunWait 启动的任何程序或脚本会继承其父脚本的环境变量的副本.

要检索环境变量, 请使用 EnvGet. 例如:

Path := EnvGet("PATH")

变量引用(VarRef)

在一个表达式中, 每个变量引用都会自动解析为它的内容, 除非它是赋值引用运算符(&) 的目标. 换句话说, 调用 myFunction(myVar) 将把 myVar 的值传递给 myFunction, 而不是变量本身. 然后, 函数将拥有自己的局部变量(参数), 其值与 myVar 相同, 但不能给 myVar 赋新值. 简而言之, 参数是 按值 来传递的.

引用操作符(&) 允许像处理值一样处理变量. &myVar 产生一个 VarRef, 它可以像其他值一样使用: 赋值给其他变量或属性, 插入数组, 传递给函数或从函数中返回等等. VarRef 可以用来赋值给原始的目标变量, 或者通过解引用来检索它的值.

为了方便起见, 函数参数可以通过在参数名前加上引用符(&) 来声明为 ByRef(按引用). 这需要调用者传递一个 VarRef, 并允许函数本身通过仅引用参数(不带百分号) 来 "解引用" VarRef.

class VarRef extends Any

VarRef 类目前还没有预定义的方法或属性, 但 value is VarRef 可以用来测试一个值是否是 VarRef.

当一个 VarRef 被用作 COM 方法的参数时, 对象本身不会被传递. 取而代之的是, 它的值被复制到一个临时的 VARIANT, 它使用变体类型 VT_BYREF|VT_VARIANT 来传递. 当方法返回时, 新的值被分配给 VarRef.

缓存

尽管变量通常被认为是持有单个值, 而该值具有不同的类型(字符串, 数字或对象), 但在 "Value is " myNumberMsgBox myNumber 等情况下, AutoHotkey 会自动在数字和字符串之间进行转换. 由于这些转换非常频繁, 所以每当将包含数字的变量转换为字符串时, 其结果都会 缓存 在该变量中.

目前, AutoHotkey v2 仅在为变量赋值纯数字时缓存纯数字, 而不是在读取它时缓存. 这保留了区分字符串和纯数字的能力(例如使用 Type 函数, 或者在向 COM 对象传递值时).

函数

函数 是脚本 执行某些操作 的基本手段.

函数可以有很多不同的目的. 一些函数可能只是执行一个简单的计算, 而另一些可以立即看到效果, 比如移动一个窗口. AutoHotkey 的优势之一就是脚本可以轻松地自动执行其他程序, 并通过简单地调用几个函数执行许多其他常见任务. 有关示例, 请参阅函数列表.

在整篇文档中, 一些常见的词汇的使用方式对于没有经验的人来说可能并不是通俗易懂的. 下面是一些与函数相关的常用单词/短语:

调用函数

调用 函数会导致程序调用, 执行或计算它. 换句话说, 函数调用 暂时将控制权从脚本转移到函数. 函数完成其目的时, 它将控制权 返回 给脚本. 换句话说, 函数调用之后的任何代码都不会执行, 直到函数完成.

但是, 有时函数可以在用户看到它的效果之前完成. 例如, Send 函数 发送 键击, 但可能会在键击到达其目的地并返回其预期效果之前返回.

参数

通常函数接受 参数, 告诉它如何操作或操作什么. 每个参数都是一个, 如字符串或数字. 例如, WinMove 移动一个窗口, 所以它的参数告诉它要移动哪个窗口以及移动到哪里. Parameter(参数) 也可以被称为 arguments(参数). 常用的缩写包括 paramarg.

传递参数

参数被 传递 给函数, 这意味着函数被调用时每个参数都会指定一个值. 例如, 可以 传递 键的名称给 GetKeyState 以确定该键是否被按下.

返回值

函数 返回 一个值, 所以函数的结果通常称为 返回值. 例如, StrLen 返回字符串中的字符数. 函数也可以在变量中存储结果, 比如当有多个结果时(参阅返回值).

命令

函数调用有时被称为 命令, 例如它 命令 程序执行特定操作. (由于历史原因, 命令 可以指代调用函数的特定样式, 其中括号被省略并且丢弃返回值. 但是, 这在技术上是一个函数调用语句.)

函数通常期望参数以特定的顺序写入, 因此每个参数值的含义取决于它在逗号分隔的参数列表中的位置. 有些参数可以省略, 在这种情况下参数可以留空, 但是其后面的逗号只能在所有剩余的参数都省略的情况下才能省略. 例如, ControlSend 的语法是:

ControlSend Keys , Control, WinTitle, WinText, ExcludeTitle, ExcludeText

方括号表示所包含的参数是可选的(方括号本身不应出现在实际的代码中). 但是, 通常还必须指定目标窗口. 例如:

ControlSend "^{Home}", "Edit1", "A"  ; 正确. 指定了控件.
ControlSend "^{Home}", "A"           ; 不正确: 参数不匹配.
ControlSend "^{Home}",, "A"          ; 正确. 省略控件.

方法

方法 是与特定对象或对象类型关联的函数. 要调用方法, 必须指定对象和方法名. 方法名称不能唯一标识函数; 相反, 方法调用时会发生什么取决于对象. 例如, x.Show() 可能会显示菜单, 显示 GUI, 引发错误或执行其他操作, 这取决于 x 是什么. 换句话说, 方法调用只是将消息传递给对象, 指示它执行某些操作. 有关详情, 请参阅对象协议对象运算符.

控制流

控制流 是执行单个语句的顺序. 通常, 语句从上到下依次执行, 但控制流语句可以重写这一点, 例如, 指定语句重复执行语句, 或者仅在满足某一条件时才这样做.

语句

语句 只是语言中最小的独立元素, 表示一些要执行的操作. 在 AutoHotkey 中, 语句包括赋值, 函数调用和其他表达式. 但是, 指令, 双冒号热键和热字串标签, 以及没有赋值的声明都不是语句; 当程序首次启动时, 在脚本 执行 之前, 它们将被处理.

执行

实施, 执行, 求值, 使生效, 等等. 执行 基本上与非编程语言有相同的含义.

主体(正文)

控制流语句的 主体 是它所应用的语句或语句组. 例如, if 语句的主体仅在满足特定条件时执行.

例如, 请考虑以下简单的指令集:

  1. 打开记事本
  2. 等待记事本出现在屏幕上
  3. 输入 "Hello, world!"

我们一步一个脚印, 当一步完成时, 我们继续下一步. 同样, 程序或脚本中的控制通常从一个语句流向下一个语句. 但是如果我们想在一个现有的记事本窗口中输入呢? 考虑一下这套经过修改的指令:

  1. 如果(If) 记事本没有运行:
    1. 打开记事本
    2. 等待记事本出现在屏幕上
  2. 否则:
    1. 激活记事本
  3. 输入 "Hello, world!"

所以我们要么打开记事本, 要么激活记事本, 取决于它是否已经在运行. #1 是 条件语句, 也被称为 if 语句; 也就是说, 只有满足条件时, 我们才执行它的 主体(正文)(#1.1 - #1.2). #2 是 else 语句; 我们只有在前一个 if 语句(#1) 的条件不满足时才执行它的主体(#2.1). 根据条件, 控制 有两种方式: #1(如果为真) → #1.1 → #1.2 → #3; 或 #1(如果为假) → #2 (else) → #2.1 → #3.

上面的指令可以转换成下面的代码:

if (not WinExist("ahk_class Notepad"))
{
    Run "Notepad"
    WinWait "ahk_class Notepad"
}
else
    WinActivate "ahk_class Notepad"
Send "Hello, world{!}"

在我们书写的指令代码中, 我们使用了缩进和编号来对语句进行分组. 脚本工作略有不同. 尽管缩进使得代码更容易阅读, 但在 AutoHotkey 中, 它并不影响语句的分组. 作为替代, 如上所示, 语句通过将它们括在花括号中进行分组. 这被称为一个 .

有关语法的详细信息 - 即如何在 AutoHotkey 中编写或识别控制流程语句 - 请参阅控制流.

细节

字符编码

字符串中的每个字符都可以用一个数字表示, 名为它的 序号字符编码. 例如, 值 "Abc" 将被表示如下:

Abc
6598990

编码: 字符串的 编码 定义了符号如何映射到序数, 以及序数到字节. 尽管有许多不同的编码, 但是由于所有由 AutoHotkey 支持的编码都包含 ASCII 作为子集, 所以字符编码 0 到 127 总是具有相同的含义. 例如, 'A' 的字符编码总是 65.

空终止符: 每个字符串都以 "空终止符" 结束, 换句话说, 一个序数为零的字符标志着字符串的结束. 字符串的长度可以通过空终止符的位置推断出来. 为了提高性能, AutoHotkey 有时也存储长度, 并允许在字符串的长度范围内使用空终止符.

注意: 由于依赖于空终止符, 许多内置函数和大多数表达式运算符通常不支持包含空终止符的字符串, 而是只读到第一个空字符. 但是, 支持对此类字符串的基本操作; 例如, 串联, ==, !==, Chr(0), StrLen, SubStr, 赋值, 参数值和 return.

原生编码: 尽管 AutoHotkey 提供了使用各种编码处理文本的方法, 但是内置的命令和函数--在某种程度上包括语言本身--都假定字符串值是在一个特定的编码中. 这被称为 原生 编码. 原生编码取决于 AutoHotkey 的版本:

注意: AutoHotkey v2 原生使用 Unicode, 并且没有 ANSI 版本.

字符: 一般来说, 本文档的其他部分使用的术语 "字符" 表示的是字符串的最小单位; ANSI 字符串的字节和 Unicode(UTF-16) 字符串的 16 位编码单元. 出于实际的原因, 字符串的长度和字符串中的位置是通过计数这些固定大小的单位来测量的, 尽管它们可能不是完整的 Unicode 字符.

FileRead, FileAppend, FileOpenFile 对象提供了读写具有特定编码的文件的文本的方法.

StrGetStrPut 函数可用于在原生编码和某些其他指定编码之间转换字符串. 然而, 这些通常只在与数据结构和 DllCall 数结合使用时有用. 直接传入或传出 DllCall 的字符串可以通过使用 AStrWStr 参数类型转换为 ANSI 或 UTF-16.

处理 AutoHotkey ANSI 和 Unicode 版本之间差异的技术可以在 Unicode 和 ANSI 下找到.

纯数字

数字或 二进制 数字是以计算机的 CPU 可以直接使用的格式(例如执行数学运算) 存储在内存中的数字. 在大多数情况下, AutoHotkey 会自动根据需要在数字字符串和纯数字之间进行转换, 很少区分这两种类型. AutoHotkey 对纯数字主要使用两种数据类型:

换句话说, 脚本受以下限制的影响:

注意: 二进制浮点格式不能精确地表示一些小数, 因此数字四舍五入到最接近的可表示数字. 这可能会导致意外结果. 例如:

MsgBox 0.1 + 0           ; 0.10000000000000001
MsgBox 0.1 + 0.2         ; 0.30000000000000004
MsgBox 0.3 + 0           ; 0.29999999999999999
MsgBox 0.1 + 0.2 = 0.3   ; 0(不相等)

解决这个问题的一个策略是避免直接比较, 而是比较相差. 例如:

MsgBox Abs((0.1 + 0.2) - (0.3)) < 0.0000000000000001

另一种策略是在比较之前显式地应用四舍五入, 例如将其转换为字符串. 在指定精确度时, 通常有两种方法可以做到这一点, 如下所示:

MsgBox Round(0.1 + 0.2, 15) = Format("{:.15f}", 0.3)

名称

AutoHotkey 使用一组相同的规则来命名各种东西, 包括变量, 函数, 窗口组, 类, 属性和方法. 规则如下.

大小写敏感性: ASCII 字符不区分大小写. 例如, CurrentDate 等同于 currentdate. 但是, 大写的非 ASCII 字符(如 'Ä') 被认为等同于他们的小写字母, 不管当前用户的区域设置如何. 这有助于脚本在多个语言环境中保持一致.

最大长度: 253 个字符.

允许的字符: 字母, 数字, 下划线(_) 和非 ASCII 字符; 然而, 第一个字符不能是数字.

保留单词: as, and, contains, false, in, is, IsSet, not, or, super, true, unset, super. 这些单词保留给将来使用或其他特定用途.

声明关键字和控制流语句的名称也是保留的, 主要用于检测错误. 这包括: Break, Catch, Continue, Else, Finally, For, Global, Goto, If, Local, Loop, Return, Static, Throw, Try, Until, While.

允许将属性, 方法和窗口组的名称作为保留字.

对象的引用

脚本仅能通过对象的 引用 间接与对象进行交互. 创建对象时, 对象是在您不能控制的地方创建的, 并给您一个引用. 将此引用传递给函数或将其存储在变量或其他对象中会创建对 同一 对象的新引用.

例如, 如果 myObj 包含对对象的引用, yourObj := myObj 将创建一个新的对同一对象的引用. 如 myObj.ans := 42 这样的改变会同时影响 myObj.ansyourObj.ans, 因为它们都指向同一个对象. 但是, myObj := Object() 只影响变量 myObj, 不影响变量 yourObj, yourObj 仍然指向原始对象.

通过简单地使用赋值将其替换为任何其他数值, 可以释放引用. 只有在所有引用都已释放后, 才会删除对象; 不能显式删除对象, 也不应该去尝试这样做. (但是, 您可以删除对象的属性, 内容或相关资源, 如数组的元素, 与 Gui 关联的窗口, Menu 对象的菜单, 等等.)

ref1 := Object()  ; 创建一个对象并储存第一个引用
ref2 := ref1      ; 创建一个对同一对象的新引用
ref1 := ""        ; 释放第一个引用
ref2 := ""        ; 释放第二个引用; 对象被删除

如果难以理解的话, 可以尝试将一个对象看作出租单位. 当你租一个单位时, 你会得到一个你可以用来访问单位的钥匙. 您可以获得更多钥匙并使用它们访问同一个设备, 但是当您使用完该设备时, 您必须将所有钥匙交还给租赁代理. 通常这个单位不会被 删除, 但也许租赁代理将有你留下的任何垃圾删除掉; 就像存储在对象中的任何值在对象被删除时被释放一样.

unixetc