Lua 调试库( debug )

news2025/1/27 13:00:48

一、调试库作用

使用调试库可以获取 Lua 环境运行过程中的变量和跟踪代码执行。

调试库主要分为两类函数:自省函数和钩子函数

  • 自省函数:允许检查一个正在运行中的程序,例如活动函数的栈、当前正在执行的代码行、局部变量的名称和值。
  • 钩子函数:允许跟踪一个程序的执行。

值得注意:

调试库的某些功能性能不高,而且会打破语言的一些固有规则。

二、自省函数

1、debug.getinfo(thread, f, what)

该函数会返回包含函数信息的表。

参数:

  • thread: 表示在 thread 中获取相应的信息
  • f: 可以给两种模式,函数或数值。第一种则是给函数,获取给定函数的信息表。第二种则是给一个数字作为 f 的值,表示栈层级:当为 0 时表示当前函数( 即 getinfo 本身), 1 表示调用 getinfo 的函数(尾调用除外,它们不计入堆栈),以此类推。如果 f 是一个大于活跃栈层级的数字,则 getinfo 返回 nil
  • what: 可选项,表示要获取哪些指定的信息。因为 getinfo 的效率不高,所以为了效率好些,可以只选择需要的内容,如果需要多个值时,可以将多个拼凑,例如 nfS
what 取值获取的值
n选择 name 和 namewhat
f选择 func
S选择 source、short_src、what、linedefined 和 lastlinedefined
l选择 currentline
L选择 activelines
u选择 nup、nparams 和 isvararg

这些字段的含义如下:

字段描述
name该字段是该函数的一个适当的名称,例如保存该函数的全局变量的名称。(可能没有值,也可能有多个名称)(只有当 f 为数值时才有该值)
namewhat该字段用于说明 name 字段的含义,可能是 “global”、“local”、“method”、“field” 或 “”(空字符串)。空字符串表示 Lua 语言找不到该函数的名称。
func该字段是该函数本身
source该字段用于说明函数定义的位置。如果函数定义在一个字符串中(通过调用 load),那么 source 就是这个字符串;如果函数定义在一个文件中,那么 source 就是使用 @ 作为前缀的文件名
short_src该字段是 source 的精简版本(最多 60 个字符),对于错误信息十分有用。
what该字段用于说明函数的类型。
- 如果 foo 是一个普通的 Lua 函数,则为 “Lua”
- 如果是一个 C 函数,则为 “C”
- 如果是一个 Lua 语言代码段的主要部分,则为 “main” 。
linedefined该字段是该函数定义在源代码中第一行的行号
lastlinedefined该字段是该函数定义在源代码中最后一行的行号
currentline表示当前该函数正在执行的代码所在的行 (只有当 f 为数值时才有该值)
istailcall返回一个布尔值,为真表示函数是被尾调用所调起(尾调用时,函数真正的调用者不在栈中)(只有 f 为数值时才有该值)
activelines该字段是一个包含该函数所有活跃行的集合。活跃行是指除空行和只包含注释的行外的其它行(该字段的典型用法是用于设置断点。大多数调试器不允许在活跃行外设置断点,因为非活跃行是不可达的)。
nups该字段是该函数的上值的个数
nparams该字段是该函数的参数个数
isvararg该字段表明该函数是否为可变长函数

返回值:

如果传递的是一个函数或是一个合理的数值(小于等于栈层级),则会返回对应函数的信息表。如果超出的栈层级,则返回 nil

值得注意:

如果假设 foo 是一个 C 函数,Lua 语言没有多少关于该函数的信息。只有字段 what、name、namewhat、nups 和 func 是有意义的。

举两个例子:

输出一个函数的信息

function foo(a, b, ...)
    print("江澎涌")
end
local info = debug.getinfo(foo)
for k, v in pairs(info) do
    print(k, "---", v)
end

使用数值调用

foo1 = function(...)
    local table = debug.getinfo(1)
    for k, v in pairs(table) do
        print(k, "---", v)
    end
end
foo1()

上面例子中调用栈的层次如下:

