Mục lục
🌟 Metatable là gì?
Metatable (bảng siêu dữ liệu) trong Lua là các bảng đặc biệt cho phép bạn thay đổi cách các bảng thông thường hoạt động. Hãy nghĩ chúng như những “quy tắc” bạn có thể thiết lập để điều khiển điều gì xảy ra khi bạn thực hiện một số hành động nhất định với một bảng. Logic của nó rất đơn giản và hoàn toàn không gây nhầm lẫn.
- Dưới đây là một bảng thông thường:
regular_Table = {} meta_rule = {} -- quy tắc của bạn ở đây
- Dưới đây là hàm dùng để thiết lập Metatable:
setmetatable(regular_Table, meta_rule)
Bây giờ bảng regular_table đã được thiết lập các phương thức meta sử dụng quy tắc trong meta_rule. Nhưng trong ví dụ này, ta chưa làm gì với bảng regular_table.
📌 Danh sách các quy tắc
Dưới đây là một số hành vi phổ biến mà bạn có thể thay đổi:
- Truy cập một khóa không tồn tại (_index)
- Gán một cặp khóa-giá trị mới (_newindex)
- Các phép toán số học (_add, _sub, _mul, _div, _mod, _pow, _unm)
- Nối chuỗi (_concat)
- Toán tử độ dài (_len)
- So sánh bằng (_eq)
- So sánh nhỏ hơn và nhỏ hơn hoặc bằng (_lt, _le)
- Gọi hàm (_call)
- Chuyển bảng thành chuỗi (_tostring)
- Thu gom rác của bảng (_gc)
🛠️ Cách sử dụng & Ví dụ
Metatable thường được dùng cho framework hoặc tạo ra mã có thể tái sử dụng.
1. Truy cập một khóa không tồn tại _index
Bạn có thể định nghĩa điều gì xảy ra khi bạn cố truy cập một khóa không tồn tại trong bảng.
local myTable = {} local meta = { __index = function(table, key) return "This key does not exist!" end } setmetatable(myTable, meta) print(myTable.nonExistentKey) -- Output: This key does not exist!
Từ ví dụ trên, bạn có thể chỉnh sửa mã để khi một khóa không tồn tại được truy cập, nó sẽ thực thi một hàm và trả về thông báo “This key does not exist!”.
Bạn cũng có thể chỉnh hàm này để thực hiện hành động cụ thể, ví dụ như thêm khóa với một giá trị mặc định.
2. Gán một cặp khóa-giá trị mới (_newindex)
Bạn có thể kiểm soát hành vi khi thêm một cặp khóa-giá trị mới vào bảng.
local myTable = {} local meta = { __newindex = function(table, key, value) print("New key-value pair added:", key, value) end } setmetatable(myTable, meta) myTable.newKey = "newValue" -- Output: New key-value pair added: newKey newValue
Chú ý rằng quy tắc trong bảng meta là _newindex, nó thiết lập quy tắc cho việc thêm chỉ số mới. Quy tắc này sẽ thực thi _newindex(table, key, value). Các tham số là cố định.
3. Các phép toán số học (_add, _sub, _mul, _div, _mod, _pow, _unm)
Bạn có thể định nghĩa hành vi tùy chỉnh cho các phép toán số học với bảng.
local table1 = {1, 2} local table2 = {3, 4} local meta = { __add = function(a, b) return {a[1] + b[1], a[2] + b[2]} end } setmetatable(table1, meta) local result = table1 + table2 print(result[1], result[2]) -- Output: 4 6
Trong Lua, hành vi mặc định khi bạn thực hiện các phép toán số học (như cộng, trừ, nhân, v.v.) trên bảng là gây lỗi. Lua không hỗ trợ sẵn các phép toán trên bảng vì bảng không được thiết kế để dùng như các thực thể toán học.
Với metatable, bạn có thể thiết lập hành vi và phương thức khi thực hiện phép toán trên bảng, nhưng nó chỉ hoạt động với các metatable bạn đã định nghĩa.
4. Nối chuỗi (_concat)
Bạn có thể định nghĩa hành vi nối bảng tùy chỉnh.
local table1 = {1, 2} local table2 = {3, 4} local meta = { __concat = function(a, b) return table.concat(a, ",") .. "," .. table.concat(b, ",") end } setmetatable(table1, meta) local result = table1 .. table2 print(result) -- Output: 1,2,3,4
Mặc định, Lua không hỗ trợ nối bảng bằng toán tử … Thực hiện nối mà không định nghĩa hành vi sẽ gây lỗi.
5. Toán tử độ dài (_len)
Bạn có thể định nghĩa hành vi tùy chỉnh cho việc tính độ dài bảng.
local myTable = {1, 2, 3} local meta = { __len = function(t) return 42 -- Một giá trị tuỳ ý end } setmetatable(myTable, meta) print(#myTable) -- Output: 42
Hành vi mặc định của toán tử # trong Lua là trả về độ dài phần mảng của bảng. Nó chỉ tính dãy liên tiếp không nil bắt đầu từ index 1.
Nó không tính các thay đổi động khi bạn dùng table.insert và sau đó table.remove ở giữa.
Nhưng bạn có thể chỉnh sửa như sau:
-- Bảng ví dụ local myTable = {1, 2, 3, nil, 5, key1 = "value1", key2 = "value2"} print(#myTable) -- Output: 3 -- Metatable với phương thức __len local meta = { __len = function(t) local count = 0 for k, v in pairs(t) do if v ~= nil then count = count + 1 end end return count end } -- Thiết lập metatable cho myTable setmetatable(myTable, meta) -- Bây giờ dùng toán tử # sẽ gọi __len print(#myTable) -- Output: 7
Hoặc bạn có thể trả về 0 vì lý do bảo mật.
6. So sánh bằng (_eq)
Bạn có thể định nghĩa hành vi tùy chỉnh khi so sánh hai bảng.
local table1 = {1, 2} local table2 = {1, 2} local meta = { __eq = function(a, b) return a[1] == b[1] and a[2] == b[2] end } setmetatable(table1, meta) setmetatable(table2, meta) print(table1 == table2) -- Output: true
Bạn có thể ghi đè hành vi so sánh bảng bằng cách đặt metatable với _eq (cho == và ~ và các phương thức khác để tùy chỉnh theo yêu cầu ứng dụng.
7. So sánh nhỏ hơn và nhỏ hơn hoặc bằng (_lt, _le)
Bạn có thể định nghĩa hành vi so sánh hai bảng sử dụng < và ⇐.
local table1 = {1} local table2 = {2} local meta = { __lt = function(a, b) return a[1] < b[1] end } setmetatable(table1, meta) setmetatable(table2, meta) print(table1 < table2)
Lua không cung cấp sẵn cơ chế so sánh bảng theo nội dung bằng toán tử quan hệ. Bạn phải duyệt và so sánh thủ công nếu muốn so sánh theo nội dung.
8. Gọi hàm (_call)
Bạn có thể làm cho bảng hoạt động như một hàm.
local myTable = {} local meta = { __call = function(table, arg) return "Called with argument: " .. arg end } setmetatable(myTable, meta) print(myTable("Hello")) -- Output: Called with argument: Hello
Khi bạn thiết lập _call, tham số đầu tiên luôn là chính bảng đó. Bạn có thể thêm các tham số khác sau.
Tương tự khi bạn dùng dấu : để gọi hàm trong bảng.
9. Chuyển bảng thành chuỗi (_tostring)
Bạn có thể định nghĩa cách bảng được chuyển sang chuỗi.
local myTable = {1, 2, 3} local meta = { __tostring = function(t) return "This is my custom table!" end } setmetatable(myTable, meta) print(myTable) -- Output: This is my custom table!
Chú ý rằng print(myTable) không gọi myTable() mà là in trực tiếp bảng. Mặc định khi bạn print() một hàm, nó sẽ in như kiểu functionx123ZEW.
10. Thu gom rác bảng (_gc)
Bạn có thể định nghĩa hành vi khi bảng bị thu gom bởi garbage collector.
local myTable = {} local meta = { __gc = function(t) print("Table is being garbage collected") end } setmetatable(myTable, meta) myTable = nil collectgarbage() -- Output: Table is being garbage collected