Continuar de cima
Diretório de artigos
metatabela
P: Por que usar metatabelas?
R: Em Lua, muitas vezes são necessárias operações entre tabelas. A metatabela fornece alguns metamétodos. Você pode obter as funções desejadas personalizando metamétodos, o que equivale a fornecer uma série de métodos em métodos orientados a objetos para você sobrecarregar.
Use o seguinte código para configurar a metatabela:
meta={
}
table={
}
setmetatable(table,meta) -- 第一个元素为子表,第二个元素为元表
Normalmente as operações na metatabela são divididas em três etapas:
- Subtabela de operação
- Verifique se existe uma metatabela
- Se houver uma meta-tabela, verifique se existe um meta-método correspondente. Se não houver um meta-método, retorne ao processamento original da operação correspondente. Se houver um metamétodo correspondente, execute o metamétodo.
Os índices de metamétodos geralmente começam com "__" (dois sublinhados, acho que isso ocorre porque nomear variáveis estáticas privadas geralmente começa com um sublinhado), como no exemplo a seguir:
__tostring
meta = {
}
table={
}
setmetatable(table, meta)
print(table)
输出:
table: 00ABA2A8
meta = {
__tostring = function () <--注意两个下划线
return "123"
end
}
table={
}
setmetatable(table, meta)
print(table)
输出:
123
O exemplo acima é equivalente a usar um método meta para sobrecarregar a função de impressão
meta = {
__tostring = function (t)
return t.name
end
}
table={
name = "233"}
setmetatable(table, meta)
print(table)
输出:
233
No exemplo acima, mesmo que não especifiquemos os parâmetros de entrada do metamétodo, devido à associação entre a subtabela e a metatabela, o metamétodo passará automaticamente a subtabela para o metamétodo como parâmetro.
__call
Vamos definir outro __call
metamétodo
meta = {
__tostring = function ()
return "456"
end,
__call = function(a)
print(a.name)
print(a)
print("call")
end
}
table={
name = "123"}
setmetatable(table, meta)
table(2) --无论table(x)中给出的x为几,结果都是一样的
输出:
123
456
call
Depois de definir __call
o metamétodo, nós o usamos table(index)
e descobrimos que __call
o metamétodo imprimiu 123, 456 e chamada.123 é o elemento da subtabela e 456 é o __tostring
resultado de retorno. Isso significa que __call
o método chama a subtabela como parâmetro a. E como nosso exemplo de impressão acima, print(a)
o método também chama __tostring
o metamétodo. O parâmetro 2 in table(2)
é obviamente ignorado.
Agora podemos determinar uma regra básica: ao usar __tostring
o __call
metamétodo sum, o primeiro parâmetro que definimos deve ser a própria tabela da subtabela. Somente definindo mais parâmetros podemos receber outros parâmetros de entrada:
meta = {
__tostring = function ()
return "456"
end,
__call = function(a,b)
print(a)
print(b)
print("call")
end
}
table={
name = "123"}
setmetatable(table, meta)
table(2)
输出:
456
2
call
__index
meta = {
}
table1 = {
age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
nil
Queremos encontrar o índice do nome na tabela, mas é claro que ele não existe. O resultado da saída é nulo.
A metatabela pode ter esse índice? A resposta é não, pois ainda estamos procurando a tabela1:
meta = {
name =1}
table1 = {
age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
nil
Agora definimos um metamétodo __index
que apontará para outra tabela de consulta
meta = {
name = 2 }
meta.__index = meta
table1 = {
age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
2
Todo o processo de pesquisa é na verdade:
- Subtabela de consulta
- Falha na consulta da subtabela, consulte a metatabela para ver se existe um
__index
metamétodo - Se sim, consulte
__index
a tabela apontada pelo meta-método
Porém, há outra situação em que é recomendado não __index
definir o metamétodo dentro da metatabela:
meta = {
name = 2,
__index = meta,
}
table1 = {
age = 1 }
setmetatable(table1, meta)
print(table1.name)
输出:
nil --不明觉厉
Você também pode usar bonecas matryoshka
meta = {
}
metaFather = {
name =4,
}
table1 ={
age =1}
meta.__index = meta --让元表的index指向meta,子表找不到就去meta里找
metaFather.__index =metaFather --让元表的index指向metaFather,子表找不到就去metaFather里找
setmetatable(meta,metaFather)
setmetatable(table1, meta)
print(table1.name)
输出:i
4 --允许套娃
__newindex
Veja 4 exemplos:
meta = {
}
table1 ={
}
table1.age = 1
setmetatable(table1, meta)
print(table1.age)
输出:
1
meta = {
}
table1 ={
age = 1}
meta.__newindex = {
}
setmetatable(table1, meta)
print(table1.age)
输出:
1
meta = {
}
table1 ={
}
meta.__newindex = {
}
setmetatable(table1, meta)
table1.age = 1
print(table1.age)
输出:
nil
meta = {
}
table1 ={
}
meta.__newindex = {
}
table1.age = 1
setmetatable(table1, meta)
print(table1.age)
输出:
1
É estranho? Na verdade, é fácil de entender. Isso se deve ao __newindex
método meta. Sua função é, na verdade, adicionar os elementos recém-adicionados da subtabela à __newindex
tabela apontada sem modificar a subtabela (é claro __newindex
que também pode ser usado como uma boneca de nidificação):
meta = {
}
table1 ={
}
meta.__newindex = {
}
setmetatable(table1, meta)
table1.age =1
print(table1.age)
print(meta.__newindex.age)
输出:
nil
1
metamétodo do operador
meta = {
__sub = function (t1,t2)
return t1.age - t2.age
end
}
table1={
age=1}
table2={
age=2}
setmetatable(table1, meta) --无论把元表设置给table1还是table2,结果都一样
print(table1 - table2)
输出:
-1
Descobrimos que ao usar o metamétodo do operador, o primeiro parâmetro não é mais a subtabela vinculada por padrão. Em vez disso, o metamétodo recebe valores em sequência de acordo com as variáveis esquerda e direita do operador. E independentemente de a metatabela estar definida como subtraendo ou minuendo, o resultado final permanece inalterado. Existem muitos métodos de operador, então não vou listá-los em detalhes aqui. Copiei uma tabela diretamente do tutorial para iniciantes:
metamétodo | descrever |
---|---|
__adicionar | Operador correspondente '+' |
__sub | Operador correspondente '-' |
__mul | Operador correspondente '*' |
__div | Operador correspondente '/' |
__mod | Operador correspondente '%' |
__unm | Operador correspondente '-' |
__concat | O operador correspondente '..' |
__Pancada | Operador correspondente '^' |
__eq | Operador correspondente '==' |
__lt | Operador correspondente '<' |
__o | Operador correspondente '<=' |
Além das operações matemáticas normais, também precisamos falar sobre os metamétodos mais especiais de julgamento lógico (as colunas em negrito na tabela acima).Esses metamétodos exigem que ambos os lados do operador estejam vinculados ao mesmo Yuan mesa. A razão é que o metamétodo não fornece um símbolo maior que. O metamétodo de calcular a>b é na verdade equivalente ao método original de calcular b<a. Isso exige que b também esteja vinculado à metatabela. Portanto, , as operações lógicas exigem que ambos os lados da operação estejam vinculados ao mesmo elemento. Tabela, vejamos o seguinte exemplo:
meta = {
__eq= function (t1,t2)
return true
end,
}
table1 = {
age = 1 }
table2 = {
name = nil }
setmetatable(table1, meta)
print(table1 == table2)
setmetatable(table2, meta)
print(table1 == table2)
输出:
false
true
Podemos descobrir que há um problema com o julgamento lógico de igualdade acima: a primeira impressão é falsa e a segunda impressão é verdadeira.
Na primeira impressão,apenas table1
a metatabela está ligada,mas table2
não a metatabela.A razão return false
é porque durante as operações lógicas,tanto os lados esquerdo como o direito precisam de ser ligados à mesma metatabela.
E na segunda tiragem foi impresso true
, mesmo que fosse diferente table1
do table2
conteúdo. Como este é nosso metamétodo personalizado, é o que retornamos por padrão true
.
Alguns exemplos. Considere os resultados de saída dos seguintes exemplos:
meta = {
__eq= function (t1,t2)
if t1.age == t2.age then
return true
end
end,
}
table1 = {
age = 1 }
table2 = {
age = 2 }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)
输出:
false
No exemplo acima, usamos __eq
o método meta para determinar age
se os valores do índice são iguais, o que é obviamente diferente, então éfalse
meta = {
__eq= function (t1,t2)
if t1.name == t2.name then
return true
end
end,
}
table1 = {
age = 1 }
table2 = {
name = nil }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)
输出:
true
No exemplo acima, usamos o método meta para determinar name
se os valores do índice são iguais. Tabela1 não tem name
índice. O padrão é nil
, tabela2 tem name = nil
, então étrue
meta = {
__eq= function (t1,t2)
if t1 == t2 then
return true
end
end,
}
table1 = {
age = 1 }
table2 = {
name = nil }
setmetatable(table1, meta)
setmetatable(table2, meta)
print(table1 == table2)
输出:
栈溢出
No exemplo acima, fazemos um julgamento dentro do meta-método t1==t2
e, ao fazer o julgamento, é equivalente a chamar __eq
o meta-método novamente, então ele fará um loop infinito e eventualmente levará ao estouro de pilha.
Outras operações metatabelas
print(getmetatable(table1)) --使用getmetatable方法获得table1的元表地址
输出:
table: 00BBA320
meta = {
name = 2 }
meta.__index = meta
table1 = {
age = 1 }
setmetatable(table1, meta)
print(rawget(table1,"nane")) --rawget方法只查询子表,无视元表index
输出:
nil
meta = {
}
table1 ={
}
meta.__newindex = {
}
setmetatable(table1, meta)
table1.age =1
print(table1.age)
rawset(table1, "age", 1) --rawset方法可以无视元表的newindex,直接修改子表
print(table1.age)
输出:
nil
1