2、traceback(thread, message, level)

返回调用栈信息

参数:

  • thread: 表示在 thread 中获取相应的信息
  • message:该参数没有限定为字符串,可以是任意的类型。如果为字符串或 nil ,则会返回调用栈的描述字符串,并且在最开始的地方拼接该 message (如果为 nil ,则不拼接)。如果为其他类型,则直接放回该值。
  • level:调用层级,0 表示 traceback 函数,1 表示调用 traceback 函数的函数,2 表示调用 traceback 函数的函数的函数 …

返回值:

如果 message 为字符串或 nil ,则会返回调用栈的描述字符串,并且在最开始的地方拼接该 message (如果为 nil ,则不拼接)。如果为其他类型,则直接返回该值。

举个例子

print("没有参数")
local function foo1()
    print(debug.traceback())
end
foo1()

--> 没有参数
--> stack traceback:
--> 	...Lua/lua_study_2022/18 调试库/自省机制-getInfo.lua:58: in local 'foo1'
--> 	...Lua/lua_study_2022/18 调试库/自省机制-getInfo.lua:60: in main chunk
--> 	[C]: in ?

print("携带 message(字符串)")
local function foo2()
    print(debug.traceback("track back message."))
end
foo2()

--> 携带 message(字符串)
--> track back message.
--> stack traceback:
--> 	...Lua/lua_study_2022/18 调试库/自省机制-getInfo.lua:64: in local 'foo2'
--> 	...Lua/lua_study_2022/18 调试库/自省机制-getInfo.lua:66: in main chunk
--> 	[C]: in ?

print("携带 message(非字符串)")
local function foo3()
    print(debug.traceback({}))
end
foo3()

--> 携带 message(非字符串)
--> table: 0x600000cf0d80

print("携带 message(非字符串)且携带 level ")
local function foo4()
    --print(debug.traceback("track back message.", 0))
    --print(debug.traceback("track back message.", 1))
    print(debug.traceback("track back message.", 2))
    --print(debug.traceback(nil, 2))
end
foo4()

--> 携带 message(非字符串)且携带 level 
--> track back message.
--> stack traceback:
--> 	...Lua/lua_study_2022/18 调试库/自省机制-getInfo.lua:81: in main chunk
--> 	[C]: in ?

3、getlocal(thread, f, var)

通过 f (可以是函数也可以栈层次)和 var 指定的索引,返回对应的变量的名称和值。此处的变量包括:局部变量,参数和临时变量。

参数

  • thread: 表示在 thread 中获取相应的信息
  • f:该值可以是函数也可以是栈层次。如果是函数,则该函数只会参数的名称,不会有其他的局部变量和临时变量(因为这个时候函数还未运行,所以对于运行时来说,都是不知道的)。如果是栈层级,则可以返回局部变量,参数和临时变量(但仅限于活跃的变量)。
  • var:参数或局部变量的索引。如果为正数,则按照变量的申明顺序(包括参数),从 1 开始往后访问所有的变量。如果为负数,则范围的是可变参数,从 -1 开始访问( -1 表示第一个)。如果索引没有对应的值,则返回 nil

值得注意的是如果栈层级传递超出可用范围,则会导致抛出异常,可以使用 getinfo 先检测栈层级是否正确( getinfo 对非法的栈层级只会返回 nil ,不会有异常)。

返回值:

返回变量名和变量值。

