Lua数据类型

Lua内部采用一种通用的基础数据结构来表示所有数据类型,Lua语言及其精简,只有字符串和表两种最基本的数据结构。然后,精简并不表示简陋,在基础数据结构的实现中,处处可以看到设计者为性能和可扩展性所作出的努力。

值与类型

Lua是一门动态类型的脚本语言,也就是说,同一个变量可以在不同时刻指向不同类型的数据。这意味着变量是没有类型的,只有值才具有类型。语言中不存在类型定义,而所有的值本身携带它们自己的类型信息。

Lua中所有的值都是一致的,这意味着所有的值都可以被放在变量中,当作为参数传递到另一个函数中,并被函数作为结果返回。

Lua中使用通用的数据结构lua_TValue来统一表示所有在Lua虚拟机中需要保存的数据类型。

C语言中实现通用数据结构

使用一个通用的数据结构来标识不同的数据类型

  • 需要一个用来存储数据类型的字段
  • 需要一个存储不同数据类型的数据

定义一个 公共的数据结构作为基础类型,存储表达这些数据的基础信息,其他类型则从此处派生,即面向对象思想。

/**
 * 定义公共的数据结构作为基础类型
 * 存储表达数据的基础信息
 * 其他具体类型从此派生
 * */
struct base{
    int type;
};
struct string{
    struct base info;
    int len;
    char *data[0];
};
struct number{
    struct base info;
    double num;
};

另一种做法是使用联合union将所有数据包进来

struct string{
    int len;
    char *data[0];
};
struct number{
    double num;
};
struct value{
    int type;
    union {
        string str;
        number num;
    } value;
};

Lua通用数据结构实现

4933701-8874e098c6300695.png
Lua通用数据结构组织

Lua通用数据结构

在Lua语言中,基本数据类型按照其值变化与否,又分为常量和变量。程序中,其值不发生改变的量成为常量,其值可变的量称为变量。

-- 将整数常量10赋值为a变量
a = 10
-- 将浮点数常量2.5赋值给a变量
a = 2.5
-- 将字符串常量"hello"赋值给a变量
a = "hello"

Lua中的变量根据作用域,分为全局变量和局部变量,局部变量由关键字local定义的。

function fn()
  local i = 1 -- 局部变量,仅在函数num()内有效
  j = 2
end
  • Lua是动态类型的脚本语言,无需类型定义。
  • Lua函数type()可获得给定变量或值得类型

Lua中变量没有预定义类型,任何变量都可以包含任意类型的值。

