객체

오토핫키에서 객체(object)는 추상 데이터 유형으로서 세 가지 기본 기능을 제공합니다:

  • GET 값을 얻는다.
  • SET 값을 설정한다.
  • CALL 메쏘드를 호출한다 (다시 말해, 메쏘드란 대상 객체를 가지고 일을 하는 함수입니다).

객체 참조는 특정 객체를 가리키는 포인터 또는 핸들("handle")입니다. 문자열과 숫자처럼, 객체 참조는 변수에 저장하고 함수로부터 반환하며 객체 안에 저장할 수 있습니다. x := y 처럼 참조를 한 변수에서 다른 변수로 복사하고 나면, 두 변수 모두 같은 객체를 가리킵니다.

IsObject를 사용하면 값이 객체인지 판별할 수 있습니다:

Result := IsObject(expression)
선택 | 내려받기

객체의 종류는 다음과 같습니다:

목차

기본 사용법

단순한 배열

배열을 만듭니다:

Array := [Item1, Item2, ..., ItemN]
Array := Array(Item1, Item2, ..., ItemN)
선택 | 내려받기

항목을 열람합니다:

Value := Array[Index]
선택 | 내려받기

항목을 할당합니다:

Array[Index] := Value
선택 | 내려받기

주어진 인덱스에 항목(들)을 삽입합니다:

Array.InsertAt(Index, Value, Value2, ...)
선택 | 내려받기

항목(들)을 추가합니다:

Array.Push(Value, Value2, ...)
선택 | 내려받기

항목을 제거합니다:

RemovedValue := Array.RemoveAt(Index)
선택 | 내려받기

마지막 항목을 제거합니다:

RemovedValue := Array.Pop()
선택 | 내려받기

배열이 비어 있지 않으면, MinIndexMaxIndex/Length는 배열에서 현재 사용중인 가장 낮은 인덱스와 가장 높은 인덱스를 돌려줍니다. 가장 낮은 인덱스는 거의 대부분 1이므로, MaxIndex는 보통 항목의 개수를 돌려줍니다. 그렇지만, 정수 키가 없다면, MaxIndex는 빈 문자열을 돌려줍니다. 반면에 Length는 0을 돌려줍니다. 배열의 내용은 인덱스 또는 For-loop로 회돌이할 수 있습니다. 예를 들어:

array := ["one", "two", "three"]

; 1부터 배열의 끝까지 반복합니다:
Loop % array.Length()
    MsgBox % array[A_Index]

; 배열의 내용을 열거합니다:
For index, value in array
    MsgBox % "Item " index " is '" value "'"
선택 | 내려받기

연관 배열

연관 배열은 유일한 키 집단과 유일한 값 집단을 포함하는 객체입니다. 각 키는 각 값에 연관되어 있습니다. 키는 문자열이나 정수또는 객체가 될 수 있는 반면, 값은 어떤 유형도 될 수 있습니다. 연관 배열을 다음과 같이 만들 수 있습니다:

Array := {KeyA: ValueA, KeyB: ValueB, ..., KeyZ: ValueZ}
Array := Object("KeyA", ValueA, "KeyB", ValueB, ..., "KeyZ", ValueZ)
선택 | 내려받기

{key:value} 표기법을 사용하면, 오직 단어 문자로만 구성된 키에는 인용부호를 생략해도 됩니다. 어떤 표현식도 키로 사용할 수는 있지만, 변수를 키로 사용하려면, 반드시 괄호로 둘러싸야 합니다. 예를 들어, {(KeyVar): Value}{GetKey(): Value}는 모두 유효합니다.

항목을 열람합니다:

Value := Array[Key]
선택 | 내려받기

항목을 할당합니다:

Array[Key] := Value
선택 | 내려받기

항목을 제거합니다:

RemovedValue := Array.Delete(Key)
선택 | 내려받기

항목을 열거합니다:

array := {ten: 10, twenty: 20, thirty: 30}
For key, value in array
    MsgBox %key% = %value%
선택 | 내려받기

연관 배열은 중간 중간 비워서 만들 수 있습니다 - 즉, {1:"a",1000:"b"}는 오직 두 개의 키-값 쌍만 들어 있습니다. 1000개가 아닙니다.

AutoHotkey v1.x에서 단순 배열과 연관 배열은 같은 것입니다. 그렇지만 []를 단순 배열로 간주하는 것이 그의 목적을 명확하게 보여주는 데 도움이 되고, 미래 버전의 오토핫키에 스크립트가 작동할 가능성을 높여줍니다. 앞으로는 단순 배열과 연관 배열 사이에 차이가 있을 수 있습니다.

객체

특성을 열람합니다:

Value := Object.Property
선택 | 내려받기

특성을 설정합니다:

Object.Property := Value
선택 | 내려받기

메쏘드를 호출합니다:

ReturnValue := Object.Method(Parameters)
선택 | 내려받기

계산한 메쏘드 이름으로 메쏘드를 호출합니다:

ReturnValue := Object[MethodName](Parameters)
선택 | 내려받기

COM 객체의 특성과 사용자-정의 객체는 매개변수를 받을 수 있습니다:

Value := Object.Property[Parameters]
Object.Property[Parameters] := Value
선택 | 내려받기

관련 항목: Object, File Object, Func Object, COM object

알려진 한계:

  • 현재 x.y[z]()x["y", z]()으로 간주되는데, 이는 지원하지 않습니다. 임시 해결책으로, (x.y)[z]()는 먼저 x.y를 평가한 다음, 그 결과를 메쏘드 호출의 목표로 사용합니다. x.y[z].Call()에는 이 한계가 없다는 것을 주목하십시오. 왜냐하면 (x.y[z]).Call()과 똑같이 평가되기 때문입니다.

객체 풀어주기

