03 Lua 数据类型

陈皮的JavaLib 发表于 2022/04/18 22:54:04 2022/04/18
【摘要】 Lua 体积小、启动速度快,一个完整的 Lua 解释器不过200k,在所有脚本引擎中,Lua 的速度可以说是最快的。所以 Lua 是作为嵌入式脚本的最佳选择。

我是陈皮,一个在互联网 Coding 的 ITer,个人微信公众号「陈皮的JavaLib」关注第一时间阅读最新技术文章。

1 数据类型

  • 字符串(string):双引号或者单引号括起来的字符。
  • 数值类型(number):所有数字,包括十进制,十六进制以及科学计数法等。
  • 布尔类型(boolean):只有真和假两个值,即 true 和 false。
  • 函数(function):可以实现某种功能的代码块。
  • 表(table):lua 语言的核心之一,类似哈希表。
  • 自定义类型(userdata):脚本用户只能使用,不能对其进行定义。
  • 线程(thread):线程类型的值是一个可用于异步计算的协同程序(轻量级有限线程)。
  • 空类型(nil):空的意思,代表什么都没有。

在 Lua 中,可以使用type函数来求出类型。

print(type("chenpi")) -- string
print(type(123)) -- number
print(type(true)) -- boolean
print(type(type)) -- function
print(type({})) -- table

2 字符串(string)

双引号和单引号括起来的字符串使用无差别,都可以对转义字符进行转义。

> print('单引号括起来的字符串\n\r这是换行后内存!')
单引号括起来的字符串
这是换行后内存!
> print("双引号括起来的字符串\n\r这是换行后内存!")
双引号括起来的字符串
这是换行后内存!
> 

字符串还可以使用两个成对的中括号包括字符串,这样存储的是字符串原始值,即使它们是跨行的。

str = [[aa
bb cc
dd
  ee
ff ff ff
]]
print(str)

-- 输出结果如下
aa
bb cc
dd
  ee
ff ff ff

3 数值类型(number)

所有数字,包括十进制,十六进制以及科学计数法等。

i = 10
j = 10.15
k = 0x12
h = 1.20e5

print(i) -- 10
print(j) -- 10.15
print(k) -- 18
print(h) -- 120000.0

4 布尔类型(boolean)

只有真和假两个值,即 true 和 false。

flag = true

if flag then
  print("flag is true")
else
  print("flag is false")
end

-- 输出 flag is true

5 表(table)

表用来存储一些互相关联的元素,每一个元素都有一个关键字和一个值组成,即键值对。表功能类似哈希表。

表中元素的值可以放任何类型的值,例如字符串,数值,表,函数等等。

-- 创建一个空表
t = {}

-- 没有显示指定键的值,系统会自动分配键123...
t = {k1 ="v1", "a", "b", "c"}
print(t[0])      -- nil
print(t[1])      -- a
print(t[2])      -- b
print(t[3])      -- c
print(t[4])      -- nil
print(t["k1"])   -- v1

对表中元素的引用,除了使用表名[键名]之外,还可以使用表名.键名,但是后面这种不可以引用键是数值的元素。

t = {k1 ="a", "b", k2 = "c", "d"}
print(t[1])      -- b
print(t[2])      -- d
print(t["k1"])   -- a
print(t.k1)      -- a
-- print(t.1)    -- 不合法,会报错

t.k1 = "aa"
t[1] = "bb"
print(t[1])      -- bb
print(t.k1)      -- aa

如果想要删除表中的元素,只需要将键对应的值设置为 nil 即可。

t = {k1 ="a", k2 = "b"}
print(t.k1)      -- a
t.k1 = nil
print(t.k1)      -- nil

遍历表中的元素,如下:

t = {k1 ="a", "b", k2 = "c", "d"}

for k, v in pairs(t) do
  print(k, v)
end

-- 输出结果如下
1       b
2       d
k2      c
k1      a

表中还可以存储函数,以下演示将定义一个函数然后赋值给表中的一个键,最后进行调用。

t = {}

t.func = function()
	print("Hello ChenPi!")
end

function t.func()
  print("Hello ChenPi!")
end

--[[
上面和此种方式一样,但是推荐使用上面的方式
function t.func()
  print("Hello ChenPi!")
end
--]]

t.func()  -- Hello ChenPi!

我们可以借助table库,使用它定义的函数来操作表。

-- 插入元素,删除元素
table.insert(表名,,)
table.remove(表名,)


t = {"a", "b", "c"}
table.insert(t, 2, "g")  -- 在第二个位置插入元素,原来键为2的值依次往后移
table.insert(t, "f")     -- 没有指定键,则插入到尾部

