메타테이블은 테이블이 이전보다 더 강력해지도록 만듭니다. 데이터에 부착되며 메타메서드라고 불리는 값을 포함합니다. 메타메서드는 특정 작업이 부착된 데이터와 함께 사용될 때 호출됩니다.
메타테이블 조작하기
테이블의 메타테이블을 추가하고 찾는 두 가지 주요 함수는 setmetatable()와 getmetatable()입니다.
local x = {}local metaTable = {} -- 메타테이블도 테이블입니다!setmetatable(x, metaTable) -- x에 metaTable이라는 메타테이블을 부여합니다!print(getmetatable(x)) --> table: [16진수 메모리 주소]
setmetatable() 함수는 메타테이블을 설정 중인 테이블도 반환하므로, 다음 두 스크립트는 동일한 작업을 수행합니다:
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
메타메서드
메타메서드는 메타테이블에 저장된 함수입니다. 테이블을 호출하거나, 테이블을 추가하거나, 심지어 테이블을 나누는 등의 작업을 수행할 수 있습니다. 사용할 수 있는 메타메서드의 목록은 다음과 같습니다:
| 메서드 | 설명 |
|---|---|
| __index(table, index) | table[index]가 인덱싱될 때 호출되며, 만약 table[index]가 nil일 경우에만 동작합니다. 테이블로도 설정할 수 있으며, 그러면 해당 테이블이 인덱싱됩니다. |
| __newindex(table, index, value) | table[index]가 설정될 때 호출되며 (table[index] = value), 만약 table[index]가 nil일 경우에만 동작합니다. 테이블로도 설정할 수 있으며, 그러면 해당 테이블이 인덱싱됩니다. |
| __call(table, ...) | 테이블이 함수처럼 호출될 때 호출됩니다. ...는 전달된 인수입니다. |
| __concat(table, value) | .. 연결 연산자가 테이블에 사용될 때 호출됩니다. |
| __unm(table) | 단항 – 연산자가 테이블에 사용될 때 호출됩니다. |
| __add(table, value) | + 덧셈 연산자입니다. |
| __sub(table, value) | – 뺄셈 연산자입니다. |
| __mul(table, value) | * 곱셈 연산자입니다. |
| __div(table, value) | / 나눗셈 연산자입니다. |
| __idiv(table, value) | // 바닥 나눗셈 연산자입니다. |
| __mod(table, value) | % 모듈러스 연산자입니다. |
| __pow(table, value) | ^ 지수 연산자입니다. |
| __tostring(table) | tostring이 테이블에서 호출될 때 호출됩니다. |
| __metatable | 존재할 경우, 메타테이블을 잠금 처리하여 getmetatable()가 이 값을 반환하게 하고, setmetatable()는 오류를 발생시킵니다. 비함수 값입니다. |
| __eq(table, value) | == 동등 연산자¹ |
| __lt(table, value) | < 미만 연산자¹ |
| __le(table, value) | <= 연산자¹ |
| __mode | 약한 테이블에서 사용되며, 테이블의 키와/또는 값이 약한지 선언합니다. 로블록스 인스턴스에 대한 참조는 절대 약하지 않음을 유의하세요. 그러한 참조를 보유하는 테이블은 결코 가비지 컬렉트되지 않습니다. |
| __len(table) | # 길이 연산자가 객체에서 사용될 때 호출됩니다. |
| __iter(table) | 일반화된 반복을 사용할 때 사용자 정의 반복자를 나타내는 데 사용됩니다. |
산술 또는 관계 메타메서드에 대한 함수를 작성할 때 두 함수 매개변수는 메타메서드를 호출한 테이블과 다른 값 간에 교환 가능하다는 점에 유의해야 합니다. 예를 들어, 벡터 작업에서 스칼라는 교환법칙이 성립하지 않습니다. 따라서 vector2 클래스를 위한 메타메서드를 작성할 경우 두 가지 시나리오 모두를 고려해야 합니다.
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a는 스칼라, b는 벡터입니다.
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a는 벡터, b는 스칼라입니다.
local vector, scalar = a, b
return vector2.new(vector.x / scalar, vector.y / scalar)
elseif (a.__type and a.__type == "vector2" and b.__type and b.__type == "vector2") then
-- a와 b 모두 벡터입니다.
return vector2.new(a.x / b.x, a.y / b.y)
end
end
function mt.__tostring(t)
return t.x .. ", " .. t.y
end
function vector2.new(x, y)
local self = setmetatable({}, mt)
self.x = x or 0
self.y = y or 0
return self
end
local a = vector2.new(10, 5)
local b = vector2.new(-3, 4)
print(a / b) -- -3.3333333333333, 1.25
print(b / a) -- -0.3, 0.8
print(2 / a) -- 0.2, 0.4
print(a / 2) -- 5, 2.5
사용법
메타테이블을 사용하는 방법은 여러 가지가 있습니다. 예를 들어 테이블을 음수로 만드는 __unm 메타메서드를 사용할 수 있습니다:
local metatable = {
__unm = function(t) -- __unm은 단항 - 연산자용입니다.
local negated = {}
for key, value in t do
negated[key] = -value -- 이 테이블의 모든 값을 부정합니다.
end
return negated -- 테이블을 반환합니다.
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
__index를 사용하여 사물을 선언하는 흥미로운 방법이 있습니다:
local metatable = {__index = {x = 1}}local t = setmetatable({}, metatable)print(t.x) --> 1
t 테이블에서 x가 인덱싱되지 않았을 때 __index가 호출되었습니다. 루아는 __index 테이블에서 x라는 인덱스를 찾기 시작했고, 찾은 후 그것을 반환합니다.
이제 간단한 함수로 쉽게 할 수 있지만, 더 많은 내용이 있습니다. 예를 들어 보십시오:
local t = {10, 20, 30}print(t(5))
일반적으로 테이블을 호출할 수 없지만, 메타테이블을 사용하면 가능합니다:
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- 인수 (5)를 값에 추가한 후 새로운 테이블(t)에 배치합니다.
end
return unpack(sum) -- 개별 테이블 값을 반환합니다.
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
테이블 추가와 같은 작업도 많이 할 수 있습니다:
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
이 코드는 테이블에서 산술을 시도하고 있기 때문에 오류가 발생하지만, 메타테이블로 시도하면 작동합니다:
local metatable = {
__add = function(t1, t2)
local sum = {}
for key, value in t1 do
sum[key] = value
end
for key, value in t2 do
if sum[key] then
sum[key] += value
else
sum[key] = value
end
end
return sum
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
local table2 = setmetatable({13, 14, 15}, metatable)
for k, v in table1 + table2 do
print(k, v)
end
메타테이블을 다루다 보면 문제에 직면할 수도 있습니다. 테이블에 새 값을 만들기 위해 __index 메타메서드를 사용해야 하지만 해당 테이블의 메타테이블에는 __newindex 메타메서드도 있는 경우, rawset() 내장 함수를 사용하여 메타메서드를 호출하지 않고 값을 설정해야 합니다. 다음 코드는 이러한 함수를 사용하지 않을 경우 발생하는 오류의 예입니다.
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10
return self[i]
end,
__newindex = function(self, i, v)
-- 일반적인 방법으로 테이블에 값을 설정하지 마세요
end
})
print(t[1]) -- 스택 오버플로우를 발생시킵니다
스택 오버플로우는 함수가 자신으로부터 너무 많은 호출을 받으려고 할 때 발생합니다. 위의 __index 함수에서 self[i]가 값으로 설정되면, 다음 줄에서 self[i]가 존재한다고 가정하고 __index 메타메서드를 호출하지 않을 것입니다. 문제는 __newindex가 값 설정을 허용하지 않는다는 것입니다. 그 존재는 표준 t[i] = v 방법으로 테이블에 값이 추가되는 것을 막습니다. 이를 해결하기 위해 rawset() 함수를 사용하십시오:
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
-- 일반적인 방법으로 테이블에 값을 설정하지 마세요
end
})
print(t[1]) --> 10
집합 데이터 타입 사용하기
집합은 순서가 없고 중복 요소가 없는 항목의 모음입니다. 항목은 집합 안에 있거나 없거나 합니다. 메타테이블을 사용하여 Luau 스크립트 내에서 집합을 구성하고 조작할 수 있습니다.
기본 방법
다음 코드는 기본 집합 기능을 포함하여 새 집합을 구성하고, 항목을 추가 및 제거하며, 집합에 항목이 포함되어 있는지 확인하고, 집합의 내용을 출력합니다.
local Set = {}
Set.__index = Set
-- 선택적 항목 목록에서 집합을 구성하는 함수
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- 집합에 항목을 추가하는 함수
function Set:add(item)
self[item] = true
end
-- 집합에서 항목을 제거하는 함수
function Set:remove(item)
self[item] = nil
end
-- 집합에 항목이 포함되어 있는지 확인하는 함수
function Set:contains(item)
return self[item] == true
end
-- 디버깅을 위해 집합을 쉼표로 구분된 리스트로 출력하는 함수
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end
집합 생성하기
새 집합은 선택적인 항목 배열을 전달하여 Set.new()를 호출함으로써 생성할 수 있습니다.
local fruits = Set.new({"사과", "레몬", "오렌지", "체리", "라임", "복숭아"})
정의상 집합은 순서 개념이 없습니다.
항목 추가하기
기존 집합에 항목을 추가하는 것은 Set:add() 메서드를 통해 가능합니다.
local fruits = Set.new({"사과", "레몬", "오렌지", "체리", "라임", "복숭아"})fruits:add("망고")
항목 제거하기
집합에서 항목을 제거하려면 항목 이름과 함께 Set:remove()를 호출합니다.
local fruits = Set.new({"사과", "레몬", "오렌지", "체리", "라임", "복숭아"})fruits:remove("오렌지")
항목 확인하기
특정 항목이 집합에 포함되어 있는지 확인하려면 Set:contains()를 사용합니다.
local fruits = Set.new({"사과", "레몬", "오렌지", "체리", "라임", "복숭아"})local result1 = fruits:contains("체리")print(result1) -- truelocal result2 = fruits:contains("수박")print(result2) -- false
추가 메서드
집합의 항목을 비교하거나 집합을 결합하거나 한 집합에서 다른 집합을 빼는 등의 추가 작업도 구현할 수 있습니다.
교집합
집합을 벤 다이어그램으로 생각할 때, 두 집합의 교집합을 아래와 같이 구할 수 있습니다. 즉, 두 집합 모두에 있는 항목입니다.
local function getIntersection(set1, set2)
local result = Set.new()
for key, value in set1 do
if set2:contains(key) then
result:add(key)
end
end
return result
end
local freshFruits = Set.new({"망고", "레몬", "오렌지", "체리", "라임", "복숭아"})
local frozenFruits = Set.new({"망고", "복숭아", "파인애플"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- 망고, 복숭아
합집합
아래 함수를 통해 두 집합의 합집합을 구할 수 있습니다. 즉, 중복 없이 두 집합의 항목을 모두 포함한 모음입니다. 이 함수는 메타테이블의 __add 메서드를 사용하여 set1 + set2의 덧셈 단축키를 제공합니다.
function Set:__add(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = true
end
return result
end
local sweetFruits = Set.new({"사과", "망고", "체리", "복숭아"})
local sourFruits = Set.new({"레몬", "라임"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- 복숭아, 라임, 사과, 체리, 레몬, 망고
차집합
아래 함수를 통해 한 집합의 모든 항목을 다른 집합의 항목에서 제거할 수 있습니다. 위의 함수와 유사하게, 이 함수는 메타테이블의 __sub 메서드를 사용하여 set1 - set2의 뺄셈 단축키를 제공합니다.
function Set:__sub(otherSet)
local result = Set.new()
for entry in self do
result[entry] = true
end
for entry in otherSet do
result[entry] = nil
end
return result
end
local allFruits = Set.new({"사과", "레몬", "망고", "체리", "라임", "복숭아"})
local sourFruits = Set.new({"레몬", "라임"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- 망고, 사과, 체리, 복숭아