掌握Lua的基本数据类型:Lua语言必备基础

前言

Lua是一门动态类型的脚本语言,这意味着同一个变量可以在不同时刻指向不同类型的数据。

Lua代码中 一般采用一下两种做法相结合的方式表示它的数据类型:

  1. 向对象的思路。定义一个公共的数据结构作为基础类型,里面存储的都是表达这个数据的基础信息,其他具体的类型是从这里派生出来的。
  2. 使用联舍( union )来将所有数据包进来。

Lua与宿主程序之间的关系:可以嵌入到宿主程序,并为宿主程序提供脚本能力,同时可以帮助拓展宿主程序。另外Lua也提供了一些工具帮助编译Lua文本(luac),执行lua脚本(lua)

在这里插入图片描述

以下介绍时都是基于lua 5.3的源码。

一、Lua基本数据类型

Lua总共有9个基本数据类型,分别是:boolean(布尔) , number(数值) , string (字符串), nil(空) , function , table (表),userdata , lightuserdata , thread(线程)。

(lua.h)

/*
** basic types
*/
#define LUA_TNONE               (-1)

#define LUA_TNIL                0
#define LUA_TBOOLEAN            1
#define LUA_TLIGHTUSERDATA      2
#define LUA_TNUMBER             3
#define LUA_TSTRING             4
#define LUA_TTABLE              5
#define LUA_TFUNCTION           6
#define LUA_TUSERDATA           7
#define LUA_TTHREAD             8

#define LUA_NUMTAGS             9

> type(nil)
nil
> type(true)
boolean
> type(6.66)
number
> type("hello")
string
> type(io.stdin)
userdata
> type(print)
function
> type(type)
function
> type({})
table

类型数据结构
LUA_TNONE无类型
LUA_TNIL空类型
LUA_TBOOLEAN布尔类型
LUA_TLIGHTUSERDATA指针void *
LUA_TNUMBER数字lua_Number
LUA_TSTRING符串TString
LUA_TTABLETable
LUA_TFUNCTION函数CClosure、LClosure
LUA_TUSERDATA指针void *
LUA_TIHREADLua虚拟机、协程lua_State

其中 LUA_TLIGHTUSERDATA和LUA_TUSERDATA一样 ,区别在于前者的分配释放由 Lua外部的使用者来完成,而后者则是通过Lua 内部来完成的。

userdata 类型允许把任意的 语言数据保存在 Lua 语言变量中 Lua 语言中,用户数据类型除了赋值和相等性测试外,没有其他预定义的操作 用户数据被用来表示由应用或语言编写的库所创建的新类型。

1.1、nil

nil 是一种只有一个 ni 值的类型,它的主要作用就是与其他所有值进行区分。Lua使用 nil 来表示无效值的情况 ,一个全局变量在第一次被赋值前的默认值就是nil, 而将 nil 赋值给全局变量则相当于将其删除。

1.2、boolean

Boolean 型具有两个值: true和false ,它们分别代表了传统布尔值。不过,Lua语言中条件测试(例如控制结构中的分支语句)将除 Boolean的false和nil外的所有其他值视为真特别的是,在条件检测中 Lua 语言把零和空字符串也都视为真。

Lua 语言支持常见的逻辑运算符: and、or、not。和条件测试一样,所有的逻辑运算将Boolean 类型的 false nil 当作假,而把其他值当作真。
逻辑运算符 and 的运算结果为:如果它的第一个操作数为“false”,则返回第一个操作数,否则返回第二个操作数。
逻辑运算 or 的运算结果为:如果它的第一个操作数不为“false”,则返回第一个操作数,否 返回第二个操作数。

Lua 5.3.6  Copyright (C) 1994-2020 Lua.org, PUC-Rio
> 1 and 2
2
> nil and 10
nil
> false and 4
false
> 0 or 5
0
> false or "hello"
hello
> nil or false
false

and or 都遵循短路求值( Short-circuit 巳valu ion )原则,即只在必要时才对第二个操作数进行求值。
not 运算符永远返回 Boolean 类型的值。