스크립트는 명시적으로 객체를 풀어주지 않습니다. 객체를 가리키는 마지막 참조가 사라지면, 그 객체는 자동으로 해제됩니다. 변수에 저장된 참조는 그 변수가 다른 값에 할당되면 자동으로 해제됩니다. 예를 들어:

obj := {}  ; 객체를 생성합니다.
obj := ""  ; 마지막 참조를 해제합니다. 그러므로 객체가 해방됩니다.
선택 | 내려받기

비슷하게, 또다른 객체의 필드에 저장된 참조점은 그 필드가 또다른 값에 할당되거나 그 객체로부터 제거될 때 자동으로 풀어집니다. 이것은 배열에도 적용됩니다. 배열은 실제로 객체입니다.

arr := [{}]  ; 객체를 담고 있는 배열을 생성합니다.
arr[1] := {}  ; 두 번째 객체를 생성합니다. 묵시적으로 첫 객체를 풀어주고 있습니다.
arr.Remove(1)  ; 두 번째 객체를 제거하고 풀어줍니다.
선택 | 내려받기

한 객체를 가리키는 모든 참조가 해제되어야 비로서 그 객체가 해방될 수 있습니다. 그래서 순환 참조가 있는 객체는 자동으로 풀어지지 않습니다. 예를 들면, x.childy를 가리키고 y.parentx를 가리킨다면, xy를 소거하는 것만으로는 충분하지 않습니다. 왜냐하면 부모 객체에 여전히 자손을 가리키는 참조가 들어있고 그 반대도 마찬가지이기 때문입니다. 이 문제를 해결하려면, 순환 참조를 제거해야 합니다.

x := {}, y := {}             ; 객체를 두 개 생성합니다.
x.child := y, y.parent := x  ; 순환 참조를 생성합니다.

y.parent := ""               ; 순환 참조를 먼저 제거해야 객체가 해방될 수 있습니다.
x := "", y := ""             ; 위의 줄이 없다면, 이 줄로는 객체를 풀어줄 수 없습니다.
선택 | 내려받기

더 자세한 내용과 고급 사용법은 참조 횟수 세기를 보십시오.

논평

구문

모든 유형의 객체는 배열 구문(각괄호)과 객체 구문 (점)을 지원합니다.

게다가, 객체 참조는 표현식에 사용할 수 있습니다:

  • 객체 참조를 = == != <>중의 하나를 사용하여 다른 값과 비교할 때, 두 값 모두 같은 객체를 가리키는 참조점일 경우에만 같다고 간주됩니다.
  • if obj, !obj 또는 obj ? x : y에서와 같이 불리언 값이 요구될 때, 객체는 언제나 참(true)으로 간주됩니다.
  • 객체의 주소는 & 주소 연산자를 사용하여 열람할 수 있습니다. 생성 시점부터 마지막 참조가 해제될 때까지 그 객체를 유일하게 식별합니다.

객체가 예상치 못한 문맥에서 사용중이면, 그 객체는 빈 문자열로 취급됩니다. 예를 들어, MsgBox %object% 는 빈 MsgBox를 보여주고 object + 1는 빈 문자열을 산출합니다. 이 행위는 바뀔 수 있으므로 여기에 의존하면 안 됩니다.

메쏘드-호출 다음에 곧바로 할당 연산자가 오면, 매개변수로 특성을 할당하는 것과 동등합니다. 예를 들어, 다음은 서로 동등합니다:

obj.item(x) := y
obj.item[x] := y
선택 | 내려받기

x.y += 1--arr[1]같은 복합 서술문을 지원합니다.

[v1.1.20+]: 특성을 설정하거나 획득할 때 매개변수를 생략할 수 있습니다. 예를 들어, x[,2]. 스크립트는 이를 이용하여 특성메타-함수의 매개변수에 기본 값들을 정의할 수 있습니다. 메쏘드 이름도 x[](a)처럼 완전히 생략 가능합니다. 스크립트는 이를 이용하여 __Call 메타-함수의 매개변수에 기본 값을 정의할 수 있습니다. 왜냐하면 그렇지 않으면 값이 제공되지 않기 때문입니다. 이것은 x.(a)와 다르며, x[""](a)과 동등한 것에 주목하십시오. COM 객체를 호출할 때 특성이나 메쏘드 이름이 생략되면, 그의 "기본 멤버"가 호출됩니다.

[], {} 또는 new 연산자로 생성된 객체에 어느 값을 사용할 수 있는지는 약간 제한이 있습니다 :

  • 정수 키는 고유의 부호있는 정수 유형으로 저장됩니다. 오토핫키 32-비트 버전은 -2147483648 부터 2147483647까지 범위의 정수키를 지원합니다. 오토핫키는 64-비트 정수를 지원합니다. 그러나 오직 오토핫키 64-비트 버전만 객체에 완전한 범위의 정수를 키로 지원합니다.
  • 위에 언급한 결과로, 정수 값의 문자열 형식은 유지되지 않습니다. 예를 들어, x[0x10], x[16] 그리고 x[00016]는 동등합니다. 이것은 또 십진 소수점이 없는 숫치 문자열에도 적용됩니다.
  • 인용부호 붙은 기호 문자열은 v1.x에서 순수하게 비-숫자로 간주됩니다. 그래서 x[1]x["1"]동등하지 않습니다. 게다가, 인용부호 붙은 기호 문자열은 ( "0x" x에서 처럼) 또다른 값과 결합합니다. 그 결과는 순수하게 비-숫자로 취급됩니다. 그렇지만, 이것은 변수에 적용되지 않습니다. 그래서 x[1]x[y:="1"]는 동등합니다. 이 문제는 v2에서 해결될 것입니다. 그래서 스크립트는 인용부호 붙은 숫치 기호를 키로 사용하는 것을 피해야 합니다.
  • 부동 소수점수는 키로 지원하지 않습니다. - 대신에 문자열로 변환됩니다. v1.x에서, 부동 소수점 기호는 원래의 형식을 유지하는 반면에 (0+1.0Sqrt(y)의 결과 같이) 순수한 부동 소수점 수는 강제로 현재의 부동 소수점 수 형식으로 변환됩니다. 일관성과 명료함을 위하여, 스크립트는 부동 소수점 기호를 키로 사용하지 말아야 합니다.
  • 문자열 키로서 "base"Insert에 사용될 때를 제외하고 특별한 의미가 있습니다.

