Lua学习

1.概述

Lua很容易迁入到其他宿主语言中,可以调用宿主语言的逻辑,宿主语言也可以调用Lua,改动游戏的时候可以不用动到C++或C写的逻辑

2.变量

类型:nil(没有有效值,null), boolean, number, string, function, userdata, thread, table,,这里面不区分long、int、float等整型浮点型的区别,都是数字类型number(默认double类型)
lua鼓励用别的语言实现自己的数据结构
lua的变量不区分类型,值区分类型。

-- 不加local,默认全局,其他的语句块或者其他的lua文件也能访问
local a = 123
print(a)

-- 单引号双引号一样
a = "string"
a = 'aaa'

--[[
多行注释
afeadf
asdfasdf
]]

--[=[[]]=]
--左中括号会和对应等号的右中括号匹配
--[==[]==]

在默认情况下,变量总是认为是全局的。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。

>print(b)
nil
> b=10
>print(b)
10
> 

如果你想删除一个全局变量,只需要将变量赋值为nil。

b = nil
print(b)      --> nil

这样变量b就好像从没被使用过一样。换句话说, 当且仅当一个变量不等于nil时,这个变量即存在。
与nil作比较需要双引号

type(X)=="nil"

thread(线程)
在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。


userdata(自定义类型)
userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。
字符串

  • 单引号间的一串字符。
  • 双引号间的一串字符。
  • [[]] 间的一串字符。
string1 = "Lua"
print("\"字符串 1 是\"",string1)
string2 = 'runoob.com'
print("字符串 2 是",string2)
--   [[]]定义字符串,转义字符不受影响
string3 = [["Lua 教程"]]
print("字符串 3 是",string3)

输出结果
"字符串 1 是"    Lua
字符串 2 是    runoob.com
字符串 3"Lua 教程"

--看下面字符匹配模式
> for word in string.gmatch("Hello Lua user", "%a+") do print(word) end
Hello
Lua
user

https://www.runoob.com/lua/lua-strings.html

字符串格式化

Lua 提供了 string.format() 函数来生成具有特定格式的字符串, 函数的第一个参数是格式 , 之后是对应格式中每个代号的各种数据。
由于格式字符串的存在, 使得产生的长字符串可读性大大提高了。这个函数的格式很像 C 语言中的 printf()。
以下实例演示了如何对字符串进行格式化操作:
格式字符串可能包含以下的转义码:

  • %c - 接受一个数字, 并将其转化为ASCII码表中对应的字符
  • %d, %i - 接受一个数字并将其转化为有符号的整数格式
  • %o - 接受一个数字并将其转化为八进制数格式
  • %u - 接受一个数字并将其转化为无符号整数格式
  • %x - 接受一个数字并将其转化为十六进制数格式, 使用小写字母
  • %X - 接受一个数字并将其转化为十六进制数格式, 使用大写字母
  • %e - 接受一个数字并将其转化为科学记数法格式, 使用小写字母e
  • %E - 接受一个数字并将其转化为科学记数法格式, 使用大写字母E
  • %f - 接受一个数字并将其转化为浮点数格式
  • %g(%G) - 接受一个数字并将其转化为%e(%E, 对应%G)及%f中较短的一种格式
  • %q - 接受一个字符串并将其转化为可安全被Lua编译器读入的格式
  • %s - 接受一个字符串并按照给定的参数格式化该字符串

为进一步细化格式, 可以在%号后添加参数. 参数将以如下的顺序读入:

  • (1) 符号: 一个+号表示其后的数字转义符将让正数显示正号. 默认情况下只有负数显示符号.
  • (2) 占位符: 一个0, 在后面指定了字串宽度时占位用. 不填时的默认占位符是空格.
  • (3) 对齐标识: 在指定了字串宽度时, 默认为右对齐, 增加-号可以改为左对齐.
  • (4) 宽度数值
  • (5) 小数位数/字串裁切: 在宽度数值后增加的小数部分n, 若后接f(浮点数转义符, 如%6.3f)则设定该浮点数的小数只保留n位, 若后接s(字符串转义符, 如%5.3s)则设定该字符串只显示前n位.