> not nil
true
> not false
true
> not 0
false
> not not 1
true
> not not nil
false

1.3、number

Lua 语言为数值格式提供了两种选择 被称为 int 64 型和被称为float 的双精度浮点型(注意,lua中“float”不代表单精度类型)。整型的 人是 Lu 5.3 要标志,也是与之前版本相 的主要区。

> type(3)
number
> type(3.3)
number
> type(3.333333333333333333333333333333333333333)
number
> type(-3)
number
> type(0.2e3)
number
> type(0x1p-1)
number

具有十进制小数或者指数的数值会被当作浮点型值,否则会被当作整型值,还可以使用科学记数法书写数值常量。

由于整型值和浮点型值 类型都是 number ,所以它 是可以相互转换的。同时 ,具有相算术值的整型值 和点型值在lua语言中是相等的。

> 1==1.0
true
> -3==-3.0
true
> 0.2e3==200
true

在少数情况 ,当需要区分整型值和浮点型值 ,可以使用函数 math.type:

> math.type(3)
integer
> math.type(3.0)
float

Lua 语言像其 语言一样 支持 0x开头的十六进制常量。

> 0x1a3
419
> 0xff
255

除了加、戚、乘、除、取负数(单目减法,即把减号当作一元运算符使用)等常见的术运算外, lua 语言还支持取整除法( floor 除法)、取模和指数运算。
Lua 语言同样支持幕运算,使用符号^表示。

> 1+2
3
> 1-2
-1
> 1*2
2
> 1/2
0.5
> 3 // 2
1
> 12 %3
0
> 2^12
4096.0

1.4、string

字符串用于表示文本。Lua 语言中的字符串是一串字节组成的序列, Lua 核心并不关心这些字节究竟以何种方式编码文本 Lua 中,字符使用 个比特位来存储。Lua 中字符串可以存储包括空字符在内的所有数值代码,这意味着可以在字符串中存储任意的
二进制数据。Lua 的字符串标准库默认处理 个比特位( 1 Byte )的字符,但是也同样可以非常优雅地处理 UTF-8 字符串。

Lua 语言中的字符串是不可变值,不能像在c语言中那样直接改变某个字符串中的某个字符,但是可以通过创建一个新字符串 的方式来达到修改的目的,例如:

> a="one two"
> b=string.gsub(a,"one","none")
> print(a)
one two
> print(b)
none two
> 

