# 函数

## 目录

- 介绍和简单示例
- 参数
- 可选参数
- 返回值给调用者
- 可变参数函数
- 局部和全局变量
- 动态调用函数
- 优化布尔求值
- 在函数中使用子程序
- Return, Exit 及一般说明
- 使用 #Include 在多个脚本间共享函数
- 函数库: 标准库和用户库
- 内置函数

## 介绍和简单示例

函数类似于子程序(Gosub), 不过它还可以从调用者那里接受参数(输入). 而且, 函数还可以返回值给调用者. 参考下面的例子, Add 函数接受两个参数并返回它们之和:

    Add(x, y)
    {
        return x + y   ; "Return" 可直接返回表达式的结果.
    }

上面就是一个函数 定义, 因为它创建了一个名称为 "Add"(不区分大小写) 的函数, 并且确立了调用它时必须准确的提供两个参数(x 和 y). 要调用此函数, 请把它的结果通过 := 运算符赋值给变量. 例如:

    Var := Add(2, 3)  ; 数字 5 将被保存到 Var.

当然, 调用函数时也可以不保存其返回值:

    Add(2, 3)

不过这种情况下, 函数的任何返回值都会被丢弃; 所以除非函数还有除了返回值之外的功能(译者注: 比如函数内修改了全局变量; 通过 ByRef 修改了变量值...等等), 否则这次调用毫无意义.

由于函数调用属于表达式语法, 所以在其参数列表中的任何变量名称都不应该用百分号包围. 同时, 表示原义字符串的变量则应该用双引号包围. 例如:

    if InStr(MyVar, "fox")
        MsgBox The variable MyVar contains the word fox.

最后, 通常在命令的参数中也可以调用函数(除了像 StringLen 中的 OutputVar 和 InputVar 这种参数之外). 另外, 在不支持表达式的参数中调用函数时必须加上 "% " 前缀(百分号加一个空格), 例如:

    MsgBox % "The answer is: " . Add(3, 2)

在原生支持表达式的参数中也允许加上 "% " 前缀, 但它只是会被忽略.

## 参数

定义函数时, 其参数都在其名称后面的括号中列出(函数名和左括号之间不能有空格). 如果函数不接受任何参数, 请把括号留空, 例如: `GetCurrentTimestamp()`.

ByRef 参数: 从函数的角度看, 参数本质上是局部变量, 除非它们被定义为 ByRef, 例如:

    Swap(ByRef Left, ByRef Right)
    {
        temp := Left
        Left := Right
        Right := temp
    }

在上述例子中, ByRef 的使用让相应的参数变成从调用者传递进来的变量的一个别名. 换句话说, 参数和调用者的变量都引用内存中相同的内容. 这样使得 Swap 函数可以通过移动 Left 的内容到 Right 中来修改调用者的变量, 反之亦然.

与之相比, 在上述例子中如果没有使用 ByRef, Left 和 Right 将是调用者变量的副本, 因此 Swap 函数不会对外部产生影响.

由于 return 只能返回一个值给函数的调用者, 所以可以使用 ByRef 返回更多的结果. 这是通过函数将调用者传递进来的变量(通常为空值的变量) 赋值实现的.

传递大字符串给函数时, 使用 ByRef 提高了性能, 并且通过避免生成字符串的副本节约了内存. 同样地, 使用 ByRef 送回长字符串给调用者通常比类似 `Return HugeString` 的方式执行的更好.

[AHK_L 60+]: 如果传递给 ByRef 参数的变量是不可修改的, 那么函数会表现的就像没有 "ByRef" 关键字一样. 例如, `Swap(A_Index, i)` 保存 A_Index 的值到 i 中, 但是当 Swap 函数返回时赋给 Left 的值会被丢弃.(译者注: 因为 A_Index 是只读的内置变量.)

[v1.1.01+]: IsByRef() 函数可以用来判断调用者是否为指定的 ByRef 参数提供了变量.

已知限制:

- 对象的属性在 ByRef 的参数中不会被视为变量. 例如, 如果 `foo.bar` 传递给 ByRef 参数, 那么它将表现的就像没有 ByRef 关键字一样.
- 不能传递 Clipboard, 内置变量或环境变量给函数的 ByRef 参数, 无论脚本中有没有 #NoEnv 语句.
- 尽管函数可以递归调用它自己, 但是如果它传递它自己的一个局部变量或非 ByRef 参数给自己的 ByRef, 那么新一层的 ByRef 参数将引用它自己那个名称的局部变量而不是之前层的. 然而, 当函数传递给它自己全局变量, 静态变量或 ByRef 参数时不会产生这样的问题.
- 如果一个参数在函数调用中被解析为一个变量(例如 `Var` 或 `++Var` 或 `Var*=2`), 它左边或右边的其他参数可能在它被传递给函数前修改这个变量. 例如, 当 Var 初始为 0 时 `MyFunc(Var, Var++)` 会意外地传递 1 和 0, 即使函数的首个参数不是 ByRef 类型时. 因为这种行为是违反常规的, 所以可能在将来的版本中改变.
- ByRef 不直接支持函数调用 COM 客户端, 或调用 COM 方法. 作为替代, 脚本必须接受或传递一个 wrapper object(包装对象) 包含特殊的 VarType(变量类型) 和变量内存地址.

## 可选参数

定义函数时, 可以把它的一个或多个参数标记为可选的. 这可以通过在参数后添加一个 `:=`(在 [v1.1.09] 及以后的版本) 或 `=`, 后面跟着参数的默认值来表示. 参数的默认值必须是下列形式中的一种: `true`, `false`, 原义的整数, 原义的浮点数或引号包围的/原义的字符串例如 "fox" 或 ""(但在 [1.0.46.13] 之前的版本中字符串只支持 "").

允许使用 `=`(没有冒号) 是为了向后兼容, 但是已经不推荐使用, 而且 AutoHotkey v2 将不再允许这样做. 不管使用哪种操作符, 字符串格式的默认值总是被双引号包围的.

下面这个函数中的 Z 参数就是一个可选参数:

    Add(X, Y, Z:=0) {
        return X + Y + Z
    }

当调用者传递 三个 参数给上面的函数时, Z 的默认值被忽略. 但当调用者仅传递 两个 参数时, Z 自动接受默认值 0.

可选参数不能孤立地放在参数列表的中间. 换句话说, 在首个可选参数右边的所有参数都必须定义为可选的. [AHK_L 31+]: 调用函数时可以省略参数列表中间的可选参数, 如下所示: 从 [v1.1.12] 开始, 动态调用函数或方法时也支持这种特性.

    MyFunc(1,, 3)
    MyFunc(X, Y:=2, Z:=0) {  ; 注意: 这里的 Z 必须是可选参数.
        MsgBox %X%, %Y%, %Z%
    }

从 [v1.0.46.13] 开始, ByRef 参数也支持默认值; 例如: `MyFunc(ByRef p1 = "")`. 每当调用者省略这样的参数时, 函数会创建一个包含默认值的局部变量; 不过, 这种情况下 "ByRef" 关键字没有任何意义.

## 返回值给调用者

如介绍中所说, 函数可以返回一个值给调用者.

    Test := returnTest()
    MsgBox % Test

    returnTest() {
      return 123
    }

如果要从函数中返回额外的结果, 可以使用 ByRef:

    returnByRef(A,B,C)
    MsgBox % A "," B "," C

    returnByRef(ByRef val1, ByRef val2, ByRef val3)
    {
      val1 := "A"
      val2 := 100
      val3 := 1.1
      return
    }

[v1.0.97+]: 可以使用对象和数组返回多值甚至是命名值:

    Test1 := returnArray1()
    MsgBox % Test1[1] "," Test1[2]

    Test2 := returnArray2()
    MsgBox % Test2[1] "," Test2[2]

    Test3 := returnObject()
    MsgBox % Test3.id "," Test3.val

    returnArray1() {
      Test := [123,"ABC"]
      return Test
    }

    returnArray2() {
      x := 456
      y := "EFG"
      return [x, y]
    }

    returnObject() {
      Test := {id: 789, val: "HIJ"}
      return Test
    }

## 可变参数函数 [AHK_L 60+]

定义函数时, 在最后一个参数后面写一个星号来标记此函数为可变参数的, 这样让它可以接收可变数目的参数:

    Join(sep, params*) {
        for index,param in params
            str .= param . sep
        return SubStr(str, 1, -StrLen(sep))
    }
    MsgBox % Join("`n", "one", "two", "three")

