Funktionen

Inhaltsverzeichnis

Einführung und einfache Beispiele

Eine Funktion ist ein wiederverwendbarer Codeblock, der durch einen Aufruf ausgeführt werden kann. Eine Funktion kann optional Parameter (Eingaben) akzeptieren und einen Wert (Ausgabe) zurückgeben. Das folgende Beispiel ist eine einfache Funktion, die zwei Zahlen akzeptiert und deren Summe zurückgibt:

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

So etwas nennt man auch eine Funktionsdefinition, weil es eine Funktion mit dem Namen "Addieren" erstellt (nicht Groß-/Kleinschreibung-sensitiv) und festlegt, dass jeder, der die Funktion aufruft, genau zwei Parameter (x und y) bereitstellen muss. Um die Funktion aufzurufen, weisen Sie ihr Ergebnis einer Variable mit dem Doppelpunkt-Gleich-Operator (:=) zu. Zum Beispiel:

Var := Addieren(2, 3)  ; Speichert die Zahl 5 in Var.

Eine Funktion kann auch aufgerufen werden, ohne ihren Rückgabewert zu speichern:

Addieren(2, 3)
Addieren 2, 3  ; Klammern sind unnötig, wenn dies am Zeilenanfang steht.

In diesem Fall wird jedoch der Rückgabewert der Funktion verworfen; diese Art des Aufrufs ist nur dann sinnvoll, wenn die Funktion einen anderen Effekt als ihren Rückgabewert hat.

Innerhalb eines Ausdrucks wird ein Funktionsaufruf ausgewertet und mit dem Rückgabewert der Funktion "ersetzt". Der Rückgabewert kann wie oben gezeigt in eine Variable gespeichert oder direkt wie folgt verwendet werden:

if InStr(MeineVar, "Fuchs")
    MsgBox "Die Variable MeineVar enthält das Wort Fuchs."

Parameter

Beim Definieren einer Funktion werden ihre Parameter in runden Klammern rechts neben ihrem Namen aufgelistet (zwischen dem Namen und der runden Startklammer sind keine Leerzeichen erlaubt). Wenn die Funktion keine Parameter akzeptiert, kann der Inhalt zwischen den runden Klammern leer gelassen werden, z.B. ErmittleAktuellenZeitstempel().

Bekannte Einschränkung:

ByRef-Parameter

Innerhalb der Funktion werden Parameter grundsätzlich als lokale Variablen behandelt, es sei denn, sie werden wie im folgenden Beispiel als ByRef markiert:

a := 1, b := 2
Tauschen(&a, &b)
MsgBox a ',' b

Tauschen(&Links, &Rechts)
{
    temp := Links
    Links := Rechts
    Rechts := temp
}

Im obigen Beispiel wird der Aufrufer durch & gezwungen, eine VarRef zu übergeben, die üblicherweise zu einer der Variablen des Aufrufers gehört. Jeder Parameter wird zu einem Alias der Variable, die durch die VarRef repräsentiert wird. Mit anderen Worten, der Parameter und die Variable des Aufrufers verweisen auf denselben Inhalt im Speicher. Auf diese Weise kann die Tauschen-Funktion die Variablen des Aufrufers ändern, indem sie den Inhalt der Links-Variable in die Rechts-Variable verschiebt, und umgekehrt.

Ohne ByRef wären Links und Rechts dagegen nur Kopien der Variablen des Aufrufers - die Tauschen-Funktion hätte keine externe Wirkung. Die Funktion könnte jedoch auch so geändert werden, dass jede VarRef explizit dereferenziert wird. Zum Beispiel:

Tauschen(Links, Rechts)
{
    temp := %Links%
    %Links% := %Rechts%
    %Rechts% := temp
}

VarRefs können als Ergänzung zu Return verwendet werden, um mehr als einen Wert zurückzugeben. Dazu muss der Aufrufer eine Referenz, die auf eine (meist leere) Variable verweist, übergeben, in der die Funktion einen Wert speichern kann.