string.format("%c", 83)                 -- 输出S
string.format("%+d", 17.0)              -- 输出+17
string.format("%05d", 17)               -- 输出00017
string.format("%o", 17)                 -- 输出21
string.format("%u", 3.14)               -- 输出3
string.format("%x", 13)                 -- 输出d
string.format("%X", 13)                 -- 输出D
string.format("%e", 1000)               -- 输出1.000000e+03
string.format("%E", 1000)               -- 输出1.000000E+03
string.format("%6.3f", 13)              -- 输出13.000
string.format("%q", "One\nTwo")         -- 输出"One\
                                        --   Two"
string.format("%s", "monkey")           -- 输出monkey
string.format("%10s", "monkey")         -- 输出    monkey
string.format("%5.3s", "monkey")        -- 输出  mon
--%10.3f,15.3输出表示总共长10位,小数点后保留3位,小数点本身算1位,输出    15.333
--%10.3s,string输出表示总共输出10位字符串,字符串只保留前3位,输出       str

匹配模式

Lua 中的匹配模式直接用常规的字符串来描述。 它用于模式匹配函数 string.find, string.gmatch, string.gsub, string.match
你还可以在模式串中使用字符类。
字符类指可以匹配一个特定字符集合内任何字符的模式项。比如,字符类 %d 匹配任意数字。所以你可以使用模式串 %d%d/%d%d/%d%d%d%d 搜索 dd/mm/yyyy 格式的日期:

实例

s =“Deadline is 30/05/1999, firm”
date=“%d%d/%d%d/%d%d%d%d”
print(string.sub(s,string.find(s,date))) –> 30/05/1999

下面的表列出了Lua支持的所有字符类:
单个字符(除 ^$()%.[]*±? 外): 与该字符自身配对

  • .(点): 与任何字符配对
  • %a: 与任何字母配对
  • %c: 与任何控制符配对(例如\n)
  • %d: 与任何数字配对
  • %l: 与任何小写字母配对
  • %p: 与任何标点(punctuation)配对
  • %s: 与空白字符配对
  • %u: 与任何大写字母配对
  • %w: 与任何字母/数字配对
  • %x: 与任何十六进制数配对
  • %z: 与任何代表0的字符配对
  • %x(此处x是非字母非数字字符): 与字符x配对. 主要用来处理表达式中有功能的字符(^$()%.[]*±?)的配对问题, 例如%%与%配对
  • [数个字符类]: 与任何[]中包含的字符类配对. 例如[%w_]与任何字母/数字, 或下划线符号(_)配对

当上述的字符类用大写书写时, 表示与非此字符类的任何字符配对. 例如, %S表示与任何非空白字符配对.例如,'%A’非字母的字符:

在模式匹配中有一些特殊字符,他们有特殊的意义,Lua中的特殊字符如下:
( ) . % + - * ? [ ^ $
‘%’ 用作特殊字符的转义字符,因此 ‘%.’ 匹配点;‘%%’ 匹配字符 ‘%’。转义字符 '%'不仅可以用来转义特殊字符,还可以用于所有的非字母的字符。
模式条目可以是:

  • 单个字符类匹配该类别中任意单个字符;
  • 单个字符类跟一个 ‘*’, 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串;
  • 单个字符类跟一个 ‘+’, 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串;
  • 单个字符类跟一个 ‘-’, 将匹配零或更多个该类的字符。 和 ‘*’ 不同, 这个条目总是匹配尽可能短的串;
  • 单个字符类跟一个 ‘?’, 将匹配零或一个该类的字符。 只要有可能,它会匹配一个;
  • %n, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串。
  • %b_xy_, 这里的 xy 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 xy 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x+1 ,读到一个 y-1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目 %b() 可以匹配到括号平衡的表达式。
  • %f[set], 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 集合 set 的含义如前面所述。 匹配出的那个空串之开始和结束点的计算就看成该处有个字符 ‘\0’ 一样。

模式:

模式 指一个模式条目的序列。 在模式最前面加上符号 ‘^’ 将锚定从字符串的开始处做匹配。 在模式最后面加上符号 ‘KaTeX parse error: Expected group after '^' at position 24: …锚定到字符串的结尾。 如果 '^̲' 和 '’ 出现在其它位置,它们均没有特殊含义,只表示自身。
捕获:
模式可以在内部用小括号括起一个子模式; 这些子模式被称为 捕获物。 当匹配成功时,由 捕获物 匹配到的字符串中的子串被保存起来用于未来的用途。 捕获物以它们左括号的次序来编号。 例如,对于模式 “(a*(.)%w(%s*))” , 字符串中匹配到 “a*(.)%w(%s*)” 的部分保存在第一个捕获物中 (因此是编号 1 ); 由 “.” 匹配到的字符是 2 号捕获物, 匹配到 “%s*” 的那部分是 3 号。
作为一个特例,空的捕获 () 将捕获到当前字符串的位置(它是一个数字)。 例如,如果将模式 “()aa()” 作用到字符串 “flaaap” 上,将产生两个捕获物: 3 和 5 。

3.语句 运算符

lua赋值支持多变量

a,b = 123, "abc"
print(a)
print(b)
--terminal: 123
--terminal: abc

--交换ab
a,b = b,a
print(a)
print(b)
--terminal: abc
--terminal: 123
age = 14
-- if boolean表达式
if age > 24 then
  print("you can work")
elseif age < 18
  print("you should go to school")
else
  print("")
end

if abc > 18 then
  print("you can work")
else
  print("you should go to school")
end
--  you should go to school,,因为abc为定义属于nil

对于lua,,,,nil和false都是不成立,,,但是0是成立的boolean,,,和c++区别

--第一个起始值,第二个结束值,第三个步长,默认步长1
for i = 1,10 do
    print("i = ", i)
end
print("--------------")
for i = 1,10,2 do
    print("i = ", i)
end

数学运算没有int float之分,按照常规认知计算,他只是number
image.pngimage.png
lua没有++ –

--关系运算符
> < >= <= == ~=不等于

4.语句

--优先级
if not (age > 70) then
else
end
a = 0
while a < 10 do
    a = a+1
    print(a)
  if(a>3) then
    break
  end
end

--类似do while
ans = 15
repeat
    print("guess the number little than 100")
    a = tonumber(io.read())

    if a == ans then
        print("right")
        break
    elseif a > ans then
        print(a,"too big")
    else
        print(a,"too small")
    end
until false

5.Table基础

table是lua唯一的内置数据结构,table长度可以自动增长
table很像键值对,键值默认从1开始,1、2、3、4……,可以自己写键值x,y
因此下面table键值对是1-1,2-2,3-3,4-5,5-“a”,6-“b”,x-123,y-456
nil不能作为键值,table可以嵌套

-- table 唯一数据结构
a ={}
a = {1,2,3,5,"a","b",x = 123,y = 456}
--类似数组,下标从1开始
print(a[1])
print(a[5])
print(a["x"])--这里会输出123,因为定义了x的键值
print(a["a"])--这里会输出nil,因为没有定义"x"的键值
print(a.y)
--lua支持a.x,语法糖
--table.key简化tablexxx["key"]

--还可以增加值
a.z = 789--a["z"]
print(a.z)

--方括号明确表示字符串键值,不写的话符号默认字符串键值
a = {1,2,"a","b",x = 123,y = 456,pos={x1 = 23,y2 = 45,["z"] = 90}}
print(a.pos.x1)
print((a["pos"])["x1"])

image.pngimage.png
使用pairs遍历,12ab都是没有声明key,lua会给这些元素添加默认值,而添加了非默认键值的键值对右hash表存储,不能保证存储位置是按照赋值顺序连续的image.png
使用ipairs遍历,非默认键值的键值对全部不遍历
image.png

6.函数

函数定义和变量很相似,函数是被看做为"第一类值(fitst-class value)",函数可以存在变量里

add = function (a,b)
    return a+b    
end
print(add(1,2))

--语法糖
function add(a,b)
    return a+b
end
print(add(1,3))

函数可以返回多个返回值

function fun1(a,b)
    return a+b,a*b
end
sum,product = fun1(1,3)
--fun1(1,2,6)6没有变量接受就丢弃了
--同样return a+b,a*b,10没有变量接受10,10也被丢弃了
print("sum:",sum,"  product:",product)

function可以以匿名函数的方式通过参数传递

function testFun(tab, fun)
  for k,v in pairs(tab) do
    print(fun(k,v))
  end
end

tab = {key1 = "valu1", key3 = "val2"}
testFun(tab, 
  function(key,val) --匿名函数
    return key.."-"..val 
  end
)

高阶函数——函数可以传递给另一个函数,这个函数的参数或者返回值都有可能是另一个函数

--高阶函数 --第一类 first class
function process(f,a,b)
    print("f:",f)
    print("a:",a)
    print("b:",b)

    print("f(a,b)",f(a,b))
end
function add(a,b)
    return a+b
end
function fun1(a,b)
    return a+b,a*b,10
end
process(add,3,5)
process(fun1,3,5)

image.png

--可变参数 -- #符号暂不深究
function add(...)
    local sum = 0
    --把所有可变参数...放进table全部接受进去
    local arg = {...}
    for k,v in ipairs(arg) do
        sum = sum + v
    end
    --#arg返回变量长度,
    return #arg, sum
end

function average(...)
    local count, sum, temp = add(...)
  --temp是nil因为接受值数量不匹配,add返回的前两个值付给了count和sum
    print("temp:",temp)
    return sum/count
end
print(average(1,2,3,4,5,6,7))

image.png

myprint = function(param)--创建匿名函数
   print("这是打印函数 -   ##",param,"##")
end

function add(num1,num2,functionPrint)
   result = num1 + num2
   -- 调用传递的函数参数
   functionPrint(result)
end
myprint(10)
-- myprint 函数作为参数传递
add(2,5,myprint)

输出结果为:
这是打印函数 -   ##    10    ##
这是打印函数 -   ##    7    ##
  • select(‘#’, …) 返回可变参数的长度。
  • select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。
--可以通过 select("#",...) 来获取可变参数的数量:
function average(...)
   result = 0
   local arg={...}
   for i,v in ipairs(arg) do
      result = result + v
   end
   print("总共传入 " .. select("#",...) .. " 个数")
   return result/select("#",...)
end

print("平均值为",average(10,5,3,4,5,6))
输出结果为:
总共传入 6 个数
平均值为    5.5

function f(...)
    a = select(3,...)  -->从第三个位置开始,变量 a 对应右边变量列表的第一个参数
    print (a)
    print (select(3,...)) -->打印所有列表参数
end

f(0,1,2,3,4,5)
输出结果为:
2
2       3       4       5

7.运算符

--# 用来求长度
print(#{1,2,3,4,5,"a","bbbb"})
print(#"aaaa")
--索引值是连续的不会出错,如果不连续可能出错.nil可能有坑
print(#{1,2,3,x = 100,y = 200})

image.png

-- ..字符串连接,原则上 少使用
--[[lua特点:字符串不可变。
构造新字符串abcddef赋值,如果ab不适用了会释放掉
性能消耗较大
]]
a = "abc".."d"
a = "abcd"
b = "def"
c = a..b
print(c)--c = abcdef

下表列出了 Lua 语言中的常用逻辑运算符,设定 A 的值为 true,B 的值为 false:

操作符描述实例
and逻辑与操作符。 若 A 为 false,则返回 A,否则返回 B。(A and B) 为 false。
or逻辑或操作符。 若 A 为 true,则返回 A,否则返回 B。(A or B) 为 true。
not逻辑非操作符。与逻辑运算结果相反,如果条件为 true,逻辑非为 false。not(A and B) 为 true。

lua中没有三目运算符
优先级:and>or

--还原三目运算符
age = 15
canVote = age > 15 and true or false
print(canVote)

image.png
是取反,&|是二进制的位运算符,a是按位取反,ba是按位异或
位移运算符<<1 相当于乘2,>>1相当于除2,<<2相当于乘2^2

8.函数

C API是用来和宿主语言交互的
package包函数,可以从lua文件引用另一个lua文件

b = [[
    \t

    \b \\
]]
--语法糖
--print(string.upper(b))
print(b.upper(b))
print(b:upper())
a = {1,23,24,12,7,90}
--table.insert(a,3,"a")
table.remove(a,2)
table.sort(a)
for k,v in pairs(a) do
    print(k,":",v)
end
--自己定义排序函数
table.sort(a, function(v1,v2) return v1>v2 end)
for k,v in pairs(a) do
    print(k,":",v)
end

image.png

local t = {}
for line in io.lines() do
    t[#t+1] = line
    if #line <= 0 then
        break
    end
end

local s = table.concat(t,"+")
print(s)
输出:
asdf
asdf
fdfdf
sdfdae
asdf 

asdf+asdf+fdfdf+sdfdae+asdf +
math.randomseed(os.time())
math.random(1,10)
math.pi
math.sin

9.协程

image.png

--create接受一个函数
--thread 线程 协程 进程,,这里返回了thread但还是协程
co = coroutine.create(
    function ()
        print("coroutine running...")
    end
)

print(type(co))--返回thread
--分次执行
co = coroutine.create(
    function ()
        print("start...")
        coroutine.yield()
        print("after yield")
    end
)
coroutine.resume(co)
print("---")
coroutine.resume(co)

image.png
进程:计算机刚出来,只有单线程…后来有了多任务,一台电脑,一边打开vs,一边别的程序,cpu同时调度,把时间分给不同的应用程序,一个应用程序执行几毫秒,另一个执行几毫秒,进行进程切换.每次切换都要保存上下文,切换到另一个程序的时候恢复这个程序的上下文
线程:同一个进程可以包含多个线程,线程的调度略轻,运行栈也有,抢占式,应用程序执行了一段时间cpu分配给另一个应用程序
协程:调度更轻,把一个任务分成多块进行,线程和进程调度是抢占式并发的运行规则.一个cpu伪并发运行多个线程,协程是主动出让的,不是并发的(怪物很多,可以让一帧执行20怪的逻辑,下一帧执行另20个怪的逻辑,分摊到不同帧执行)

https://baijiahao.baidu.com/s?id=1663040868828329048&wfr=spider&for=pc
https://blog.csdn.net/weixin_42621338/article/details/82906950
一、进程
刘大胖打开电脑,想写点东西,于是打开WPS,突然又想和女朋友(反正我不信)聊聊天,就又打开了微信PC端,这时操作系统就会为这两个程序生成两个进程,如图:

二、线程
每个进程至少包含一个线程,线程是CPU调度和执行的基本单位,WPS中可以一边接收文字输入、一边自动保存,这时会有两个进程,如图:

三、并发
刘大胖在WPS中输入文字,程序自动保存,但并没有出现卡顿,这是因为CPU在执行多线程的时候采用时间分片,由于CPU切换非常快,刘大胖才感觉不到卡,其实某一个时间点上CPU只会执行一个线程,这种多个线程切换执行就叫做并发,如图:

四、并行
刘大胖最近升级了电脑的CPU,从一个核升到了两个核,这样执行多线程的时候CPU就可以两个核一块执行了,这就是并行,如图:

五、总结
进程:其实是操作系统对一个正在运行的程序的一种抽象线程:线程是CPU调度的最小单位,是在线程内实现多任务的保证并发:一个CPU核心通过时间切换执行多个线程并行:多个CPU核心同时执行多个线程

执行完的协程不会重复执行,不会重复返回到上一次yield的地方重新执行.
coroutine可以有返回值,和传入参数,注意多次resume传参时,yield挂起那行语句等号右侧先执行,等号左侧下次执行赋值
每次执行完等号右方的yield后,都在等号处暂停,等号左边的变量等待后续赋值
coroutine.yield(…):挂起正在运行的协程,传入的参数会作为上一次resume启动的返回值,而yield挂起的返回值是下一次resume启动传入的参数。

co = coroutine.create(
    function(a,b)
        print("1 a=",a,"b=",b)
        --等号右侧yield会执行,其参数作为resume整个语句的值返回
        --等号左侧v1v2等待下次执行再次赋值
        --第一个值resume是执行成功返回true(失败返回false)
        --第二个值是yield的返回值"x"由r2接受
        --第三个值是yield的返回值 1 由r3接受
        v1,v2 = coroutine.yield("x",1)

        print("2 a=",a,"b=",b)
        print("2 v1=",v1,"v2=",v2)
        coroutine.yield("x",2)
    end
)
--使用resume传入值,把123,456作为ab传进去,执行完yield挂起
r1,r2,r3 = coroutine.resume(co,123,456)
print(r1,r2,r3)
--后续的resume没有办法在重新传值给coroutine.create的参数,但resume的传入值没有被丢弃
--再次传值作为yield挂起那条语句的yield返回值,由v1v2两个变量接收新的传值
coroutine.resume(co,7,8)

image.png
coroutine.warp(func):创建一个协程,并传入一个函数(function),返回的是传入的函数,创建后的协程会进入挂起状态。唤醒该协程只需调用返回的协程函数即可。(该线程无法使用coroutine.status())

fun1 = coroutine.wrap(
    function (i)
        print("-----")
        print(i)
        print("-----")
    end
)
print(type(fun1))
--warp创建好就怪,fun1调用这个返回的协程即可唤醒该协程
fun1(123)

image.pngimage.pngimage.png

10.Lua面向对象—实现对象和类

lua本身没有定义类,只能用table实现
lua中一般面向对象的对象用self,不用this

local monster = {
    name ="Monster1",
    HP = 100;
    pos = {x = 100, y = 200},
    TakeDamage = function(self, damage)
      self.HP = self.HP - damage
  	end
}
--属性
print(monster.HP)

--[[增加方法
monster.TakeDamage = function(damage)
    monster.HP = monster.HP - damage
end
monster.TakeDamage(10)
print(monster.HP)]]

--上面并没有实现面向对象编程,因为不是不同实例
--增加方法 需要传入修改的对象实例
monster.TakeDamage = function(self, damage)
    self.HP = self.HP - damage
end
--创建monster1实例对象
local monster1 = {HP = 123}
monster.TakeDamage(monster1,10)
print(monster1.HP)

--语法糖 :
monster:TakeDamage(10) --monster.TakeDamage(monster,10)
--默认self是第一个传入的参数
function monster:TakeDamage(damage)
    self.HP = self.HP - damage
end --monster.TakeDamage(monster,damage)

local monster1 = monster
print(monster)
print(monster1)
--两者指向了同一地址,是引用,,,00de81a0

image.png
想要实现类需要用到元表.meta table
__index 元方法
这是 metatable 最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。

--meta table 元表
CMonster = {
    name = "Monster", 
    HP = 100,
    TakeDamage = function(self,damage)
        self.HP = self.HP - damage
    end
}

--定义实例,而不是一个类型
--希望自定义的实例,有初始值使用自己的初始值
--没有初始值,使用元表的值
--通过元表定义实例的行为
objMonster = {x = 100, y = 200}
print(objMonster.HP) --nil
--设置objMonster的元表是类的表
--__index用来去meta table中索引table中不存在的键值
setmetatable(objMonster, {__index = CMonster})
print(objMonster.HP) --100

--__index和__newindex不一样,修改值添加值默认是修改__newindex创建的元素
--如果需要增加修改的值是元表的元素,setmetatable需要标记__newindex
--setmetatable(objMonster, {__index = CMonster,__newindex = CMonster})
--除非是C++的静态变量,所有静态实例共享数据,才把__newindex也指向元表
objMonster.HP = 123
objMonster.MP = 234

print(objMonster.HP) --123
print(CMonster.HP)   --100
CMonster = {
    name = "Monster", 
    HP = 100,
    TakeDamage = function(self,damage)
        self.HP = self.HP - damage
    end,
    ShowInfo = function(self)
        print(self.name,self.HP)
    end
}
CMonster.__index = CMonster

function CMonster:new(name,hp,x,y)
    local obj = {}
    obj.name = name
    obj.HP = hp
    obj.x = x
    obj.y = y
    --{__index = CMonster}额外构造了一张表
    --把这张表作为了元表
    --setmetatable(obj, {__index = CMonster})
    --__index表示元方法必须指向一个表,才能去这个表查找
    --无论这个__index是哪个表的,实例对象的__index也好,新构造的表的__index也好,元表的__index
    setmetatable(obj,CMonster)
    return obj
end
--对表做函数调用
setmetatable(CMonster,{__call = function(self,name,hp)
    return self.new(name,hp,100,100)
end
})
--new方法就是CMonster的new
local obj1 = CMonster:new("monster1",30,100,200)
--CMonster的函数调用,self是Monster自身,再调用new
local obj2 = CMonster("monster2",20,100,200)
obj2:ShowInfo()
local obj3 = CMonster("monster3",10,200,500)
obj3:ShowInfo()

image.png

11.Lua面向对象—实现继承和多态

--修改为local,,,否则global的话再t9也能调用.不是想要的结果
local CMonster = {
    name = "Monster", 
    HP = 100,
    TakeDamage = function(self,damage)
        self.HP = self.HP - damage
    end,
    ShowInfo = function(self)
        print(self.name,self.HP)
    end
}
CMonster.__index = CMonster

function CMonster:new(name,hp,x,y)
    local obj = {}
    obj.name = name
    obj.HP = hp
    obj.x = x
    obj.y = y
    setmetatable(obj,CMonster)
    return obj
end

setmetatable(CMonster,{__call = function(self,name,hp)
      --注意是:,,,如果是.就出错
    return self:new(name,hp,100,100)
end
})
--需要返回这个类型
return CMonster

lua的引用是require,路径查找规则是package.path可以打印出来

--继承monster
local CM = require("t8")
--package.path,,,,?.lua;是查看同级目录下面的lua文件
--print(package.path)

local MagicMonster = {
    MP = 200,
    Attack = function (self)
        print("Attack..")
        self.MP = self.MP - 10
    end
}
setmetatable(MagicMonster,{
    __index = CM,
    __call = function(self, name, hp)
        local obj = CM(name,hp)
        setmetatable(obj,{__index = MagicMonster})
        return obj
    end
})

--多态
function MagicMonster:ShowInfo()
    print("Magic Monster:",self.name, self.HP)
end

local obj2 = MagicMonster("m1",100)
obj2:ShowInfo()
--调用继承来的方法
obj2:TakeDamage(10)
obj2:ShowInfo()

image.png

12.Lua实现私有成员

--私有成员
--lua 哲学
--if you do not want to access something inside an object, just do not do it
--暴露的信息越少越好

function GetMonster()
    local self = {HP = 123, Name = "monster1", otherdata = {a = 1, b = 2}}
    local function GetHP()
        return self.HP
    end

    local function TakeDamage(_, count) --notice the first parameter
        self.HP = self.HP - count
        if self.HP <= 0 then
            self.HP = 0
        end
    end
    
    local function GetName()
        return self.Name
    end

    --并不返回表元素本身,返回的是封装好的函数
    --返回的不是原始的表,因此原始数据是安全的
    return {GetHP = GetHP, TakeDamage = TakeDamage, GetName = GetName}
end

local monster = GetMonster()
print(monster:GetHP())
monster:TakeDamage(20)
print(monster:GetHP())
monster.HP = 100 -->并没有给原始HP值赋值,而是给返回的表新增了字段

image.png

local Monster = {}
Monster.HP = 100
Monster.x = 123
Monster.y = 456
Monster.Type = "Monster"

function Monster:GetHP()
    return self.HP
end

function Monster:TakeDamage(damage)
    self.HP = self.HP - damage
end

function Monster:SetHP(hp)
    self.HP = hp
end

function  Monster:new()
    local obj = {HP = Monster.HP,x = Monster.x, y =  Monster.y} --default value
    setmetatable(obj,Monster)
    Monster.__index = {GetHP = Monster.GetHP, TakeDamage = Monster.TakeDamage,Type = Monster.Type}
  --obj.Type = "A",obj-->tab,Type-->key,"A"-->value  
  Monster.__newindex = function(tab,key,value)
        if key == "Type" then
            print("Forbiden operate")
            return
        end
        --rawset把table中的字段更新成传入的key value
        rawset(tab,key,value)
    end
    return obj
end

return Monster
local CMonster = require("t10")
local obj = CMonster:new()

print(obj:GetHP())
obj:TakeDamage(10)
print(obj:GetHP())
--私有不能访问
--obj:setHP(200)  私有,因为t10中设置元表是新建的表(其中并无setHP)

print(obj.Type)
obj.Type = "Monster A"--禁止执行
print(obj.Type)
print(obj.z)
obj.z = "zzzz"
print(obj.z)

image.png

13.介绍lua闭包

--闭包 --不管 定义 本质 怎么实现
--counter定义的i是内部匿名函数的upvalue
function counter()
    local i = 0

    return function()
        i = i + 1
        return i
    end
end
--执行时c1 = 匿名函数function() i=i+1 return i end
local c1 = counter()
print(c1())
print(c1())
print("--------------")
local c2 = counter()
print(c2())
print(c1())
--c1 c2互不影响,可以理解为闭包将函数的环境保存了且相互独立
--lua所有写的函数本质都是闭包
--不仅包含了函数的定义还包含了函数的运行环境(upvalue)

--c1,c2是两个实例
--c1,c2是建立在同一个函数,同一个局部变量的不同实例上面的两个不同的闭包
--闭包中的upvalue各自独立,调用一次counter()就会产生一个新的闭包

image.png
闭包:通过调用含有一个内部函数加上该外部函数持有的外部局部变量(upvalue)的外部函数(就是工厂)产生的一个实例函数
闭包组成:外部函数+外部函数创建的upvalue+内部函数(闭包函数)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值