调用可变参数函数时, 通过保存在函数的最后参数中的对象可以访问剩余的参数. 函数的首个超出参数是 `params[1]`, 第二个是 `params[2]` 以此类推. 和所有的标准对象一样, 使用 `params.MaxIndex()` 可以确定最大的索引值(这里为参数的数目). 但是如果没有参数, MaxIndex 会返回空字符串.

注意:

- "可变" 参数只可以出现在显式参数(形参) 列表的末尾.
- RegEx 调出函数不是可变参数的, 虽然允许使用 "可变的" 参数但是那只是留空而已.
- 回调函数通过地址而不是数组传递剩余参数.

### 可变参数函数调用

虽然可变参数函数可以 接受 可变数目的参数, 不过在函数调用中使用相同的语法可以把数组作为参数传递给 任何 函数:

    substrings := ["one", "two", "three"]
    MsgBox % Join("`n", substrings*)

注意:

- 在源数组中参数的编号从 1 开始.
- 数组中的可选参数可以完全省略.
- 调用自定义函数时数组参数可以包含命名项, 在其他情况下则不支持命名项.
- 目标函数也可以是可变的, 此时命名项被复制, 即使它们没有一致的形式参数.
- 这样的语法还可以用于调用对象的方法或获取对象的属性; 例如, `Object.Property[Params*]`. 从 [v1.1.12] 开始, 它还可以用于设置属性.

已知限制:

- 只有最右边的那个参数才可以这样展开. 例如, 支持 `MyFunc(x, y*)` 但不支持 `MyFunc(x*, y)`.
- 在星号(`*`) 和参数列表中最后的形式参数间不能存在任何的非空白字符.

## 局部变量和全局变量

### 局部变量

局部变量是特定于单个函数的, 只在该函数内可见. 因此, 局部变量可能具有与全局变量相同的名称, 并且两个变量都有单独的内容. 不同的函数也可以安全地使用相同的变量名.

当函数返回值时, 所有非静态的局部变量都自动释放(变为空).

像 Clipboard, ErrorLevel 和 A_TimeIdle 这样的内置变量不会是局部的(它们可以从任何地方访问), 并且不能被重新声明.

默认情况下, 函数假定是局部的. 在函数内访问或创建的变量默认为局部的, 但以下情况除外:

- 超级全局变量, 包括 classes(类).
- 动态变量引用, 如果不存在同名的局部变量, 则可以解析为现有的全局变量.
- 创建伪数组的命令, 即使只声明第一个元素, 也可以将所有创建的元素作为全局变量.

如下所示, 也可以重写默认值(通过声明变量或改变函数的模式).

强制局部模式 [v1.1.27+]: 如果函数的第一行是单词 "local", 则假定所有变量引用(甚至是动态的) 都是局部的, 除非它们在函数 内 声明为全局的. 与默认模式不同, 强制局部模式具有以下行为:

- 函数内部没有声明时, 超级全局变量(包括类) 不能被访问.
- 动态变量引用遵循与非动态变量引用相同的规则. 只有在函数中声明的全局变量才能被访问.
- 创建伪数组的 StringSplit 和其他命令遵循与非动态变量引用相同的规则(避免共同的混淆源).
- LocalSameAsGlobal 警告永远不会被强制局部函数中的变量引发.

### 全局变量

要在函数中引用现有的全局变量(或创建新的), 需要在使用前声明此变量为全局的. 例如:

    LogToFile(TextToLog)
    {
        global LogFileName  ; 此全局变量之前已经在函数外的某个地方赋值了.
        FileAppend, %TextToLog%`n, %LogFileName%
    }

假设全局模式: 如果函数需要访问或创建大量的全局变量, 通过在函数的首行使用单词 "global" 或声明局部变量可以把函数定义为假设其所有的变量都是全局的(除了它的参数). 例如:

    SetDefaults()
    {
        global  ; 如果此函数的首行是类似于 "local MyVar" 这样的, 那么这个单词可以省略.
        MyGlobal := 33  ; 把 33 赋值给全局变量, 必要时首先创建这个变量.
        local x, y:=0, z  ; 在这种模式中局部变量必须进行声明, 否则会假设它们为全局的.
    }

函数还可以使用这种假设全局模式来创建全局数组, 例如赋值给 `Array%A_Index%` 的循环.

超级全局变量 [v1.1.05+]: 如果全局声明出现在任何函数的外面, 默认情况下它可以对所有函数有效 (强制局部模式的函数除外). 这样可以避免在每个函数中重复声明变量的需要. 不过, 如果声明了含有相同名称的函数参数或局部变量, 那么它会优先于全局变量. 由 class 关键字创建的变量也是超级全局的.

### 静态变量

静态变量总是隐式的局部变量, 但和局部变量的区别是它们的值在多次调用期间是记住的. 例如:

    LogToFile(TextToLog)
    {
        static LoggedLines := 0
        LoggedLines += 1  ; 保持局部的计数(它的值在多次调用期间是记住的).
        global LogFileName
        FileAppend, %LoggedLines%: %TextToLog%`n, %LogFileName%
    }