Die Übergabe von langen Zeichenketten an eine Funktion via ByRef ist performanter und verhindert, dass die Zeichenketten doppelten Speicherplatz verbrauchen, weil von ihnen eine Kopie erstellt werden muss. Auch die Rückgabe von langen Zeichenketten an den Aufrufer via ByRef ist in der Regel performanter als z.B. Return LangeZeichenkette. Was die Funktion erhält, ist jedoch nicht eine Referenz zur Zeichenkette, sondern eine Referenz zur Variable. Zukünftige Verbesserungen könnten die Verwendung von ByRef für solche Zwecke überflüssig machen.

Bekannte Einschränkungen:

Optionale Parameter

Beim Definieren einer Funktion können beliebig viele Parameter als optional gekennzeichnet werden.

Fügen Sie := an, gefolgt von einer direkt geschriebenen Zahl, einer in Anführungszeichen gesetzten Zeichenkette wie "fox" oder "", oder einem Ausdruck, der jedes Mal ausgewertet werden soll, wenn der Parameter mit seinem Standardwert initialisiert werden muss. Zum Beispiel würde X:=[] jedes Mal ein neues Array erzeugen.

Fügen Sie ? oder := unset an, um einen Parameter zu definieren, der standardmäßig ungesetzt ist.

Die folgende Funktion hat einen Z-Parameter, der als optional gekennzeichnet ist:

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

Ruft man die Funktion mit drei Parametern auf, wird der Standardwert von Z ignoriert. Ruft man die Funktion jedoch nur mit zwei Parametern auf, bekommt die Z-Variable automatisch den Wert 0 zugewiesen.

Es ist nicht möglich, optionale Parameter isoliert in der Mitte der Parameterliste anzugeben. Mit anderen Worten: Alle Parameter rechts vom ersten optionalen Parameter müssen ebenfalls als optional gekennzeichnet sein. Allerdings können optionale Parameter in der Mitte der Parameterliste weggelassen werden, wenn die Funktion aufgerufen wird. Zum Beispiel:

MeineFunk(1,, 3)
MeineFunk(X, Y:=2, Z:=0) {  ; Beachten Sie, dass Z in diesem Fall optional bleiben muss.
    MsgBox X ", " Y ", " Z
}

ByRef-Parameter können auch einen Standardwert haben, z.B. MeineFunk(&p1 := "") {. Wenn die Funktion ohne diesen Parameter aufgerufen wird, wird eine lokale Variable mit dem angegebenen Standardwert erstellt, d.h. die Funktion wird sich so verhalten, als würde das Symbol "&" fehlen.

Ungesetzte Parameter

Um einen Parameter ohne Angabe eines Standardwerts als optional zu markieren, verwenden Sie das Schlüsselwort unset oder das Suffix ?. In diesem Fall wird, wenn der Parameter weggelassen wird, die entsprechende Variable keinen Wert haben. Mit IsSet können Sie ermitteln, ob der Parameter einen Wert erhalten hat, wie unten gezeigt:

MeineFunk(p?) {  ; Äquivalent zu MeineFunk(p := unset)
    if IsSet(p)
        MsgBox "Aufrufer hat " p " übergeben"
    else
        MsgBox "Aufrufer hat nichts übergeben"
}

MeineFunk(42)
MeineFunk

Der Versuch, den Wert des Parameters zu lesen, wenn er keinen hat, wird als Fehler gewertet, was auch für jede uninitialisierte Variable gilt. Um einen optionalen Parameter an eine andere Funktion weiterzugeben, auch wenn der Parameter keinen Wert hat, verwenden Sie den Vielleicht-Operator (Var?). Zum Beispiel:

Begrüßen(Titel?) {
    MsgBox("Hallo!", Titel?)
}

Begrüßen "Begrüßung"  ; Titel ist "Begrüßung"
Begrüßen             ; Titel ist A_ScriptName

Werte an den Aufrufer zurückgeben

Wie bereits in der Einführung erwähnt, kann eine Funktion einen Wert via Return an ihren Aufrufer zurückgeben.

MsgBox returnTest()

returnTest() {
    return 123
}

Wenn eine Funktion mehr als ein Ergebnis zurückgeben soll, können Sie auch ByRef (&) verwenden:

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

returnByRef(&val1, &val2, val3)
{
    val1 := "A"
    val2 := 100
    %val3% := 1.1  ; % wird verwendet, weil & weggelassen wurde.
    return
}

Sie können Objekte und Arrays verwenden, um mehrere Werte oder sogar benannte Werte zurückzugeben:

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
}