-- 输出 agbcf
for i = 1, #t do
    print(t[i])
end


table.remove(t, 2)    -- 删除键为2的元素
table.remove(t)       -- 省略键,则删除最后一个元素

-- 输出 abc
for i = 1, #t do
    print(t[i])
end



-- 排序函数
table.sort(表名, 排序规则)  -- 排序规则可以省略
t = {"d", "a", "e", "c", "h", "g"}
table.sort(t) -- 对值进行排序
-- 输出 acdegh
for i = 1, #t do
    print(t[i])
end


function mysort(a, b)
    return a > b
end
table.sort(t, mysort)
-- 输出 hgedca
for i = 1, #t do
    print(t[i])
end

其实 Lua 中各种函数库(例如 table 库,string 库,math 库等等)就是使用表来存储一系列的函数,所以我们也可以自定义自己的函数库。

-- 定义一个空表
chenpifunc = {}

-- 定义第一个函数
chenpifunc.func1 = function()
	print("Hello ChenPi!")
end

-- 定义第二个函数
chenpifunc.func2 = function(a, b)
	local r = a + b
	return r
end

-- 使用函数库
chenpifunc.func1()
print(chenpifunc.func2(5, 8))

-- 输出结果如下
Hello ChenPi!
13

我们可以借助表来实现数组类型,每一个数组元素底层其实映射着键1,2,3 … 。

arr = {1, 2, "a", "b", 3, true, 10.51}

-- 打印的数数组的地址
print(arr)

-- 遍历数组
for k, v in pairs(arr) do
  print(k, v)
end

-- 打印数组类型
print(type(arr))

-- 输出结果如下
table: 0x2406730
1       1
2       2
3       a
4       b
5       3
6       true
7       10.51
table

5.1 全局表 _G

我们定义的全局变量都存储在全局表_G中。

name = "陈皮"
age = 18
print(_G["name"])  -- 陈皮
print(_G.age)  -- 18

t = {name = "Hello Lua"}

func = function()
  print("Hi func")
end

print(_G["t"]["name"])  -- Hello Lua
_G.func()  -- Hi func

5.2 复制表方式实现面向对象

其实可以通过表来实现面向对象编程,我们知道表可以存储键值对,还可以将一个函数赋值给表中的一个键,所以我们可以将表作为一个类使用。

-- 定义一个表,代表一个类模板
Person = {}
Person.name = "ChenPi"
Person.eat = function(p)
  print(p.name.." is eating")
end

print(Person.name) -- ChenPi
Person.eat(Person) -- ChenPi is eating

我们可以定义一个函数,对上面定义的 Person 表进行克隆它的元素,模拟生成新对象(新表)进行使用。

function clone(t)
  local inst = {}
  for k, v in pairs(t) do
    inst[k] = v
  end
  return inst
end

-- 定义一个表,代表一个类模板
Person = {}
Person.name = "ChenPi"
Person.eat = function(p)
  print(p.name.." is eating")
end

-- 克隆对象
local p = clone(Person)
print(p.name) -- Hi ChenPi
p.eat(p) -- I am eating
p:eat() -- I am eating 等同于调用eat函数,并把自身作为参数传入函数

其实我们可以在表 Person 中定义一个 new 函数,还可以传入参数,从而可以构造指定参数的对象,如下:

function clone(t)
  local inst = {}
  for k, v in pairs(t) do
    inst[k] = v
  end
  return inst
end

-- 定义一个表,代表一个类模板
Person = {}
Person.name = "ChenPi"
Person.eat = function(p)
  print(p.name.." is eating")
end

Person.new = function(name)
  local self = clone(Person)
  self.name = name
  return self
end


-- 克隆对象
local p = Person.new("陈皮")
print(p.name) -- 陈皮
p:eat() -- 皮 is eating

我们还可以模拟类继承关系,如下:

function clone(t)
  local inst = {}
  for k, v in pairs(t) do
    inst[k] = v
  end
  return inst
end

-- 定义一个表,代表一个类模板
Person = {}
Person.name = "ChenPi"
Person.eat = function(p)
  print(p.name.." is eating")
end

Person.new = function(name)
  local self = clone(Person)
  self.name = name
  return self
end

-- 子类定义
Student = {age = 18}
Student.study = function()
  print("Student is studying...")
end
    
Student.new = function(name)
  local self = Person.new(name)
  -- 将子类的元素放入父类中,重复的键还可以达到重写作用
  for k, v in pairs(Student) do
    self[k] = v
  end
  return self