静态初始化: 在 [1.0.46] 以前的版本中, 所有的静态变量都是以空值开始; 所以要检查静态变量首次被使用的唯一方法是检查它是否为空值. [v1.0.46+]: 静态变量可以初始化为 `""` 外的其他值, 通过在后面跟着 `:=` 或 `=` 及后面这些形式的其中一种: `true`, `false`, 原义的整数, 原义的浮点数或引号包围的/原义的字符串, 如 `"fox"`. 例如: `static X:=0, Y:="fox"`. 每个静态变量只初始化一次(在脚本开始执行前).

[AHK_L 58+]: 支持 `Static var := expression`. 根据这些表达式在脚本中出现的顺序对它们进行计算, 紧接着才进入脚本的自动执行段.

假设静态模式 [v1.0.48+]: 通过在函数的首行使用单词 "static" 可以把函数定义为假设其所有的变量都是静态的(除了它的参数). 例如:

    GetFromStaticArray(WhichItemNumber)
    {
        static
        static FirstCallToUs := true  ; 静态声明初始化仍然只运行一次(在脚本执行前).
        if FirstCallToUs  ; 在首次调用而不在后续的调用时创建静态数组.
        {
            FirstCallToUs := false
            Loop 10
                StaticArray%A_Index% := "Value #" . A_Index
        }
        return StaticArray%WhichItemNumber%
    }

在假设静态模式中, 任何非静态变量都必须声明为局部变量或全局变量(例外情况与假设局部模式相同, 除非强制局部模式也有效).

[v1.1.27+]: 可以通过指定 `local` 然后 `static` 的方式将强制局部模式和假设静态模式组合在一起使用, 如下面演示的那样. 允许函数使用强制局部模式的规则, 但默认情况下创建静态变量.

    global MyVar := "This is global"
    DemonstrateForceStatic()

    DemonstrateForceStatic()
    {
        local
        static
        MyVar := "This is static"
        ListVars
        MsgBox
    }

### 关于局部和全局变量的更多信息

通过逗号分隔多个变量这样可以在同一行声明它们, 例如:

    global LogFileName, MaxRetries := 5
    static TotalAttempts := 0, PrevResult

[v1.0.46+]: 通过后面跟着 `:=` 或 `=` 及任意表达式, 局部或全局变量可以在声明的同一行初始化(在声明时 `=` 运算符和 `:=` 作用相同). 与静态初始化不同, 在每次调用函数时都会对局部变量和全局变量进行初始化, 但只在控制流实际达到它们所在的语句时. 换句话说, 像 `local x := 0` 这样的一行和写成单独的两行有同样的效果: `local x` 后面跟着 `x := 0`.

因为单词 local, global 和 static 都是在脚本运行时立即处理的, 所以不能使用 IF 语句有条件的声明变量. 换句话说, IF 或 ELSE 的区块内的声明无条件对声明和函数的闭括号之间的所有行生效. 同时还需注意当前还不支持声明动态变量, 例如 `global Array%i%`.

对于创建伪数组的命令(例如 StringSplit), 如果假设全局模式没有生效或数组的首个元素已经声明为局部变量时创建的数组是局部的(如果函数的某个参数被传递时也是如此, 即使此参数为 ByRef 类型, 因为参数类似于局部变量). 相反地, 如果首个元素已经声明为全局的, 那么创建全局的数组. 不过, 后面 混乱的常见根源 也适用于这些情况. StringSplit 创建的数组首个元素为 ArrayName0. 对于其他创建数组的命令, 例如 WinGet List, 首个元素为 ArrayName(即没有数字). [v1.1.27+]: 当强制局部模式生效时, 这些命令遵循与普通变量引用一致的规则; 也就是说, 任何未声明为全局的伪数组元素都将是局部的, 即使其他元素声明为全局的.

