Bootstrap

自由学习记录(24)

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.pathpackage.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.pathpackage.cpath 来自定义 require 的搜索路径。例如:

package.path = package.path .. ";./custom/?.lua"

local customModule = require("mymodule")

上述代码会让 require 搜索 ./custom/mymodule.lua


7. 动态加载模块

如果只想加载模块而不缓存,可以直接使用 loadfiledofile

  • dofile: 加载并立即运行脚本。
dofile("example.lua") -- 执行 example.lua 中的代码

local chunk = loadfile("example.lua")
chunk() -- 执行加载的代码块
 

相比之下,require 更安全且高效,因为它具有模块缓存机制


8. requiremodule 的历史

Lua 5.1 曾经使用 module 函数来定义模块,但后来在 Lua 5.2 开始逐步弃用。现代 Lua 模块通常直接用表返回。


总结

  • requireLua 中模块化编程的核心工具
  • 通过 package.pathpackage.cpath 实现模块搜索。
  • 加载的模块会被缓存到 package.loaded,避免重复加载。
  • 现代 Lua 使用 return 的表来组织模块内容。

print(d)结果为nil

;