Lua 语言中的字符串 是自动内存管理的对象之一 ,这意味着 Lua 语言会负责字符串的分配和释放,开发人员无须关注。可以使用长度操作符( # )获取字符串的长度(返回字符串占用的字节数,在某些编码中,这个值可能与字符串中字符的个数不同)。

> print(#a)
7
> print(#"hello")
5

可以使用连接操作符 . .(两个点)来进行字符串连接 如果操作数中存在数值,那lua 语言会先把数值转换成字符串。

> "hello" .. "world"
helloworld
> "value is " .. 9
value is 9

在 lua 语言中,字符串是不可变量,字符串连接总是创建一个新字符串,不会改变原来作为操作数的字符串:

> a="hello "
> a .. "world"
hello world
> print(a)
hello 

可以使用一对双引号或单引号来声明字符串常量。使用双引号和单引号声明字符串是等价的 它们两者唯一的区别在于,使用双引号声明的字符串中出现单引号时,单引号可以不用转义;使用单引号声明的字符串中出现双引号时,双引号可以不用转义。
Lua 语言中的字符串支持下列 语言风格的转义字符:

字符含义
\a响铃( bell)
\b退格( back space )
\f换页( for feed)
\n换行( newline)
\r回车( riage return )
\t水平制表符( horizontal tab )
\v垂直制表符( vertical tab )
\\反斜杠( backslash )
\”双引号( double quote )
\’单引号( single quote )

此外,在字符串 中,还可以通过转义序列 \ddd 和\xhh 来声明字符。

像长注释 多行注释一样 ,可以使用一对双方括号来声明长字符串 多行字符串常量。方括号括起来的内容可以包括很多行,并且内容中的转义序列不会被转义 此外,如果多行字符串中的第一个字符是换行符,那么这个换行符会被忽略 多行字符串在声明包含大段代码的字符串时非常方便,例如:

html=[[
<html>
	<head>
		<title> HTML PAGE</title>
	</head>
	<body>
		<a href="http://www.baidu.com">baidu</a>
	</body>
</html>
]]

Lua 语言在运行时提供了数值与字符串之间的自动转换,针对字符串的所有算术操作会尝试将字符串转换为数值 Lua 语言不仅仅在算术操作时进行这种强制类型转换,还会在任何需要数值的情况下进行。相反,当 Lua 语言发现在 要字符串的地方出现了数值时,它就会把数值转换为字符串。

1.5、function

Lua 语言中,函数( Function )是对语句和表达式进行抽象的主要方式,函数既可以用于完成某种特定任务,也可以只是进行一些计算然后返回计算结果。
Lua 既可以 Lua 语言编写 函数, 也可以调c语言编写的函数。

function add(a)
	local sum=0
	for i=1,#a do
		sum=sum+a[i]
	end
	return sum;
end

Lua 语言中一种与众不同但又非常有用的特性是允许一个函数返回多个结果。

function maximum (a) 
	local mi = 1
	local m = a[mi]
	fo r i = 1, #a do 
		if a[i] > m then 
			mi = i; m = a[i] 
		end 
	end 
	return m, mi --返回最大值及其索引
end

Lua 语言中的函数可以是可变长参数函数,即可以支持数量可变的参数。

function add ( ... ) 
	local s = 0 
	for _, v in ipai { ... } do 
		s = s + v 
	end 
	return s
end

1.6、table

表( Table )是 lua 语言中最主要和强大的数据结构 使用表, Lua语言可以以一种简单、统一且高效的方式表示数组、集合、记录和其他很多数据结构。Lua语言也使用表来表示包( package )和其他对象。
Lua 言中的表本质上是一种辅助数组,这种数组不仅可以使用数值作为索引,由可以使用字符串或其他任意类型的值作为索引( nil 除外)。

Lua 言中的表要么是值要么是变量 ,它们都是对象。使用构造器表达式创建表,其最简单的形式是 {}:

> a={}
> k="x"
> a[k]=11
> a[20]="great"
> a["x"]
11
> k=20
> a[k]
great
> a["x"]=a["x"]+11
> a["x"]
22

表永远是匿名的,表本身和保存表的变量之间没有固定的关系。对于一个表而言,当程序中不再有指向它的引用时,垃圾收集器会最终删除这个表并重用其占用的内存。

> a = {} 
> a ["x"] = 10 
> b = a 
> b["x"]
10 
> b["x"] = 20 
> a["x"]
20
> a = nil 
> b = nil

同一个表中存储的值可以具有不同的 索引, 并可以按需增长以容纳新的元素。未经初始化的表元素为 nil ,将 nil 值给
表元素可以将其删除。

当把表当作结构体使用时, 可以把索引当作成员名称使用( a. name 等价于 a[“name”]。

表构造器( Table Constructor )是用来创建和初始化表的表达式,是 Lua 语言中独有
是最有用、最灵活的机制之一。

> days ={"Sunday","Monday","Tuesday","Wednesday"}
> print(days[2])
Tuesday

lua 提供了一种初始化记录式表的特殊语法:

a = {x = 10, y= 20}
-- 等价于
a = {} ; a.x = 10; a.y = 20

无论使用哪种方式创建表,都可以随时增加或删除表元素:

w = {x = 0, y = 0, label ="hello world"}
x = {math.sin(0), math.sin (1) , math.sin(2)}
w[1] ="new" -- 把键1 增加到w表中
w.x = nil	-- 删除字段‘x’

如果想表示常见的数组或列表,那么只需要使用整型作为索引的表即可。同时,也不需要预先声明表的大小,只需要直接初始化我们需要的元素即可:

a={}
for i=1,10 do
	a[i]=io.read()
end

1.7、userdata

userdata 完全用户数据;指向一块内存的指针,通过为userdata 设置元表,lua 层可以使用该 userdata 提供的功能。userdata 为 lua 补充了数据结构,解决了 lua 数据结构单一的问题。可以在 c 中实现复杂的数据结构,生成库继而导出给 lua 使用。
注意:userdata 指向的内存需要由 lua 创建,同时 userdata 的销毁也交由 lua gc 来自动回收。

1.8、lightuserdata

lightuserdata是轻量用户数据。也是指向一块内存的指针,但是该内存由 c 创建,同时它的销毁也由 c 来完成。不能为它创建元表,轻量用户数据只有类型元表;通常用于 lua 想使用 c 的结构,但是不能让 lua 来释放的结构;在游戏客户端中用的比较多。

1.9、thread

线程(本质上是协程)。lua 中的协程和虚拟机都是 thread 类型。

二、Lua 通用数据结构的实现

Lua 内部用一个宏表示哪些数据类型需要进行GC ( Garbage Collection ,垃圾回收)操作:

(lobject.h)

/* Bit mark for collectable types */
#define BIT_ISCOLLECTABLE       (1 << 6)

/* raw type tag of a TValue */
#define rttype(o)       ((o)->tt_)

#define iscollectable(o)        (rttype(o) & BIT_ISCOLLECTABLE)

LUA_TSTRING (包括LUA_TSTRING )之后的数据类型都需要进行GC操作。这些需要进行GC操作的数据类型都会有一个Common Header宏定义的成员,并且这个成员在结构体定义的最开始部分。

(lobject.h)

/*
** Header for string value; string bytes follow the end of this structure
** (aligned according to 'UTString'; see next).
*/
typedef struct TString {
  CommonHeader;
  lu_byte extra;  /* reserved words for short strings; "has hash" for longs */
  lu_byte shrlen;  /* length for short strings */
  unsigned int hash;
  union {
    size_t lnglen;  /* length for long strings */
    struct TString *hnext;  /* linked list for hash table */
  } u;
} TString;

(lobject.h)

/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
*/
#define CommonHeader    GCObject *next; lu_byte tt; lu_byte marked

CommonHeader里面的几个成员定义如下:

  1. next :指向下 GC链表的成员。
  2. tt:表示数据的类型,即前面的那些表示数据类型的宏。
  3. marked GC相关的标记位。

同时,还有 个名为GCheader 的结构体,其中的成员只有Common eader:

(lobject.h)

/*
** Common type has only the common header
*/
struct GCObject {
  CommonHeader;
};

在Lua 中就使用了GCUnion 联合体将所有需要进行垃圾回收的数据类型囊括了进来:

(lstate.h)

/*
** Union of all collectable objects (only for conversions)
*/
union GCUnion {
  GCObject gc;  /* common header */
  struct TString ts;
  struct Udata u;
  union Closure cl;
  struct Table h;
  struct Proto p;
  struct lua_State th;  /* thread */
};

仅表示需要进行垃圾回收的数据类型还不够,还有几种数据类型是不需要进行垃圾回收的,中将GCObject 和它们 起放在了联合体Value 中:

(lobject.h)

/*
** Union of all Lua values
*/
typedef union Value {
  GCObject *gc;    /* collectable objects */
  void *p;         /* light userdata */
  int b;           /* booleans */
  lua_CFunction f; /* light C functions */
  lua_Integer i;   /* integer numbers */
  lua_Number n;    /* float numbers */
} Value;

为了清楚数据到底是什么类型的,lua代码中又有了TValuefields ,它用于将Value 类型结合在一起:
(lobject.h)

#define TValuefields    Value value_; int tt_

最后形成了Lua中的TValue结构体, Lua 中的任何数据都可以通过该结构体表示:
(lobject.h)

typedef struct lua_TValue {
  TValuefields;
} TValue;

Lua通用数据结构的组织:
lua_Tvalue

TValuefields
Value value_
int tt_
Value
GCObject *gc
void *p
lua Number n
int b
GCObject
CommonHeader
CommonHeader
GCObject *next;
lu_byte tt
lu_byte marked
GCUnion
GCObject gc
struct TString ts
struct Udata u
union Closure cl
struct Table h
struct Proto p
struct lua_State th
  1. 具体类型中有Common Header 用来存放所有数据类型都通用的字段。
  2. TValue 作为统一表示所有数据的数据结构,内部使用了联合体Value 将所有数据都包起来。

在具体的代码中, TValue用于统一地表示数据,而一旦知道了具体的类型,就需要使用具体的类型了 因此,代码中有不少涉及TValue与具体类型之间转换的代码,其主要逻辑都是将TValue中的tt value 具体类型的数据进行转换。
(lobject.h)

/* Macros to set values */
#define settt_(o,t)     ((o)->tt_=(t))

#define setfltvalue(obj,x) \
  { TValue *io=(obj); val_(io).n=(x); settt_(io, LUA_TNUMFLT); }

#define chgfltvalue(obj,x) \
  { TValue *io=(obj); lua_assert(ttisfloat(io)); val_(io).n=(x); }

#define setivalue(obj,x) \
  { TValue *io=(obj); val_(io).i=(x); settt_(io, LUA_TNUMINT); }

#define chgivalue(obj,x) \
  { TValue *io=(obj); lua_assert(ttisinteger(io)); val_(io).i=(x); }

#define setnilvalue(obj) settt_(obj, LUA_TNIL)

#define setfvalue(obj,x) \
  { TValue *io=(obj); val_(io).f=(x); settt_(io, LUA_TLCF); }

#define setpvalue(obj,x) \
  { TValue *io=(obj); val_(io).p=(x); settt_(io, LUA_TLIGHTUSERDATA); }

#define setbvalue(obj,x) \
  { TValue *io=(obj); val_(io).b=(x); settt_(io, LUA_TBOOLEAN); }

#define setgcovalue(L,obj,x) \
  { TValue *io = (obj); GCObject *i_g=(x); \
    val_(io).gc = i_g; settt_(io, ctb(i_g->tt)); }

#define setsvalue(L,obj,x) \
  { TValue *io = (obj); TString *x_ = (x); \
    val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); \
    checkliveness(L,io); }

#define setuvalue(L,obj,x) \
  { TValue *io = (obj); Udata *x_ = (x); \
    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TUSERDATA)); \
    checkliveness(L,io); }

#define setthvalue(L,obj,x) \
  { TValue *io = (obj); lua_State *x_ = (x); \
    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTHREAD)); \
    checkliveness(L,io); }