확장 사용법

함수 참조 [v1.1.00+]

변수 func에 함수 이름이 들어 있다면, 그 함수는 두 가지 방법으로 호출할 수 있습니다: %func%() 또는 func.(). 그렇지만, 이렇게 하려면 매번 함수 이름을 결정해야 하는데, 함수가 여러번 호출된다면 효율적이지 못합니다. 수행성능을 개선하기 위해, 스크립트는 함수에 대한 참조를 열람해 그것을 저장해 나중에 사용할 수 있습니다:

Func := Func("MyFunc")
선택 | 내려받기

함수는 다음과 같은 구문을 사용하여 참조로 호출할 수 있습니다:

RetVal := %Func%(Params)     ; v1.1.07+을 요구합니다
RetVal := Func.Call(Params)  ; v1.1.19+을 요구합니다
RetVal := Func.(Params)      ; 권장하지 않습니다
선택 | 내려받기

함수 참조의 추가 특성들에 관한 정보는 Func Object를 참조하십시오.

배열의 배열

오토핫키는 "다-차원" 배열을 지원합니다. 다른 배열 안에 배열을 투명하게 저장하면 됩니다. 예를 들어, 테이블은 행의 배열로 표현할 수 있습니다. 반면에 각 행 자체는 열의 배열입니다. 그 경우, xy열의 내용은 아래의 방법중 하나를 사용하여 설정할 수 있습니다:

table[x][y] := content  ; A
table[x, y] := content  ; B
선택 | 내려받기

table[x]가 존재하지 않으면, AB는 두 가지 점에서 다릅니다:

  • A는 실패하는 반면 B는 자동으로 객체를 만들어 그것을 table[x] 안에 저장합니다.
  • tablebase메타-함수를 정의합니다. 다음과 같이 호출합니다:
    table.base.__Get(table, x)[y] := content   ; A
    table.base.__Set(table, x, y, content)     ; B
    결과적으로, B는 객체가 전체적인 할당에 대하여 맞춤 행위를 정의하도록 허용합니다.

table[a, b, c, d] := value와 같은 다-차원 할당은 다음과 같이 처리됩니다:

  • 오직 하나의 키만 남아 있다면, 할당을 수행하고 반환된다. 그렇지 않으면:
  • 객체에서 목록에 있는 첫 번째 키를 찾는다.
  • 비-객체가 발견되면, 실패한다.
  • 객체가 발견되지 않으면, 하나를 만들어 저장한다.
  • 재귀적으로 하위-객체를 요청하면서, 남아 있는 키와 값을 건넨다. - 위에서 아래로 반복한다.

다음 행위는 오직 스크립트로-만든 객체에만 적용됩니다. 좀 더 전문화된 유형의 객체, COM 객체나 COM 배열은 적용되지 않습니다.

함수 배열

함수 배열은 단순히 함수 이름이나 참조를 담고 있는 배열입니다. 예를 들어:

array := [Func("FirstFunc"), Func("SecondFunc")]

; 각 함수를 호출한다, "foo"를 매개변수로 건넨다:
Loop 2
    array[A_Index].("foo")

; 각 함수를 호출한다. 묵시적으로 배열 자체를 매개변수로 건넨다:
Loop 2
    array[A_Index]()

FirstFunc(param) {
    MsgBox % A_ThisFunc ": " (IsObject(param) ? "object" : param)
}
SecondFunc(param) {
    MsgBox % A_ThisFunc ": " (IsObject(param) ? "object" : param)
}
선택 | 내려받기

하위-호환을 위해, array[A_Index]에 함수 참조가 아니라 함수 이름이 들어 있다면, 두 번째 형태는 배열(array)을 매개변수로 건네지 않습니다. 그렇지만, array[A_Index]array.base[A_Index]으로부터 상속되었다면, 배열(array)이 매개변수로 건네집니다.

맞춤 객체

스크립트로 만든 객체는 미리 정의된 구조를 가질 필요가 없습니다. 대신에, 각 객체는 특성과 메쏘드를 그의 base 객체로부터 상속받아야 합니다 (그렇지 않으면 "프로토타입" 또는 "class"라고 알려진). 특성과 메쏘드는 언제든지 객체에 추가할 수도 (제거할 수도) 있습니다. 이런 변경은 이로부터 파생된 모든 객체에 영향을 미칩니다. 보다 복잡하고 전화된 상황을 위해, 베이스 객체는 자신을 상속받은 객체들의 표준 행위를 오버라이드 할 수 있습니다. 메타-함수를 정의해 오버라이드 합니다.

Base 객체는 그냥 평범한 객체이며, 전형적으로 두 가지 방식으로 생성됩니다:

class baseObject {
    static foo := "bar"
}
; 또는
baseObject := {foo: "bar"}
선택 | 내려받기

객체를 또다른 객체로부터 파생시켜 생성하기 위해, 스크립트는 base 특성에 할당하거나 new 키워드를 사용할 수 있습니다:

obj1 := Object(), obj1.base := baseObject
obj2 := {base: baseObject}
obj3 := new baseObject
MsgBox % obj1.foo " " obj2.foo " " obj3.foo
선택 | 내려받기

언제든지 한 객체의 base를 재할당할 수 있습니다. 객체가 상속받은 모든 특성과 메쏘드를 효과적으로 교체합니다.

프로토타입

프로토타입 또는 base 객체는 다른 객체와 마찬가지로 구성하고 조작합니다. 예를 들어, 특성 하나와 메쏘드 하나를 가진 평범한 객체는 다음과 같이 구성할 수 있습니다:

; 객체를 생성한다.
thing := {}
; Store a value.
thing.foo := "bar"
; 함수 참조를 저장함으로써 메쏘드를 생성한다.
thing.test := Func("thing_test")
; 메쏘드를 호출한다.
thing.test()

thing_test(this) {
   MsgBox % this.foo
}
선택 | 내려받기

thing.test()가 호출되면, thing는 자동으로 매개변수 리스트의 첫번째로 삽입됩니다. 그렇지만, 하위 호환을 위해, 이런 일은 함수가 (참조가 아니라) 이름으로 (베이스 객체로부터 상속받는 것이 아니라) 직접적으로 객체에 저장될 경우에만 일어납니다. 관례적으로 함수는 객체의 유형과 메쏘드의 이름을 결합해 짓습니다.

객체는 또다른 객체가 그로부터 파생해 나가면 프로토타입(prototype) 또는 베이스(base)가 됩니다:

other := {}
other.base := thing
other.test()
선택 | 내려받기

이 경우, otherfoo를 상속받고 testthing으로부터 상속을 받습니다. 이런 상속 관계는 역동적입니다. 그래서 thing.foo가 변경되면, 그 변경이 other.foo에 반영됩니다. 스크립트가 other.foo에 할당하면, 그 값은 other에 저장됩니다. thing.foo에 변경을 더 가하더라도 other.foo에는 영향을 미치지 않습니다. other.test()가 호출되면, 그의 this 매개변수에는 thing이 아니라 other를 가리키는 참조가 들어갑니다.

클래스 [v1.1.00+]

어원으로 말하자면 "클래스"란 공통의 특성이나 속성을 가지고 있는 것들의 한 범주 또는 집합입니다. base 객체나 prototype 객체는 객체 집합에 대하여 특성과 행위를 정의하기 때문에, 클래스(class) 객체라고 부를 수도 있습니다. 편의를 위해, 베이스 객체는 아래에 보여주는 바와 같이 "class" 키워드를 사용하여 정의할 수 있습니다:

class ClassName extends BaseClassName
{
    InstanceVar := Expression
    static ClassVar := Expression

    class NestedClass
    {
        ...
    }

    Method()
    {
        ...
    }

    Property[]  ; 각괄호는 선택적이다
    {
        get {
            return ...
        }
        set {
            return ... := value
        }
    }
}
선택 | 내려받기

스크립트가 적재될 때, 이 코드는 객체를 하나 구성하고 그것을 전역 변수 (또는 v1.1.05+이라면 수퍼-전역 변수) ClassName에 저장합니다. v1.1.05 이전이라면, 이 클래스를 함수 안에서 참조하려면, 그 함수가 assume-global이 아닌 한, global ClassName와 같은 선언이 필요합니다. extends BaseClassName가 있으면, BaseClassName은 반드시 또다른 클래스의 완전한 이름이어야 합니다 (그러나 v1.1.11에서는 정의된 순서는 문제가 되지 않습니다). 각 클래스의 완전한 이름이 object.__Class에 저장됩니다.

이 문서 안에서 클래스("class")라는 단어는 그 자체로 보통 class 키워드로 구성된 클래스 객체를 의미합니다.

클래스 정의는 변수 선언, 메쏘드 정의, 내포 클래스 정의를 포함할 수 있습니다.

실체 변수 [v1.1.01+]

실체 변수는 클래스의 각 실체가 자신만의 클래스 사본을 가진 것입니다. 보통의 할당처럼 선언되지만, this. 접두사가 생략되어 있습니다 (클래스 몸체 안에서 직접적으로 사용될 경우에만 생략합니다):

InstanceVar := Expression
선택 | 내려받기

이런 선언은 클랫의 새 실체가 new 키워드로 생성될 때마다 매번 평가됩니다. 메쏘드 이름 __Init은 이런 목적을 위해 예약되어 있으며, 다른 스크립트에서 사용하면 안 됩니다. __New() 메쏘드는 베이스 클래스에 정의된 선언을 포함하여 그 모든 선언이 평가된 후에 호출됩니다. 표현식은 다른 실체 변수와 메소드에 this를 통하여 접근할 수 있지만, 모든 다른 변수 참조는 전역적이라고 간주됩니다.

실체 변수에 접근하려면 (메쏘드 안에서도 마찬가지로), 언제나 목표 객체를 지정합니다; 예를 들어, this.InstanceVar.

[v1.1.08+]: 이 클래스에 x가 미리 선언되어 있다면 x.y := z와 같은 선언도 지원됩니다. 예를 들어, x := {}, x.y := 42x를 선언하고 또 this.x.y를 초기화합니다.

정적/클래스 변수 [v1.1.00.01+]

정적/클래스 변수는 클래스 자체에 속하지만, (하위-클래스를 포함하여) 파생 객체가 상속받을 수 있습니다. 실체 변수처럼 선언되지만, static 키워드를 사용합니다:

static ClassVar := Expression
선택 | 내려받기

정적 선언은 오직 자동-실행 섹션 전에 한 번만 평가됩니다. 그리고 순서대로 스크립트에 나타납니다. 각 선언은 값을 object 클래스에 저장합니다. 표현식 안의 모든 변수 참조는 전역적이라고 간주됩니다.

클래스 변수에 접근하려면, 언제나 클래스나 파생 객체를 지정합니다. 예를 들어, ClassName.ClassVar.

[v1.1.08+]: 이 클래스에 x가 미리 정의되어 있다면 static x.y := z와 같은 표현도 지원합니다. 예를 들어, static x := {}, x.y := 42x를 선언하고 또 ClassName.x.y를 초기화합니다.

내포 클래스