$ lua
> print(type(x))
nil
> x = 10
> print(type(x))
number
> x = "hello"
> print(type(x))
string
> x = print
> print(type(x))
function
> print(type(type(y))
string
  • Lua中有8种基本类型 nilbooleannumberstringuserdatafunctionthreadtable

Lua是一种动态类型的语言,在语言中没有类型定义的语法,每个值都“携带”了自身的类型信息。

  1. number 数值型:所有数字包括十六进制和科学计数法
  2. boolean 逻辑型:相当于其他语言中的布尔型,只有真假两个值,代码中以truefalse表示。
  3. string 字符串型:代码中单引号和双引号包裹的字符序列
  4. function 函数型:代理中实现某种功能的语句块
  5. table 表:Lua核心数据类型之一,类似于哈希表。
  6. userdata 自定义型:脚本用户只能使用,而不能对其进行定义。
  7. thread 线程:线程类型的值是一个可用于异步计算的协同程序。(轻量有限线程)
  8. nil 空类型:空,什么也没有。
$ lua

> print(type(print))
function

> print(type('hello'))
string

> print(type(100))
number

>print(type(1.23))
number

> print(type(nil))
nil

> print(type(true))
boolean

Nil

Lua中特殊的类型Nil仅有一个值即nil,一个全局变量尚未赋值前默认值均为nil,为全局变量赋值为nil即可删除删除该变量。nil是一种类型,Lua将nil用于表示无效值。变量在第一次赋值前的默认值是nil,将nil赋予给一个变量等于删除了它。

  • nil是一种类型,它只有一个值nilnil的主要功能是用于区别其他任何值。
  • 一个全局变量在第一次赋值前的默认值为nil,将nil赋予一个全局变量等同于删除它。
  • Lua将nil用于表示一种“无效值(non-value)”的情况,即没有任何有效值的情况。

Boolean

布尔类型可选值为truefalse,Lua中的nilfalse表示假,其他值均为真。C或Perl程序员或许对此会感到惊讶。

  • boolean布尔类型拥有两个可选值:falsetrue,与传统布尔值一样。
  • boolean不是一个条件值的唯一表示方式,在Lua中任何值都可以表示一个条件。
  • Lua将falsenil视为假,而将除此之外的其他值视为真。
  • Lua中数字0和空字符串也都视为真。

Number

Number类型用于表示实数,和C/C++中的double类型很类似,可使用math.floormath.ceil进行取整操作。Lua的number类型就是用双精度浮点数来实现的。

  • Lua中number数字类型用于表示实数
  • Lua中没有整数类型,因为没有必要。

一直以来都存在一个关于浮点数算术错误的误解,有人担心即是对浮点数进行简单的递增运算都有可能导致错误的结果。而事实上,只要使用双精度来表示一个整数,就不会出现“四舍五入(Rounding)”的错误。因此,Lua中的数字可表示任何32位整数,而不会产生四舍五入的错误。此外,当今大多数CPU的浮点数运算速度和整数一样快,而有的CPU的浮点数运算可能还更快一些。

Strings

C语言并不像C++那样,自带处理字符串类型的库,这导致有非常多的C语言库都自己实现了一个处理字符串的类型和先关的API。一般来说,要表示一个字符串,核心在于:

  • 字符串长度
  • 指向存放字符串内存数据的指针

在Lua实现中,Lua字符串一般都会经历一个内化internalization的过程,即字符串实际上是被内化(internalization)的一种数据。简单来说,每个存放Lua字符串的变量,实际上存放的并不是一份字符串的数据副本,而是这份字符串的引用。即两个完全一样的Lua字符串在Lua虚拟机中只会存储一份。在这种理念下,每当新建一个字符串时,首先都会去检查当前系统中是否已经有一份相同的字符串数据,如果存在则直接复用,并将引用指向已经存在的字符串数据,否则就重新创建出一份新的字符串数据。

因此,字符串在Lua中是一个不可变的数据,改变一个字符串变量的数据并不会影响原来字符串的数据。每个Lua字符串在创建时都会插入到Lua虚拟机内部的一个全局的哈希表中。这意味着:

  1. 创建相同的Lua字符串并不会引入新的动态内存分配,所有相对便宜,但仍有全局哈希表查询的开销。
  2. 内容相同的Lua字符串不会占用多份存储空间
  3. 已经创建好的Lua字符串之间进行相等性比较时是O(1)时间度的开销,而不是O(n)

为了实现内化(internalizatioin),在Lua虚拟机中必然要有一个全局的位置存放当前系统中的所有字符串,以便在新创建字符串时,先到这里来查找是否已经存在同样的字符串。Lua虚拟机使用一个散列桶来管理字符串。

Lua在字符串实现上使用内化(internalization)这种方案的优点在于:进行字符串数据的比较和查找操作时,性能会提升不少,因为这两个操作的核心都是字符串的比较。传统的字符串比较算法是根据字符串长度诸位来进行对比,这个时间复杂度和字符串长度线性相关。而内化(internalization)之后,在已知字符串散列值的情况下,只需要一次整数的比较即可。这个实现还有另一大好处,那就是空间优化。多份相同的字符串在整个系统值只存在一份副本。

当然,这种实现并不是完全没有缺陷的,创建新字符串时首先会检查系统中是否有相同的数据,只有当不存在的情况下才创建,这与直接创建字符串相比,多了一次查找过程。好在Lua实现中,查找字符串的操作消耗并不算大。

小结一下

  • 在Lua虚拟机中存在一个全局的数据区,用来存放当前系统中所有字符串。
  • 同一个字符串数据,在Lua虚拟机中可能有一份副本,一个字符串一旦创建,将是不可变更的。
  • 变量存放的仅仅是字符串的引用,而不是实际内容。

Lua字符串特征

  • Lua中的字符串表示“一个字符序列”
  • Lua完全采用8位编码,Lua字符串中的字符可具有任何数值编码,包括数值0。
  • Lua中可将任意二进制数据存储到一个字符串中
  • Lua的字符串是不可变的值(immutable values)
$ lua
> str = "hello world"
> s = string.gsub(str, "hello", "hi")
> print(str, s)
hello world    hi world
  • Lua中不能像在C语言中那样直接修改字符串的某个字符,而是应该根据修改要求来创建一个新的字符串。
  • Lua中的String指的是字符的序列,Lua是8位字节,所以字符串可包含任何数值的字符,包括嵌入的0.
  • Lua中可存储任意的二进制数据在一个字符串中。
  • Lua中字符串是不可修改的
  • Lua可使用单引号或双引号标识字符串

Lua中字符串的转义

\a bell
\b back space 后退
\f form feed  换页
\n newline 换行
\r carriage return 回车
\t horizontal tab 制表
\v vertical tab 
\\ backslash
\" double quote 双引号
\' single quote 单引号
\[ left square bracket 左中括号
\] right square bracket 右中括号

[[...]] 可表示多行字符串,可嵌套但不会转义。

类型转换

Lua会自动在Strings和Numbers之间自动进行类型转换

$ lua

> print("10"+1)
11.0

> print("10" == 10)
false

> print("hello"+1)
stdin:1: attempt to perform artithmetic on a string value

字符串连接
.. 是Lua中的字符串连接符,当在一个数字后使用..,必须加上空格以防止解释出错。

$ lua 
> print(10 .. 20)
1020

若须显式的将string转换成数字,可使用 tonumber() 函数,若string不是正确的数字函数返回nil。反之,tostring()可将数字转换为字符串。

-- file tonumber.lua
line = io.read() -- read a line
n = tonumber(line) -- try to convert it to a number

if n==nil then
  error(line .. ' is not a valid number')
else
  print(n*2)
end

Lua字符串和其他对象一样,都是自动内存管理机制所管理的对象。这意味着无需担心字符串的分配和是释放,Lua会处理这些事情。

一个字符串可小到仅包含一个字母,也可以达到包含整本书。Lua能够高效地处理长字符,Lua层序中操作100K或1M的字符串是很常见的。

字符串字面量(literal string)需使用一对匹配的单引号或双引用界定。

str = "hello"
s = "hi"

根据编程风格,应坚持在程序中使用相同类型的引号。除非字符串本身包含引号,那么可使用另一种引号来节点字符串字面量。或使用反斜杠对引号进行转义。

Lua字符串可包含类似C语言中的转义序列

  • \a 响铃
  • \b 退格(back space)
  • \f 提供表格(form feed)
  • \n 换行
  • \r 回车
  • \t 水平TAB
  • \v 垂直TAB
  • \\ 反斜杠
  • \" 双引号
  • \‘ 单引号
$ lua
> str = "one line\nnext linen\" in quotes\", 'in quotes'"
> print(str)
one line
next linen"in quotes", 'in quotes'

> str = "a backslash inside quotes:\'\\\'"
> print(str)
a backslash inside quotes:'\'

> str = "a simple way:'\\'"
> print(str)
a simple way:'\'

可通过数值来指定字符串中的字符,数值以转义字符\<ddd>给出,<ddd>是一个至多3个十进制数字组成的序列。

str1 = "lua\n123"
str2 = "lu\97"
print(str1, str2)

可用一对匹配的[[]]来界定一个字母字符串,就像写块注释。以这种形式书写的字符串可延伸多行,Lua不会解释其中的转义字符。

html = [[
<html>
    <head>
        <title>page title</title>
    </head>
    <body>
        <a href="http://www.lua.org">lua</a>
    </body>
</html>
]]

如果字符串的第一个字符是换行字符(new line),Lua会忽略它。这种写法对于那种含有程序代码的字符串尤为有用。

若字符串中可能包含a=b[c[i]],或者可能需要包含已经被注释掉的代码。为应对这种情况,需要在两个左方括号间加上任意数量的等号,类似[===]

Lua提供了运行时的数字与字符串的自动转换,在一个字符串上应用算术操作时,Lua会尝试将这个字符串转换成一个数字。

print("10"+1) -- 11
print("10+1") -- 10+1

Lua不仅在算术操作中会使用强制转换,还会在其他任何需要数字的地方这么做。相反,在Lua期望一个字符串但却达到一个数字时,它也会将数字转换成字符串。

print(10 .. 20) -- 1020
--[[
Lua中`..`是字符串连接操作符
直接在数字后输入它时,必须要用一个空格分隔,否则Lua会将第一个点认为是一个小数点。
--]]

Lua中可在字符串前放置#操作符来获取字符串的长度。

str = "hello"
print(#str) -- 5

Table

使用表来统一表示Lua中的一切数据,是Lua区分其他语言的一个特色,这个特色从最开始的Lua版本保持至今,很大的原因是为了在设计上保持简介。Lua表分为数组和散列表两部分,其中数组部分不想其他语言那样,从0开始作为第一个索引,而是从1开始。散列表部分可以存储任何其他不能存放在数组部分的是数据,唯一的要求是键值不能为nil。尽管内部实现上区分了这2个部分,但对使用者而言却是透明的。使用Lua表,可以 模拟出其他各种数据类型 -- 数组、链表、树等。虽然设计上简介,并对使用者更加透明友好,但若使用不当,还是会造成效率性能上的差异。

Table类型实现了一种抽象的“关联数组”,关联数组是一种具有特殊索引方式的数组,索引通常是字符串string或数字number类型,但也可以是除nil之外的任意类型的值。

在内部实现上,Table通常实现为一个哈希表、一个数组、或两者的混合。具体的实现为何种方式,动态依赖于具体的Table的键分布特点。

table可以以不同类型的值构成,可以包含所有类型的值(除nil外),table是Lua中唯一的一种数据结构,可以用来描述原始的数组、符号表、集合、记录、图、树...。用来表述记录时,Lua使用域名作为索引。

根索引一样,table每个域中的值也可以是任何类型(除了nil)。特别的是,因为函数本身也是值,所以table的域中也可以存放函数。

Functions

Lua中函数是第一类值(一等公民),函数是一种数据类型,意味着函数可存储在变量中,可作为函数的参数,可作为函数的返回值。具名函数的定义本质上是匿名函数对变量的赋值。

function fn()
end
-- 等价于
fn = function()
end

Lua可调用Lua或C实现的函数,Lua中所有标准库都是C实现的,包括String库、Table库、IO库、OS库、Math库、debug库...

Userdata

userdata类型可用来将任意C语言数据保存在Lua变量中,userdata类型相当于一块原生的内存,除了赋值和相同性质判断外,Lua没有为它预定义任何操作。然后,可通过使用metatable元表为userdata自定义彝族操作。

userdata不能在Lua中创建出来,也不能在Lua中修改,只能通过C语言API进行操作。这一点保证了诉诸程序完全掌握其中的数据。

  • Lua中userdata可将 C数据存在在Lua变量中
  • userdata中除了赋值和相等比较外没有预定义的操作。
  • userdata用于描述应用程序或使用C实现的库创建的新类型
str = "hello world"
newstr = string.gsub(str, "hello", "hi")
print(newstr) -- hi world
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值