Variadische Funktionen

Beim Definieren einer Funktion können Sie den letzten Parameter mit einem Sternchen versehen, um die Funktion als variadisch zu kennzeichnen, wodurch ihr eine variable Anzahl von Parametern übergeben werden kann:

Verbinden(sep, params*) {
    for index,param in params
        str .= param . sep
    return SubStr(str, 1, -StrLen(sep))
}
MsgBox Verbinden("`n", "eins", "zwei", "drei")

Beim Aufruf einer variadischen Funktion können überschüssige Parameter über ein Objekt abgerufen werden, das im letzten Parameter der Funktion gespeichert ist. Der erste überschüssige Parameter ist params[1], der Zweite ist params[2] und so weiter. Da es sich hierbei um ein Array handelt, kann params.Length verwendet werden, um die Parameteranzahl zu ermitteln.

Der Versuch, eine nicht-variadische Funktion mit mehr Parametern als möglich aufzurufen, wird als Fehler gewertet. Schreiben Sie * als letzten Parameter (ohne Parametername), um eine Funktion beliebig viele Parameter akzeptieren zu lassen, ohne ein Array zum Speichern der überschüssigen Parameter zu erstellen.

Hinweis: Der "variadische" Parameter kann nur am Ende der formalen Parameterliste stehen.

Variadische Funktionsaufrufe

Während variadische Funktionen eine variable Anzahl von Parametern akzeptieren, kann ein Array von Parametern an eine beliebige Funktion übergeben werden, indem die gleiche Syntax auf einen Funktionsaufruf angewendet wird:

TeilZketten := ["eins", "zwei", "drei"]
MsgBox Verbinden("`n", TeilZketten*)

Hinweise:

Bekannte Einschränkungen:

Lokale und globale Variablen

Lokale Variablen

Lokale Variablen sind funktionsspezifisch und nur innerhalb dieser Funktion sichtbar. Folglich können lokale und globale Variablen denselben Namen, aber einen unterschiedlichen Inhalt haben. Verschiedene Funktionen können ohne Bedenken die gleichen Variablennamen benutzen.

Alle lokalen Variablen, die nicht statisch sind, werden nach Abschluss der Funktion automatisch freigegeben (leer gemacht), mit Ausnahme von Variablen, die an ein Closure oder eine VarRef gebunden sind (solche Variablen werden zusammen mit dem Closure oder der VarRef freigegeben).

Interne Variablen wie A_Clipboard und A_TimeIdle sind nie lokal (sie sind von überall zugänglich) und können nicht neu deklariert werden. (Dies gilt nicht für interne Klassen wie Object, die als globale Variablen vordefiniert sind.)

Funktionen sind standardmäßig im Assume-Local-Modus. Variablen, die innerhalb einer Assume-Local-Funktion verwendet oder erstellt werden, sind standardmäßig lokal, aber es gibt folgende Ausnahmen:

Das Standardverhalten kann auch überschrieben werden, indem die Variable mit dem Schlüsselwort local deklariert wird oder indem der Modus der Funktion geändert wird (wie unten gezeigt).

Globale Variablen

Jede Variablenreferenz in einer Assume-Local-Funktion kann in eine globale Variable aufgelöst werden, wenn sie nur gelesen wird. Wenn eine Variable jedoch in einer Zuweisung oder mit dem Referenzoperator (&) verwendet wird, ist sie standardmäßig automatisch lokal. Dadurch können Funktionen globale Variablen lesen oder globale oder interne Funktionen aufrufen, ohne dass diese innerhalb der Funktion deklariert werden müssen. Außerdem wird das Skript vor unbeabsichtigten Nebeneffekten geschützt, wenn der Name einer zugewiesenen lokalen Variable mit dem einer globalen Variable identisch ist. Zum Beispiel:

InDateiLoggen(ZuLoggenderText)
{
    ; LogDateiName bekam bereits außerhalb dieser Funktion einen Wert zugewiesen.
    ; FileAppend ist eine vordefinierte globale Variable, die eine interne Funktion enthält.
    FileAppend ZuLoggenderText "`n", LogDateiName
}

