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_, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 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
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"])
使用pairs遍历,12ab都是没有声明key,lua会给这些元素添加默认值,而添加了非默认键值的键值对右hash表存储,不能保证存储位置是按照赋值顺序连续的
使用ipairs遍历,非默认键值的键值对全部不遍历
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)
--可变参数 -- #符号暂不深究
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))
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})
-- ..字符串连接,原则上 少使用
--[[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)
是取反,&|是二进制的位运算符,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
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.协程
--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)
进程:计算机刚出来,只有单线程…后来有了多任务,一台电脑,一边打开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)
coroutine.warp(func):创建一个协程,并传入一个函数(function),返回的是传入的函数,创建后的协程会进入挂起状态。唤醒该协程只需调用返回的协程函数即可。(该线程无法使用coroutine.status())
fun1 = coroutine.wrap(
function (i)
print("-----")
print(i)
print("-----")
end
)
print(type(fun1))
--warp创建好就怪,fun1调用这个返回的协程即可唤醒该协程
fun1(123)
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
想要实现类需要用到元表.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()
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()
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值赋值,而是给返回的表新增了字段
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)
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()就会产生一个新的闭包
闭包:通过调用含有一个内部函数加上该外部函数持有的外部局部变量(upvalue)的外部函数(就是工厂)产生的一个实例函数
闭包组成:外部函数+外部函数创建的upvalue+内部函数(闭包函数)