Lua基础知识总结笔记-八股文

2024-09-24 11:10:11 浏览数 (2)

前言

一些感觉不足以单独开一篇文章写的lua相关内容,我都会尽量的放在这里。

不止是八股,也会有一些平常学习的小心得。

目前有点乱,以后内容补充多了会整理切分

Lua语言相关

Lua的基础数据类型有哪些

基础数据类型的使用

nil

  • 用途:表示一个变量没有任何值。
  • 示例
代码语言:lua复制
local x = nil
if x == nil then
    print("x is nil")
end

boolean

  • 用途:表示布尔值,用于条件判断。
  • 示例
代码语言:lua复制
local enabled = false
if enabled then
    print("Feature is enabled")
else
    print("Feature is disabled")
end

number

  • 用途:表示数值,可以是整数或浮点数。
  • 示例
代码语言:lua复制
local num = 10
local result = num * 2
print(result) -- 输出 20
  • 用途:表示文本字符串,用于字符串操作。
  • 示例
代码语言:lua复制
local str = "Hello, World!"
print(str) -- 输出 Hello, World!
print(string.len(str)) -- 输出 13

table

  • 用途:表示复合数据类型,可以当作数组或哈希表使用。
  • 示例
代码语言:lua复制
local table = {"apple", "banana", "cherry"}
for i, fruit in ipairs(table) do
    print(i .. ": " .. fruit)
end
-- 输出
-- 1: apple
-- 2: banana
-- 3: cherry

function

  • 用途:表示可调用的对象,即函数。
  • 示例
代码语言:lua复制
local add = function(a, b)
    return a   b
end
print(add(1, 2)) -- 输出 3

userdata

  • 用途:表示用户定义的数据类型,通常用于存储 C 语言中的指针或结构体。
  • 示例
代码语言:lua复制
local ptr = ffi.new("int[10]")
ptr[0] = 42
print(ptr[0]) -- 输出 42

thread

  • 用途:表示协程(coroutine),即轻量级的线程。
  • 示例
代码语言:lua复制
local co = coroutine.create(function()
    print("Inside coroutine")
end)
coroutine.resume(co) -- 输出 "Inside coroutine"

常见操作

1. 类型检查
  • 用途:检查变量的类型。
  • 示例
代码语言:lua复制
local x = "hello"
if type(x) == "string" then
    print(x .. " is a string")
end
2. 类型转换
  • 用途:将一种类型转换为另一种类型。
  • 示例
代码语言:lua复制
local num = tonumber("123") -- string类型转为number类型
print(num   1) -- 输出 124

Lua的面向对象怎么做

在 Lua 中,面向对象编程主要是通过元表(metatable)和表(table)来实现的。Lua 不像其他语言那样有类的概念,而是使用表来模仿类的行为。

对象的概念

在 Lua 中,一个表(table)可以被视为一个对象。每个对象都有自己的状态(数据成员)和行为(成员函数)。

对象实现思路

封装

封装是指将数据和操作数据的方法绑定在一起,隐藏对象的具体实现细节。在这个过程中,我们定义一个类(即原型对象),从这个类派生出具体的对象。每个具体的对象都有自己的状态(成员变量),并通过成员函数来操作这些状态。

为了实现这一过程,我们可以将原型对象作为具体对象的元表(metatable),这样当具体对象找不到某个方法时,就会去原型对象中查找。此外,具体对象通过 self 来在成员函数中调用自身的成员变量。

继承

继承允许子类继承父类的特性和行为。在这个过程中,我们同样可以从父类的类对象派生出子类的类对象。子类可以覆盖或扩展父类的行为。

当子类定义了与父类同名的函数时,由于子类可以直接找到这些函数,因此不会去父类中查找,这就导致了子类会隐藏父类的同名函数。

多重继承

多重继承允许一个子类继承多个父类的功能。在实现多重继承时,我们需要保存传递过来的所有父类对象,并形成一个父类列表。

然后,我们可以设置子类的元表的 __index 属性为一个查找函数。这个查找函数会在父类列表中遍历,寻找相应的字段或方法。

私有性

私有性是指只有对象自身可以访问其内部状态,外部代码无法直接访问这些状态。为了实现私有性,我们可以利用局部变量和闭包。

具体来说,可以创建一个表来保存私有变量,另一个表来保存公共的字段和接口函数。内部的公共函数通过闭包来访问私有成员变量,并将包含公共接口的表返回出去。

详细实现可参考笔者的另一篇文章

Lua学习笔记:实现一个Lua Class生成器

__index和 __newindex

  • __index 是一个特殊的元方法,当尝试访问一个表中不存在的键时,Lua 会调用这个方法。这个方法可以用来提供默认值或者实现继承行为。
  • __newindex 也是一个特殊的元方法,当尝试向表中添加一个新的键或更新一个已存在的键时,Lua 会调用这个方法。这个方法可以用来拦截对表的修改操作,从而实现只读表或者其他自定义的行为。

Lua的只读表怎么实现

要创建一个只读表,可以使用元表中的 __newindex 方法来阻止对表的任何修改:

代码语言:lua复制
深色版本
local ro_table = setmetatable({}, {
    __newindex = function()
        error("Attempt to modify a read-only table")
    end
})

ro_table.x = 1  -- 这将抛出错误

