Lua | 一篇文章讲清Lua语法及热更新

目录

一、环境搭建

二、Lua语法

1.输出print、单行注释、多行注释

2.变量

(1)nil

(2)number

(3)string

(3.1)字符串长度

(3.2)字符串拼接

(3.3)多行打印

(3.4)类型转换

(3.5)字符串操作(大小写、反转、查找、截取、重复)

(3.6)字符串与ASCII互转

(4)boolean

(5)function

(5.1)无参无返回值

(5.2)有参无返回值

(5.3)有一个返回值

(5.4)有多个返回值

(5.5)匿名函数

(5.6)可变形参函数

(5.7)获取函数类型

(5.8)函数嵌套

(5.9)函数嵌套-闭包

(6)table

(6.1)一维数组

 (6.2)unpack函数

(6.3)二维数组

 (6.4)自定义索引

(6.5)迭代器遍历

(6.5.1)ipairs

 (6.5.2)pairs

(6.6)字典

(6.7)类

(6.7.1)类函数声明

 (6.8)表操作

(6.8.1)插入

(6.8.2)删除

(6.8.3)排序

(6.8.4)拼接 

(7)thread

(7.1)协程创建

(7.2)协程运行

(7.3)协程挂起

(7.4)协程状态

 3.运算符

(1)算术运算符(+  -  *  /  %  ^)

(2)条件运算符(<  >  <=  >=  ==  ~=)

(3)逻辑运算符(and  or  not,支持短路规则)

4.条件分支语句

(1)单分支

(2)双分支

(3)多分支

5.循环语句

(1)for循环

(1.1)数值for循环

 (1.2)泛型for循环

(2)while...do...end循环

(3)repeat...until...循环

6.多脚本执行

(1)本地变量和全局变量

(2)require("脚本名")

(3)package.loaded["脚本名"]

(4)大G表

(5)dofile

7.特殊用法

(1)多变量赋值

(2)多返回值

(3)逻辑与、逻辑或

(4)实现三目运算符

8.元表

(1)设置元表

(2)特定操作

(2.1)__tostring

(2.2)__call

(2.3)运算符重载

(2.4)__index

(2.4.1)__index是表

(2.4.2)__index是方法

(2.5)__newindex

(2.5.1)__newindex是函数

(2.5.1)__newindex是表

(2.6)获取元表

9.面向对象

(1)封装

(2)继承

(3)多态

(4)总结

10.Lua其他库

(1)数学计算

(2)操作系统库-时间

(3)文件操作

(3.1)写文件

(3.2)读文件

(4)其他-路径

11.垃圾回收

三、热更新

1.插件安装

2.创建Lua脚本

3.unity中调用Lua脚本

4.项目升级

(4.1)Lua脚本更改

(4.2)unity中调用Lua 

(4.3)效果展示 


一、环境搭建

  • LuaForWindows:Releases · rjpcomputing/luaforwindows (github.com)
  • IDE:运用的是sublime_text

sublime_text快捷方式:

  • Ctrl+B:运行
  • Ctrl+L:选中当前行
  • Ctrl+/:快速注释
  • Ctrl+Shift+/:快速块注释

二、Lua语法

1.输出print、单行注释、多行注释

print("hello world")
--单行注释--[[
多行注释
多行注释
]]--[[
多行注释
多行注释
--]]--[[
多行注释
多行注释
]]--

2.变量

  • lua中四种简单的变量类型:nil、string、boolean、number。除此之外还有:function、table、userdata、thread。
  • 不需要声明变量类型;可通过type函数获取变量类型,type函数返回值是string。
  • 变量可随便赋值。Lua 中的变量是动态类型的,这意味着变量没有预定义的类型,类型会在运行时根据赋给变量的值来决定。
  • lua中没有赋值过的变量,默认类型是nil。

(1)nil

a=nil
print(a)

(2)number

        所有的数值都是number类型。

a=10
print(a)a=10.5
print(a)

(3)string

        在 Lua 中,字符串可以用双引号"、单引号'或者长括号[[ ]]来表示。

a="hello"
print(a)
(3.1)字符串长度

        使用#运算符可以获取字符串长度。