내포 클래스 정의는 클래스 객체를 또다른 전역 변수가 아니라 또다른 클래스 객체 안에 저장되도록 허용합니다. 위의 예제에서, class NestedClass는 객체를 구성하고 그것을 ClassName.NestedClass에 저장합니다. 하위-클래스는 NestedClass를 상속받거나 자신의 내포 클래스로 그것을 오버라이드할 수 있습니다 (이 경우 new this.NestedClass를 사용하여 적절한 클래스를 실체화할 수 있습니다).

class NestedClass
{
    ...
}
선택 | 내려받기

메쏘드

메쏘드 정의는 함수 정의와 모습이 똑 같습니다. 각 메쏘드는 숨은 매개변수로 this가 있는데, 이것은 전형적으로 클래스로부터 상속받은 객체를 가리키는 참조점을 담고 있습니다. 그렇지만, 메쏘드가 어떻게 호출되는 가에 따라 클래스 자체나 상속받은 클래스를 가리키는 참조점을 담을 수도 있습니다. 메쏘드는 이 클래스 객체에 참조로 저장됩니다.

Method()
{
    ...
}
선택 | 내려받기

메쏘드 안에서, 의사-키워드 base를 사용하면, 파생 클래스 안에 오버라이드된 수퍼-클래스의 메쏘드나 특성에 접근할 수 있습니다. 예를 들어, 위에 정의된 클래스에서 base.Method()BaseClassName에 정의된 Method 버전을 호출합니다. 메타-함수는 호출되지 않습니다; 그렇지 않으면, base.Method()는 마치 BaseClassName.Method.(this)처럼 행위합니다. 다시 말해,

  • base.Method()는 언제나 현재 메소드가 정의되어 있는 클래스의 베이스를 요청합니다. this가 전적으로 그 클래스 또는 다른 클래스의 sub-class로부터 파생되었을지라도 말입니다.
  • base.Method()는 묵시적으로 this를 첫 (숨은) 매개변수로 건넵니다.

base는 다음에 점. 또는 각괄호 []가 따라오면 특별한 의미를 가집니다. 그래서 obj := base, obj.Method()와 같은 코드는 작동하지 않습니다. 스크립트는 base의 특별한 행위를 불능으로 만들 수 있습니다. 거기에다 비어있지-않은 값을 할당하면 됩니다; 그렇지만, 이것은 권장하지 않습니다. 왜냐하면 변수 base는 꼭 비어 있어야 합니다. 스크립트가 #NoEnv를 빼먹으면 수행성능이 저하될 수 있기 때문입니다.

Properties [v1.1.16+]

특성 정의는 스크립트가 특정한 키를 설정하거나 열람할 때마다 메쏘드를 실행하도록 허용합니다.

Property[]
{
    get {
        return ...
    }
    set {
        return ... := value
    }
}
선택 | 내려받기

Property는 그냥 호출하는 데 사용되는 특성의 이름일 뿐입니다. 예를 들면, obj.Propertyget을 호출하는 반면에 obj.Property := valueset을 호출합니다. get이나 set 안에서, this는 요청 중인 객체를 가리킵니다. set 안에서, value에는 할당된 값이 담깁니다.

매개변수는 특성 이름의 오른쪽에 각괄호로 둘러싸 건넬 수 있습니다. 특성을 정의할 때와 호출할 때 모두 마찬가지입니다. 각괄호를 사용하는 외에도, 특성의 매개변수는 메쏘드의 매개변수와 같은 방식으로 정의됩니다 - 선택적, ByRef 그리고 가변 매개변수를 지원합니다.

get이나 set의 반환값은 해당 특성을 요청한 하위-표현식의 결과가 됩니다. 예를 들어, val := obj.Property := 42set의 반환 값을 val에 저장합니다.

각 클래스는 특성의 절반만 또는 둘 다 온전하게 정의할 수 있습니다 (특성=속성). 클래스가 특성을 오버라이드 하면 base.Property를 사용하여 자신의 기본 클래스에 정의된 특성에 접근할 수 있습니다. get이나 set이 정의되어 있지 않으면, 기본 클래스가 처리할 수 있습니다. set이 정의되어 있지 않고 메타-함수나 기본 클래스에 의하여 처리되지 않은 경우, 값을 객체에 저장하면 그 특성이 불능이 되는 효과가 있습니다.

내부적으로, getset은 두 개의 다른 메쏘드입니다. 그래서 변수를 공유할 수 없습니다 (this에 저장한 변수는 가능합니다).

메타-함수는 객체의 메쏘드와 특성에 접근을 보다 넓게 제어합니다. 그러나 더 복잡하고 에러를 일으키는 경향이 더 높습니다.

구성과 파괴

파생 객체가 new 키워드로 생성될 때마다 [requires v1.1.00+], 그의 기반 클래스에 정의된 __New 메쏘드가 호출됩니다. 이 메쏘드는 매개변수를 받아, 그 객체를 초기화하고 new 연산자의 결과를 값을 돌려줌으로써 오버라이드할 수 있습니다. 객체가 파괴될 때, __Delete가 호출됩니다. 예를 들어:

m1 := new GMem(0, 20)
m2 := {base: GMem}.__New(0, 30)

class GMem
{
    __New(aFlags, aSize)
    {
        this.ptr := DllCall("GlobalAlloc", "uint", aFlags, "ptr", aSize, "ptr")
        if !this.ptr
            return ""
        MsgBox % "New GMem of " aSize " bytes at address " this.ptr "."
        return this  ; 'new' 연산자를 사용하면 이 줄은 생략할 수 있다.
    }

    __Delete()
    {
        MsgBox % "Delete GMem at address " this.ptr "."
        DllCall("GlobalFree", "ptr", this.ptr)
    }
}
선택 | 내려받기

__Delete는 "__Class"를 키로 가지는 객체에 호출되지 않습니다. 클래스 객체가 이 키를 기본값으로 가집니다.

메타-함수

메쏘드 구문:
class ClassName {
    __Get([Key, Key2, ...])
    __Set([Key, Key2, ...], Value)
    __Call(Name [, Params...])
}