Um andernfalls innerhalb einer Funktion auf eine bestehende globale Variable zu verweisen (oder eine neue zu erstellen), muss die Variable vor ihrer Verwendung als global deklariert werden. Zum Beispiel:

DatenVerzSetzen(Verz)
{
    global LogDateiName
    LogDateiName := Verz . "\Meine.log"
    global DatenVerz := Verz  ; Deklaration mit Zuweisung (siehe unten).
}

Assume-Global-Modus: Wenn eine Funktion viele globale Variablen verwenden oder erstellen muss, kann die Funktion so definiert werden, dass sie alle ihre Variablen (außer ihren Parametern) global behandelt. Dazu muss in ihrer ersten Zeile das Wort "global" stehen. Zum Beispiel:

StandardwerteSetzen()
{
    global
    GlobaleVar := 33  ; Speichert 33 in eine globale Variable und erstellt vorher sie bei Bedarf.
    local x, y:=0, z  ; Lokale Variablen müssen in diesem Modus deklariert werden, sonst werden sie global behandelt.
}

Statische Variablen

Statische Variablen sind immer implizit lokal, unterscheiden sich aber von lokalen Variablen, da ihre Werte zwischen den Aufrufen gespeichert werden. Zum Beispiel:

InDateiLoggen(ZuLoggenderText)
{
    static GeloggteZeilen := 0
    GeloggteZeilen += 1  ; Behält einen Zähler lokal aufrecht (sein Wert wird zwischen den Aufrufen gespeichert).
    global LogDateiName
    FileAppend GeloggteZeilen ": " ZuLoggenderText "`n", LogDateiName
}

Es ist möglich, eine statische Variable bei ihrer Deklaration zu initialisieren. Fügen Sie einfach nach dem Variablennamen den Operator := an, gefolgt von einem beliebigen Ausdruck. Zum Beispiel: static X:=0, Y:="fox". Statische Deklarationen werden genauso ausgewertet wie lokale Deklarationen, außer dass nach erfolgreicher Auswertung einer statischen Initialisierung (oder einer Gruppe von verbundenen Initialisierungen) diese effektiv aus dem Kontrollfluss entfernt und kein zweites Mal ausgeführt wird.

Verschachtelte Funktionen können als statisch deklariert werden, um zu verhindern, dass sie nicht-statische lokale Variablen der äußeren Funktion erfassen.

Assume-Static-Modus: Eine Funktion kann so definiert werden, dass sie alle ihre undeklarierten lokalen Variablen (außer ihren Parametern) statisch behandelt. Dazu muss in ihrer ersten Zeile das Wort "static" stehen. Zum Beispiel:

VomStatischenArrayAbrufen(ElementNummer)
{
    static
    static ErsterAufruf := true  ; Jede Initialisierung einer statischen Deklaration wird nur einmal ausgeführt.
    if ErsterAufruf  ; Erstellt ein statisches Array beim ersten Aufruf, danach nicht mehr.
    {
        ErsterAufruf := false
        StatischesArray := []
        Loop 10
            StatischesArray.Push("Wert Nr. " . A_Index)
    }
    return StatischesArray[ElementNummer]
}

Im Assume-Static-Modus muss jede Variable, die nicht statisch sein soll, als lokal oder global deklariert werden (mit den gleichen Ausnahmen wie im Assume-Local-Modus).

Mehr zu lokalen und globalen Deklarationen

Es ist möglich, mehrere Variablen in derselben Zeile zu deklarieren. Trennen Sie sie wie folgt durch Kommas:

global LogDateiName, MaxVersuche := 5
static GesamtVersuche := 0, VorherErgebnis

Es ist möglich, eine Variable bei ihrer Deklaration zu initialisieren. Fügen Sie einfach nach dem Variablennamen eine Zuweisung an. Im Gegensatz zu statischen Initialisierungen werden lokale und globale Initialisierungen bei jedem Funktionsaufruf ausgeführt. Mit anderen Worten, eine Zeile wie local x := 0 hat den gleichen Effekt wie, als würde man zwei einzelne Zeilen schreiben: local x, gefolgt von x := 0. Es kann ein beliebiger Zuweisungsoperator verwendet werden, allerdings erfordert eine Verbundzuweisung wie global Trefferanzahl += 1, dass der Variable zuvor ein Wert zugewiesen wurde.