全局禁用

代码语言:lua复制
-- 设置全局表的元表 禁止创造全局变量
setmetatable(_G, {
    __newindex = function(t, key, value)
        print("Cannot create global variable '" .. key .. "'")
    end
})

Lua与C交互(栈)

Lua 与 C 的交互主要通过 Lua 的 C API 实现。在 Lua 中,所有的值都是放在栈上的。Lua 的 C API 提供了一系列的函数来从 C 调用 Lua 代码,并从 Lua 调用 C 函数。

GC(四个阶段,标记清扫最主要)

Lua 使用引用计数和周期性的全局扫描(mark-and-sweep)来实现垃圾回收。当一个值不再被引用时,它会被回收。GC 过程包括几个阶段:

  • 标记(Marking):标记所有活动的对象。
  • 清扫(Sweeping):清理未被标记的对象。
  • 重置(Resetting):重置引用计数。
  • 最终化(Finalization):调用对象的 __gc 元方法(如果存在的话)。

GC过程中的黑白灰三状态颜色标记

在 Lua 的垃圾回收(GC)过程中,对象的状态通常被分为三类:黑色(black)、灰色(gray)和白色(white)。这些颜色用来表示对象的状态,以便进行标记-清扫(mark-and-sweep)算法的实现。下面详细解释每种颜色的意义:

  • 黑色(Black) 黑色对象是指已经被标记为可达(reachable)的对象。这些对象不会被垃圾回收器回收。在标记阶段,从根集开始,递归地标记所有可达的对象为黑色。
  • 灰色(Gray) 灰色对象是指正在被处理的对象。这些对象已经标记为可达,但是它们指向的其他对象还没有被标记。因此,灰色对象是处于标记过程中的对象。当一个对象被标记为灰色之后,它需要处理完它所指向的所有对象,才能变为黑色。
  • 白色(White) 白色对象是指尚未被标记的对象。在垃圾回收开始时,所有的对象都是白色的。如果一个对象在整个标记过程中都没有被标记(即保持白色),那么这个对象被认为是不可达的,最终会被回收。

标记-清扫算法是 Lua 中垃圾回收的基本算法。它分为两个主要阶段:

标记(Marking):

  • 从根集(root set)开始,这些根集通常包括全局变量、活动函数的局部变量和其他活跃对象。
  • 递归地标记所有从根集可达的对象为黑色或灰色。
  • 如果一个对象被标记为灰色,则标记它所指向的所有对象。 当一个对象的所有引用都被处理完毕后,它就变成了黑色。 清扫(Sweeping):
  • 清除所有未被标记的对象(即白色对象),因为它们是不可达的。
  • 释放这些对象占用的内存,并将它们从数据结构中移除。

table的内部结构实现(数组部分和hash部分)遍历方法(3种各自的区别)

Lua 的 table 结构实际上是由一个数组部分和一个哈希表部分组成的混合结构。数组部分用于快速访问连续的索引(通常是数字),而哈希表部分用于非连续的索引或其他类型的键。

遍历方法

Lua 中有三种常见的遍历表的方法:

  • pairs pairs 返回一个迭代器函数、表和一个初始索引。 适用于遍历数组部分和哈希部分的所有元素。for k, v in pairs(my_table) do print(k, v) end
  • ipairs ipairs 只遍历数组部分的元素,并按索引顺序返回。 适用于遍历具有连续数字索引的表。
代码语言:lua复制
for i, v in ipairs(my_table) do
    print(i, v)
end

next

next 函数手动遍历表。

需要显式地管理迭代状态。

代码语言:lua复制
local k, v = next(my_table)
while k do
    print(k, v)
    k, v = next(my_table, k)
end

字符串是如何存的(只存一份)

Lua 中的字符串一旦创建就不会改变,并且相同内容的字符串在内存中只会保存一份。这意味着如果你有两个字符串字面量 "hello",它们实际上指向同一个内存地址。

Lua和C/C 相互传递的变量是如何防止被GC的

当 Lua 中的值被传递给 C 函数时,可以使用 luaL_ref 来保存这个值的引用,从而防止它被垃圾回收。当不再需要这个值时,使用 luaL_unref 释放引用。

闭包

闭包是一个函数与其相关的引用环境组合而成的一个整体。在 Lua 中,闭包允许函数访问其外部作用域中的变量,即使该函数在其定义的作用域之外被调用也是如此。

代码语言:lua复制
local x = 10

local function f()
    print(x)
end

x = 20
f()  -- 输出 20

在这个例子中,f 是一个闭包,因为它记住了定义它的上下文中 x 的值。即使 x 的值后来改变了,f 仍然可以访问到原始的 x 的值。

尾声

一开始只是想做一个面试碰到,或者网上面经总结的Lua八股笔记,在腾讯云开发者社区写习惯以后在别的地方写文章总觉得有点别扭,于是干脆把本地写的内容放在腾讯云社区。

以后再碰到相关八股内容也会补充到这篇文章

并且尽量让内容不止于八股,而是可以从八股衍生出去思考。

当然笔者的水平有限,答案以参考lua手册和ai回答以及网上文章得出,其中纰漏谬误难以避免,若能指出,不胜感激。

若有侵权也可联系笔者指出删除(⊙﹏⊙)

0 人点赞