#define setclLvalue(L,obj,x) \
  { TValue *io = (obj); LClosure *x_ = (x); \
    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TLCL)); \
    checkliveness(L,io); }

#define setclCvalue(L,obj,x) \
  { TValue *io = (obj); CClosure *x_ = (x); \
    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TCCL)); \
    checkliveness(L,io); }

#define sethvalue(L,obj,x) \
  { TValue *io = (obj); Table *x_ = (x); \
    val_(io).gc = obj2gco(x_); settt_(io, ctb(LUA_TTABLE)); \
    checkliveness(L,io); }

#define setdeadvalue(obj)       settt_(obj, LUA_TDEADKEY)



#define setobj(L,obj1,obj2) \
        { TValue *io1=(obj1); *io1 = *(obj2); \
          (void)L; checkliveness(L,io1); }

总结

  1. 任何需要进行垃圾回收处理的 数据类型,必然以CommonHeader作为该结构体定义的最开始部分 。就像C++类的实现,CommonHeader是一个基类的所有成员,而其他需要回收处理的数据类型均从这个基类继承下来。
  2. GCUnion 联合体,将所有需要进行垃圾回收的数据类型全部囊括其中,这样定位和查找不同类型的数据时就方便多了。
  3. table 表是lua 中唯一的数据结构;既可以表示 hashtable 也可表示为 array;配合元表可以定制表复杂的功能(如实现面对对象编程中的类以及相应继承的功能)。

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion Long

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值