在函数(强制局部模式生效时除外) 中, 任何动态变量引用, 例如 `Array%i%`, 总是解析为局部变量, 仅当这样名称的局部变量不存在而全局变量存在时才解析为全局变量. 如果两者都不存在并且需要创建此变量时, 当假设全局模式没有生效时它被创建为局部变量. 因此, 仅当函数被定义为假设全局函数时, 函数中才可以手动创建全局数组(通过使用类似 `Array%i% := A_Index` 的方法).

混乱的常见根源: 任何 非 动态引用都会在脚本运行时创建那个变量. 例如, 在函数外使用时, `MsgBox %Array1%` 会在脚本运行的时候创建 Array1 为全局变量. 相反地, 在函数中使用 `MsgBox %Array1%` 会在脚本运行的时候创建 Array1 为函数的一个局部变量(除非假设全局模式生效), 即使 Array 和 Array0 都声明为全局的.

## 动态调用函数

[v1.0.47.06+]: 通过百分号可以动态调用函数(包括内置函数). 例如,`%Var%(x, "fox")` 将调用名称保存在 Var 中的函数. 同样地, `Func%A_Index%()` 将调用 Func1() 或 Func2() 等, 这取决于 A_Index 的当前值.

[v1.1.07.00+]: 在 `%Var%()` 中的 Var 可包含函数名或函数对象. 如果此函数不存在, 则调用默认基对象的 __Call 元函数.

如果由于下面的某个原因无法调用函数, 计算包含调用的表达式时可能会过早静默停止, 这样可能会产生问题.

- 调用不存在的函数, 这可以通过使用 `If IsFunc(VarContainingFuncName)` 来避免. 除了内置函数, 被调用函数的定义必须真实存在于脚本中, 通过类似 #Include 或对库函数的非动态调用的方法.
- 传递过少的参数, 这可以通过检查 IsFunc() 的返回值来避免(这是强制参数的数目加上一的数字). [v1.0.48+]: 注意允许传递过多的参数; 每个额外的参数在被完全计算(包括任何函数调用) 后丢弃.

最后, 对函数的动态调用比正常调用稍慢, 因为正常的调用在脚本开始运行前解析(查询).

## 优化布尔求值

当在表达式中使用 AND, OR 和三元运算符时, 会对它们进行优化来提高性能(不管当前是否存在函数调用). 通过不计算表达式中那些不影响最终结果的部分来进行优化运算. 为了说明这个概念, 请看这个例子:

    if (ColorName != ""  AND not FindColor(ColorName))
        MsgBox %ColorName% could not be found.

在上面的例子中, 如果 ColorName 变量为空则永远不会调用 FindColor() 函数. 这是由于 AND 的左侧结果将为 false, 因此其右边不可能让最终的结果为 true.

由于此特性, 所以需要注意到, 如果在 AND 或 OR 的右侧调用函数, 那么函数可能永远不会产生副作用(例如改变全局变量的内容).

还需要注意在嵌套的 AND 和 OR 串联表达式的求值优化. 例如, 在后面的表达式中每当 ColorName 为空时只会进行最左边的比较. 这是因为此时最左边的比较已经足以确定最终的结果:

    if (ColorName = "" OR FindColor(ColorName, Region1) OR FindColor(ColorName, Region2))
        break   ; 搜索内容为空或找到了匹配.

从上面的例子可以看出, 任何耗时的函数一般应该在 AND 或 OR 的右侧调用从而提高性能. 这种方法还能避免调用接受了一个不合适的值例如空字符串的函数.

[v1.0.46+]: 三元条件运算符(?:) 也通过不计算丢弃的分支进行求值优化.

## 在函数中使用子程序

尽管在一个函数中不能定义其他函数, 但它可以含有子程序. 与其他子程序一样使用 Gosub 运行它们且使用 Return 返回(此时 Return 属于 Gosub 而不是函数).

已知限制: 当前每个子程序(标签) 的名称在整个脚本中必须是唯一的. 如果存在重复的标签, 在运行前程序会通知您.

如果函数使用 Gosub 跳转到公共子程序(在函数外部的子程序), 那么所有外面的变量都是全局的而且在子程序返回前无法引用函数自身的局部变量. 不过, A_ThisFunc 仍将包含正在执行的函数名称.

尽管不能使用 Goto 从函数中跳转到外面, 但可以在函数中使用 Gosub 到外部/公共的子程序, 然后在那里使用 Goto.