a="hello world"
print(#a)
a="一"    
print(#a)  --3
(3.2)字符串拼接

        可以使用..运算符来拼接字符串。也可以使用string.format来进行拼接。

a="一"    
print("一个汉字长度:"..#a)  --一个汉字长度是3
print(string.format("今年 %s 岁",'18'))
(3.3)多行打印

        在Lua中,可以使用多行字符串来打印多行文本。多行字符串可以通过在字符串的开始和结束使用双方括号[[]]来创建。也可以通过行末尾添加"\n"来进行多行打印。

a=[[1.hello
2.world]]
print(a)
(3.4)类型转换

        在Lua中,可以使用 tostring 函数将一个值转换为字符串。这个函数可以接受任何基本类型的值作为参数,并返回该值的字符串表示形式。

a=false
print(tostring(a))
(3.5)字符串操作(大小写、反转、查找、截取、重复)

        字符串相关操作,不会改变原字符串。Lua中索引从1开始。

str="YsL"
str1=string.upper(str)
print("原字符串:"..str..",全部大写为:"..str1)
str1=string.lower(str)
print("原字符串:"..str..",全部小写为:"..str1)
str1=string.reverse(str)
print("原字符串:"..str..",反转字符串为:"..str1)--原字符串:YsL,全部大写为:YSL
--原字符串:YsL,全部小写为:ysl
--原字符串:YsL,反转字符串为:LsY
str="YsL"
print("---------字符串查找")
print(string.find(str,"sL")) --2  3print("---------字符串截取")
print(string.sub(str,2))     --sL
print(string.sub(str,2,2))   --sprint("---------字符串重复:"..string.rep(str,3)) --字符串重复:YsLYsLYsL
print("---------字符串修改")
print(string.gsub("Session",'s','*'))    --Se**ion	2

        string.gsub()返回两个结果,第二个值表示修改了几个字符。

(3.6)字符串与ASCII互转
a=string.byte("Lua",1)
print(a) --76a=string.char(a)
print(a) --L

(4)boolean

a=false
print(a)
print(type(a)) --boolean
print(type(type(a))) --string

(5)function

        在Lua中,函数是一种可以封装代码以便重复使用的基本结构。函数可以接受参数,并且可以返回一个或多个值。Lua不支持重载,但重载后并不报错,默认调用最后声明的函数。

(5.1)无参无返回值
--先声明
function fun1()print("my fun1")
end--再调用
fun1()
(5.2)有参无返回值
function fun3(a)print(a)
endfun3(1)
fun3("hello world")
fun3(true)
fun3(nil) --nil
fun3()    --nil
fun3(1,2) --1     实参和形参个数不匹配,会补空或丢弃
(5.3)有一个返回值
function fun4(a)   --直接return即可return a
endtemp=fun4(1)
print(temp)
(5.4)有多个返回值
function fun5(a)   --支持返回多个值return a,-1
endtemp1,temp2,temp3=fun5(10) --支持多个变量来接取返回值
print(temp1)
print(temp2)
print(temp3)    --nil
(5.5)匿名函数
fun2=function()print("my fun2")
endfun2()
(5.6)可变形参函数
function fun7(...)arg={...}        --arg表存储参数for i=1,#arg doprint(arg[i])end
end
fun7(1,2,3,4,"hello",false)
(5.7)获取函数类型
fun6=function()print("my fun6")
end
print(type(fun6))   --function
(5.8)函数嵌套
function fun8()return function()print("函数嵌套")end
end--调用方式1
fun9=fun8()
fun9()
--调用方式2
fun8()()
(5.9)函数嵌套-闭包

        在Lua中,函数可以嵌套在其他函数内部,这时候内部的函数可以访问外部函数的局部变量,这种结构称为闭包(closure)。闭包的强大之处在于,即使外部函数的执行已经结束,闭包仍然可以记住外部函数中的变量。

function fun10(x)return function(y)print(x+y)    --临时变量x的生命周期变长end
endclose=fun10(1)
close(2)

         即使 fun10执行完毕,close依然可以访问x这个变量。

(6)table

        表是 Lua 中唯一的数据结构,可以用来表示数组、字典等复杂数据结构。表在 Lua 中是非常强大的,因为它们可以包含所有类型的值。

(6.1)一维数组
a={1,2,3,4,nil,true,"hello",nil} --8个元素     如果数组的最后一个元素是 nil,Lua 通常会将其视为数组的结束,导致长度计算时忽略该 nil 元素。
print("数组a的长度:"..#a)  --7      lua5.1.5版本中,nil如果是数组最后一个元素,计算数组长度时则会忽略该元素。用#获计算长度不准确
print(a[0]) --nil  lua索引从1开始
print("第一个元素:"..a[1]) --1print("---------------一维数组遍历")
for i=1,#a doprint(a[i])
end
 (6.2)unpack函数

        在Lua中,unpack函数是一个非常有用的内置函数,它可以从表中取出一个连续的序列,并返回这些值。这个函数在Lua 5.1中是全局的,但从Lua 5.2开始,它被移到了table库中,即table.unpack

a = {1,2,3,4,nil,true,"hello",nil} 
print(unpack(a))  --1	2	3	4	nil	true	hello

        使用unpack可以很方便地将表中的值作为多个参数传递给函数。

        unpack或table.unpack接受三个参数:表、起始索引和结束索引。如果不指定起始索引和结束索引,默认情况下它会从第一个元素开始,到表的长度结束。

print(unpack(a,2,4))  --2	3	4
(6.3)二维数组
a={{1,2,3},{4,nil,"hello"}}
print(a[1][3]) --3
print(a[2][3]) --helloprint("---------------二维数组遍历")
for i=1,#a dob=a[i]for j=1,#b doprint(b[j])end
end
 (6.4)自定义索引

        Lua的表不仅可以使用整数索引(不建议自定义整数索引),还可以使用字符串或其他Lua值(除了nil)作为键(后续会讲)。

a={[0]=1,2,3,[-1]=4,[4]=5}
print(#a)   --2   会去除索引不正常的元素
print(a[0]) --1for i=1,#a doprint(a[i])  --2 3
enda={[1]=1,[2]=1,[4]=1,[6]=1}
print(#a)   --6
print(unpack(a)) --1	1	nil	1	nil	1
(6.5)迭代器遍历

         使用#计算长度并不准确,建议使用迭代器遍历:

  • 如果想要处理的是严格意义上的数组(索引连续的列表),使用ipairs更合适,因为它的性能可能会更好一些。
  • 如果需要处理的是一个混合了数组和字典风格的表,或者你不确定表的索引是否连续,那么使用pairs会更安全,它能确保遍历到表中的所有元素(nil除外)。
(6.5.1)ipairs

        从索引1开始遍历,索引小于0或者断序后无法遍历。

a={[0]=0,1,2,3,nil,4,5,[10]=6,c=7,nil}for i,k in ipairs(a) doprint("i:"..i.."-k:"..k)
end
-- i:1-k:1
-- i:2-k:2
-- i:3-k:3
 (6.5.2)pairs
a={[0]=0,1,2,3,nil,4,5,[10]=6,c=7,nil}for i,k in pairs(a) doprint("i:"..i.."-k:"..k)
end--i:1-k:1
--i:2-k:2
--i:3-k:3
--i:5-k:4
--i:6-k:5
--i:0-k:0
--i:10-k:6
--i:c-k:7
(6.6)字典

         在Lua中,并没有专门的“字典”类型,但是表(table)结构在Lua中是非常灵活的,通常用来实现类似字典的功能。表可以存储键值对,其中键和值可以是任意类型的数据(除了nil)。当使用字符串作为键时,表就类似于其他语言中的字典或哈希表。

a={["name"]="Lisa",["age"]=22,["sex"]="girl",["1"]=true,c=56}print("-------------访问")
print(a["name"])
print(a.c)
print(a.name)--虽然可以通过这种方法得到值,但键不能是数字,如a.1不对print("-------------修改")
a["age"]=20
print(a["age"])print("-------------新增")
a["Country"]="TaiLand"
print(a.Country)print("-------------删除")
a["Country"]=nil
print(a.Country)print("-------------字典的遍历,使用pairs")
for i,k in pairs(a) doprint(i,k)
end
(6.7)类

        Lua中默认没有面向对象,所以不能new。

Student={age=12,name="Lisa",fun0=function(t) --在函数中需要调用表本身的属性和方法时,可以把自己当参数传进来print("fun0:"..t.name..":"..t.age) end,fun1=function()--print("fun1:"..name..":"..age) --错误print("fun1:"..Student.name..":"..Student.age) --在函数中调用表本身的属性和方法,需要指明表名称end
}
--新增变量
Student.sex="girl"print(Student.age)
print(Student.sex)Student.fun0(Student)
Student:fun0() --语法糖:冒号调用方法时,会默认把调用者作为第一个参数传入方法中
Student.fun1()
(6.7.1)类函数声明

        和普通函数申明的两种方式(普通函数、匿名函数)相同,除此之外,也可以使用:来申明。

  • 当声明一个方法时,使用 : 语法允许定义一个需要隐式 self 参数的函数。self 实际上指的是调用方法的对象实例,而不是类名。
  • 当调用一个方法时,如果使用 : 语法,Lua会自动将调用该方法的表(对象)作为第一个参数传递给该方法。
Student.fun2=function()print("fun2")
endfunction Student.fun3()print("fun3")
endfunction Student:fun4() --语法糖:冒号声明方法时,会默认把调用者作为第一个参数传入方法中。self表示默认传入的第一个参数,self不是this。(self 实际上指的是调用方法的对象实例,而不是类名)print("fun4:"..self.name)
endStudent.fun2()
Student.fun3()Student:fun4()
Student.fun4(Student)
 (6.8)表操作
(6.8.1)插入
t1={{name=1,age=10},{name=2,age=11}}
t2={name=3,age=12}print(#t1)  --2
table.insert(t1,t2)print(#t1)  --3
print(t1[3]["age"])  --12
(6.8.2)删除
table.remove(t1) --默认删除最后一个元素table.remove(t1,1) --第二个参数,指定移除的元素索引
(6.8.3)排序

        在Lua中,table.sort 函数用于对表中的元素进行排序。这个函数可以对表中的元素进行原地(in-place)排序,也就是说,排序操作会直接改变原始表,而不是创建一个已排序的新表。

t3={12,53,6,2,4}
table.sort(t3)
print(unpack(t3)) --2	4	6	12	53

         默认情况下,table.sort 对表进行升序排序。如果想自定义排序顺序,可以提供一个排序函数作为table.sort的第二个参数。这个排序函数需要接受两个参数,并返回一个布尔值,表示第一个参数是否应该在排序后位于第二个参数之前。

table.sort(t3, function(a,b)if a>b thenreturn trueend
end)
print(unpack(t3)) --53	12	6	4	2

        上述代码表示:如果第一个参数大于第二个参数,就返回true,这样table.sort就会把较大的数放在前面。 

(6.8.4)拼接 

        在Lua中,table.concat函数用于将表中的元素连接成一个字符串。你可以指定一个连接符(也称为分隔符),用来在合并时插入元素之间。如果不指定连接符,默认使用空字符串(也就是没有分隔符)。

        table.concat还可以接受第二个和第三个可选参数,分别代表起始索引和结束索引,用于指定连接表中的哪一部分。

t4={1,"hello","world"}
str=table.concat( t4, "-", 1, 3 )
print(str) --1-hello-world

(7)thread

        Lua中的thread类型是指协同程序(coroutines),它们是由coroutine库提供的。在Lua中,术语"thread"通常指的是这些协同程序,而不是操作系统级别的线程。Lua的协同程序是协作式的,而非抢占式的,这意味着它们不会自行中断以让其他线程运行,而是显式地通过yield和resume来交换控制权。(Lua的线程可以让你编写出看起来像是同时执行的代码,但实际上它们是分时执行的,一个线程让出控制权后,另一个线程才会开始执行。)

        Unity 协程是与引擎的事件循环集成在一起的,允许在等待时继续处理其他事件和渲染更新。这与 Lua 中需要显式地调用 coroutine.resume 来恢复协程的执行有很大的不同。在 Unity 中,你只需要启动协程,引擎会在合适的时间点自动恢复它的执行。

        Lua中使用coroutine.create创建协程,每执行一次coroutine.resume,协程函数会前进至下一个coroutine.yield。

(7.1)协程创建

        协程的创建有两种方式:

  • coroutine.create():常用方式,返回线程类,协程的本质是线程对象
fun=function()print("hello 0223")
endco=coroutine.create(fun)
print(type(co)) --thread 
  • coroutine.wrap():返回函数类型
fun=function()print("hello 0223")
endco1=coroutine.wrap(fun)
print(type(co1))--function
(7.2)协程运行

        对应两种创建方式,协程运行也不同:

  • coroutine.resume:对应create创建方式
  • 按函数方式运行
coroutine.resume(co)  --对应create创建方式co1() --按函数方式运行
(7.3)协程挂起
fun2=function ()local i=1while true doprint(i)i=i+1--挂起coroutine.yield(i) --返回iend
endco2=coroutine.create(fun2)temp1,temp2=coroutine.resume(co2) 
print(temp1,temp2) --true 	2     --true表示协程运行成功,temp2才是返回值itemp1,temp2=coroutine.resume(co2) --运行一次执行一次
print(temp1,temp2) --true	3
(7.4)协程状态
  • coroutine.status(co)返回协同程序co的状态,可能的值有running、suspended、normal和dead。
  • coroutine.running() 得到正在运行的协程编号,要在函数内部打印。

        coroutine.wrap 返回的是一个函数,而不是协程对象,所以不能对它使用 coroutine.status。因为 coroutine.status 期望它的参数是一个协程,而不是一个函数。

fun=function()print("hello 0223")print(coroutine.running()) 
end
co=coroutine.create(fun)
coroutine.resume(co)
print(coroutine.status(co)) -- hello 0223
-- thread: 00D5E500
-- dead
fun=function()print("hello 0223")print(coroutine.running()) coroutine.yield("hello")
end
co=coroutine.create(fun)
coroutine.resume(co)
print(coroutine.status(co)) -- hello 0223
-- thread: 00C9DD90
-- suspended

 3.运算符

        lua不支持位运算( & |)和三目运算符( ? :),也不支持++、--、 +=、 -=等算术运算符。

(1)算术运算符(+  -  *  /  %  ^)

print("加法:"..1+2)
print("123"+1)        --字符串进行算术运算,会自动转成number  
print("减法:"..1-2)
print("乘法:"..1*2)
print("除法:"..1/2)
print("余数:"..1%2)  
print("幂运算:"..3^2) --加法:3
--124
--减法:-1
--乘法:2
--除法:0.5
--余数:1
--幂运算:9

(2)条件运算符(<  >  <=  >=  ==  ~=)

print("1<2?"..tostring(1<2))
print("1>2?"..tostring(1>2))
print("1==2?"..tostring(1==2))
print("1~=2?"..tostring(1~=2))--1<2?true
--1>2?false
--1==2?false
--1~=2?true

(3)逻辑运算符(and  or  not,支持短路规则)

print(true and false)
print(true or false)
print(not false)--false
--true
--true

4.条件分支语句

        Lua 中的条件分支语句主要是通过 ifthenelseifelse 和 end 关键字来实现的。Lua中没有switch。

(1)单分支

a = 10
if a>5 then print("a>5")
end

(2)双分支

a = 10
if a>5 then print("a>5")
else print("false")
end

(3)多分支

a = 10
if a>5 then print("a>5")
elseif a==5 then   --注意:elseif要连着写print("a=5")
else print("a<5")
end

5.循环语句

        在Lua中,循环语句允许重复执行一段代码多次。Lua提供了几种循环语句,包括for循环、while...do...end循环和repeat...until...循环。

(1)for循环

        Lua中的for循环有两种形式:数值for循环和泛型for循环。

(1.1)数值for循环
print("--------------for,默认递增1")
for i=1,5 do  print(i)
endprint("--------------for,自定义增量,递增2")
for i=1,5,2 do  print(i)
endprint("--------------for,自定义增量,递减1")
for i=5,1,-1 do  print(i)
end
 (1.2)泛型for循环

        用于迭代表(table)和其他迭代器。

for key, value in pairs(mytable) doprint(key, value)
end

(2)while...do...end循环

a=5
while a<10 doprint(a)a=a+1
end

(3)repeat...until...循环

        repeat...until...循环保证至少执行一次,即使条件从一开始就是假的。所以要防止死循环。

a=5
repeat print(a)a=a+1
until a>10

6.多脚本执行

(1)本地变量和全局变量

        在Lua中,变量可以是全局的或局部的,这取决于它们是如何声明的。

  • 全局变量在Lua中不需要明确声明就可以使用。如果你赋值给一个未声明的变量,Lua会自动将其视为全局变量。全局变量在整个Lua程序中都是可见的,除非它们被特定的环境或模块限制。
  • 局部变量在Lua中使用关键字local进行声明。它们的作用域被限制在了它们被声明的代码块内。这意味着它们只能在定义它们的函数内部、控制结构内或代码块内被访问。
a=10         --全局变量
b="hello"    --全局变量
for i=1,5 doc=i*2    --c也是全局变量
end
print(c)     --10for i=1,5 dolocal d=i*2  --局部变量
end
print(d)     --nil

(2)require("脚本名")

        在Lua中,require函数用于加载和运行库或模块。

  • 新建一个test.lua脚本(脚本可以return 一个局部变量)
print("this is test")
testA=10     --全局变量
local x = 1  --局部变量
x=x+1
return x
  • 在别的脚本中执行test.lua
require("test")        --注意是同一个路径
print(testA)           --可调用另一个脚本的全局变量
print(x)               --不可调用另一个脚本的局部变量--this is test
--10
--nil

        当使用require来加载一个模块时,Lua会检查该模块是否已经加载过。如果已经加载过,Lua不会再次加载该模块,而是直接返回之前加载的结果。这是通过一个内部的包加载表(package.loaded)来实现的,其中存储了所有已加载模块的状态。如果在上述代码之后仍然require("test") ,则不会输出this is test。

(3)package.loaded["脚本名"]

        如果想检查一个模块是否已经被执行过(即已经被加载过),你可以检查 package.loaded 表中对应的条目。如果条目存在并且不是 nil,则表示该模块已经被加载过了。

print(package.loaded["test"])   --2
package.loaded["test"]=nil      --卸载
print(testA) --但是还会输出

        执行了print(package.loaded["test"])并且得到了输出 2,这是因为名为 "test" 的模块被加载,并且它返回了值:2。

temp=require("test") --可以加local,不加的话会存在_G中
print(temp)       --2
print(_G["temp"]) --2

        卸载后print(testA)仍然输出10,是因为:在Lua中,全局变量是存储在全局环境_G中的变量,这是一个存储所有全局变量的表。如果你加载并运行了一个Lua脚本,它定义了全局变量,即使之后你卸载了这个脚本,这些全局变量仍然会留在全局环境_G中,除非你显式地删除它们或者重置它们的值。

(4)大G表

        在 Lua 中,全局环境是由一个名为 _G 的特殊表来表示的。这个表被称为“全局表”(Global Table),简称“大G表”。它包含了所有的全局变量,你可以通过它访问或修改这些变量。Lua中的全局变量实际上是_G表的键值对。当你在Lua中创建一个全局变量时,实际上是在_G表中添加了一个元素。同样,访问一个全局变量就是在查询_G表。

for i,k in pairs(_G) doprint(i,k)
end

        需要注意的是,直接操作 _G 表可能会影响到全局环境的完整性,所以通常建议谨慎使用,尤其是在大型项目或者多人合作的项目中。使用局部变量(local)可以避免不必要的全局污染。

(5)dofile

        在Lua编程语言中,dofile 和 require 都是用来加载和执行Lua文件的,但它们之间存在一些重要的区别。

  • dofile:这个函数会加载并执行指定的文件,但不会对其进行任何形式的缓存。也就是说,每次调用 dofile 时,Lua都会重新加载和执行该文件。如果你有一个文件需要多次执行,而且每次执行的结果都可能不同,那么 dofile 是一个不错的选择。
  • require:这个函数也会加载并执行指定的文件,但它会对文件进行缓存。当你第一次 require 一个文件时,Lua会加载和执行该文件,然后把文件的返回值存储在一个特殊的表中。当你再次 require 同一个文件时,Lua不会重新加载和执行该文件,而是直接返回这个表中的值。这意味着,如果你的文件只需要被执行一次,或者每次执行的结果都应该是相同的,那么 require 是一个更好的选择。
r=dofile("test.lua")   --注意需要加后缀
print(r)--this is test
--2

7.特殊用法

(1)多变量赋值

        个数不对应,自动补nil或省略。

a,b,c,d,e=1,"hello",true,nil
print(a,b,c,d,e)--1	hello	true	nil	nil

(2)多返回值

        个数不对应,自动补nil或省略。

function test()return 10,20,30
end
a,b=test()
print(a,b)

(3)逻辑与、逻辑或

        任何类型都能连接。只有nil和false才是假,0是真,"false"也是真。

print(1 and 2) --2   --1满足,判断2,返回2
print(0 and 1) --1   --0满足,判断1,返回1
print(1 or 2)  --1   --1满足,返回1(短路原则)
print(nil and 0) --nil
print(false or 1) --1
print(nil or "hello") --hello
print("false" or "1") --false
print("false" and "nil") --nil

(4)实现三目运算符

        lua不自持三目运算符。但是利用and和or可以实现三目运算符。

x,y=3,2
res=(x>y) and x or y
print(res) --3x,y=3,4
res=(x>y) and x or y
print(res) --4

8.元表

        在Lua中,元表(Metatables)提供了一种方式来改变表的行为。每个表可以有一个元表,而这个元表中可以包含特殊的键,这些键对应的值是函数或者表,这些函数或表定义了原表在特定操作下的行为。

  • 任何表都可以作为另一个表的元表
  • 任何表都可以有自己的元表
  • 在子表中进行一些特定操作时,会执行元表中的内容

(1)设置元表

meta={}     --用于表示元表
mytable={}  --用于表示子表
--设置元表
setmetatable(mytable,meta) --参数一:子表,参数二:元表

(2)特定操作

(2.1)__tostring

        相当于重写字符串转换方法。当子表要被当作字符串使用时,会默认调用元表中的tostring方法。

meta2={__tostring=function() return "hello meta2"end
} 
mytable2={} 
setmetatable(mytable2,meta2) 
print(mytable2) --hello meta2
meta3={__tostring=function(t) return t.nameend
} 
mytable3={name="Lisa"
} 
setmetatable(mytable3,meta3) 
print(mytable3) --Lisa
(2.2)__call

        当子表被当作函数来使用时,会默认调用这个元表中的__call。(默认不允许把普通的表当函数来调用,只有子表的元表声明了__call,子表才可以当函数调用)

        当你给一个表设置了元表,并且在这个元表中定义了 __call 函数后,你就可以直接调用这个表,就好像它是一个函数一样。Lua 将自动把表作为第一个参数传递给 __call 函数。

meta4={__tostring=function(t) return t.nameend,__call=function() print("今天周五")end
} 
mytable4={name="Linda"
} 
setmetatable(mytable4,meta4) 
mytable4() --今天周五
meta5={__tostring=function(t) return t.nameend,__call=function(a,b)  --当希望传参数时,默认第一个参数是调用者自己,所以,print(a)时,会调用__tostringprint(a,b)
end
} 
mytable5={name="Linda"
} 
setmetatable(mytable5,meta5) 
mytable5(1) --Linda  1
  • 当执行 mytable5(1) 时,Lua 会自动将mytable5作为第一个参数传递给__call 定义的函数,即a为mytable5。__call 被触发
  • 根据 __call 方法的定义,它会打印两个参数:a 和 b,即print(a)和print(b)
  • 由于 print(a) 被调用,这里的 a 是 mytable5,它会触发 __tostring 元方法,将 mytable5 转换为字符串,即它的 name 属性 "Linda"
  • 然后 print(b) 会打印传递的参数 1
(2.3)运算符重载

        在Lua中,元表(metatable)允许你改变表(table)的行为,其中之一就是通过元方法(metamethods)来重载运算符。运算符重载可以让你自定义两个表之间的运算行为,比如加法、减法、乘法等。(普通的表之间不支持加减乘除比较等)

  1. Lua在处理算术运算符的元方法(比如__add用于重载加法)时,只需要其中一个操作数有元表并且元表中实现了相应的元方法。Lua会首先检查左操作数(第一个操作数)的元表,如果没有找到对应的元方法,它会检查右操作数(第二个操作数)的元表。
  2. 比较运算符重载,需要确保两个子表的元表为同一个。
  • __add:定义了表相加的行为,(a + b)
  • __sub:定义了表相减的行为,(a - b)
  • __mul:定义了表相乘的行为,(a * b)
  • __div:定义了表相除的行为,(a / b)
  • __mod:定义了表进行模运算的行为,(a % b)
  • __pow:定义了表的乘幂运算,(a ^ b)
  • __unm:定义了表的取负运算,(-a)
  • __concat:定义了表的连接运算,(a .. b)
  • __len:定义了获取表长度的操作,(#a)
  • __eq:定义了表的相等比较,(a == b)
  • __lt:定义了表的小于比较,(a < b)
  • __le:定义了表的小于等于比较,(a <= b)
meta6={__add=function(t1,t2) --相当于运算符重载,当子表使用+运算符时,会调用该方法return t1.total+t2.total
end,__sub=function(t1,t2) --相当于运算符重载,当子表使用-运算符时,会调用该方法return t1.total-t2.total
end,__eq=function (t1,t2)if(t1.total==t2.total) thenreturn trueendreturn falseend
} 
mytable6=
{total=10
} 
setmetatable(mytable6,meta6) mytable7=
{total=10
} 
print(mytable6+mytable7) --20
print(mytable6-mytable7) ---0
print(mytable6==mytable7)--false  --因为不是同一个元表setmetatable(mytable7,meta6)      --设置为同一个元表
print(mytable6==mytable7) --true  
(2.4)__index

        当你尝试访问一个表中不存在的索引时,如果这个表的元表有__index元方法,Lua会调用这个方法。

        rawget:用于从表中获取一个键对应的值,但不触发任何元方法(metatable中的__index方法)。简单来说,它可以直接从表中获取值,而忽略任何可能已经设置的元方法。
 

        __index可以是一个函数或者一个表。

(2.4.1)__index是表

        如果__index是一个表,当你尝试访问第一个表中不存在的索引时,Lua会在__index指定的表中查找这个索引。

mytable8={}
meta8=
{age=10
}
setmetatable(mytable8,meta8)
print(mytable8.age)  --nilmeta8.__index=meta8  --元表补充__index,表指向自己,这种方法需要写到元表外
print(mytable8.age)  --10meta8.__index={age=20}  --元表补充__index,这种写法可写在元表内,也可在元表外
print(mytable8.age)  --20print(rawget(mytable8,"age")) --nil
mytable={}
meta=
{age=10
}
setmetatable(mytable,{__index=meta})
print(mytable.age)  --10
(2.4.2)__index是方法

        如果__index是一个函数,当你尝试访问表中不存在的索引时,Lua会调用这个函数。这个函数的第一个参数是表本身,第二个参数是被访问的索引。

        Lua 中如果 __index 元方法是一个函数且没有返回值,默认的行为是返回 nil。

mytable={}
meta=
{age=10
}
setmetatable(mytable,meta)
meta.__index= function()return 11
end
print(mytable.age)  --11
mytable={}
meta=
{age=10
}
setmetatable(mytable,meta)
meta.__index= function(table,value)value = 11return value
end
print(mytable.age) --11
(2.5)__newindex

        __newindex:当子表有元表,并且元表中有__newindex,你尝试对子表中不存在的属性赋值时:

  • 如果__newindex是一个函数,Lua会调用这个函数,传入子表、键和值作为参数。
  • 如果__newindex是一个表,Lua会在这个__newindex表中进行赋值操作,而不是在原始表中。

  __newindex可以用来控制对表的修改,实现只读表,或者在字段被修改时执行特定的行为。

(2.5.1)__newindex是函数
mytable11={}
print("子表地址"..tostring(mytable11))
meta11={__newindex=function(table,key,value) --参数table为子表print(table,key,value) end
}
print("元表地址"..tostring(meta11))
setmetatable(mytable11,meta11)mytable11.age=20         --对子表mytable11中不存在的属性age赋值--子表地址table: 00A53478
--元表地址table: 00A531D0
--table: 00A53478	age	20
mytable11={}
print("子表地址"..tostring(mytable11))meta11={__newindex=function(table,key,value) --参数table为子表print(table,key,value) end
}
print("元表地址"..tostring(meta11))
setmetatable(mytable11,meta11)mytable11.age=20         --对子表mytable11中不存在的属性age赋值
print(mytable11.age)     --nil
print(meta11.age)        --nil --子表地址table: 00F431F0
--元表地址table: 00F43240
--table: 00F431F0	age	20
--nil
--nil

         如果你对一个表的不存在的属性进行赋值,并且这个表有一个元表(metatable)且元表中重载了__newindex方法,那么Lua将调用这个__newindex方法而不是在原始表中创建这个属性。这意味着,如果__newindex方法没有显式地在表中创建这个属性,那么即使你尝试设置它,这个属性也不会存在于原始表中。

mytable11={}
print("子表地址"..tostring(mytable11))meta11={__newindex=function(table,key,value) --参数table为子表print(table,key,value) rawset(table,key,value)          --实际在子表中创建属性end
}
print("元表地址"..tostring(meta11))
setmetatable(mytable11,meta11)mytable11.age=20         --对子表mytable11中不存在的属性age赋值
print(mytable11.age)     --20
print(meta11.age)        --nil --子表地址table: 00A83308
--元表地址table: 00A833D0
--table: 00A83308	age	20
--20
--nil
(2.5.1)__newindex是表
mytable10={}
meta10={__newindex={}
}
setmetatable(mytable10,meta10)mytable10.age=20              --对子表mytable10 中不存在的属性age赋值
print(mytable10.age)          --nil
print(meta10.age)             --nil
print(meta10.__newindex.age)  --20
(2.6)获取元表
print(getmetatable(mytable))

9.面向对象

        Lua 本身不是一种面向对象编程语言,但它提供了机制,如元表(metatables)和元方法(metamethods),允许开发者模拟面向对象编程的特性,如封装和继承。

(1)封装

--定义一个Object类
Object={}
print("Object类:"..tostring(Object))
Object.id=10
Object.__index=Object--Object类方法
function Object:Test()   --冒号:第一个参数是自己,运用self使用变量值print("Test function:"..self.id)    
end--Object类构造函数
function Object:new()--运用元表local obj={} 	--创建子表setmetatable(obj,self)return obj  	--返回子表       
end--创建Object子对象
temp=Object:new()    --或者temp=Object.new(Object)
print("子对象:"..tostring(temp))   
print(temp.id)   --10
temp:Test()      --Test function:10--查看temp的元表
print("子对象的元表:"..tostring(getmetatable(temp)))
temp.id=2        --为temp创建了一个id属性,因为元表中没有__newindex。所以之前Object的id不变
print(Object.id) --10
temp:Test()      --Test function:2--Object类:table: 00469648
--子对象:table: 00469698
--10
--Test function:10
--子对象的元表:table: 00469648
--10
--Test function:2

         如果Object.__newindex=Object,那么Object.id也会为2。但这通常不是面向对象编程中期望的行为,因为这样会导致所有 Object 类型的实例共享相同的 id 属性值,而不是每个实例拥有自己的 id 值。通常,你会希望每个实例都有自己的属性集,因此,通常会在 __newindex 方法中为实例表设置新的属性,而不是在其元表中设置。

(2)继承

-- 定义一个Object类
Object={}
print("Object类:"..tostring(Object))
Object.id=10
Object.__index=Object--Object类构造函数
function Object:new()--运用元表local obj={} 	--创建子表setmetatable(obj,self)return obj  	--返回子表       
end--创建Object类方法subClass,参数为子类
function Object:subClass(className)_G[className]={} local obj=_G[className] --操作子类,不能写成local obj=classNameobj.base=self          --定义一个base属性,self为Objectsetmetatable(obj,self) return obj
end--创建Object子类Person
Object:subClass("Person")   --Person的元表是Object,Object设置了__index
print(Person.id)            --10

(3)多态

         多态性允许我们通过父类的引用来操作不同的子类对象,并且在运行时确定调用哪个类的重写方法。(相同的方法,执行不同的逻辑)
        父类= new 子类对象()

--创建一个Object的子类Father
Object:subClass("Father")
Father.__index=Father
Father.PosX=0;
Father.PosY=0
function Father:Move()self.PosX=self.PosX+1self.PosY=self.PosY+1print(self.PosX,self.PosY)
end
print("Father类:"..tostring(Father))--创建一个Father的子类Son
Father:subClass("Son")
Son.__index=Son 
function Son:Move() --重写Move--想使用base.Move() ,print("self.base:"..tostring(self.base)) --self的base是Fatherself.base.Move(self)----要避免把基类表传入方法中,所以要用. --self是Son的实例--self.base:Move() --base用:会有问题,会共用posX posY   --self.base为father,默认参数也为father,那么就是father的Pos变化
end
print("Son类:"..tostring(Son))--创建son的实例
local son1=Son:new()
son1:Move()local son2=Son:new()
son2:Move()--Father类:table: 00BD96C0
--Son类:table: 00BD9788
--self.base:table: 00BD96C0
--1	1
--self.base:table: 00BD96C0
--1	1

(4)总结

  • 封装:父类new函数中创建一个空表,并设置空表的元表为父类。需要运用__index。
  • 继承:父类实现一个方法,用于创建子类。函数参数为一个子表,并使用大G表来进行操作该子表。同样需要设置子表的元表为父类。需要运用__index。
  • 大坑:一定要注意self是指子类还是父类,可以打印一下地址查看! 

10.Lua其他库

        Lua 自带的标准库提供了一些基础的功能,除了上文提到的基础库(提供了Lua脚本与Lua解释器交互的基本函数,比如assert, error, ipairs, pairs, pcall, print, select, tonumber, tostring, type, unpack, xpcall等)、包管理(提供了Lua模块加载和构建的功能,主要函数包括require和module)、字符串处理、表处理(如table.insert,table.remove, table.sort等)、还有数学计算、输入/输出(提供了文件操作的函数)以及操作系统库(提供了与操作系统交互的函数)等库。

(1)数学计算

print("绝对值:"..math.abs(-11))
print("弧度转角度:"..math.deg(math.pi))
print("cos90度,参数是弧度:"..math.cos(90*(math.pi/180)))print("向下取整:"..math.floor(0.56)) --0
print("向上取整:"..math.ceil(0.56))  --1print("最大值:"..math.max(1,2))   --2
print("最小值:"..math.min(4,5))   --4--将一个浮点数分解为其整数部分和小数部分
print(math.modf(1.2))   --1   0.2print("幂运算:"..math.pow(2,5))
print("幂运算:"..2^5)   --不利用math库,使用 ^ 运算符通常更简洁math.randomseed(os.time()) --必须初始化随机种子,否则随机数一定是固定的
print("随机数:"..math.random(0,100))
print("随机数:"..math.random(0,100))print("开方:"..math.sqrt(4))

(2)操作系统库-时间

print(os.time())
print(os.time({year=2024,month=2,day=26}))
nowTime=os.date("*t")
print(nowTime.year.."/"..nowTime.month.."/"..nowTime.day)--1711019493
--1708920000
--2024/3/21

(3)文件操作

(3.1)写文件
f=io.open("Lesson_20_IO.txt","w")
f.write(f,"流水从不争先,争的是滔滔不绝。\n")
f.write(f,"不盼刹那绽放,盼的是生生不息。")
io.close(f)
(3.2)读文件

        read函数是最常用的读取方法之一。read函数可以带有不同的参数来指定读取内容的方式:

  • "*l"或者"*line": [默认]读取下一行的内容,若为文件尾,则返回nil
  • "*n"或者"*number": 读取一个数字
  • "a"或者"*all": 从当前位置读取整个文件,若为文件尾,则返回空字串
  • number: 读取number个字符

        在Lua中,如果你想要将文件的读取光标(读取位置)移动到文件的第一行,你可以使用seek方法。seek方法用于设置和获取文件的当前位置,并且有三个选项:"set""cur""end"。如果你想要将光标移动到文件的开始,你应该使用"set"模式,并且将偏移量设置为0。

f=io.open("Lesson_20_IO.txt","r")
txt=""
while true dostr=f.read(f)if str==nil thenbreakendtxt=txt..strprint(txt)
endf:seek("set")  --将光标移动到文件的开始,还支持参数:"cur", "end"
txt=f:read(6)
print(txt) --流水f:seek("cur", 6)  -- 将光标向前移动10个字节
txt=f:read(6)
print(txt) --争先
txt=f:read("*number")
print(txt) --nilio.close(f)--流水从不争先,争的是滔滔不绝。
--流水从不争先,争的是滔滔不绝。不盼刹那绽放,盼的是生生不息。
--流水
--争先
--nil

(4)其他-路径

print("lua脚本加载路径:"..package.path)
path=package.path..";C:\\"
print("增加一个路径:"..path)--lua脚本加载路径:;.\?.lua;D:\soft\Lua\5.1\lua\?.lua;D:\soft\Lua\5.1\lua\?\init.lua;D:\soft\Lua\5.1\?.lua;D:\soft\Lua\5.1\?\init.lua;D:\soft\Lua\5.1\lua\?.luac
--增加一个路径:;.\?.lua;D:\soft\Lua\5.1\lua\?.lua;D:\soft\Lua\5.1\lua\?\init.lua;D:\soft\Lua\5.1\?.lua;D:\soft\Lua\5.1\?\init.lua;D:\soft\Lua\5.1\lua\?.luac;C:\

11.垃圾回收

        lua中有自动定时进行GC操作。unity中热更新开发,尽量不要去用自动垃圾回收(推荐:切场景或者内存不够用时)

test={id=1024,index=0}
print("获取当前lua占用内存字节数(kb):"..collectgarbage("count"))test=nil  --释放资源
collectgarbage("collect")  --进行垃圾回收
print("获取当前lua占用内存字节数(kb):"..collectgarbage("count"))--获取当前lua占用内存字节数(kb):20.5048828125
--获取当前lua占用内存字节数(kb):19.392578125

三、热更新

1.插件安装

  • 在unity资源商店中找到moonsharp 插件,并导入到unity中。

2.创建Lua脚本

        创建Lua脚本test.lua,并放入StreamingAssets文件夹下。

function MyLua()print("hello lua")  --lua的print函数在Unity中是无法输出的Debug.Log("Hello unity,I am Lua.")return "hello lua"
end

3.unity中调用Lua脚本

        下方实例是用来展示:实现Lua调用unity中Debug类,并在unity中成功调用Lua中的方法。

using MoonSharp.Interpreter;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;public class LuaManager : MonoBehaviour
{void Start(){//创建Lua脚本解释器Script luaScript = new Script();//在Lua中调用Unity提供的Debug功能UserData.RegisterType<Debug>();luaScript.Globals["Debug"] = typeof(Debug);//获取lua脚本位置string luaPath = System.IO.Path.Combine(Application.streamingAssetsPath, "test.lua");//读取lua脚本string luaText = System.IO.File.ReadAllText(luaPath);//运行lua脚本DynValue dynValue=luaScript.DoString(luaText);if(dynValue == null ){Debug.Log("test.lua不存在");return;}//获取lua脚本中的函数DynValue luaFunction = luaScript.Globals.Get("MyLua");if(luaFunction == null ){Debug.Log("MyLua 函数不存在");return;}DynValue result = luaScript.Call(luaFunction);Debug.Log("unity中运行结果:"+result.ToString());Debug.Log("unity中运行结果:" + result.ToObject<string>());}
}

4.项目升级

        Lua利用unity中的GameObject类、Vector3类创建一个物体并返回。

(4.1)Lua脚本更改

function MyLua()go=GameObject("LuaObj")go.transform.position=Vector3(1,1,1)print("hello lua")  --lua的print函数在Unity中是无法输出的Debug.Log("Hello unity,I am Lua.")return go
end

(4.2)unity中调用Lua 

    private void Test(){//获取lua脚本位置,lua脚本可以放在服务器端,通过www下载,下载后再运行Lua脚本string luaPath = System.IO.Path.Combine(Application.streamingAssetsPath, "test.lua");//读取lua脚本string luaText = System.IO.File.ReadAllText(luaPath);if(luaText==""){return;}//创建Lua脚本解释器Script luaScript = new Script();//在Lua中调用Unity提供的功能UserData.RegisterType<Debug>();UserData.RegisterType<GameObject>();UserData.RegisterType<Transform>();UserData.RegisterType<Vector3>();//将Debug类的类型信息赋值给Lua中的全局变量Debug。这样Lua脚本就可以通过Debug这个全局变量来访问Debug类的静态方法和属性。luaScript.Globals["Debug"] = typeof(Debug);//Debug是一个静态类,其方法可以直接在Lua中调用,而GameObject和Vector3是需要实例化的对象,它们在Lua中的使用需要C#层面上的支持和注册。//创建一个函数委托,这个委托指向一个方法CreateGameObject,这个方法接受一个string参数并返回一个GameObject实例。这样Lua脚本就可以通过调用GameObject("name")来创建一个新的GameObject实例。//在Lua中创建Unity对象通常需要调用Unity的API函数,而不仅仅是访问类型信息。对于GameObject,你不能直接使用typeof(GameObject),因为GameObject不仅有静态方法,还有实例方法和属性。如果你只是传递类型信息,Lua将不知道如何创建GameObject的实例。luaScript.Globals["GameObject"] = (System.Func<string, GameObject>)CreateGameObject;luaScript.Globals["Vector3"] = (System.Func<float, float, float, Vector3>)CreateVector3;//运行lua脚本DynValue dynValue = luaScript.DoString(luaText);if (dynValue == null){Debug.Log("test.lua不存在");return;}DynValue luaFunction = luaScript.Globals.Get("MyLua");if (luaFunction == null){Debug.Log("MyLua 函数不存在");return;}DynValue result = luaScript.Call(luaFunction);//返回结果是字符串//Debug.Log("unity中运行结果:" + result.ToString());//Debug.Log("unity中运行结果:" + result.ToObject<string>());//返回结果是物体GameObject go=result.ToObject<GameObject>();go.AddComponent<Rigidbody>();}static GameObject CreateGameObject(string name){return new GameObject(name);}static Vector3 CreateVector3(float x, float y, float z){return new Vector3(x, y, z);}

(4.3)效果展示 

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

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

相关文章

归并算法详细解析

归并排序 1945年&#xff0c;约翰冯诺依曼&#xff08;John von Neumann&#xff09;发明了归并排序&#xff0c;这是典型的分治算法的应用。归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&#xff08;Di…

数学建模(Topsis python代码 案例)

目录 介绍&#xff1a; 模板&#xff1a; 案例&#xff1a; 极小型指标转化为极大型&#xff08;正向化&#xff09;&#xff1a; 中间型指标转为极大型&#xff08;正向化&#xff09;&#xff1a; 区间型指标转为极大型&#xff08;正向化&#xff09;&#xff1a; 标…

RequestResponse使用

文章目录 一、Request&Response介绍二、Request 继承体系三、Request 获取请求数据1、获取请求数据方法&#xff08;1&#xff09;、请求行&#xff08;2&#xff09;、请求头&#xff08;3&#xff09;、请求体 2、通过方式获取请求参数3、IDEA模板创建Servlet4、请求参数…

WEB 表单练习题

任务如图&#xff1a; <html><head><meta charest"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> </head><body><table width"…

Google的MELON: 通过未定位图像重建精确3D模型的突破性算法

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

算法沉淀——贪心算法七(leetcode真题剖析)

算法沉淀——贪心算法七 01.整数替换02.俄罗斯套娃信封问题03.可被三整除的最大和04.距离相等的条形码05.重构字符串 01.整数替换 题目链接&#xff1a;https://leetcode.cn/problems/integer-replacement/ 给定一个正整数 n &#xff0c;你可以做如下操作&#xff1a; 如果…

数据结构:堆的创建和使用

上一期我们学习了树和二叉树的定义&#xff0c;其中我们了解到了两种特殊的二叉树&#xff1a;满二叉树和完全二叉树。 今天我们还要学习一种新的结构&#xff1a;堆 那这种结构和二叉树有什么联系呢&#xff1f;&#xff1f;&#xff1f; 通过观察我们可以发现&#xff0c;…

鸿蒙一次开发,多端部署(三)应用UX设计原则

设计原则 当为多种不同的设备开发应用时&#xff0c;有如下设计原则&#xff1a; 差异性 充分了解所要支持的设备&#xff0c;包括屏幕尺寸、交互方式、使用场景、用户人群等&#xff0c;对设备的特性进行针对性的设计。 一致性 除了要考虑每个设备的特性外&#xff0c;还…

C# 读取二维数组集合输出到Word预设表格

目录 应用场景 设计约定 范例运行环境 配置Office DCOM 实现代码 组件库引入 核心代码 DataSet转二维数组 导出写入WORD表格 调用举例 小结 应用场景 存储或导出个人WORD版简历是招聘应用系统中的常用功能&#xff0c;我们通常会通过应用系统采集用户的个人简历信息…

云主机搭建与服务软件部署

文章目录 登录访问云电脑与云电脑传输文件配置ssh服务ssh连接云电脑使用scp传输文件云端服务软件部署与实现外部访问首先购买云主机,以阿里云服务器 ECS为例子,官网购买就行了,选择默认安装了windows server 2022服务器系统 登录访问云电脑 购买完成进入控制台,能看到创建…

使用CUDA 为Tegra构建OpenCV

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;MultiArch与Ubuntu/Debian 的交叉编译 下一篇&#xff1a;在iOS中安装 警告&#xff1a; 本教程可能包含过时的信息。 使用CUDA for Tegra 的OpenCV 本文档是构建支持 CUD…

谷歌具身智能最新进展:RT-H 机器人通用灵巧抓取

随着 GPT-4 等大型语言模型与机器人研究的结合愈发紧密&#xff0c;人工智能正在越来越多地走向现实世界&#xff0c;因此具身智能相关的研究也正受到越来越多的关注。在众多研究项目中&#xff0c;谷歌的「RT」系列机器人始终走在前沿&#xff08;参见《大模型正在重构机器人&…

各位老板,你需要的工厂数字孪生可视化库在这

各位老板是不是很喜欢下面这种有逼格的大屏,下面介绍一下怎么实现的,保证有所收获。 Cesium是一个开源的WebGL JavaScript库&#xff0c;用于创建高性能的三维地球、地图和虚拟环境。它支持在浏览器中实现高质量的地球模拟&#xff0c;同时提供了丰富的功能特点&#xff0c;使得…

k8s系列之十五 Istio 部署Bookinfo 应用

Bookinfo 应用中的几个微服务是由不同的语言编写的。 这些服务对 Istio 并无依赖&#xff0c;但是构成了一个有代表性的服务网格的例子&#xff1a;它由多个服务、多个语言构成&#xff0c;并且 reviews 服务具有多个版本。 该应用由四个单独的微服务构成。 这个应用模仿在线书…

模板高级使用(非类型模板参数,特化,分离编译)

文章目录 模板没有实例化取内嵌类型报错问题非类型模板参数模板的特化函数模板的特化类模板的特化1.全特化2.偏特化 模板的分离编译 模板没有实例化取内嵌类型报错问题 首先在这里分享一个模板的常见报错问题。就是模板的在没有实例化的情况下去取模板类里面的内嵌类型这时候的…

代码随想录|Day25|回溯05|491.非递减子序列、46.全排列、47.全排列II

491. 非递减子序列 本题并不能像 90.子集II 那样&#xff0c;使用排序进行树层去重。虽然题目没有明确不能排序&#xff0c;但如果排序了&#xff0c;集合本身就是递增子序列&#xff0c;这是LeetCode示例2中没有出现的。 所以本题的关键在于&#xff0c;如何在不排序的情况下对…

2024格行VS华为VS飞猫哪个是最值得购买随身WiFi?中兴随身WiFi好用吗?

经常出差旅行&#xff0c;或者户外工作的朋友因为长期在外&#xff0c;手机流量经常不够用&#xff0c;想必都是随身WiFi的忠实用户&#xff0c;但是也都被这款产品割韭菜割的头皮发麻。今天&#xff0c;我们统计了市面上最靠谱的、最热销、口碑最好的几款随身WiFi。排名依据来…

Java学习笔记(17)

集合进阶 单列集合 Collection List set Add clear remove contains isempty size Add方法可能也会添加失败 同理&#xff0c;可能删除失败 Contains细节 为什么要重写equals&#xff1f; 因为contains底层用的是object类中的equals方法&#xff0c;比较的是地址值&#xf…

为什么穷人什么都懂,就是不懂赚钱?2024金矿项目! 2024创业好项目 !2024创业新项目新商机! 2024超级机会

为什么穷人什么都懂&#xff0c;就是不懂赚钱&#xff1f;有位网友是这么说的&#xff0c;穷人的思维有一个致命的缺陷&#xff0c;就是追求确定性&#xff0c;进而失去了可能性。而赚钱的真相实际上非常残酷。世界上能够赚钱的事情必定是不确定的&#xff0c;能够赚取巨额财富…

万亿参数GPU!算力提升30倍!英伟达新核弹B200重磅发布!

关注文章底部的公众号,获取每日AI资讯 前沿 3月18日-21日期间,英伟达在美国圣何塞召开GTC大会。创始人黄仁勋也在GTC大会上,做了一场长达两小时的开幕演讲,展示了其在AI芯片、机器人、汽车等领域的最新研发成果和技术进展,号称让全世界用上AI。 全球头号人工智能领域开发…