lua一些散杂疑惑
大G是增加可以通过字符串实现继承的功能
元表里面用别的表里的变量,如果自己这个表里没有这个变量,当然是用别人的,但如果自己的表里有了这个变量,就优先用自己的
如果 元表的self[调用的键] 查无此“变量”,那会返回nil,不报错
Object = {} -- 创建一个表 Object
function Object:new(o) -- 定义一个方法 new
o = o or {} -- 如果没有传入对象,则创建一个新表
setmetatable(o, self) -- 设置新表的元表为 Object 本身
self.__index = self -- 确保未找到的字段从 Object 中查找
return o -- 返回新创建的对象
end
-- 使用 Object:new 创建一个实例
local instance = Object:new()
print(instance) -- 输出一个 table
function Object:new()
并不是在创建一个变量叫 Object
,而是为 Object
表定义了一个方法 new
。
在 Lua 中:
- 函数本质上是一种值,就像数字、字符串或表一样。
- 当函数作为一个表的字段时,这个函数就成为该表的一个“方法”。
一个名对应一个变量,函数或表都算一个变量
面向对象
在 Lua 中,面向对象(OOP)的概念是通过元表和函数来实现的。虽然 Lua 本身并没有内置的 OOP 机制,但它提供了足够灵活的元表和表结构,使得实现封装、继承和多态等 OOP 特性成为可能。
- 封装:隐藏对象的内部实现,仅通过公开的接口访问数据。
- 继承:一个对象可以继承另一个对象的属性和方法。
- 多态:通过相同的接口实现不同的行为。
简单的对象和方法
-- 定义类
local MyClass = {}
MyClass.__index = MyClass -- 设置元表
-- 构造函数
function MyClass:new(value)
local obj = setmetatable({}, self) -- 创建对象并绑定元表
obj.value = value or 0 -- 初始化属性
return obj
end
-- 定义方法
function MyClass:getValue()
return self.value
end
function MyClass:setValue(value)
self.value = value
end
-- 使用类
local obj = MyClass:new(10) -- 创建对象
print(obj:getValue()) -- 输出:10
obj:setValue(20)
print(obj:getValue()) -- 输出:20
封装私有属性
local MyClass = {}
function MyClass:new(value)
local obj = {} -- 对象
local privateValue = value or 0 -- 私有属性
-- 公有方法
function obj:getValue()
return privateValue
end
function obj:setValue(value)
privateValue = value
end
return obj
end
-- 使用类
local obj = MyClass:new(10)
print(obj:getValue()) -- 输出:10
obj:setValue(20)
print(obj:getValue()) -- 输出:20
-- print(obj.privateValue) -- 错误:无法直接访问私有属性
还挺巧妙的,把函数变量声明在myclass的new方法里面,通过不local的全局可访问能力达到public的效果(但如果止步于此,等这个函数死了之后,里面的的变量还是会死,所以return obj),而闭包则可以让存了setvalue,getvalue这两个方法的obj返回,里面的单独设置的local变量,则被obj的两个方法形成了新的束缚,所以一直活在new方法里面,但是产生不了可以直接点出的联系
完整的一次封装
-- 定义基类
local Animal = {}
Animal.__index = Animal --如果 __index 是一个表,Lua 会在这个表中查找键
--如果 __index 是一个函数,Lua 会调用这个函数并将表和键作为参数传入
--通过 Animal.__index = Animal,我们让 Animal 表可以在对象的元表中作为 __index 的值。
--当对象无法找到某个键时,Lua 就会在 Animal 表中查找。
-- 构造函数
function Animal:new(name)
local obj = setmetatable({}, self)
obj.name = name or "Unnamed"
return obj
end
-- 定义方法
function Animal:speak()
print(self.name .. " makes a sound.")
end
---------------------------------------------------
-- 定义子类
local Dog = setmetatable({}, {__index = Animal}) -- Dog 继承自 Animal
Dog.__index = Dog
-- 构造函数(重写)
function Dog:new(name, breed)
local obj = Animal.new(self, name) -- 调用基类构造函数
obj.breed = breed or "Unknown"
return obj
end
-- 定义方法(重写)
function Dog:speak()
print(self.name .. " barks.")
end
-- 定义方法(扩展)
function Dog:getBreed()
return self.breed
end
---------------------------------------------------
-- 使用基类
local animal = Animal:new("Generic Animal")
animal:speak() -- 输出:Generic Animal makes a sound.
-- 使用子类
local dog = Dog:new("Buddy", "Golden Retriever")
dog:speak() -- 输出:Buddy barks.
print(dog:getBreed()) -- 输出:Golden Retriever
元表
元表的实际用途是模拟面向对象编程,使用元表来实现类的继承和对象方法。
重载操作:定义自定义的算术或比较操作符行为。
表的默认值:使用 __index 为表设置默认值。
元表是为表设计的,不能直接为其他类型(如数字、字符串)设置元表。
如果需要为字符串或数字定义行为,可以通过 元表代理 来实现。
使用元表时,请确保设计的行为符合预期,避免不必要的复杂性。
__call:定义表作为函数调用时的行为。
__tostring:定义表被 tostring 转换时的行为。
__len:定义 # 运算符的行为(获取长度)。
__index:控制表的“读取”操作。
如果访问的键不存在,则会调用元表中的 __index 方法。
__newindex:控制表的“写入”操作。
如果试图向表中写入不存在的键,则触发 __newindex。
__index
是一张表时,Lua 会在这个表中查找访问的键。
local defaults = {x = 10, y = 20}
local point = {}
setmetatable(point, {__index = defaults})
print(point.x) -- 输出:10
print(point.z) -- 输出:nil,因为 `defaults` 中没有 `z`
通过 __index
,可以为表设置默认值。当键不存在时返回默认值,而不报错或返回 nil
。
local defaults = {x = 0, y = 0}
local point = {}
setmetatable(point, {__index = defaults})
print(point.x) -- 输出:0
print(point.y) -- 输出:0
使用 __index
函数,可以在键访问时动态计算返回值。
local myTable = {}
local mt = {
__index = function(t, key)
return "Dynamic value for key: " .. key
end
}
setmetatable(myTable, mt)
print(myTable.abc) -- 输出:Dynamic value for key: abc
print(myTable.xyz) -- 输出:Dynamic value for key: xyz
通过 __index
,可以模拟对象的继承关系。 同时,也可以代理访问
-- 父类
local Parent = {name = "Parent"}
function Parent:sayHello()
print("Hello from " .. self.name)
end
-- 子类
local Child = {name = "Child"}
setmetatable(Child, {__index = Parent})
Child:sayHello() -- 输出:Hello from Child
local source = {a = 1, b = 2}
local proxy = {}
setmetatable(proxy, {__index = source})
print(proxy.a) -- 输出:1(从 `source` 中获取)
print(proxy.b) -- 输出:2(从 `source` 中获取)
如果__index
是表,并且该表本身也有元表,Lua 会递归查找
__index的键如果在对象表里已经有了,
就不用在传入的那个表里找是否存在这个键,然后递归下去了
local grandparent = {x = 1}
local parent = {}
setmetatable(parent, {__index = grandparent})
local child = {}
setmetatable(child, {__index = parent})
print(child.x) -- 输出:1(从 `grandparent` 中获取)
rawget绕过元表,尝试获取对象表里的值,没有找到就返回nil
rawset则绕过元表,同理修改
local defaults = {x = 10}
local point = {}
setmetatable(point, {__index = defaults})
print(point.x) -- 输出:10
print(rawget(point, "x")) -- 输出:nil(`point` 本身没有 `x`)
一个综合的例子
local defaults = {x = 0, y = 0}
local point = {}
local mt = {
__index = function(t, key)
-- 优先从默认值中查找
if defaults[key] then
return defaults[key]
end
-- 动态生成其他值
return "Dynamic-" .. key
end
}
setmetatable(point, mt)
print(point.x) -- 输出:0(默认值)
print(point.y) -- 输出:0(默认值)
print(point.z) -- 输出:Dynamic-z
__eq:定义相等 (==)
__lt:定义小于 (<)
__le:定义小于等于 (<=)
local t1 = {value = 10}
local t2 = {value = 20}
local mt = {
__lt = function(a, b)
return a.value < b.value
end,
__eq = function(a, b)
return a.value == b.value
end
}
setmetatable(t1, mt)
setmetatable(t2, mt)
print(t1 < t2) -- 输出:true
print(t1 == t2) -- 输出:false
元表本质上是一张表,它可以定义一组元方法,用来控制对表的某些操作的行为
元表的核心机制是通过元方法来实现的,元方法是元表中的特定键(如 __add、__index 等)
setmetatable(table, metatable)
为 table 设置元表为 metatable
getmetatable(table)
获取一个表的元表,如果没有设置元表,则返回 nil
__add:定义加法 (+)
__sub:定义减法 (-)
__mul:定义乘法 (*)
__div:定义除法 (/)
__mod:定义取模 (%)
__pow:定义幂运算 (^)
local t1 = {value = 10}
local t2 = {value = 20}
local mt = {
__add = function(a, b)
return {value = a.value + b.value}
end
}
setmetatable(t1, mt)
setmetatable(t2, mt)
local result = t1 + t2
print(result.value) -- 输出:30
表之间的加减乘除默认会报错
定义加号,需要传入两个参数,如果是要两个表里的age相加,那么里面必须要有age
运算符只有小于和小于等于,官方没给别的,要自己取反
--concat是..的重写
协程
wrap创造的协程不返回是否执行成功
协程的yield可以返回值,然后用多个变量去接收
第一个默认为是否执行成功true 或者false
在 Lua 中,coroutine.wrap 是用于创建协同程序的一种方式,它与 coroutine.create 类似,但有一些显著的区别和特点。
coroutine.wrap 接收一个函数,返回一个新的函数(不是协同程序)。当调用这个返回的函数时,实际上是启动或恢复对应的协同程序,并返回协同程序运行的结果。
协程的本质是一个线程对象
加了local的变量不会存到大G表里
require()
在 Lua 中,require
是一个用于加载和运行模块的函数,广泛用于组织代码和实现模块化开发。以下是关于 require
的详细讲解:
1. 基本作用
require
用于加载 Lua 脚本或 C 模块,并执行加载的代码。如果模块已经加载过一次,require
会直接返回加载的结果,而不会重复加载。
-- example.lua 文件
local M = {}
M.greet = function()
print("Hello, Lua!")
end
return M
-- 主脚本 main.lua
local example = require("example")
example.greet() -- 输出:Hello, Lua!
require("example")
加载example.lua
文件。- 加载成功后返回文件中
return
的值。
2. 加载机制
require
的加载行为由 Lua 的 package.path
和 package.cpath
决定。它们定义了 Lua 和 C 模块的搜索路径:
package.path
: 用于搜索 Lua 文件的路径,例如.lua
或.luac
文件。package.cpath
: 用于搜索 C 模块的路径,例如动态链接库(.dll
、.so
等)。
搜索路径示例
print(package.path) -- 输出类似: -- ./?.lua;./?/init.lua;/usr/local/share/lua/5.1/?.lua;... print(package.cpath) -- 输出类似: -- ./?.so;/usr/local/lib/lua/5.1/?.so;...
说明:
?
是占位符,会被替换为模块名。例如,require("example")
会尝试加载./example.lua
或./example/init.lua
。- 如果路径中找不到模块,会报错:
module 'example' not found
。
3. 避免重复加载
Lua 内部维护了一个全局表 package.loaded
,用来记录已经加载的模块。每次调用 require
时,Lua 会先检查这个表,只有模块尚未加载时才会重新加载。
local module1 = require("example") local module2 = require("example") -- module1 和 module2 是同一个表对象 print(module1 == module2) -- 输出:true
4. 模块结构
为了更好地组织模块,Lua 社区约定了标准的模块结构。一个模块通常:
- 定义为一个表。
- 包含需要暴露的功能。
- 在文件末尾使用
return
返回这个表。
示例:简单模块
-- mymodule.lua local M = {} M.add = function(a, b) return a + b end M.sub = function(a, b) return a - b end return M-- main.lua
local mymodule = require("mymodule")
print(mymodule.add(5, 3)) -- 输出:8
print(mymodule.sub(5, 3)) -- 输出:2
5. 加载非 Lua 文件
require
也可以加载 C 模块,通常是通过动态链接库实现的。例如,加载一个 example.so
文件:
local example = require("example") -- 会从 package.cpath 搜索路径加载
需要注意的是,C 模块需要定义特定的入口点,以便 Lua 调用。
6. 自定义 require
的搜索路径
可以通过修改 package.path
或 package.cpath
来自定义 require
的搜索路径。例如:
package.path = package.path .. ";./custom/?.lua" local customModule = require("mymodule")
上述代码会让 require
搜索 ./custom/mymodule.lua
。
7. 动态加载模块
如果只想加载模块而不缓存,可以直接使用 loadfile
或 dofile
:
dofile
: 加载并立即运行脚本。
dofile("example.lua") -- 执行 example.lua 中的代码local chunk = loadfile("example.lua")
chunk() -- 执行加载的代码块
相比之下,require
更安全且高效,因为它具有模块缓存机制。
8. require
和 module
的历史
Lua 5.1 曾经使用 module
函数来定义模块,但后来在 Lua 5.2 开始逐步弃用。现代 Lua 模块通常直接用表返回。
总结
require
是 Lua 中模块化编程的核心工具。- 它通过
package.path
和package.cpath
实现模块搜索。 - 加载的模块会被缓存到
package.loaded
中,避免重复加载。 - 现代 Lua 使用
return
的表来组织模块内容。
print(d)结果为nil