Da die Wörter local, global und static sofort beim Start des Skripts verarbeitet werden, kann eine Variable nicht bedingt unter Nutzung einer IF-Anweisung deklariert werden. Mit anderen Worten, eine Deklaration innerhalb eines IF- oder ELSE-Blocks wird bedingungslos für die gesamte Funktion wirksam (aber alle in der Deklaration enthaltenen Initialisierungen sind immer noch bedingt). Eine dynamische Deklaration wie global Array%i% ist nicht möglich, da alle nicht-dynamischen Referenzen zu Variablen wie Array1 oder Array99 bereits in Adressen aufgelöst worden wären.

Funktionen dynamisch aufrufen

Obwohl ein Funktionsaufrufausdruck normalerweise mit einem direkt geschriebenen Funktionsnamen beginnt, kann das Ziel des Aufrufs ein beliebiger Ausdruck sein, der ein Funktionsobjekt erzeugt. Im Ausdruck GetKeyState("Shift") ist GetKeyState eigentlich eine Variablenreferenz, obwohl sie üblicherweise auf eine schreibgeschützte Variable verweist, die eine interne Funktion enthält.

Ein Funktionsaufruf wird als dynamisch bezeichnet, wenn das Ziel des Aufrufs während der Skriptausführung ermittelt wird, nicht vor dem Start des Skripts. Die Syntax ist dieselbe wie bei normalen Funktionsaufrufen; der einzige offensichtliche Unterschied besteht darin, dass bestimmte Fehlerprüfungen für nicht-dynamische Aufrufe beim Laden des Skripts durchgeführt werden, während sie für dynamische Aufrufe nur bei der Skriptausführung durchgeführt werden.

Zum Beispiel würde MeineFunk() das in MeineFunk enthaltene Funktionsobjekt aufrufen, das entweder der tatsächliche Name einer Funktion sein kann, oder eine Variable, der eine Funktion zugewiesen wurde.

Es können auch andere Ausdrücke als Ziel eines Funktionsaufrufs verwendet werden, einschließlich Doppeldereferenzen (double-derefs). Zum Beispiel würde MeinArray[1]() die Funktion aufrufen, die im ersten Element von MeinArray enthalten ist, während %MeineVar%() die Funktion aufrufen würde, die in der Variable enthalten ist, deren Name in MeineVar enthalten ist. Mit anderen Worten, der Ausdruck vor der Parameterliste wird zuerst ausgewertet, um ein Funktionsobjekt abzurufen, danach wird dieses Objekt aufgerufen.

Wenn der Zielwert aus einem der folgenden Gründe nicht aufgerufen werden kann, wird ein Error ausgelöst:

Grundsätzlich sollte der Aufrufer einer Funktion vor dem Funktionsaufruf wissen, was jeder Parameter bedeutet und wie viele es gibt. Handelt es sich jedoch um einen dynamischen Aufruf, wird der Code der Funktion oft an den Funktionsaufruf angepasst, sodass in solchen Fällen die Fehlerursache nicht unbedingt in falschen Parameterwerten liegen muss, sondern auch ein Schreibfehler in der Funktionsdefinition sein kann.

Boolesche Kurzschlussauswertung

AND, OR und der ternäre Operator innerhalb eines Ausdrucks reduzieren die Auswertung auf ein Minimum, um die Performanz zu erhöhen (egal ob Funktionsaufrufe vorhanden sind oder nicht). Beim Kurzschließen werden nur Bereiche des Ausdrucks ausgewertet, die Einfluss auf das Endergebnis haben. Das folgende Beispiel zeigt, wie das genau funktioniert:

if (FarbeName != "" AND not FindeFarbe(FarbeName))
    MsgBox FarbeName " konnte nicht gefunden werden."

Im Beispiel oben wird die FindeFarbe()-Funktion nie aufgerufen, wenn die FarbeName-Variable leer ist, weil die Auswertung der linken Seite von AND False ergibt, wodurch die rechte Seite nicht mehr in der Lage wäre, das Endergebnis auf True zu bringen.

Dieses Verhalten führt dazu, dass die Nebeneffekte einer Funktion (wie z.B. die Änderung des Inhalts einer globalen Variable) nie auftreten werden, wenn diese Funktion auf der rechten Seite von AND oder OR aufgerufen wird.