尽管一般不鼓励使用 Goto, 但它能用来在同个函数中的一个位置跳转到其他位置. 这样有助于简化包含许多个返回点而所有这些点在返回前需要进行一些清理的复杂函数.

函数可以包含能被外部调用的子程序, 例如计时器, GUI g-标签和菜单项. 通常把它们封装到一个单独的文件中供 #Include 使用, 这样避免了它们和脚本的自动执行段的冲突. 不过, 还存在下面这些限制:

- 如果它们所在的函数曾经被正常调用, 那么这样的子程序应该只使用静态和全局变量(不使用局部变量). 这是由于中断函数调用线程的子程序线程(反之亦然) 可以改变被中断线程看到的局部变量的值. 此外, 在任何时候函数返回到其调用者时, 它包含的所有局部变量都被置空来释放占用的内存.
- 这样的子程序应该只使用全局变量(不使用静态变量) 作为 GUI 控件的变量.
- 当一个子程序线程进入函数后, 在此线程中任何对动态变量的引用都被视为是全局的(包括创建数组的那些命令).

## Return, Exit 及一般说明

如果函数内的执行流在遇到 Return 前到达了函数的闭括号, 那么函数结束并返回空值(空字符串) 给其调用者. 当函数明确省略 Return 的参数时也返回空值.

当函数使用 Exit 命令终止当前线程时, 其调用者不会接收到返回值. 例如, 这个语句 `Var := Add(2, 3)` 中, 如果 `Add()` 退出了那么 `Var` 会保持不变. 如果函数出现了运行时错误, 例如运行了一个不存在的文件(当 UseErrorLevel 选项不存在时), 也会出现同样的情况.

如果要返回一个额外的容易记住的值, 那么函数可以修改 ErrorLevel 的值来实现.

要使用一个或多个空值(空字符串) 调用函数, 可以使用空的引号对, 例如: `FindColor(ColorName, "")`.

因为调用函数不会开启新线程, 所以函数对设置例如 SendMode 和 SetTitleMatchMode 做出的任何改变对其调用者同样有效.

函数的调用者可以传递不存在的变量或数组元素给它, 当函数期望和 ByRef 一致的参数时这很有用. 例如, 调用 `GetNextLine(BlankArray%i%)` 会自动地创建局部或全局的变量 `BlankArray%i%`(是局部还是全局取决于调用者是否在函数内并且它的假设全局模式是否有效).

在函数中使用 ListVars 时, 它会显示函数的局部变量及其内容. 这样可以帮助调试脚本.

## 风格和命名约定

您也许会发现, 如果给复杂函数中的特定变量加上独特的前缀会让函数更容易阅读和维护. 例如, 使用 "p" 或 "p_" 开头来命名函数中的每个参数可以让它们的性质一目了然, 尤其是当函数中有许多局部变量吸引您的注意力的时候. 同样地, 前缀 "r" 或 "r_" 可以用于 ByRef 参数, 而 "s" 或 "s_" 可以用于静态变量.

在定义函数时可以使用 One True Brace(OTB) 风格. 例如:

    Add(x, y) {
        return x + y
    }

## 使用 #Include 在多个脚本间共享函数

可以使用 #Include 指令(即使在脚本的顶部) 从外部文件中加载函数.

说明: 当脚本的执行流遇到函数定义时, 它会跳过函数(使用一种瞬时的方法) 并在函数闭括号后恢复执行. 因此, 开始执行时执行流不会陷入函数中, 也不会因为在脚本的最顶部存在一个或多个函数而影响到自动执行段.

## 函数库: 标准库和用户库 [v1.0.47+]

脚本中可以不需要通过 #Include 而调用外部文件中的函数. 要达到此目的, 必须在下面某个库文件夹中存在与函数同名的文件:

    %A_ScriptDir%\Lib\  ; 本地库, 脚本所在目录中的 Lib 文件夹 - 需要 [AHK_L 42+].
    %A_MyDocuments%\AutoHotkey\Lib\  ; 用户库.
    directory-to-the-currently-running-AutoHotkey.exe\Lib\  ; 标准库, 当前运行的 AutoHotkey.exe 所在目录中的 Lib 文件夹.

