Metatables cho phép các bảng trở nên mạnh hơn trước đây. Chúng được kết nối với dữ liệu và chứa các giá trị được gọi là metamethông số. Metatethông số được kích hoạt khi một hành động nhất định được sử dụng với dữ liệu đó.
Hãy xem xét mã sau đây:
local list = {1, 2}print(list[3])
Bạn có thể mong đợi mã này để tìm kiếm trong danh sách cho chỉ mục thứ ba trong danh sách, tìm không thấy bất cứ điều gì và trả lại nil. Điều đó không đúng, tuy nhiên. Điều thực sự xảy ra là mã tìm kiếm trong danh sách cho chỉ mục thứ ba, không tìm thấy bất
Làm việc với bảng dữ liệu
Hai chức năng chính để thêm và tìm bảng dữ liệu, là setmetatable và getmetatable
local x = {}local metaTable = {} -- metaTables là các bảng, cũng vậy!setmetatable(x, metaTable) -- Đưa x một bảng dữ liệu được gọi là bảng dữ liệu meta!print(getmetatable(x)) --> table: [hexadecimal memory address]
Hàm setmetatable cũng trả về bảng mà bạn đang cài đặt metatable, vì vậy hai hàm này làm điều tương tự:
local x = {}setmetatable(x, {})
local x = setmetatable({}, {})
Metameths
Metamethông là các hàm được lưu trong một metatable. Chúng có thể đi từ gọi một bảng, đến thêm một bảng, thậm chí chia bảng cũng như vậy. Đây là danh sách các hàm metamethông sẵn có:
Phương pháp | Mô tả |
---|---|
__index(bảng, index) | Lửa khi table[index] được xếp hạng, nếu table[index] là nil. Cũng có thể được đặt cho một bảng, trong đó bảng đó sẽ được xếp hạng. |
__newindex(bảng, index, giá trị) | Lửa khi table[index] cố gắng được đặt (table[index] = value), nếu table[index] là nil. Cũng có thể được đặt vào một table, trong đó table[index] sẽ được indexed. |
__gọi(bảng, ...) | Lửa khi bạn gọi bảng như một chức năng, ... là những lý do đã được truyền. |
__concat(bảng, giá trị) | Lửa khi .. operator concatenation được sử dụng trên bảng. |
__unm(bảng) | Lửa khi hàm – operator được sử dụng trên bảng. |
__add(bảng, giá trị) | Người dùng +. |
__sub(bảng, giá trị) | The - subtraction operator. |
__mul(bảng, giá trị) | Người * nhân bản. |
__div(bảng, giá trị) | The / division operator. |
__idiv(bảng, giá trị) | The // chia sàn divider. |
__mod(bảng, giá trị) | Nhà sản xuất modul % |
__pow(bảng, giá trị) | ^ operator. |
__tostring(bảng) | Được gọi khi đã gọi được để string được gọi trên bảng. |
__metitable | Nếu hiện có, khóa bảng hiện, để getmetable trả về nó thay vì bảng hiện và không đặt bảng hiện sẽ lỗi. Giá trị không hợp lệ. |
__eq(bảng, giá trị) | The == bằng nhau như operator¹ |
__lt(bảng, giá trị) | The < dưới dưới operator¹ |
__le(bảng, giá trị) | The >>> operator1 |
__chế độ | Được sử dụng trên những bảng yếu, xác định whether các chìa khóa và/hoặc giá trị của bảng yếu. Ghi chú: Tham khảo đến các bảng yếu của Roblox không bao giờ yếu. Tables that hold such references will never be garbage collected. |
__gc(bảng) | Được kích hoạt khi bảng đã thu thập rác. Ghi chú: Trên Roblox, metamethode này đã bị vô hiệu hóa. |
__len(bảng) | Được kích hoạt khi biểu tượng # dài được sử dụng trên Objet. |
__iter(bảng) | Được sử dụng để chỉ khi sử dụng kẻ lặp chung. |
Nên lưu ý rằng khi viết các hàm cho cả arithmetic hoặc metamethông số quan hệ đều hai tham số hàm đều có thể đổi vị trí giữa các cột trong bảng mà chạy metamethông số. Ví dụ, khi thực hiện các hàm trên vector2 bằng cách sử dụng hệ số nhân thì bạ
local vector2 = {__type = "vector2"}
local mt = {__index = vector2}
function mt.__div(a, b)
if type(a) == "number" then
-- a là một biểu tượng, b là một vectơ
local scalar, vector = a, b
return vector2.new(scalar / vector.x, scalar / vector.y)
elseif type(b) == "number" then
-- a là một vectơ, b là một hàm số
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
-- cả a và b đều là véctor
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.333333333333, 1.25
print(b / a) -- hệ điều hành: -0.3, 0.8
print(2 / a) -- 0.2, 0.4
print(a / 2) -- 5, 2.5
Sử dụng Metatables
Có nhiều cách để sử dụng metatables, ví dụ như metamethode __unm (để làm cho một table negative):
local metatable = {
__unm = function(t) -- __unm dành cho unary - operator
local negated = {}
for key, value in t do
negated[key] = -value -- đảo lại tất cả các giá trị trong bảng này
end
return negated -- quay trở lại bảng
end
}
local table1 = setmetatable({10, 11, 12}, metatable)
print(table.concat(-table1, "; ")) --> -10; -11; -12
Đây là một cách thú vị để tuyên bố những gì bằng cách sử dụng __index :
local metatable = {__index = {x = 1}}local t = setmetatable({}, metatable)print(t.x) --> 1
__index đã được kích hoạt khi x được lưu trong bảng và không được tìm thấy. Lua sau đó tìm kiếm trên bảng __index cho một chỉ mục tên là x, và, tìm thấy một trong số này, trả lại nó.
Bây giờ bạn có thể dễ dàng làm điều đó với một chức năng đơn giản, nhưng có rất nhiều hơn nữa nơi đó đến từ. Lấy ví dụ này:
local t = {10, 20, 30}print(t(5))
Bây giờ, rõ ràng bạn không thể gọi một bảng. Đó chỉ điên, nhưng (tuyệt vời, tuyệt vời!) với metatables bạn có thể.
local metatable = {
__call = function(t, param)
local sum = {}
for i, value in ipairs(t) do
sum[i] = value + param -- Thêm argument (5) vào giá trị, sau đó đặt nó vào bảng mới (t).
end
return unpack(sum) -- Trả lại giá trị cột riêng lẻ
end
}
local t = setmetatable({10, 20, 30}, metatable)
print(t(5)) --> 15 25 35
Bạn cũng có thể làm nhiều hơn, chẳng hạn như thêm các bảng!
local table1 = {10, 11, 12}local table2 = {13, 14, 15}for k, v in table1 + table2 doprint(k, v)end
Điều này sẽ xảy ra lỗi nói rằng bạn đang cố gắng thực hiện arithmetic trên một bảng. Hãy thử này với một bảng tối ưu.
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
Sử dụng trường hợp
Bây giờ, tất cả các ví dụ này có thể được thực hiện như một chức năng đơn giản, nhưng bạn có thể làm nhiều hơn thế. Hãy thử một chương trình đơn giản sẽ lưu một số khi một vấn đề toán có thể laggy được đưa vào nó.
Đối với cái này, chúng ta sẽ sử dụng metamethode __index để làm cho nó đơn giản:
local function mathProblem(num)
for i = 1, 20 do
num = math.floor(num * 10 + 65)
end
for i = 1, 10 do
num += i - 1
end
return num
end
local metatable = {
__index = function(object, key)
local num = mathProblem(key)
object[key] = num
return num
end
}
local t = setmetatable({}, metatable)
print(t[1]) -- Sẽ chậm vì đây là lần đầu tiên sử dụng số này, vì vậy nó phải thực hiện chức năng toán.
print(t[2]) -- sẽ chậm vì đây là lần đầu tiên sử dụng số này.
print(t[1]) -- will be fast because it's just grabbing the number from the table.
Rawset, Rawget, Rawequal
Khi chơi với metatables, bạn có thể gặp một số vấn đề. What happens if you need to use the __index metamethod to create new values in a table, but that table's metabel ... cũng có một __newindex metamethod in it? Bạn sẽ muốn sử
local t = setmetatable({}, {
__index = function(self, i)
self[i] = i * 10 -- chỉ là một ví dụ
return self[i]
end,
__newindex = function(self, i, v)
--đừng làm bất cứ điều gì vì chúng tôi không muốn bạn đặt giá trị vào bảng theo cách thức thông thường
end
})
print(t[1]) -- Causes a C-Stack overflow
Bây giờ tại sao nó gây ra một lỗi trong đối tượng? Lỗi trong đối tượng xảy ra khi bạn cố gắng gọi một hàm từ chính nó quá nhiều lần, nhưng điều gì sẽ khiến nó x
Vấn đề là __newindex không cho phép chúng tôi đặt giá trị. Sự hiện diện của nó ngăn cản giá trị được thêm vào bảng với phương pháp tiêu chuẩn t[i] = v . Để vượt qua điều này, bạn sử dụng chức năng rawset.
local t = setmetatable({}, {
__index = function(self, i)
rawset(self, i, i * 10)
return self[i]
end,
__newindex = function(self, i, v)
--đừng làm bất cứ điều gì vì chúng tôi không muốn bạn đặt giá trị vào bảng theo cách thức thông thường
end
})
print(t[1]) -- prints 10
Sử dụng Set Datatype
Một bộ là một bộ sưu tập các mục không có thứ tự và không có thành phần lặp lại. Một mục được là hay nó không được bao gồm trong một cài đặt. Sử dụng metatables, bạn có thể xây dựng và thao tác với các bộ trong Lua scripts.
Các phương pháp cơ bản
Mã sau đây bao gồm các chức năng cơ bản, cho phép bạn xây dựng các bộ dữ liệu mới, thêm và xóa một vật phẩm, kiểm tra có một vật phẩmtrong bộ dữ liệu hoặc xuất nội dung của bộ dữ cài đặt.
local Set = {}
Set.__index = Set
-- Hàm toán để xây dựng một bộ dữ liệu từ một danh sách tùy chọn các mục
function Set.new(items)
local newSet = {}
for key, value in items or {} do
newSet[value] = true
end
return setmetatable(newSet, Set)
end
-- Hàm để thêm một mục vào một cài đặt
function Set:add(item)
self[item] = true
end
-- Hàm để xóa một thành phần khỏi một cài đặt
function Set:remove(item)
self[item] = nil
end
-- Hàm để kiểm tra có phải một bộ dữ liệu chứa một vật phẩm
function Set:contains(item)
return self[item] == true
end
-- Chức năng để xuất bộ định dạng như một danh sách dấu câu cho mục đích debug
function Set:output()
local elems = {}
for key, value in self do
table.insert(elems, tostring(key))
end
print(table.concat(elems, ", "))
end
Tạo một bộ
Một bộ mới có thể được tạo bằng cách gọi Set.new() với một loạt tùy chọn các mục để thêm vào.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})
Ghi chú rằng theo định nghĩa, một bộ dữ liệu không có khái niệm về việc đặt hàng.
Thêm vật phẩm
Thêm một mục vào bộ đã tồn tại có thể được thực hiện bằng cách sử dụng phương thức Set:add() .
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:add("Mango")
Loại bỏ mục
Để xóa một mục từ một cài đặt, gọi Set:remove() với tên mục.
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})fruits:remove("Orange")
Kiểm tra tìm kiếm
Để kiểm tra có phải một bộ gồm một vật phẩmnhất định, hãy sử dụng Set:contains() .
local fruits = Set.new({"Apple", "Lemon", "Orange", "Cherry", "Lime", "Peach"})local result1 = fruits:contains("Cherry")print(result1) -- đúnglocal result2 = fruits:contains("Watermelon")print(result2) -- false
Các phương pháp bổ sung
Những hành động hữu ích khác có thể được thực hiện cho các bộ dữ liệu, cho phép bạn so sánh các mục giữa các bộ dữ liệu, kết hợp các bộ dữ liệu hoặc trừ một bộ dữ liệu khỏi một bộ dữ liệu khác.
Giao điểm
Khi xem các bộ dữ liệu như Venn đồ thị, bạn có thể nhận được intersection của hai bộ dữ liệu như sau, có nghĩa là các mục xuất hiện ở cả hai bộ dữ liệu.
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
Liên minh
Bạn có thể nhận được liên hệ của hai bộ với các chức năng sau, có nghĩa là một bộ sưu tập các mục trong cả hai bộ với không có duplicates. Ghi nhớ rằng chức năng này sử dụng metatable __add để cung cấp một tổng quát tắt của set1
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
Tách
Bạn có thể xóa tất cả các mặt hàng trong một bộ dữ liệu từ các mặt hàng trong một bộ dữ liệu khác thông qua chức năng sau đây. Tương tự như chức năng trên trên, điều này sử dụng metatable __sub để cung cấp một lối trừ dữ nhanh 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