Beachten Sie auch, dass die Kurzschlussauswertung verschachtelte ANDs und ORs stufenweise abarbeitet. Wenn z.B. FarbeName im folgenden Ausdruck leer ist, wird nur der Vergleich ganz links durchgeführt, weil die linke Seite dann ausreichen würde, um das Endergebnis zweifelsfrei zu bestimmen:

if (FarbeName = "" OR FindeFarbe(FarbeName, Region1) OR FindeFarbe(FarbeName, Region2))
    break   ; Nichts zu suchen oder Übereinstimmung gefunden.

Wie die Beispiele oben zeigen, sollten umfangreiche (zeitaufwendige) Funktionen in der Regel auf der rechten Seite von AND oder OR aufgerufen werden, um die Performanz zu erhöhen. Diese Technik kann auch verwendet werden, um den Aufruf einer Funktion zu verhindern, wenn ihr ein Wert übergeben wurde, den sie für unpassend hält (z.B. eine leere Zeichenkette).

Der ternäre Bedingungsoperator (?:) wendet auch die Kurzschlussauswertung an, indem er nur die gewinnende Abzweigung auswertet.

Verschachtelte Funktionen

Eine verschachtelte Funktion ist eine Funktion, die in einer anderen Funktion definiert ist. Zum Beispiel:

außen(x) {
    innen(y) {
        MsgBox(y, x)
    }
    innen("eins")
    innen("zwei")
}
außen("Titel")

Der Zugriff auf eine verschachtelte Funktion kann nicht via Name außerhalb der äußeren Funktion erfolgen, aber überall innerhalb dieser Funktion, einschließlich innerhalb anderer verschachtelter Funktionen (mit Ausnahmen).

Standardmäßig kann eine verschachtelte Funktion auf jede statische Variable der äußeren Funktion zugreifen, sogar dynamisch. Allerdings wird eine nicht-dynamische Zuweisung innerhalb einer verschachtelten Funktion typischerweise in eine lokale Variable aufgelöst, wenn die äußere Funktion weder eine Deklaration noch eine nicht-dynamische Zuweisung für diese Variable hat.

Standardmäßig "erfasst" eine verschachtelte Funktion automatisch eine nicht-statische lokale Variable einer äußeren Funktion, wenn die folgenden Voraussetzungen erfüllt sind:

  1. Die äußere Funktion muss die Variable auf mindestens eine der folgenden Arten referenzieren:
    1. Indem sie mit local deklariert wird oder als Parameter oder verschachtelte Funktion.
    2. Als nicht-dynamisches Ziel einer Zuweisung oder des Referenzoperators (&).
  2. Die innere Funktion (oder eine darin verschachtelte Funktion) muss auf die Variable nicht-dynamisch verweisen.

Eine verschachtelte Funktion mit erfassten Variablen wird Closure genannt.

Ein dynamischer Zugriff auf nicht-statische lokale Variablen der äußeren Funktion ist nicht möglich, es sei denn, sie wurden erfasst.

Explizite Deklarationen haben immer Vorrang vor lokalen Variablen der umschließenden Funktion. Zum Beispiel deklariert local x eine Variable, die lokal für die aktuelle Funktion ist, unabhängig von einem x in der äußeren Funktion. Globale Deklarationen in der äußeren Funktion beeinflussen auch verschachtelte Funktionen, es sei denn, sie wurden durch eine explizite Deklaration überschrieben.

Wenn eine Funktion als Assume-Global deklariert ist, sind alle lokalen oder statischen Variablen, die außerhalb dieser Funktion erstellt wurden, für die Funktion selbst oder eine ihrer verschachtelten Funktionen nicht direkt zugänglich. Eine verschachtelte Assume-Static-Funktion hingegen kann immer noch auf die Variablen der äußeren Funktion zugreifen, es sei denn, die Funktion selbst ist als statisch deklariert.

Funktionen sind standardmäßig im Assume-Local-Modus, was auch für verschachtelte Funktionen gilt, sogar innerhalb einer Assume-Static-Funktion. Wenn jedoch die äußere Funktion im Assume-Global-Modus ist, verhalten sich verschachtelte Funktionen so, als gelte standardmäßig Assume-Global, außer dass sie auf die lokalen und statischen Variablen der äußeren Funktion verweisen können.