함수 구문:
MyGet(this [, Key, Key2, ...])
MySet(this [, Key, Key2, ...], Value)
MyCall(this, Name [, Params...])

ClassName := { __Get: Func("MyGet"), __Set: Func("MySet"), __Call: Func("MyCall") }
선택

메타-함수는 목표 객체 안에 키를 요청했지만 안에서 발견할 수 없을 때 일어날 행위를 정의합니다. 예를 들어, obj.key에 값이 할당되지 않았다면, __Get 메타-함수를 요청합니다. 비슷하게, obj.key := value__Set를 요청하고 obj.key()__Call를 호출합니다. 이 메타-함수들 (또는 메쏘드들)은 obj.base, obj.base.base 등등에 정의할 필요가 있습니다.

스크립트가 목표 객체 안에 존재하지 않는 키를 열람하거나 설정하거나 또는 호출하면, 베이스 객체가 다음과 같이 요청됩니다:

  • 이 베이스 객체가 적절한 메타-함수를 정의하고 있으면 그것을 호출합니다. 메타-함수가 명시적으로 반환(return)되면, (그 메타-함수가 무엇 때문에 호출되었는지와 상관없이) 그 반환 값을 연산의 결과로 사용합니다 그리고 스크립트에 제어를 반환합니다. 그렇지 않으면, 아래에 기술된 대로 계속 진행합니다.

    Set: 메타-함수가 할당을 처리하면, 그 할당 값을 반환해야 합니다. 이렇게 하면 a.x := b.y := z와 같이 할당을 연쇄적으로 할 수 있습니다. 반환 값은 z의 원래 값과 다를 수 있습니다 (예를 들어, 어느 값을 할당할 수 있는지 제한이 부과된 경우).

  • 베이스 객체의 필드에서 일치하는 키를 찾습니다.
  • [v1.1.16+]: 특성에 상응하는 키가 발견되고 거기에 get이나 set이 (적절하게) 구현되어 있다면, 그 특성을 요청하고 반환합니다. 이것이 메쏘드 호출이면, get을 요청합니다.
  • 아무 키도 발견되지 않으면, 재귀적으로 이 베이스 객체의 베이스를 요청합니다 (이 리스트의 상단에서 시작해, 차례로 각 단계마다 적용합니다). 아직 완료하지 못했다면, 키가 메타-함수에 의하여 추가된 경우 이 바탕 객체를 검색해 다시 부합하는 키를 찾습니다.

    하위 호환의 문제 때문에, 이 단계는 키가 발견 되었을지라도 set 연산에 대해서만 수행됩니다 (단, set을 구현한 특성을 정의했다면 예외입니다).

  • 여러 매개변수가 get이나 set에 주어지고 키가 발견되면, 그의 값을 점검합니다. 그 값이 객체이면, 그것을 요청하여 나머지 매개변수들을 처리합니다. 그리고 더 이상 진행하지 않습니다.
  • 키가 발견되면,
    Get: 그 값을 돌려줍니다.
    Call: 그 값이 함수 이름이거나 참조이면, 그것을 호출합니다. 대상 객체를 첫 매개변수로 건넵니다 (this).

일치하는 키를 메타-함수가 객체에 저장하지만 반환(return)하지 않으면, 그 행위는 마치 키가 처음부터 그 객체에 존재한 것과 같습니다. __Set을 사용하는 예제는 배열의 배열 서브-클래싱하기를 참조하십시오.

연산이 여전히 처리되지 않았다면, 이것이 내장 함수인지 아니면 내장 특성인지 점검하십시오:

  • Get: 기카 "base"이면, 그 객체의 base를 돌려줍니다.
  • Set: 키가 "base"이면, 그 객체의 base를 설정합니다 (또는 그 값이 객체가 아니면 제거합니다).
  • Call: 적용할 수 있으면 내장 메쏘드를 호출합니다.

여전히 연산이 처리되지 않았다면,

  • Get 그리고 Call: 빈 문자열을 돌려줍니다.
  • Set: 한 개의 키 매개변수만 주어졌다면, 그 키와 값을 목표 객체에 저장하고 할당된 값을 돌려줍니다. 여러 매개변수가 주어지면, 새 객체를 만들고 거기에 첫 번째 매개변수를 키로 사용하여 저장합니다. 그 다음, 그 새 객체를 요청해 나머지 매개변수를 처리합니다. (배열의 배열 참조.)

알려진 한계:

  • 반환값 없이 return을 사용하면 return ""과 동등합니다. 이 행위는 미래 버전에서는 바뀔 수 있습니다. 기본 행위를 오버라이딩하지 않고, 메타-함수로부터 "피신"하기 위하여 return을 사용할 가능성이 있습니다.

동적 특성

__Get__Set를 사용하여 값들을 특정 방식으로 계산하거나 제한할 수 있는 특성을 구현할 수 있습니다. 예를 들어, R, G, B 그리고 RGB 특성을 가지고 "Color" 객체를 구현 하는 데 사용할 수 있습니다. 여기에서 오직 RGB 값만 실제로 저장됩니다:

red  := new Color(0xff0000), red.R -= 5
cyan := new Color(0x00ffff)

MsgBox % "red: " red.R "," red.G "," red.B " = " red.RGB
MsgBox % "cyan: " cyan.R "," cyan.G "," cyan.B " = " cyan.RGB

class Color
{
    __New(aRGB)
    {
        this.RGB := aRGB
    }

    __Get(aName)
    {
        if (aName = "R")
            return (this.RGB >> 16) & 255
        if (aName = "G")
            return (this.RGB >> 8) & 255
        if (aName = "B")
            return this.RGB & 255
    }