如果该值没有已知的名称(例如 for 循环中的临时变量),则会是以 “( ” 括号开头的变量来代替名称

举个例子:

用栈层级进行调用

local outerParam = "外部局部变量"
globalParam = "全局变量"
local function foo(funParamA, funParamB, ...)
    local localParamX
    do
        local localParamC = funParamA - funParamB
    end
    local localParamA = 1
    print("------------------------------------")
    print("遍历变量:")
    -- for 循环只是为了临时模拟临时变量,所以一次循环就退出
    for i = 1, 2 do
        while true do
            local name, value = debug.getlocal(1, localParamA)
            if not name then
                break
            end
            print(name, "---", value)
            localParamA = localParamA + 1
        end
        break
    end

    print("------------------------------------")
    print("遍历可变参数:")
    localParamA = -1
    while true do
        local name, value = debug.getlocal(1, localParamA)
        if not name then
            break
        end
        print(name, "---", value)
        localParamA = localParamA - 1
    end
end
foo(1, 200, "jiang", "pengyong")

--> ------------------------------------
--> 遍历变量:
--> funParamA	---	1
--> funParamB	---	200
--> localParamX	---	nil
--> localParamA	---	4
--> (for state)	---	1
--> (for state)	---	1
--> (for state)	---	1
--> i	---	1
--> ------------------------------------
--> 遍历可变参数:
--> (vararg)	---	jiang
--> (vararg)	---	pengyong

对函数进行调用

local function foo(funParamA, funParamB, ...)
    local localParamX = "江澎涌"
end
for i = 1, math.huge do
    local name, value = debug.getlocal(foo, i)
    if not name then
        break
    end
    print(name, "---", value)
end

-- 不会有局部变量和临时变量,因为此时函数还未运行
--> 遍历局部变量(函数):
--> funParamA	---	nil
--> funParamB	---	nil

4、debug.setlocal(thread, level, var, value)

将值 value 设置给位于栈层级为 level 的函数中索引为 var 的局部变量。

参数:

  • thread: 表示在 thread 中获取相应的信息
  • level:栈层级,和 getlocal 的 f 是一样的(只是不能传递函数,必须是数值)
  • var:变量索引
  • value:需要设置的值

返回值:

  • 如果没有具有给定索引(var)的局部变量,该函数将返回 nil
  • 如果 level 超出合理的栈层级,则会抛出异常。 可以调用 getinfo 来检查级别是否有效
  • 如果一切正常则返回局部变量的名称

举个例子:

function showLocalParam()
    for i = 1, math.huge do
        local name, value = debug.getlocal(2, i)
        if not name then
            break
        end
        print(name, "---", value)
    end
end

local name = "jiang pengyong"
local age = 29
do
    local name = "江澎涌"

    print("-------------------------------------")
    print("设置前:")
    showLocalParam()

    print("-------------------------------------")
    -- 修改的是外部的 name ,而非内部的 name
    print("debug.setlocal",debug.setlocal(1, 1, "jiang"))

    print("-------------------------------------")
    print("设置后:")
    showLocalParam()
end

--> 设置前:
--> name	---	jiang pengyong
--> age	    ---	29
--> name	---	江澎涌
--> -------------------------------------
--> debug.setlocal	name
--> -------------------------------------
--> 设置后:
--> name	---	jiang
--> age	    ---	29
--> name	---	江澎涌

5、getupvalue(f, up)

返回函数 f 索引为 up 的上值的名称和值。如果给定索引没有上值,则该函数返回 nil

如果无法知道变量的名称,则会返回以 “(” 开头的变量名。(例如临时变量)

举个例子:

通过下面代码可以遍历闭包的所有上值

要区分好上值和全局变量的概念,我们平常所说的全局变量其实是存储在上值的 _ENV 中,编译器会做一层转换(这个在 “环境(_G 和 _ENV)” 一章中已有详细分享);而上值指的是函数所能访问到的非局部变量(例如下面代码段的 age、heavy 变量)。如果函数中不使用该变量,则该变量就不会被算作是上值(例如 heavy),如果函数中没有使用到任何的全局变量,则 _ENV 都会不存在(注意这里连函数 print 都不能为全局的,否则达不到该条件,具体可以看第二个例子)。

name = "江澎涌"
local age = 29
function foo()
    local heavy = 120
    return function()
        print(age)
        -- 如果没有调用 heavy ,则 heavy 就不是该闭包的上值
        --print(heavy)
        local func = debug.getinfo(1, "f").func
        for i = 1, math.huge do
            local n, v = debug.getupvalue(func, i)
            if not n then
                break
            end
            print(n, "--------", v)
        end
    end
end
foo()()

--> 29
--> _ENV	--------	table: 0x600001470200
--> age	    --------	29

getupvalue 获取上值(不使用全局变量,连 _ENV 都会没有)

这里的 print、getinfo、getupvalue、huge 都需要转为 loacl ,才能避免编译过程中编译器自动为全局变量加上 _ENV ,导致上值引入了 _ENV 变量。

name = "江澎涌"
local print = print
local getinfo = debug.getinfo
local getupvalue = debug.getupvalue
local huge = math.huge
local age = 29
function foo()
    local heavy = 120
    return function()
        print(age)
        local func = getinfo(1, "f").func
        for i = 1, huge do
            local n, v = getupvalue(func, i)
            if not n then
                break
            end
            print(n, "--------", v)
        end
    end
end
foo()()

-- 可以看到 _ENV 变量不见了,因为没有引用到任何的 “全局变量”
--> 29
--> print	--------	function: 0x106ac4ac0
--> age	    --------	29
--> getinfo	--------	function: 0x106ac5e30
--> huge	--------	inf
--> getupvalue	--------	function: 0x106ac6500

6、debug.setupvalue(f, up, value)

将值 value 设置给给函数 f 的索引为 up 的上值。

如果给定索引没有上值,则该函数返回 nil。 否则,它返回上值的名称。

name = "江澎涌"
local age = 29
local function foo()
    local heavy = 120
    function showinfo(func)
        for i = 1, math.huge do
            local n, v = debug.getupvalue(func, i)
            if not n then
                break
            end
            print(n, "--------", v)
        end
    end

    return function()
        print(age)
        local func = debug.getinfo(1, "f").func
        print("设置上值前:")
        showinfo(func)

        -- 这里的 2 刚好就是指 age 变量
        debug.setupvalue(func, 2, 116)

        print("设置上值后:")
        showinfo(func)
    end
end
foo()()

--> 29
--> 设置上值前:
--> _ENV	--------	table: 0x600000f38040
--> age	--------	29
--> 设置上值后:
--> _ENV	--------	table: 0x600000f38040
--> age	--------	116

7、debug.debug()

该函数可以让用户进入交互模式,运行用户输入的每个字符串。

可以使用简单的命令和其他调试工具,可以检查全局和局部变量、更改它们的值、计算表达式等。

使用 cont 结束此函数

值得注意,debug.debug 命令并未按词法嵌套在任何函数中,因此无法直接访问局部变量。

8、debug.setmetatable(value, table)

table(可以是nil)设置为 value 作为元表,会将 value 返回。

setmetatable 的区别在于,debug.setmetatable 绕开了元表原有的检测机制。例如:无论 value 的元表是否设置了 __metatable ,对元表进行保护,不被外部篡改,都能进行修改。

例子在下面小节结合 debug.getmetatable 给出

9、debug.getmetatable(value)

返回给定 value 的元表,如果没有元表则返回 nil 。

getmetatable 的区别在于,debug.getmetatable 也是绕开了元表的检测机制。例如:无论 value 的元表是否设置了 __metatable ,都能获取到真正的元表。

举个例子

local table = {}
local mt = { __metatable = "protect metatable" }

-- 给 table 设置一个带有 __metatable 的元表,用于保护元表
print(setmetatable(table, mt))
-- 如果使用 getmetatable 则只能获取到 __metatable 的值
print(getmetatable(table))
-- 如果使用 debug.getmetatable 则可以获取到 table 的元表
print(debug.getmetatable(table), mt)

-- setmetatable 设置会报错,因为 table 的元表被保护者,会抛 "cannot change a protected metatable" 异常
--setmetatable(table, nil)
-- debug.setmetatable 则可以正常设置
print(debug.setmetatable(table, nil))
-- 通过打印,验证设置有效
print(getmetatable(table))

10、debug.setuservalue(udata, value, n)

将给定的 value 设置为给定的 udata 相关联的第 n 个用户值。

但 udata 必须是一个完整的 userdata。

如果设置成功,则会返回 udata;如果 userdata 没有该值,则返回 nil。

11、debug.getuservalue(u, n)

获取与 u (userdata 类型)相关联的第 n 个用户值,会返回该值以及一个布尔值。

如果 userdata 没有该值,布尔值为 false。

12、debug.getregistry()

返回注册表。注册表是一个特殊的 Lua 表,可以被用于存储全局数据或跟踪引用。但正常的使用不建议使用它来作为全局变量的储存点。

registry = debug.getregistry()
registry.name = "江澎涌"
for i, v in pairs(registry) do
    print(i, "-----", v)
end

--> 1	-----	thread: 0x7f88ed008e08
--> 2	-----	table: 0x60000293c200
--> FILE*	-----	table: 0x60000293c540
--> name	-----	江澎涌
--> _IO_output	-----	file (0x7ff85810fc98)
--> _CLIBS	-----	table: 0x60000293c300
--> _IO_input	-----	file (0x7ff85810fc00)
--> _LOADED	-----	table: 0x60000293c280
--> _PRELOAD	-----	table: 0x60000293c440

13、debug.upvalueid(f, n)

从给定函数返回编号为 n 的上值的唯一标识符(作为轻型用户数据)。

这些唯一标识符允许程序检查不同的闭包是否共享 upvalues。 共享一个上值(即访问同一个外部局部变量)的 Lua 闭包将为这些上值索引返回相同的 id。

14、debug.upvaluejoin(f1, n1, f2, n2)

使 Lua 闭包 f1 的第 n1 个上值引用 Lua 闭包 f2 的第 n2 个上值。

三、传入协程参数

在第二节中,可以看到大多函数的第一个函数是 thread ,这意味着可以传入一个协程。

以 trackback 为例,对一个协程进行栈的获取

co = coroutine.create(function()
    local x = 10
    coroutine.yield(x)
    error("coroutine error............")
end)

print("运行返回", coroutine.resume(co))
print("打应栈", debug.traceback(co))

--> 运行返回	true	10
--> 打应栈	stack traceback:
--> 	[C]: in function 'coroutine.yield'
--> 	.../Lua/lua_study_2022/18 调试库/自省机制-协程.lua:9: in function <.../Lua/lua_study_2022/18 调试库/自省机制-协程.lua:7>

print("运行返回", coroutine.resume(co))
print("打应栈", debug.traceback(co))

--> 运行返回	false	.../Lua/lua_study_2022/18 调试库/自省机制-协程.lua:10: coroutine error............
--> 打应栈	stack traceback:
--> 	[C]: in function 'error'
--> 	.../Lua/lua_study_2022/18 调试库/自省机制-协程.lua:10: in function <.../Lua/lua_study_2022/18 调试库/自省机制-协程.lua:7>

print("获取局部变量", debug.getlocal(co, 1, 1)) --> 获取局部变量	x	10

四、钩子函数

1、debug.sethook(thread, hook, mask, count)

设置钩子

参数:

  • thread: 表示在 thread 中设置相应的钩子
  • hook:钩子被触发时,便会表用该函数。函数的第一个参数是事件类型,包括 call(也包括尾调用)、returnlinecount,对于 line 事件则会有第二个参数,表示行号。
  • mask:字符串掩码,可以是以下的任意字符组合
掩码描述
c每次 Lua 调用函数时都会调用钩子
r每次 Lua 从函数返回时调用钩子
l每次 Lua 进入一个新的代码行时调用这个钩子
对于检测频率,只需要填入 count 参数就行
  • count:指定以什么频率获取 count 事件

关闭钩子:

当不带参数调用时,debug.sethook 关闭钩子

钩子的使用:

在一个钩子里面,可以用 level 为 2 调用 getinfo 来获取更多关于正在运行的函数的信息(0 级是 getinfo 函数,level 1 是钩子函数)

举个例子:

debug.sethook(print, "l")

print("江澎涌")
--> line	15
--> 江澎涌

print("小朋友")
--> line	16
--> 小朋友

debug.sethook()
--> line	17

2、debug.gethook(thread)

返回 thread 的当前挂钩设置,

会返回三个值:当前挂钩函数、当前挂钩掩码和当前挂钩计数(这些值都是 debug.sethook 函数设置)。

五、写在最后

Lua 项目地址:Github传送门 (如果对你有所帮助或喜欢的话,赏个star吧,码字不易,请多多支持)

如果觉得本篇博文对你有所启发或是解决了困惑,点个赞或关注我呀

公众号搜索 “江澎涌”,更多优质文章会第一时间分享与你。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1094318.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java多线程悲观锁和乐观锁

悲观锁&#xff1a; 一上来就加锁&#xff0c;没有安全感&#xff0c;每次只能一个线程进入访问完毕后&#xff0c;再解锁。 线程安全&#xff0c;性能较差 乐观锁&#xff1a; 一开始不上锁&#xff0c;认为是没有问题的&#xff0c;大家一起跑&#xff0c;等要出现线程安全问…

SpringBoot--手写组件动态更新@Value的值

原文网址&#xff1a;SpringBoot--手写组件动态更新Value的值_IT利刃出鞘的博客-CSDN博客 简介 本文手写组件&#xff0c;动态更新SpringBoot里Value的值&#xff08;无需重启服务&#xff09;。 不是可以用RefreshScope吗&#xff1f;为什么要手写组件&#xff1f; 动态更…

docker数据卷+挂载(命令讲解+示例)

在容器中管理数据主要有两种方式&#xff1a; 数据卷&#xff08;Volumes&#xff09; 、挂载主机目录 (Bind mounts)。 一、数据卷 数据卷是一个可供一个或多个容器使用的特殊目录&#xff0c;可以在容器之间共享和重用。 特点&#xff1a; 对 数据卷 的修改会立马生效对 …

动态规划简述;斐波那契数列自顶向下和自底向上

概述 动态规划就是把一个问题分解为若干子问题&#xff0c;把子问题的解累加起来&#xff0c;就是当前问题的值。 斐波那契数列&#xff08;自顶向下&#xff09; 一个很好的演示demo&#xff0c; 在进行运算时&#xff0c;要用上备忘录&#xff08;缓存&#xff09;&#x…

从硬件结构到软件

先说说体系冯诺依曼的体系结构&#xff0c;有利于我们后面理解操作系统&#xff0c;软件再怎么发展&#xff0c;也必须遵守硬件的规则。 一 五大硬件理解 如下图: 1 为什么要有输入输出设备 很久以前&#xff0c;我们都是把指令打成孔&#xff0c;有孔无孔表示0,1&#xff0c…

柯桥日常口语学习|生活英语|实用口语口语天天练

1. How far is it from here? 离这儿有多远&#xff1f; 2. Can you give me a hand? 能帮帮我吗&#xff1f; 3. I cant lift my right arm. 我无法举起我的右手臂。 4. This bridge was built two years ago. 这座桥是在两年前建造的。 5. You should eat more. 你应该…

Go语言入门心法(一): 基础语法

Go语言入门心法(一) Go语言入门心法(二): 结构体 Go语言入门心法(三): 接口 一: go语言中变量认知 go语言中变量的定义: &#xff08;要想飞|先会走&#xff09;||&#xff08;翻身仗|抹遗憾 &#xff09; |&#xff08;二八定律&#xff09;(先量变)|(再质变)||&#xf…

vue3实现刻度尺

期望实现效果如下&#xff1a; 一、基本使用 安装slide-ruler&#xff0c;根据文档实现内容 https://github.com/wusb/slide-ruler/blob/master/README-zh_CN.md 二、进一步处理 1、直接复制slide-ruler核心文件&#xff0c;在此基础上进一步处理 处理1&#xff1a;刻度朝向…

思维模型 秩序

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。秩序是事物正常运行的基石。有序的安排是成功的先决条件。 1 秩序的应用 1.1 秩序在不同科学领域中的应用 物理学和天文学&#xff1a; 物理学家通过研究原子和分子的有序排列来理解物质的…

思维模型 正/反 木桶理论

本系列文章 主要是 分享 思维模型&#xff0c;涉及各个领域&#xff0c;重在提升认知。 1 正/反 木桶理论的应用 1.1 木桶理论的应用 1.1.1 正木桶理论在考试中的应用 小明是一名理科高中生&#xff0c;他在学习过程中发现自己在数理化方面表现较好&#xff0c;但在语文和英…

基于Vue+webpack之H5打包资源优化

前言 基于公司的业务以及今年接触到的项目大部分都是APP混合开发&#xff0c;即原生Android/ios H 5页面开发APP。项目从产品需求的评审到方案的评审再到开发提测...这一套流程下来让我收货颇多。总想找个时间好好记录一番&#xff0c;大概还是自己懒惰了&#xff0c;一直拖到…

【Vue面试题二十二】、什么是虚拟DOM?如何实现一个虚拟DOM?说说你的思路

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;什么是虚拟DOM&#xff…

vim、gcc/g++、make/Makefile、yum、gdb

vim、gcc/g、make/Makefile、yum、gdb 一、Linux编辑器vim1、简介2、三种模式的概念&#xff08;1&#xff09;正常/普通/命令模式(Normal mode)&#xff08;2&#xff09;插入模式(Insert mode)&#xff08;3&#xff09;末行/底行模式(last line mode) 3、三种模式的切换4、正…

langchain 加载各种格式文件读取方法

参考&#xff1a;https://python.langchain.com/docs/modules/data_connection/document_loaders/ https://github.com/thomas-yanxin/LangChain-ChatGLM-Webui/blob/master/app.py 代码 可以支持pdf、md、doc、txt等格式 from langchain.document_loaders import Unstruct…

数据库管理-第109期 19c OCM考后感(20231015)

数据库管理-第109期 19c OCM考后感&#xff08;202301015&#xff09; 距离上一篇又过了两周多&#xff0c;为啥又卡了这么久&#xff0c;主要是后面几个问题&#xff1a;1. 9月1日的19c OCM upgrade考试木有过&#xff0c;因为有一次免费补考机会就又预约了10月8日的考试&…

IoT知识点补充

MySQL vs NoSQL数据库&#xff08;MongoDB&#xff09; 这里举的例子使用MySQL存储用户信息和博客文章的关系数据&#xff0c;同时使用MongoDB存储博客文章的评论&#xff0c;因为评论可以是不同结构的半结构化数据。 MySQL部分如下 import java.sql.Connection; import jav…

普冉PY32系列(九) GPIO模拟和硬件SPI方式驱动无线收发芯片XL2400

目录 普冉PY32系列(一) PY32F0系列32位Cortex M0 MCU简介普冉PY32系列(二) Ubuntu GCC Toolchain和VSCode开发环境普冉PY32系列(三) PY32F002A资源实测 - 这个型号不简单普冉PY32系列(四) PY32F002A/003/030的时钟设置普冉PY32系列(五) 使用JLink RTT代替串口输出日志普冉PY32…

初出茅庐的小李博客之SPI工作模式

SPI的工作模式 SPI&#xff08;Serial Peripheral Interface&#xff09;是一种同步串行通信协议&#xff0c;常用于连接微控制器和外围设备。SPI有四种模式&#xff0c;分别是0、1、2、3模式。 0模式&#xff1a;时钟空闲时为低电平&#xff0c;数据在时钟的下降沿采样&#…

Using sunbeam to deploy openstack (by quqi99)

作者&#xff1a;张华 发表于&#xff1a;2023-10-15 版权声明&#xff1a;可以任意转载&#xff0c;转载时请务必以超链接形式标明文章原始出处和作者信息及本版权声明(http://blog.csdn.net/quqi99) What’s sunbeam sunbeam是一个部署openstack的工具&#xff0c;它会用ju…

利用ChatGPT练习口语

目录 ChatGPT 这两天发布了一个激动人心的新功能&#xff0c;App端&#xff08;包括iOS和Android&#xff09;开始支持语音对话以及图片识别功能。 这两个功能一如既往的优先开放给Plus用户使用&#xff0c;现在将App更新到最新版本&#xff0c;就能体验。 为什么说激动人心&a…