関数
目次
導入と簡単な例[編集]
関数はサブルーチン(Gosub)に似たものであるが、呼び出し元から引数を受け入れることができる。また、呼び出し元に値を返すこと(戻り値)も可能である。
以下の例は、二つの値を受けてその合計値を返す関数である。
Add(x, y)
{
Return x + y ; これは「式」として扱われる
}
上記の例は関数の定義であり、名前を「Add」であり(英字の大小は区別されない)、呼び出し時には必ず二つのパラメタ( x および y )が必要となるのがわかる。
関数の呼び出す際に、代入演算子(:=
)を利用することで関数の結果を格納することができる。
以下に例を挙げる:
Var := Add(2, 3) ; 数値の 5 が変数に格納される
関数の結果を変数に格納しないでも呼び出しは可能である。
Add(2, 3)
上記の例では関数の戻り値はどこにも利用されずに破棄される。戻り値を返す以外の効果を持たない関数をこのように呼び出しても何の意味もない。
関数コールは式の一種であるため、変数の参照にはパーセント記号(%
)で変数を囲む必要はない。その一方で、リテラル値はダブルクォート("
)で囲む必要がある。
以下に例を挙げる:
If InStr(MyVar, "fox") MsgBox The variable MyVar contains the word fox.
最後に、関数は式であるため、あらゆるコマンドのパラメタで利用が可能である(ただし OutputVar と InputVar は除く)。
パラメタに式が利用できないコマンドの場合は、「%
」接頭辞を付与することで適応が可能となる。
以下に例を挙げる:
MsgBox, % "The answer is: " . Add(3, 2)
「%
」接頭辞は、式が許容されるパラメタで利用しても良いが、無視される。
関数の引数[編集]
関数の定義の際に、引数は関数名の直後の開き括弧に続いて列挙する(関数名と開き括弧の間に空白を入れてはならない)。
もし、関数が引数を一つも必要としない場合は、引数の列挙を行わずにすぐに括弧を閉じる。以下に例を挙げる:
GetCurrentTimestamp()
ByRef パラメタ[編集]
関数側から見るとパラメタは全てローカル変数であるが、ByRef をパラメタに指定することで呼び出し元の変数そのもの扱うことができるようになる。
以下に例を挙げる:
Swap(ByRef Left, ByRef Right) { temp := Left Left := Right Right := temp }
この例では、ByRef がついたパラメタはどれも呼び出し元が引き渡した変数のエイリアス(別名)となる。
言い換えると、ByRef の付いたパラメタと呼び出し元のパラメタはメモリ上では同じ箇所を示している。
従って、この関数では呼び出し元の変数の内容を入れ替えることが可能となる。
反対に、ByRef を双方のパラメタに付けなかったとしたら、Left も Right も呼び出し元変数の内容のコピーを格納した単なるローカル変数となり、その内容を入れ替えたとしても関数以外に効果をもたらすことは無い。
Return は単一の値しか返すことが出来ないので、付加的な値を受け渡すために ByRef を利用するのも手である。
この場合、呼び出し元は値を格納したい変数(通常は空)を引数として渡し、関数側ではその変数に値を格納するといった処理を行う。
関数との間で非常に大きなサイズの文字列の受け渡しにも ByRef が有効となる。というのも、通常の引数の場合はローカル変数とするために、パラメタとして受け渡された変数の値を丸々コピーするが、サイズが大きい場合はその分パフォーマンスが低下してしまう。
そこで ByRef を使うとこの値のコピーが省略される。また、戻り値として巨大な文字列を返す場合も同様で、あらかじめ ByRef で格納対象とする変数を指定してやることで、Return で渡しコピーを格納するといった処理が避けることが可能となる。
AHKL AHKL [L60+] 変更可能な変数以外のパラメタがByRef で受け渡された場合、関数側では ByRef が無いものとして振る舞うようになる。
例えば、「Swap(A_Index, i)
」とした場合、 i には A_Index の内容が格納されるが、変数 A_Index には読み取り専用なので ByRef が無いものとして扱われ何も起きない。
既知の制限事項:
- Clipboard、組み込み変数、環境変数(
#NoEnv
が指定されていないとき)は ByRef パラメタとして引き渡すことは出来ない。 - 関数は自分自身を再帰的に呼び出すことは可能だが、自身のローカル変数や非ByRefパラメタを、ByRef パラメタとして引き渡した場合、再帰的に呼ばれた方の ByRef パラメタは、呼び出し元側のローカル変数ではなく、自身のローカル変数を参照する。これは グローバル変数や静的変数・ByRef パラメタを引き渡した場合はこの限りではない。
- 関数の呼び出しの際に変数に値が格納されるような場合(例: Var, Var++, Var*=2 など)は、その左右のにある変数はパラメタが引き渡される前に変数内容が変更される。例えば、「
Func(Var, Var++)
」とした場合で、Varの初期値が0だったときは、意図せずに 0 と 1 が引き渡される。これは第1引数が ByRef でない場合でも同様である。この振る舞いは直感的には理解しがたいものがあるので、将来的には変更される可能性がある。
任意パラメタ(引数のデフォルト値)[編集]
関数の定義の際に、いくつかのパラメタを任意指定にしたい場合があるだろう。
その際は、引数に続いてイコール(=
)でデフォルト値を付与することで実現可能である。
以下の例ではパラメタ Z は任意となっている。
Add(X, Y, X=0){ Return X + Y + Z }
上例の場合、呼び出し元が3つのパラメタを引き渡した際は、引数Zは設定されているデフォルト値は無視され引き渡された値が格納され、2つのパラメタを渡した場合は、Z にはデフォルト値の 0 が格納されるようになる。
任意パラメタはパラメタリストの先頭や真ん中に孤立して存在させることは出来ない。つまり、任意パラメタは最後尾(一番右)から順に並べる必要がある。
AHKL AHKL [L31+] ダイナミック関数コールの場合を除いて、パラメタリスト内の中部にある任意パラメタは関数コールの際に除去される。
Func(1,, 3)
Func(X, Y=2, Z=0) { ; この場合でも Z は依然として任意パラメタとなっている。
MsgBox %X%, %Y%, %Z%
}
v1.0.46.13以降では、ByRefパラメタもデフォルト値を設定することが出来るようになった。
例: 「Func(ByRef p1="")」。呼び出し元がパラメタを省略した場合は、ローカル変数としてデフォルト値を格納されたものが引数となり、ByRefが無いかのような扱いとなる。
パラメタのデフォルト値は次のいずれかのみ指定可能である: True, False, リテラル数値, リテラル小数、引用符で囲ったリテラル文字列("fox"など)。式等はサポートされない。
可変長引数の関数[編集]
AHKL AHKL [L60+] 関数の定義の際に、最後の引数の変数名の直後にアスタリスク(*
)を付与することで可変長引数とすることができ、任意の個数のパラメタを受けとることが可能となる。
Join(sep, params*) {
for index,param in params
str .= param . sep
Return SubStr(str, 1, -StrLen(sep))
}
MsgBox % Join("`n", "one", "two", "three")
可変長引数の関数がコールされた場合、可変パラメタは配列オブジェクトの要素として最後のパラメタに格納される。
可変な要素の最初のパラメタは param[1]、次のパラメタは param[2] といった具合に格納されていく。
通常のオブジェクトと同様に params.MaxIndex()
で配列の最大の添え字(この場合はパラメタの数と同値)を得ることができる。
その一方で、パラメタが全て省略された場合は、MaxIndex は空文字を返す。
パラメタリストとして新規オブジェクトが生成されるので、最も単純な可変長引数を持つ関数は便利なことに配列を生成するのに非常に便利となっている。
; Three methods of creating an array:
arr := Object(1, x, 2, y, 3, z)
arr := Object(), arr.Insert(1, x, y, z)
arr := Array(x, y, z)
Array(values*) {
Return values
}
注意事項:
- 「可変長」にすることが出来る引数は最後のものだけである。
- RegEx Callout には可変長引数の関数は利用できない。「可変長」の引数をもったものでもエラーとはならないが該当変数は空となる。
- コールバック関数の場合、配列の最後の要素としてコールバック関数自体のアドレスを持つ。
可変長引数を関数に渡す[編集]
可変長引数関数は任意の個数のパラメタを受け入れる事が可能である。これにより、関数コールと同じ文法を適応することで、パラメタ配列を*任意の*関数や、オブジェクトのセッター/ゲッターに受け渡すことが可能となった。
substrings := Object(), substrings._Insert(1, "one", "two", "three") MsgBox % Join("`n", substrings*)
注意点:
- 元となる配列におけるパラメタ番号は1から開始されること。
- オプションパラメタは配列からは除去されている。
- 配列内のパラメタは、関数の引数として、数値順またはフィールド名に沿って引き渡される。
- 対象となる関数もまた可変長引数の場合で、可変長引数よりも前の引数名と被らないものは、同名のフィールドとして可変長引数の項目としてコピーされる。
既知の制限事項:
- これは物理的に最後のパラメタのみに適応可能。
Func(x, y*)
はOKだが、Func(x*, y)
はNG。 - 最後のパラメタとアスタリスクの間に空白を入れてはならない。
- オブジェクトのゲッター・セッター・メソッドに対しての名前付きパラメタはサポートされない。
関数内の変数[編集]
変数の有効範囲[編集]
通常の関数では、関数の中で使用される関数の中で使用される変数(組み込み変数を除く)は、関数内でのみ有効なローカル変数となる。
ローカル変数は、関数の呼出しごとに作成され、関数から戻る際に破棄される。
よって、以下のスクリプトは期待通りの動作をしない。
;xの値を設定する関数を作りたい
SetX(val){
x := val
}
x := 0
SetX(10)
MsgBox,%x%
グローバル変数[編集]
関数内から通常の変数(グローバル変数)にアクセスするには、 global
に続いてアクセスしたい変数名を記述する。複数の変数名を ,
で区切ってまとめて書くことができる。
以下のスクリプトは期待通りの動作になる。
;xの値を設定する関数を作りたい
SetX(val){
global x
x := val
}
x := 0
SetX(10)
MsgBox,%x%
また、変数の使用を宣言すると同時に、変数に値を代入することも出来る。
この時、 =
演算子は、比較演算子ではなく :=
演算子とみなされる。
SetX(val){ global x = val }
条件分岐を使用しても、条件によって変数をグローバルにするかローカルにするかを切り替えることは出来ない。
スタティック変数[編集]
同じ関数の中で共有される変数を作成するには static
に続いて変数名を書く。複数の変数名を ,
で区切ってまとめて書くことができる。
スタティック変数は、関数の中でしか参照できないが、同じ関数でひとつの変数が共有される。
AAA(){ static CalledTimes := 0 CalledTimes++ MsgBox,%CalledTimes%回目 }
スタティック変数の初期化では、数値や文字列の定数を代入することのみ可能。
スタティック変数は、スクリプトの起動時に一度だけ初期化される。
デフォルトの有効範囲をグローバル/スタティックにする[編集]
関数の最初に global
または static
とだけ書いた行があると、関数内に記述された全ての変数がグローバルまたはスタティック変数となる。
ただし、別途 global, local, static
を用いた宣言した変数はそれぞれの有効範囲となる。
変数の使用宣言と同様、複数の変数名を ,
で区切ってまとめて書いたり、宣言と同時に代入することも可能。
SetX(val){ global x = val }
関数の最初に global
が無くても、 local
の変数宣言があれば、それ以外の変数はグローバル変数として扱われる。
関数内で Array%i%
のような動的変数を使用した場合、ローカル変数として扱われる。
ただし、その名前のローカル変数が存在せず、グローバル変数なら存在する場合、そのグローバル変数が使われる。
ローカルにもグローバルにも存在せず、変数を新たに作成しなければならない場合、デフォルトの有効範囲で作成される。
デフォルトでスタティックにした場合でも変数の初期化もできるが、デフォルトにする宣言行とは別の行にする必要がある。
MyFunc() { static static xxx := 100 }
配列を生成するコマンドでの変数の振る舞い[編集]
StringSplitコマンドなどで配列を作成する場合、通常はローカル変数として作成される。
ただし、配列の最初の要素がglobal/static宣言されている場合は、全ての要素がグローバル/スタティック変数として作成される。
関数内での変数の初期化[編集]
スタティック変数と異なり、グローバル変数とローカル変数は宣言行で関数を含めた式を利用した初期化ができる。
MyFunc() { global xxx := 100 * 200 local yyy := WinExist("A") }
これは実質的に以下と等価であり、グローバル変数の初期化宣言を行っていても実際にその関数を呼び出さない限りグローバル変数に値はセットされない。
MyFunc() { global xxx local yyy xxx := 100 * 200 yyy := WinExist("A") }
関数の呼び出し[編集]
関数は、次の例のように式の中で使用できる。
Sum := Add(2,3)
上記の例では、Add関数の引数xとして 2
、引数yとして 3
を与えて呼び出し、その結果を変数 Sum
に代入している。
Add(Add(1,2),3+4)
のように、関数呼び出しの引数に式や関数呼び出しを記述することも可能。
その場合、まず内側の式が左の引数から順に計算され、次にそれらを引数として外側の関数が実行される。
変数に代入せず、関数の呼び出しだけを行うことも可能である。
下記の例では、2つの引数の和をダイアログ表示する関数を定義し、引数に2と3を与えて呼び出している。
ShowAdd(x,y){ MsgBox,% x+y } ShowAdd(2,3)
関数の中からほかの関数を呼び出すことも可能である。
呼び出しの深度の上限は159回で、160回目の呼び出しをしようとするとAutoHotkeyのプログラムが不正終了する。
再帰呼び出し[編集]
関数の中からその関数自身を呼び出すテクニックを再帰呼び出しという。
下記の例は、引数で与えられたnの階乗を求める関数である。
Factorial(n){ If n=1 Return 1 Else Return n*Factorial(n-1) }
なお、再帰呼び出しを行う関数で、 ByRef
による参照渡しの引数にローカル変数を与えると、呼び出し先の当該変数が参照されるようになってしまう。
よって、以下のスクリプトは正しく動作しない。
Factorial(ByRef n){ If n=1;(2)ここのnでは呼び出し元のxではなく、呼び出された側のxが参照されてしまう
Return x := n-1 Factorial(x);(1)ここでローカル変数xを参照渡しすると
n *= x }
動的呼び出し[編集]
「%funcName%(param1, param2, ...)
」とすることで関数の動的呼び出しが可能となる。変数funcName には呼び出したい関数の名前を格納する。
「func%A_Index%(param1, param2, ...)
」のように関数名の一部だけに変数展開を行うことも可能。
関数の戻り値は関数の呼び出しが成功した場合は関数の戻す値となる。関数が見つからないまたは呼び出し不可能な場合は空文字列が返る。
呼び出せる関数はパラメタの数が同一または少ないものまでだが、多いものでもパラメタのデフォルト値を設定しているものは呼び出せる。
また ByRef が指定されているパラメタに値そのものを渡した場合も呼び出しは失敗する。
Loop, 5 MsgBox, % MyFunc%A_Index%(100*A_Index) MyFunc1() { Return A_ThisFunc } MyFunc2(param1) { Return A_ThisFunc . "`n" . param1 } MyFunc3(param1, param2) { Return A_ThisFunc . "`n" . param1 . "`n" . param2 } MyFunc4(param1, param2="222", param3="333") { Return A_ThisFunc . "`n" . param1 . "`n" . param2 . "`n" . param3 } MyFunc5(ByRef param1) { param1 += 1000 Return A_ThisFunc . "`n" param1 }
- MyFunc1 はパラメタ数が少ないが、呼び出しは成功する。
- MyFunc2 はパラメタ数が同一で、呼び出しは成功する。
- MyFunc3 はパラメタ数が多いので、呼び出しは失敗する。
- MyFunc4 はパラメタ数が多いが、パラメタのデフォルト値が設定されているので呼び出しは成功する。
- MyFunc5 はパラメタ数が同一だが、ByRef に変数でなく値を渡しているので呼び出しは失敗する。
関数が存在するか、およびパラメタの数を確認する場合は IsFunc() を利用する。
補足: 呼び出す関数のチェックをスクリプトロード時に行う通常の関数呼び出しとは異なり、 実行時に関数名やパラメタの調査を行うので、動的関数呼び出しはパフォーマンスが多少落ちる。
関数内からのGosub/Goto/Exit[編集]
関数内からは、関数の内外のサブルーチンを Gosubで呼び出すことができる。
関数外のサブルーチンを呼び出した場合、呼び出されたサブルーチンからは関数内のローカル変数は呼び出せない。一方、関数に globalが宣言されていなくても、全てのグローバル変数を利用できる。
関数内からは、関数内のラベルにのみ Gotoでジャンプできる。
関数外のラベルにGotoでジャンプしようとした場合、その行は無視される。
関数内で実行中のスレッドを終了する Exitコマンドを実行すると、関数の呼び出し元に戻ることなくその時点でスレッドが終了する。
関数ライブラリスクリプト[編集]
関数ライブラリスクリプトとは #Include を利用しないでも、 関数のあるファイルを検索して自動的でインクルードを行う機能およびそのスクリプト仕様である。
ライブラリスクリプトにはライブラリディレクトリ場所に格納することで自動検索の対象となる。 未知の関数名が見つかった段階で、ライブラリディレクトリのスクリプトの検索を開始する。
- まず「関数名.ahk」のファイルを検索し該当関数があるかどうかを調査する。
- 関数が見つかったら該当ファイルをインクルード対象とする。
- 上記で見つからない場合で、関数名が「
XXX_YYY
」のように「_
」で区切られた形式の場合、 最初の「_
」の前の部分の名前のスクリプト(この例では「XXX.ahk
」)内を検索して、 該当関数があるかどうかを調べる。
- 関数が見つかったら該当ファイルをインクルード対象とする。
- 見つからない場合は、次のライブラリディレクトリを検索しにいく。
インクルード対象ファイルが見つかったらライブラリファイル内の該当の関数のみをインクルードするのではなく、 #Include でおこなうのと同様にファイル丸ごとインクルードされる。
インクルード対象ファイルは読み込みスクリプトの最後尾の位置に追加されるようにしてインクルードされる。
なお、どのライブラリディレクトリを検索しても関数が見つからない場合はエラーとなる。
ライブラリディレクトリ[編集]
自動検索対象となるディレクトリは以下のようなものがある。
ユーザライブラリ | ユーザ毎に定義できるライブラリで自由に使える%A_MyDocuments%\AutoHotkey\Lib\
|
スタンダードライブラリ | ユーザに関係なく利用できるライブラリで汎用的なものを置く{AutoHotkey.exeのあるディレクトリ}\Lib
|
スクリプトライブラリ | AHKL AHKL 特定スクリプトに限定したライブラリ%A_ScriptDir%/Lib
|
用途に関しての説明があるが実際の利用方法は自由である。 公式フォーラムで Standard Library と表記されているものは汎用的な機能を持ったコンポーネントが多い。
各ライブラリディレクトリの検索順序は以下の通り(AHKLのみスクリプトライブラリを最初に検索)。
[スクリプトライブラリ > ユーザライブラリ > スタンダードライブラリ
ライブラリスクリプトに関するその他の事項[編集]
- ライブラリスクリプト内でも、他のライブラリスクリプトの関数を呼び出し可能である。
- ライブラリスクリプト内に#Includeを記述した場合、パスの基準フォルダはそのライブラリスクリプトがあるフォルダになる。
- ahk2exe.exeでコンパイルする場合、使用されているライブラリスクリプトも読み込んでコンパイルされる。
この時、ahk2exe.exeは、AutoHotkey.exeがあるフォルダ内の何らかのフォルダ(通常は Compiler
)に置かれている必要がある。
- スクリプトロード時にのみ動作するので、動的関数呼び出しは考慮されない。
- ライブラリスクリプトをインクルードする際、関数のあるファイルを丸ごとインクルードするので、関数名やサブルーチンラベル名、ホットキーなどの定義が重複する可能性がある。
この場合は重複エラーとなり起動することは出来ないので、ライブラリスクリプトは最小限の構成にすべきである。
関数によるスクリプトのモジュール化[編集]
関数の定義だけを書いたスクリプトを #Include指令で読み込んで使うようにすることで、スクリプトの保守性を向上させられる。
他の人が作ったスクリプトを自分のスクリプトに組み込みたい場合などに、変数名の重複を気にしなくてすむので便利である。
組み込み関数[編集]
AutoHotkeyには、あらかじめ定義された組み込み関数が用意されている。
これらは、普通の関数と同じように使用できる。
定義されている組み込み関数については、コマンドリファレンスを参照。
組み込み関数は、同じ名前の関数を定義することで上書きできる。
その他[編集]
- 関数内で Gui,Add コマンドの
V
オプションで指定する変数名はグローバル変数でなければならない。 - 関数内の ループ をしている途中で Return を行うとループは即座に中断して呼び出し元に復帰する。
- 関数の呼び出しによって新たなスレッドが立ち上がることはない。従って ErrorLevel や A_ThisHotkey などスレッド固有の情報は引き継がれる。
- 関数内で ListVars コマンドを実行すると、関数内限定の変数とグローバル変数の両方を見ることが出来る。