    __Set(aName, aValue)
    {
        if aName in R,G,B
        {
            aValue &= 255

            if      (aName = "R")
                this.RGB := (aValue << 16) | (this.RGB & ~0xff0000)
            else if (aName = "G")
                this.RGB := (aValue << 8)  | (this.RGB & ~0x00ff00)
            else  ; (aName = "B")
                this.RGB :=  aValue        | (this.RGB & ~0x0000ff)

            ; 'Return'을 사용하여 새 키-값 쌍이 만들어지지 않았음을 알려야 합니다.
            ; 또한 'x := clr[name] := val'에서 무엇을 'x'에 저장할지 정의합니다:
            return aValue
        }
    }
}
선택 | 내려받기

함수로서의 객체

obj.func(param)와 같이 호출하면, obj.func는 함수 이름이나 객체를 담을 수 있습니다. obj.func에 객체가 들어 있다면, 그 객체는 그 메쏘드 이름 대신에 (obj.func)[obj]()처럼 obj로 요청할 수 있습니다. 대부분의 경우 obj.func[obj]는 존재하지 않습니다. 대신에 obj.func's __Call 메타-함수가 요청됩니다. 이를 이용하면 아래 예제에 보여주는 바와 같이 함수 호출의 행위를 추상적인 방식으로 바꿀 수 있습니다:

; 함수 배열을 위한 프로토타입을 생성한다.
FuncArrayType := {__Call: "FuncType_Call"}
; 함수 배열을 생성한다.
funcArray := {1: "One", 2: "Two", base: FuncArrayType}
; 배열을 메쏘드로 사용하는 객체를 생성한다.
obj := {func: funcArray}
; 메쏘드를 호출한다.
obj.func("foo", "bar")

FuncType_Call(func, obj, params*)
{
    ; 함수 리스트를 호출한다.
    Loop % ObjMaxIndex(func)
        func[A_Index](params*)
}

One(param1, param2) {
    ListVars
    Pause
}
Two(param1, param2) {
    ListVars
    Pause
}
선택 | 내려받기

이 테크닉을 사용하면 메타-함수처럼 행위하는 객체를 만들 수 있습니다. 예를 들어, 이전 섹션에서 다룬 특성과 비슷하게 동적 특성들을 정의할 수 있습니다. (그렇지만, 이 예제는 특성 지원으로 대체되었음을 주의하십시오.) 예를 들어:

blue := new Color(0x0000ff)
MsgBox % blue.R "," blue.G "," blue.B

class Properties
{
    __Call(aTarget, aName, aParams*)
    {
        ; 이 특성 객체에 이 절반-특성에 대한 정의가 들어 있다면, 그것을 호출합니다. 
        ; this.HasKey(aName)를 사용하지 않도록 주의합니다. 이것을 사용하면 __Call 안으로 재귀가 일어납니다.
        if IsObject(aTarget) && ObjHasKey(this, aName)
            return this[aName].(aTarget, aParams*)
    }
}

class Color
{
    __New(aRGB)
    {
        this.RGB := aRGB
    }

    class __Get extends Properties
    {
        R() {
            return (this.RGB >> 16) & 255
        }
        G() {
            return (this.RGB >> 8) & 255
        }
        B() {
            return this.RGB & 255
        }
    }

    ;...
}
선택 | 내려받기

배열의 배열을 서브-클래싱하기

table[x, y] := content와 같이 다중-매개변수 할당 때문에 묵시적으로 새로운 객체가 생성될 때, 그 새 객체는 보통 베이스가 없고 그러므로 맞춤 메쏘드도 없고 특별한 행위도 없습니다. __Set는 아래에 보여주는 바와 같이 이런 객체들을 초기화하는 데 사용할 수 있습니다.

x := {base: {addr: Func("x_Addr"), __Set: Func("x_Setter")}}

; 값을 할당한다. 묵시적으로 x_Setter를 사용하여 하위-객체를 만든다.
x[1,2,3] := "..."

; 값을 열람하고 예제 메쏘드를 호출한다.
MsgBox % x[1,2,3] "`n" x.addr() "`n" x[1].addr() "`n" x[1,2].addr()

x_Setter(x, p1, p2, p3) {
    x[p1] := new x.base
}

x_Addr(x) {
    return &x
}
선택 | 내려받기

x_Setter는 네 개의 필수 매개변수가 있기 때문에, 두 개 이상의 키가 있을 경우에만 호출됩니다. 위의 할당이 일어날 때, 다음과 같은 일이 일어납니다:

  • x[1]는 존재하지 않습니다. 그래서 x_Setter(x,1,2,3)가 호출됩니다 ("..."는 건네지지 않습니다. 매개변수가 턱없이 모자라기 때문입니다).
    • x[1]x와 베이스가 같은 객체가 새로 할당됩니다.
    • 반환값은 없습니다 – 할당은 계속됩니다.
  • x[1][2]는 존재하지 않습니다. 그래서 x_Setter(x[1],2,3,"...")이 호출됩니다.
    • x[1][2]x[1]와 베이스가 같은 객체가 새로 할당됩니다.
    • 반환값은 없습니다 – 할당은 계속됩니다.
  • x[1][2][3]은 존재하지 않습니다. 그러나 x_Setter는 네 개의 매개변수를 요구하고 세 개 밖에 없으므로 (x[1][2], 3, "..."), 호출되지 않고 할당은 여느 때처럼 완료됩니다.

기본 베이스 객체

비-객체 값이 객체 구문과 함께 사용될 때, 기본 베이스 객체가 요청됩니다. 이것은 디버깅에 사용하거나 또는 문자열이나, 숫자, 그리고/또는 변수에 대하여 객체-류의 행위를 전역적으로 정의하는데 사용할 수 있습니다. 기본 베이스는 비-객체 값을 가지고 .base를 사용하여 접근이 가능합니다; 예를 들면, "".base. 기본 베이스는 "".base := Object()처럼 설정(set)할 수 없지만, 기본 베이스는 "".base.base := Object()처럼 그 자체로 베이스가 있습니다.