end

-- 创建子类对象
local s = Student.new("陈皮")
print(s.name) -- 陈皮
print(s.age)  -- 18
s:eat() -- 陈皮 is eating
s.study() -- Student is studying...

5.3 函数闭包方式实现面向对象

我们可以定义一个函数闭包,来创建对象(表)使用,如下所示:

function Person(name)
  local self = {}
  self.name = name
  self.eat = function(p)
    print(p.name.." is eating")
  end
  return self
end

local p = Person("陈皮")
p:eat() -- 陈皮 is eating

同样,模拟类继承关系,也是可以使用函数闭包的方式,如下所示:

function Person(name)
  local self = {}
  self.name = name
  self.eat = function(p)
    print(p.name.." is eating")
  end
  return self
end

local p = Person("陈皮")
p:eat() -- 陈皮 is eating

function Student(name)
  local self = Person(name)
  self.study = function()
    print(self.name.." is studying...")
  end
  return self
end

local s = Student("狗蛋")
s:eat()  -- 狗蛋 is eating
s.study()  -- 狗蛋 is studying...

6 函数

函数是 function 类型的对象,函数可以被赋值给一个变量,函数中可以返回一个函数,函数可以比较,函数可以作为 table 表中的值。

-- 函数定义1
function 函数名(参数)
	函数内容
	...
end

-- 例如
function sum( i, j)
  return i + j
end
print(sum(10, 23))  -- 函数调用


-- 函数定义2
函数名 = function(参数)
    函数内容
    ...
end

-- 例如
sum = function(i, j)
  return i + j
end
print(sum(10, 23))  -- 函数调用

函数可以不返回值,返回一个值,返回多个值。return 关键字只能在语句块的结尾一句,即 end 之前,else 之前,或者 until 之前。

function chenpi()
	print("no return")  -- no return
end

chenpi()

function chenpi()
	return "one return"
end

print(chenpi()) -- one return

function chenpi(a, b)
	local x = a + b
	local y = a - b
	return x, y
end

i, j = chenpi(10, 5)
print(i)  -- 15
print(j)  -- 5

如果函数返回值是另一个函数的最后一个参数,或者是多值赋值表达式的最后一个参数,那么函数的所有返回值都能被使用。否则,函数的所有返回值只有第一个返回值才被使用。

function func()
	local x, y, z = 1, 2, 3
	return x, y, z
end

print("Hello", "ChenPi", func())      -- Hello   ChenPi  1       2       3    
print("Hello", func(), "ChenPi")      -- Hello   1       ChenPi

a, b, c, d = 0, func()                -- 0 1 2 3

a, b, c, d = func(), 4                -- 1 4 nil nil

Lua 中,函数还可以定义可变参数。

function 函数名(...)           -- 三个点代表参数个数不确定
function 函数名(a, b, ...)     -- 2个固定参数,其他参数不确定
function 函数名(a, ...)        -- 1个固定参数,其他参数不确定
-- 假设定义如下函数
function chenpi(a, b, ...)
	print(a)
	print(b)
	print(arg[1])
	print(arg[2])
	print("-------")
end
    
chenpi()            -- a=nil,b=nil,arg={}
chenpi(1)           -- a=1,b=nil,arg={}
chenpi(1, 2)        -- a=1,b=2,arg={}
chenpi(1, 2, 3)     -- a=1,b=2,arg={3}
chenpi(1, 2, 3, 4)  -- a=1,b=2,arg={3,4}

-- 输出结果如下
nil
nil
nil
nil
-------
1
nil
nil
nil
-------
1
2
nil
nil
-------
1
2
3
nil
-------
1
2
3
4
-------

函数尾调用,当函数的最后返回值是调用另一个函数时,称为尾调函数。Lua 在调用尾调函数时,会先弹出当前函数的栈空间,然后再调用尾调函数,这样能降低函数层层调用过程中的栈消耗,适用于函数递归调用。

-- 以下函数调用耗时比较久
function func(i)
	while i < 15 do
		i = i + 1
		print(i)
		func(i)
	end
	return i
end

func(0)

-- 改变顺序,使用尾调函数,执行速度很快(而且1000还比15次数大很多)
function func(i)
	if i > 1000 then
		return i
	end
		
	i = i + 1
	print(i)
	func(i)  -- func(i) + 1 不算尾调函数
end

func(0)

本次分享到此结束啦~~

如果觉得文章对你有帮助,点赞、收藏、关注、评论,您的支持就是我创作最大的动力!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。