例如, 当脚本调用不存在的函数 `MyFunc()` 时, 程序会在用户库中搜索名为 "MyFunc.ahk" 的文件. 如果没有找到, 会继续在标准库中搜索此文件. 如果仍然没有找到匹配的文件且函数的名称含有下划线(例如 `MyPrefix_MyFunc`), 那么程序会在两个库中搜索名为 `MyPrefix.ahk` 的文件, 如果找到则加载它. 这样使得 `MyPrefix.ahk` 可以同时包含 `MyPrefix_MyFunc` 函数和其他名称以 `MyPrefix_` 开始的相关函数.

[AHK_L 42+]: 支持本地库且本地库优先于用户库和标准库.

只有如 `MyFunc()` 这样直接的函数调用才能自动导入库. 如果仅动态或间接调用该函数, 就像计时器或 GUI 事件, 那么库必须在脚本中明确的(显式) 导入, 例如: `#Include <MyFunc>`

虽然库文件通常只包含和它的文件名称相同的单个函数, 但它还可以包含仅被此函数调用的私有函数和子程序. 然而, 这样的函数应该使用相当明确清晰的名称, 因为它们仍属于全局命名空间; 也就是说, 可以在脚本的任意位置调用它们.

如果库文件使用 #Include, 那么 #Include 的工作目录为库文件自身所在的目录. 利用这个特性可以重定向到较大的库文件, 其中包含此函数及相关内容.

脚本编译器(ahk2exe) 同样支持库函数. 不过, 它要求在编译器目录的上一级目录中存在 AutoHotkey.exe 的副本(通常是这样). 如果不存在 AutoHotkey.exe, 编译器仍可以运行但无法自动包含库函数.

从库中导入的函数和其他函数执行的一样好, 因为在脚本开始执行前已经预加载了它们.

## 内置函数

在内置函数参数列表末尾的任何可选参数可以完全省略. 例如, `WinExist("Untitled - Notepad")` 是有效的, 因为它的其他三个参数被认为是空的.

如果脚本定义了与内置函数同名的函数, 那么内置函数会被覆盖. 例如, 脚本会调用它自己定义的 WinExist() 函数来代替标准的那个. 然而, 这样脚本将无法调用原来的函数.

在 DLL 文件中的外部函数可以使用 DllCall() 调用.

下列内置函数都可以通过点击它们的名称查看详细信息.

### 常用函数

函数 | 描述
--- | ---
FileExist | 检查文件或文件夹是否存在且返回它的属性.
GetKeyState | 获取指定键盘按键是否被按下的状态, 当指定按键被按下时返回 true(1) 否则返回 false(0).
InStr | 从左侧或右侧截取给定字符串的一部分.
RegExMatch | 判断字符串是否包含某个匹配模式(正则表达式) .
RegExReplace | 替换字符串中匹配模式(正则表达式) 出现的地方.
StrLen | 获取给定字符串的长度.
StrReplace | 用新的字符串替换指定字符串出现的地方.
StrSplit | 使用指定的分隔符把一个字符串分解成多个子字符串并保存到数组中.
SubStr | 从字符串的指定位置获取一个或多个字符.
WinActive | 检查指定的窗口是否处于活动状态, 并返回其唯一 ID(HWND).
WinExist | 检查是否存在指定的窗口, 并返回第一个匹配窗口的唯一 ID(HWND).

### 杂项函数

