# 概念和约定

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

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

## 目录

- 值
  - 字符串
  - 数字
  - 布尔值
  - 空值
  - 对象
  - 对象协议
- 变量
  - 未初始化变量
  - 内置变量
  - 变量引用(VarRef)
  - 环境变量
  - 缓存
- 函数
  - 方法
- 控制流
- 细节
  - 字符编码
  - 纯数字
  - 名称
  - 对象引用

## 值

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

AutoHotkey 支持这些类型的值:

- 字符串(文本)
- 数字(整数和浮点数)
- 对象

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

其他一些相关的概念:

- 布尔值
- 空值

### 字符串

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

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

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

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

### 数字

AutoHotkey 支持这些数字格式:

- 十进制整数, 如 `123`, `00123` 或 `-1`.
- 十六进制整数, 如 `0x7B`, `0x007B` 或 `-0x1`.
- 十进制浮点数, 如 `3.14159`.

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

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

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

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

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

### 布尔值

布尔值 可以是 true 或 false. 布尔值用于表示具有两种可能状态的任何东西, 例如表达式的 真假. 例如, 当 x 小于或等于 y 的值时, 表达式 `(x <= y)` 为 true. 布尔值也可以表示 yes 或 no, on 或 off, down 或 up(如 GetKeyState) 等等.

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

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

### 空值

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

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

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

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

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

### 对象

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

- 对象是不能被包含的; 它们只是引用. 例如, `alpha := []` 创建一个新数组并在 alpha 中存储一个引用. `bravo := alpha` 复制引用(不是对象) 到 `bravo`, 因此两者都引用同一个对象. 当一个数组或变量被认为包含一个对象时, 它实际上包含的是对该对象的引用.
- 只有在引用同一个对象时, 两个对象引用才被视为相等的.
- 当结果需要布尔值时, 对象总是被视为 true , 例如在 `if obj`, `!obj` 或 `obj ? x : y`.
- 每个对象都有一个唯一的地址(内存中的位置), ObjPtr 函数可以检索该地址, 但通常不直接使用. 此地址唯一标识对象, 但仅在释放对象之前.
- 在某些情况下, 当一个对象被使用在不期望对象的任何上下文中, 它可能会被当作一个空字符串. 例如, `MsgBox(myObject)` 显示一个空的消息框. 在其他情况下, 可能会抛出 TypeError(这应该成为未来的标准).

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

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

- 包含项目或 元素 的集合. 例如, 数组包含一系列项, 而 Map 则将键与值相关联. 对象允许将一组值视为一个值, 赋值给单个变量, 传递给函数或从函数返回, 等等.
- 表示真实或概念性的东西. 例如: 如屏幕上某个位置的 X 坐标和 Y 坐标; 通讯录中的联系人, 包括姓名, 电话号码, 电子邮件地址等. 通过将对象与其他对象组合, 对象可用于构建更复杂的信息集.
- 封装一个或一组服务, 允许脚本的其他部分将重点放在任务上, 而不是该任务是如何执行的. 例如, File 对象提供了文件读取或写入数据的方法. 如果将信息写入文件的脚本函数接受 File 对象作为参数, 它不需要知道文件是如何打开的. 可以重复利用相同的函数将信息写入其他目标, 如 TCP/IP 套接字或 WebSocket(通过用户定义的对象).
- 以上的组合. 例如, Gui 表示 GUI 窗口; 它向脚本提供了创建和显示图形用户界面的方法; 它包含一组控件, 并通过 Title 和 FocusedCtrl 等属性提供有关窗口的信息.

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

### 对象协议

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

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

- Get(获取) 属性.
- Set(设置) 属性, 使用 `:=`.
- Call(调用) 方法, 使用 `()`.

属性 只是可以设置和/或检索的对象的某个方面. 例如, 数组具有 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_KeyDelay 和 A_TitleMatchMode 代表控制脚本行为的设置, 并为每个线程保留不同的值. 这允许由新线程(如热键, 菜单, 计时器等) 启动的子例程在不影响其他线程的情况下更改设置.

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

相关: 内置变量列表.

### 环境变量

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

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

要检索环境变量, 请使用 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 " myNumber` 和 `MsgBox myNumber` 等情况下, AutoHotkey 会自动在数字和字符串之间进行转换. 由于这些转换非常频繁, 所以每当将包含数字的变量转换为字符串时, 其结果都会 缓存 在该变量中.

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

### 相关

- 变量: 基本用法和示例.
- 变量的容量和内存占用: 有关限制的详细信息.

## 函数

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

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

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

调用函数

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

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

参数

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

传递参数

  参数被 传递 给函数, 这意味着函数被调用时每个参数都会指定一个值. 例如, 可以 传递 键的名称给 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" 将被表示如下:

A | b | c
65 | 98 | 99 | 0

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

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

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

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

- AutoHotkey 的 Unicode 版本使用 UTF-16. UTF-16 字符串中最小的元素是两个字节(16 位). 序号在 0 到 65535(U+FFFF) 范围内的 Unicode 字符是由一个与序号相等的单一的 16 位编码单元来表示, 而在 65536(U+10000) 到 1114111(U+10FFFF) 范围内的字符是由 代理项对 表示的; 也就是说, 需要在 0xD800 和 0xDFFF 范围内的两个 16 位编码单元来表示一个字符. (关于代理项对的进一步解释以及对它们进行编码或解码的方法, 请自行在网上搜索.)

- AutoHotkey 的 ANSI 版本使用系统默认的 ANSI 代码页, 这取决于系统区域设置或 "非 Unicode 程序的语言" 系统设置. ANSI 字符串的最小元素是一个字节(8 位). 但是, 某些代码页包含由多个字节序列表示的字符(这些字符总是非 ASCII 字符).

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

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

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

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

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

### 纯数字

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

- 64 位有符号整数(int64).
- 64 位二进制浮点数(IEEE 754 国际标准的 double 或 binary64 格式).

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

- 整数必须在 64 位有符号整数范围内; 也就是说, -9223372036854775808(-0x8000000000000000 或 -263) 到 9223372036854775807(0x7FFFFFFFFFFFFFFF 或 263-1) 的范围内. 如果表达式中的整数常量超出此范围, 则只使用较低的 64 位(值被截断). 虽然可以在字符串中包含更大的值, 但是将字符串转换为数字的尝试(例如在数学运算中使用该字符串) 都会导致类似的截断.

- 浮点数字通常支持 15 位精度.

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

    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.ans` 和 `yourObj.ans`, 因为它们都指向同一个对象. 但是, `myObj := Object()` 只影响变量 myObj, 不影响变量 yourObj, yourObj 仍然指向原始对象.

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

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

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

dftg