เมตาตารางทำให้ตารางมีความสามารถมากขึ้นกว่าเดิม พวกมันถูกแนบกับข้อมูลและมีค่าที่เรียกว่าเมธอดเมตา เมธอดเมตาจะถูกเรียกเมื่อมีการใช้การกระทำบางอย่างกับข้อมูลที่มันแนบอยู่
จัดการเมตาตาราง
ฟังก์ชันหลักสองฟังก์ชันสำหรับการเพิ่มและค้นหาเมตาตารางของตารางคือ setmetatable() และ getmetatable().
local x = {}
local metaTable = {} -- เมตาตารางก็เป็นตารางเช่นกัน!
setmetatable(x, metaTable) -- ให้ x มีเมตาตารางชื่อ metaTable!
print(getmetatable(x)) --> table: [hexadecimal memory address]ฟังก์ชัน 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__index ถูกเรียกเมื่อ x ถูกดัชนีในตารางและไม่พบ Luau จากนั้นค้นหาผ่านตาราง __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 do
print(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 คุณจะต้องใช้ฟังก์ชันในตัวของ Luau 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]) -- ทำให้เกิด stack overflowการเกิด stack overflow จะเกิดขึ้นเมื่อคุณพยายามเรียกฟังก์ชันจากตัวมันเองมากเกินไป ในฟังก์ชัน __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ใช้ตารางอ่อน
ตัวแปรที่ถือว่าตาราง ฟังก์ชัน เธรด และ ยูเซอร์ดาต้า รวมถึง Instances จะเก็บอ้างอิงไปยังวัตถุเหล่านั้นแทนที่จะเป็นสำเนาของมัน การกำหนดวัตถุให้กับตัวแปรอื่นจะสร้างอ้างอิงอีกอันไปยังวัตถุเดียวกัน:
local map = {key = 1}
local mapReference = map
print(map == mapReference) --> true
print(map == {key = 1}) --> false
local function func() end
local funcReference = func
print(func == funcReference) --> true
local part = Instance.new("Part")
local partReference = part
print(part == partReference) --> trueวัตถุที่ไม่สามารถเข้าถึงได้ผ่านอ้างอิงที่แข็งแกร่งจะกลายเป็นที่เหมาะสมสำหรับการเก็บขยะ การตั้งค่าตัวแปรหนึ่งเป็น nil จะไม่ทำให้วัตถุเหมาะสมสำหรับการเก็บขยะหากอ้างอิงที่แข็งแกร่งอื่นยังชี้ไปยังวัตถุเดียวกัน:
local child = {}
local parent = {Child = child}
child = nil
print(parent.Child) --> table: [hexadecimal memory address]แม้ว่า child จะไม่ชี้ไปยังตารางลูกอีกต่อไป แต่ parent.Child ยังคงชี้ไปยังมัน ตราบใดที่ตารางพ่อแม่ยังสามารถเข้าถึงได้ อ้างอิงของมันจะทำให้ตารางลูกยังสามารถเข้าถึงได้เช่นกัน
ตารางอ่อน จะถือคีย์ ค่าหรือทั้งสองอย่างโดยไม่ป้องกันไม่ให้การเก็บขยะสามารถเรียกคืนวัตถุที่ไม่สามารถเข้าถึงได้อีกต่อไป ตั้งค่าเขตข้อมูล __mode ของเมตาตารางของตารางเพื่อควบคุมว่าการอ้างอิงใดเป็นอ่อน:
- "k" ทำให้คีย์เป็นอ่อน
- "v" ทำให้ค่าเป็นอ่อน
- "kv" หรือ "vk" ทำให้ทั้งคีย์และค่าเป็นอ่อน
ตัวอย่างโค้ดต่อไปนี้สร้างตารางที่มีค่าที่อ่อน:
local children = setmetatable({}, {
__mode = "v",
})
do
local child = {}
children.example = child
endหลังจากบล็อก do สิ้นสุด ค่าที่เก็บอยู่ใน children.example จะไม่มีการอ้างอิงที่แข็งแกร่งและกลายเป็นที่เหมาะสมสำหรับการเก็บขยะ เมื่อการเก็บขยะเรียกคืนตารางลูก Luau จะลบรายการของมันออกจาก children การเก็บขยะเป็นกระบวนการที่ไม่สามารถคาดเดาได้ ดังนั้นอย่าพึ่งพาว่ารายการจะหายไปเมื่อใด
โหมดใด ๆ ที่คุณไม่ระบุจะยังคงเป็นอ้างอิงที่แข็งแกร่ง คีย์ในตาราง __mode = "v" ยังคงเป็นการอ้างอิงที่แข็งแกร่ง ดังนั้นวัตถุที่เก็บไว้เป็นคีย์จะยังคงสามารถเข้าถึงได้และรายการของมันจะยังคงอยู่ในตาราง
สำหรับ Instances การเก็บขยะจะใช้กับการอ้างอิง Luau แทนที่จะเป็นวัตถุเอง เอนจินจะเก็บข้อมูลอินสแตนซ์ไว้ภายนอก VM ของ Luau ดังนั้นการเก็บอ้างอิง Luau จะไม่ลบอินสแตนซ์ และอินสแตนซ์ที่ยังคงอยู่ในโมเดลข้อมูลจะไม่ทำให้การอ้างอิง Luau ของมันยังคงมีชีวิตอยู่ เพื่อปล่อยอินสแตนซ์ ให้เรียก Instance:Destroy() และลบการอ้างอิงที่เหลืออยู่ทั้งหมดออก
ใช้ประเภทเซ็ต
เซ็ต คือการรวบรวมของรายการที่ไม่มีลำดับและไม่มีองค์ประกอบที่ซ้ำกัน รายการหนึ่ง มี หรือ ไม่มี อยู่ในเซ็ต โดยใช้เมตาตาราง คุณสามารถสร้างและจัดการเซ็ตภายในสคริปต์ 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({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})โปรดทราบว่าตามคำนิยาม เซ็ตไม่มีแนวคิดเรื่องการจัดลำดับ
เพิ่มรายการ
การเพิ่มรายการลงในเซ็ตที่มีอยู่สามารถทำได้ผ่านวิธี Set:add()
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:add("Mango")ลบรายการ
ในการลบรายการออกจากเซ็ต ให้เรียก Set:remove() พร้อมกับชื่อรายการ
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
fruits:remove("Orange")ตรวจสอบรายการ
ในการตรวจสอบว่าเซ็ตมีรายการเฉพาะหรือไม่ ให้ใช้ Set:contains()
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local result1 = fruits:contains("Cherry")
print(result1) -- true
local result2 = fruits:contains("Watermelon")
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({"Mango", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
local frozenFruits = Set.new({"Mango", "Peach", "Pineapple"})
local commonFruits = getIntersection(freshFruits, frozenFruits)
commonFruits:output() -- Mango, Peachการรวม
คุณสามารถรับ การรวม ของสองเซ็ตด้วยฟังก์ชันต่อไปนี้ หมายถึงการรวบรวมรายการในทั้งสองเซ็ตโดยไม่มีรายการซ้ำ โปรดทราบว่าฟังก์ชันนี้ใช้เมตาตาราง __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({"Apple", "Mango", "Cherry", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local allFruits = sweetFruits + sourFruits
allFruits:output() -- Peach, Lime, Apple, Cherry, Lemon, Mangoการหักลบ
คุณสามารถลบรายการทั้งหมดในเซ็ตหนึ่งจากรายการในเซ็ตอื่นผ่านฟังก์ชันต่อไปนี้ คล้ายกับฟังก์ชันข้างต้น ฟังก์ชันนี้ใช้เมตาตาราง __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({"Apple", "Lemon", "Mango", "Cherry", "Lime", "Peach"})
local sourFruits = Set.new({"Lemon", "Lime"})
local sweetFruits = allFruits - sourFruits
sweetFruits:output() -- Mango, Apple, Cherry, Peach