Jede Funktionsdefinition erzeugt eine schreibgeschützte Variable, die die Funktion selbst enthält, d.h. ein Func- oder Closure-Objekt. Anwendungsbeispiele finden Sie weiter unten.

Statische Funktionen

Jede verschachtelte Funktion, die keine Variablen erfasst, ist automatisch statisch, d.h. jeder Aufruf der äußeren Funktion verweist auf dasselbe Func. Das Schlüsselwort static kann verwendet werden, um eine verschachtelte Funktion explizit als statisch zu deklarieren; in diesem Fall werden alle nicht-statischen lokalen Variablen der äußeren Funktion ignoriert. Zum Beispiel:

außen() {
    x := "äußerer Wert"
    static innen() {
        x := "innerer Wert"  ; Erstellt eine lokale Variable
        MsgBox Type(innen)  ; Zeigt "Func" an
    }
    innen()
    MsgBox x  ; Zeigt "äußerer Wert" an
}
außen()

Eine statische Funktion kann nicht auf andere verschachtelte Funktionen außerhalb ihres eigenen Körpers verweisen, es sei denn, diese sind explizit als statisch deklariert. Beachten Sie, dass eine nicht-statische verschachtelte Funktion (selbst wenn sie im Assume-Static ist) ein Closure werden kann, wenn sie einen Funktionsparameter referenziert.

Closures

Ein Closure ist eine verschachtelte Funktion, die an einen Satz freier Variablen gebunden ist. Freie Variablen sind lokale Variablen der äußeren Funktion, die auch von verschachtelten Funktionen verwendet werden. Closures ermöglichen einer oder mehreren verschachtelten Funktionen, ihre Variablen mit der äußeren Funktion zu teilen, selbst nach Rückkehr der äußeren Funktion.

Um ein Closure zu erstellen, definieren Sie einfach eine verschachtelte Funktion, die auf die Variablen der äußeren Funktion verweist. Zum Beispiel:

Begrüßer_erstellen(f)
{
    begrüße(Subjekt)  ; Dies wird wegen f ein Closure sein.
    {
        MsgBox Format(f, Subjekt)
    }
    return begrüße  ; Das Closure zurückgeben.
}

g := Begrüßer_erstellen("Hallo, {}!")
g(A_UserName)
g("Welt")

Closures können auch mit internen Funktionen wie SetTimer oder Hotkey verwendet werden. Zum Beispiel:

app_hotkey(tastenname, app_titel, app_pfad)
{
    aktivieren(tastenname)  ; Dies wird wegen app_titel und app_pfad ein Closure sein.
    {
        if WinExist(app_titel)
            WinActivate
        else
            Run app_pfad
    }
    Hotkey tastenname, aktivieren
}
; Win+N aktiviert oder startet Notepad.
app_hotkey "#n", "ahk_class Notepad", "notepad.exe"
; Win+W aktiviert oder startet WordPad.
app_hotkey "#w", "ahk_class WordPadClass", "wordpad.exe"

Eine verschachtelte Funktion ist automatisch ein Closure, wenn sie alle nicht-statischen lokalen Variablen der äußeren Funktion erfasst. Die Variable, die zum Closure selbst gehört (z.B. aktivieren), ist ebenfalls eine nicht-statische lokale Variable, d.h. jede verschachtelte Funktion, die auf ein Closure verweist, ist automatisch ein Closure.

Jeder Aufruf der äußeren Funktion erzeugt neue Closures, die sich von allen vorherigen Aufrufen unterscheiden.

Es wird davon abgeraten, eine Referenz zu einem Closure in eine der freien Variablen der äußeren Funktion zu speichern, da dies einen Referenzzyklus erzeugt, der unterbrochen werden muss (z.B. durch Leeren der Variable), bevor das Closure freigegeben werden kann. Ein Closure kann jedoch bedenkenlos auf sich selbst und auf andere Closures über deren Originalvariablen verweisen, ohne einen problematischen Referenzzyklus zu erzeugen. Zum Beispiel:

timertest() {
    x := "tack!"
    tick() {
        MsgBox x           ; x macht dies zu einem Closure.
        SetTimer tick, 0   ; Originalvariable des Closure kann bedenkenlos verwendet werden.
        ; SetTimer t, 0    ; Das Erfassen von t würde einen Referenzzyklus erzeugen.
    }
    t := tick              ; Das ist okay, da t oben nicht erfasst wird.
    SetTimer t, 1000
}
timertest()

Jedes Mal, wenn die äußere Funktion aufgerufen wird, werden alle ihre freien Variablen als Gruppe im Speicher reserviert. Diese eine Gruppe freier Variablen ist mit allen Closures der Funktion verlinkt. Wenn die Originalvariable des Closure (tick im obigen Beispiel) von einem anderen Closure innerhalb derselben Funktion erfasst wird, ist ihre Lebensdauer an die Gruppe gebunden. Die Gruppe wird nur dann gelöscht, wenn es keine Referenzen auf eine ihrer Closures mehr gibt, außer denen in den Originalvariablen. Dadurch können sich Closures gegenseitig referenzieren, ohne einen problematischen Referenzzyklus zu verursachen.

Closures, die nicht von anderen Closures erfasst werden, können vor der Gruppe gelöscht werden. Alle freien Variablen innerhalb der Gruppe, einschließlich der erfassten Closures, können nicht gelöscht werden, solange ein solches Closure existiert.

Return, Exit und allgemeine Bemerkungen

Wenn die Ausführung die geschweifte Endklammer der Funktion vor einem Return erreicht, endet die Funktion und gibt einen leeren Wert (eine leere Zeichenkette) zurück. Ein leerer Wert wird auch zurückgegeben, wenn die Funktion den Parameter von Return explizit weglässt.

Wenn eine Funktion Exit zum Terminieren des aktuellen Threads verwendet, erhält ihr Aufrufer überhaupt keinen Rückgabewert. Das Var in Var := Addieren(2, 3) bliebe beispielsweise unverändert, wenn Addieren() via Exit beendet wird. Das gleiche geschieht, wenn die Funktion durch Throw oder einen Laufzeitfehler (z.B. Ausführen einer nicht-existierenden Datei) beendet wird.

Um eine Funktion mit einem oder mehreren leeren Werten (leeren Zeichenketten) aufzurufen, verwenden Sie zwei aufeinanderfolgende Anführungszeichen. Zum Beispiel: FindeFarbe(FarbeName, "").

Da der Aufruf einer Funktion keinen neuen Thread startet, wirken sich Änderungen an Einstellungen wie SendMode und SetTitleMatchMode auch auf den Aufrufer aus.

ListVars kann, wenn es in einer Funktion verwendet wird, die Namen und Inhalte von lokalen Variablen anzeigen. Dies kann helfen, ein Skript zu debuggen.

Stil- und Namenskonventionen

Bei komplexen Funktionen kann es hilfreich sein, spezielle Variablen mit eindeutigen Präfixen zu versehen, um die Übersichtlichkeit und Wartbarkeit des Skripts zu verbessern. Zum Beispiel können die Namen der Parameter einer Funktion mit "p" oder "p_" beginnen, damit die Parameter auf den ersten Blick leichter zu erkennen sind, insbesondere dann, wenn eine Funktion mehrere Dutzend lokale Variablen enthält. Ebenso kann das Präfix "r" oder "r_" für ByRef-Parameter und "s" oder "s_" für statische Variablen verwendet werden.

Je nach Bedarf kann der One True Brace (OTB) Style zum Definieren von Funktionen verwendet werden. Zum Beispiel:

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

Skripte mittels #Include auf dieselben Funktionen zugreifen lassen

Die #Include-Direktive kann verwendet werden, um Funktionen aus einer externen Datei zu laden.

Interne Funktionen

Eine interne Funktion gilt als überschrieben, wenn im Skript eine Funktion gleichen Namens definiert ist. Zum Beispiel könnte man anstelle der normalen WinExist-Funktion eine selbstdefinierte Funktion verwenden. Allerdings kann danach die originale Funktion nicht mehr aufgerufen werden.

Externe Funktionen, die sich in DLL-Dateien befinden, können via DllCall aufgerufen werden.

Eine Liste aller internen Funktionen finden Sie unter Alphabetischer Funktionsindex.