자동 변수 초기화

set 연산의 목표로 빈 변수가 사용될 때, 직접적으로 __Set meta-함수에 건네집니다. 그래서 새 객체를 변수에 삽입할 기회가 있습니다. 간략하게 하기 위해, 다음 예제는 다중 매개변수를 지원하지 않습니다; 물론 가변 함수를 사용하면 가능합니다.

"".base.__Set := Func("Default_Set_AutomaticVarInit")

empty_var.foo := "bar"
MsgBox % empty_var.foo

Default_Set_AutomaticVarInit(ByRef var, key, value)
{
    if (var = "")
        var := Object(key, value)
}
선택 | 내려받기

의사-특성

객체의 "사탕 구문"을 문자열과 숫자에 적용할 수 있습니다.

"".base.__Get := Func("Default_Get_PseudoProperty")
"".base.is    := Func("Default_is")

MsgBox % A_AhkPath.length " == " StrLen(A_AhkPath)
MsgBox % A_AhkPath.length.is("integer")

Default_Get_PseudoProperty(nonobj, key)
{
    if (key = "length")
        return StrLen(nonobj)
}

Default_is(nonobj, type)
{
    if nonobj is %type%
        return true
    return false
}
선택 | 내려받기

내장 함수도 역시 사용할 수 있지만, 이 경우 괄호를 빼먹으면 안 됩니다. 주의하십시오:

"".base.length := Func("StrLen")
MsgBox % A_AhkPath.length() " == " StrLen(A_AhkPath)
선택 | 내려받기

디버깅

값을 객체처럼 취급되도록 허용하면 바람직하지 않습니다. 비-객체 값이 요청될 때마다 경고 상자가 나타날 수 있습니다:

"".base.__Get := "".base.__Set := "".base.__Call := Func("Default__Warn")

empty_var.foo := "bar"
x := (1 + 1).is("integer")

Default__Warn(nonobj, p1="", p2="", p3="", p4="")
{
    ListLines
    MsgBox 비-객체 값이 부적절하게 요청되었습니다.`n`n특히: %nonobj%
}
선택 | 내려받기

구현

참조횟수-세기

오토핫키는 기본적인 참조횟수-세기 매커니즘을 사용하여 한 객체가 사용하는 자원이 더 이상 참조되지 않을 때 자동으로 해제합니다. 스크립트 저자는 이 메커니즘을 명시적으로 요청하면 안 됩니다. 단 객체를 가리키는 포인터가 관리되지 않아서 직접 처리할 경우는 제외합니다. 더 자세한 정보는 ObjAddRef를 참조하십시오.

; 객체의 참조 횟수를 증가시켜 "살아 있도록 유지한다":
ObjAddRef(address)
...
; 개체의 참조 횟수를 감소시켜 해제되도록 한다:
ObjRelease(address)
선택 | 내려받기

그렇지만, ObjAddRef는 Object(obj)를 통하여 주소를 최초로 획득할 때는 사용할 필요가 없습니다.

일반적으로 한 객체의 주소의 각 새 사본은 객체 참조로 간주해야 합니다. 단, 그 스크립트가 ObjAddRef 그리고/또는 ObjRelease를 적절하게 호출할 책임이 있는 경우는 예외입니다. 예를 들면, x := address와 같은 것을 통하여 주소가 복사될 때마다, ObjAddRef 가 호출되어 참조 횟수를 증가시켜야 합니다. 비슷하게, 스크립트가 그 객체의 주소의 특별한 사본과의 작업이 끝날 때마다, ObjRelease를 호출해야 합니다. 이렇게 하면 그의 마지막 참조가 사라질 때 확실하게 객체가 해제됩니다 - 그 전에는 해제되지 않습니다.

객체의 마지막 참조가 해제될 때 코드를 실행하려면, __Delete 메타-함수를 구현합니다.

알려진 한계:

  • 순환 참조를 먼저 깨야 객체를 풀 수 있습니다. 자세한 정보와 예는 객체 해제하기를 참조합니다.
  • 프로그램이 끝날 때 정적 변수와 전역 변수의 참조는 자동으로 해제되지만, 비-정적 지역 변수나 표현식 평가 스택에 있는 참조들을 자동으로 해제되지 않습니다. 이런 참조는 함수나 표현식이 정상으로 끝내도록 허용해야만 해제가 됩니다.

프로그램이 종료할 때 객체가 사용하는 메모리를 운영체제가 요구함에도 불구하고, 그 객체를 가리키는 모든 참조가 풀리지 않는 한, __Delete는 호출되지 않습니다. 이것은 임시 파일과 같이, 운영 체제가 자동으로 요구하지 않는 다른 자원을 풀어버리면 문제가 될 수 있기 때문입니다.

객체를 가리키는 포인터

어떤 경우는 DllCall을 통하여 객체를 외부 코드에 건넬 필요가 있습니다. 또는 그것을 이진 데이터 구조로 저장하여 나중에 열람할 필요가 있습니다. 객체의 주소는 x := &obj로 열람할 수 있습니다; 그렇지만, 변수 obj가 소거되면, 그 객체는 일찌기 해제되어 버릴 수 있습니다. 이런 일이 일어나지 않도록 확인하려면, 위에 보여준 바와 같이 ObjAddRef를 사용하거나 아래에 보여주는 바와 같이 Object() 함수를 사용합니다:

address := Object(object)
선택 | 내려받기

게다가, 이 함수를 사용하여 주소를 다시 참조로 변환할 수 있습니다:

object := Object(address)
선택 | 내려받기

어느 경우든 객체의 참조-횟수는 자동으로 증가됩니다. 그래서 객체는 너무 이르게 해제되지 않습니다.

이 함수는 예를 들어 COM 객체 래퍼파일 객체와 같이 Object()로 생성되지 않은 객체에도 똑 같이 적용되는 것에 주목하십시오.