函数 | 描述
--- | ---
Asc | 返回指定字符串第一个字节或 UTF-16 编码单元的数值.
Chr | 返回指定数字所代表的字符编码中相对应的字符串(通常只有一个字节).
DllCall | 调用 DLL 文件中的函数, 例如标准的 Windows API 函数.
Exception | 创建一个用于抛出自定义异常的对象.
FileOpen | 打开文件, 从其中读取特定内容和/或将新内容写入其中.
Format | 根据格式字符串, 设置可变数量的输入值的格式.
Func | 获取指定函数的引用.
GetKeyName/VK/SC | 获取按键的名称/文本, 虚拟按键码或扫描码.
Hotstring | 在脚本运行时创建, 修改, 启用或禁用热字串.
IL_XXX | ListView 或 TreeView 控件用来添加图标/图片, 创建或删除 ImageLists 的函数.
InputHook | 创建一个用于收集或拦截键盘输入的对象.
IsByRef | 如果指定的 ByRef 参数(按地址传递的参数) 作为变量提供, 则返回一个非零数字.
IsFunc | 如果脚本中存在指定的函数, 则返回一个非零数字.
IsLabel | 如果脚本中存在指定的标签, 则返回一个非零数字.
IsObject | 如果指定的值是对象, 则返回一个非零数字.
LoadPicture | 从文件中加载图片并返回位图或图标句柄.
LV_XXX | 用于增加, 插入, 修改或删除 ListView 行/列的函数, 还可以用于从中获取数据.
MenuGetHandle | 检索菜单的 Win32 菜单句柄.
MenuGetName | 检索给定其底层 Win32 菜单句柄的菜单的名称.
NumGet | 返回在指定地址+偏移的位置存储的二进制数.
NumPut | 以二进制格式把数字存储到指定地址+偏移的位置.
ObjAddRef / ObjRelease | 增加或减少对象的引用计数.
ObjBindMethod | 创建绑定函数对象, 它能够调用给定对象的方法.
ObjGetBase | 检索对象的基对象.
ObjRawGet | 绕过对象的元函数, 检索对象中的键值对.
ObjRawSet | 绕过对象的元函数, 存储或覆盖对象中的一组键值对.
ObjSetBase | 设置对象的基对象.
ObjXXX | 相当于对象类型的内置方法的函数, 如 ObjInsertAt. 通常建议使用相应的方法.
OnClipboardChange | 注册一个每当剪贴板内容发生改变时都会运行的函数或函数对象.
OnError | 指定一个在未处理错误发生时自动运行的函数.
OnExit | 指定一个在脚本退出时自动运行的函数.
OnMessage | 监听消息/事件.
Ord | 返回指定字符串中第一个字符的序号值(数字字符编码).
SB_XXX | 用于向状态栏控件添加文本/图标或划分状态栏的函数.
StrGet | 从内存地址复制字符串, 并可以将其在不同代码页间进行转换.
StrPut | 复制字符串到内存地址, 并可以对其在不同代码页间进行转换.
RegisterCallback | 创建机器码地址, 当它被调用时会重定向到脚本中的函数.
Trim / LTrim / RTrim | 移除字符串的开头和/或末尾处的某些字符.
TV_XXX | 用于添加, 修改或删除 TreeView 单元格的函数, 还可用于从其中获取数据.
VarSetCapacity | 增加变量的容量或释放其内存.

### 数学函数

函数 | 描述
--- | ---
Abs | 返回 Number 的绝对值.
Ceil | 返回 Number 的向上取整(不带任何 .00 后缀).
Exp | 返回 e(近似值为 2.71828182845905) 的 N次幂.
Floor | 返回 Number 的向下取整(不带任何 .00 后缀).
Log | 返回 Number 的对数(底数为 10).
Ln | 返回 Number 的自然对数(底数为 e).
Max / Min | 返回 一个或多个数字的最大/最小值.
Mod | 返回被除数 Dividend 除以除数 Divisor 后得到的余数.
Round | 返回 Number 四舍五入到 N 个小数位的数字.
Sqrt | 返回 Number 的平方根.
Sin / Cos / Tan | 返回 Number 的 sine(正弦)/cosine(余弦)/tangent(正切) 值.
ASin / ACos / ATan | 返回 Number 的 arcsine(反正弦)/arccosine(反余弦)/arctangent(反正切) 的弧度值.

### COM

函数 | 描述
--- | ---
ComObjActive | 检索已注册的 COM 对象.
ComObjArray | 创建用于 COM 的安全数组.
ComObjConnect | 将 COM 对象的事件源连接到具有给定前缀的函数.
ComObjCreate | 创建 COM 对象.
ComObject | 创建表示要作为参数或返回值传递的类型化值的对象.
ComObjEnwrap / ComObjUnwrap | 包装/解包 COM 对象.
ComObjError | 启用或禁用 COM 错误通知.
ComObjFlags | 检索或改变控制 COM 包装器对象行为的标志.
ComObjGet | 返回由 COM 组件提供的对象引用.
ComObjMissing | 创建要传递给 COM 方法的 "缺少参数" 对象.
ComObjParameter | 包装要作为参数传递给 COM 方法的值和类型.
ComObjQuery | 查询 COM 对象的接口或服务.
ComObjType | 从 COM 对象检索类型信息.
ComObjValue | 检索存储在 COM 包装器对象中的值或指针.

### 其他函数

Polyethene 的命令函数: 为每个有 OutputVar 的 AutoHotkey 命令提供了可调用的函数. 可以通过 #Include 把这个库包含在任何脚本中.

dftg