lua學習總結

簡述

Lua是一個很小的編程語言,很多人將其與Python高級語言進行比較。
Lua有以下的特點:
(1)純C語言實現,源碼小,可以很好地與C/C++融合。可自行編譯,生成靜態庫。
(2)語法簡單,靈活,易學。

我也同時學習了Python,相比之下,Lua精簡,功能簡單,可用的庫少,但是語言的實現寫得如此精簡也很不錯了!與C/C++程序結合就能顯示它的強大能力,適用於要求可配置性很高的C/C++程序中,比如遊戲。
如果想寫純腳本程序,那就選Python吧,它是強大,庫多,喜歡單幹。

好,我們學習Lua吧!

基礎

數據類型

Lua的常用數據類型有:
(1)Nil (空) <==> C語言中的NULL
(2)number 所有數據,包含整數小數
(3)string 字串類型
(4)boolean 布爾類型 true, false
(5)function 函數類型
(6)table 表類型

其實,Lua中的數據類型遠不止上面的這幾個,比如file.
table在Lua中是最重要的數據類型,Lua的很多思想都是基於表來實現的,比如模塊、對象。
boolean 除Nil與false爲假以爲,其它都爲真。
string
\ 轉義字符
\[
\]
\ddd 3個十進制的數據表示一個字符,高位填0
[[ ... ]] 表示多行字符串,其中字串無轉義


a = [[ \t \n [[ ]]

print(a)

\t \n [[

當字符串被參於數值運算時,Lua會試圖將字串轉成數值

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

print("123"* "12")--> 1476

當數值參於字串運算時,Lua也會嘗試將其轉換成字符串

print(123 .. 12)-->12312

print(10 =="10") --> 永遠都是false

-- 除非

print(10 == ("10"+ 0)) --> true

print("10"== (10 .. ""))--> true

總之:操作符決定了兩邊操作數的轉換類型。


表達式

(1)算術運算符  + - * / ^ (加減乘除冪)
(2)關係運算符 < > <= >= == ~= (不等於)
     nil只與自己相等,table, usedate, function, 只比對象不比內容的值,相當於C語言中的指針比較
(3)邏輯運算符 and or not
          and, or 的運算結果不是true與false

    
    a andb -- 如果a爲false,則返回a,否則b

    a orb -- 如果a爲true,則返回a,否則b

    a andb or c -- 類似C中的 a ? b : c

        not 結果返回true或false

(4)優先級
        ^ 與 .. 是右連接

表結構

days = {"sun","Mon", "Tue","Wed", "Thu","Fri", "Sat"}

print(days[0])--> nil

print(days[1])--> Sun

注意: Lua中,表的索引是從1開始的,不是0!

Polyline = { color = "blue", thickness = 2, npoints = 4,

{x = 0, y = 0},

{x = -10, y = 0},

{x = -10, y = 10},

{x = 0, y = 10}

}

Polyline["color"]--> "blue"

Polyline.color --> "blue"

Polyline[1] --> {x = 0, y = 0}

Polyline[1].x --> 0

表中的元素都是由key-value組成。如果沒有指定key,那就默認以1開始的整數做key,如上4個{x,y}。如果指定了名稱,則以string類型爲key,如color, thickness, npoints。其實,表的key還可以爲table, function, file, boolean任意類型。value可以爲任何Lua類型。

Person = {["name"] ="Peter", ["age"] = 28}-- 等價於

Person = {name = "Peter", age = 28}

{x=0, y=0} --> {["x"]=0, ["y"]=0}

{"red","green", "blue"}--> {[1]="red", [2]="green", [3]="blue"}

基本語法

(1)多元素賦值

a, b = 10, 'Hi'--> 等價於

a = 10; b = 'Hi'

如果左邊與右邊個數不一致時,多的忽略,少的填Nil。

(2)代碼塊chunk。

使用local創建的一個局部變量,可以避免命名衝突,而比全局變量高效。

(3)控制結構

<pre name="code" class="cpp">----------------------------------
if condition1 then

...
else if condition2 then
...
else
 ...
end

----------------------------------
while condition do
...
end
	
----------------------------------
repeat
...
until condition
----------------------------------

for 語句
for var=exp1, exp2, exp3 do

...

end

---> exp1: 初始值

---> exp2: 終止值

---> exp3: 步進


for k in pairs(days) do

print(k .. ' = ' .. days[key])
end


for i, val in ipairs(days) do

print(i .. ' = ' .. val)

end

(4)函數

定義格式:


function func_name (arguments_list)

...

end


當函數的參數只有一個且爲string或table類型時,可以不加括號。如:

dofile "text.lua"

table_print {a=12, b=30}

函數的返回值可以是多個:

function foo(a, b)

return a+b, a-b

end


aa, bb = foo(32, 11)

print(aa, bb) --> 43, 21

(5)閉包

Python與Lua都有閉包的特性,我記得好像Java也有,但是C與C++是沒有的。


-- 調用newCounter()返回一個function,而這個function引用了newCounter()函數內部的局部變量local i

----------------------------
function newCounter()

    local i = 0

    return function()

<span style="white-space:pre">	</span>i = i + 1

<span style="white-space:pre">	</span>return i

    end

end

c1 = newCounter

print(c1()) --> 1

print(c2()) --> 2

newCounter = nil -- 就算我把這個函數銷燬了

print(c1()) --> 3 還是生效

爲什麼?因爲在Lua中,所有的變量都是“指針”。所有的變量都是從堆裏分配出來的,不像C與C++,局部變量是從棧裏分配,一旦函數退出,所有局部變量都要被釋放。Lua中的所有變量都是從堆裏申請的。對象的釋放是自動進行的,只有這個對象的引用計數爲0了纔會被釋放掉。

爲了便於理解,我寫一段C++的僞代碼:


class CounterFunc
{
public:
    CounterFunc(int i*)
    {
    <span style="white-space:pre">	</span>m_pCnt = i;
    }

    int operator()() 
    {
<span style="white-space:pre">	</span>++i;
<span style="white-space:pre">	</span>return i;
    }

private:
    int *m_pCnt;
}

CounterFunc* newCounter()
{
    int *i = new int;
    return new CounterFunc(i);
}

int main()
{
    CounterFunc &c1 = *newCounter()

    cout << c1() << endl;

    cout << c1() << endl;
}


熟悉C++的學友們一下就看懂了。

(6)非全局函數

表中的函數實現方式:


Lib = {}
Lib.foo = function(x, y) return x+y end

----------------------------------------------
Lib = {
foo = function(x, y) return x+y end
}

----------------------------------------------
Lib = {}

function Lib.foo(x, y) return x+y end

局部函數

local foo = function(x, y)

    return x+y

end

------------------------------------------------
local function foo (x, y)

    return x+y

end


(7)尾調函數

當函數的最後返回結果是調用另一個函數,稱之爲尾調函數。Lua的在調用尾調函數時,先是彈出當前函數的棧空間,然後再調用尾調函數,從而降低了函數層層調動過程中的棧消耗,非常適用於函數遞歸調用。

加載文件與運行

當我們需要加載與運行已有文件中的Lua代碼時,可以用以下幾種方式:

(1)loadstring( str ) --- 加載字串

f = loadstring "a = 12; print('a=' .. a)"

f() --> a=12

loadstring()本是函數,由於函數是string,所以沒有加括號。loadstring()執行完之後,只是加載解析,並沒有執行。返回的是一個function類型。執行該函數便可以運行。相當於:

f = loadstring "function() a = 12; print('a='..a) end"

(2)loadfile( file_name ) --- 加載文件

這個函數相當於從文件裏讀出string,然後再調用loadstring(file_text)實現加載功能。

(3)dofile( file_name ) --- 加載並執行文件

相當於loadfile()之後,返回一個函數,再調用這個函數。


function dofile( file_name )

    local f = assert(loadfile(file_name))

    f()

end

(4)require( file_name )

這個函數是通過調用dofile()來實現的。不同的是,每次加載執行一個文件時,require()都會記錄,避免重複加載。另外,如果給定的路徑找不到文件,require()會到指定的路徑下去找輸到加載的文件。文件名稱可以省去.lua後綴。

協同函數


關於這個功能,我的理解是:是非搶佔式任務,由任務主動放棄執行權來達到任務切換的目的。


function foo (x, y)

    while ture do

<span style="white-space:pre">	</span>local a = x + y

<span style="white-space:pre">	</span>local b = x - y

<span style="white-space:pre">	</span>x, y = coroutine.yield(a, b)

    end

end

------------------------------------------
co = coroutine.create(foo)

print(coroutine.resume(co, 1, 2)) --> true 3 -1

print(coroutine.resume(co, 7, 1)) --> true 8 6


這裏最不好理想的就是yield()與resume()參數與返回值的問題。這裏分步驟詳細解釋一下:
(1)co = coroutine.create(foo),創建一個協同任務,返回co,此時co的狀態爲suspend。
(2)coroutine.resume(co, 1, 2),程序轉而執行foo(x, y),x,y的傳入的值正是resume()中的(1, 2)。

(3)foo(1, 2)執行到coroutine.yield(a, b),(a, b)分別爲(3,-1)。程序執行yield(3, -1)切換到主任務,resume()函數返回第一個值爲bool,後面的正是yield()中傳入的參數。
(4)coroutine.resume(co, 7, 1),程序跳到foo()中的coroutine.yield()行繼續執行。其中resume()函數中帶的參數(7, 1),在foo()函數中,x, y = coroutine.yield() 通過返回值的形式賦給了(x, y)。
(5)跳到(3),如此重複。

可見,Lua並沒有實現多任務並行執行,而是任務之間通過resume()與yield()函數進行切換。我想,其主要的作用還是用於將功能以任務的形式進行隔離,便於維護。

Metatables and Metamethods

元表與元方法,這是一個很重要的概念。可以爲table設置或指定metatable,用於指定某些運算符對應的操作方法。

* setmetatable(table, meta_table) 設置元表
* getmetatable(table) 獲取元表

這個元素,可以是個函數。


tb = {

----> <metatable> = {

__add = function : xxx 加法操作

__sub = function : xxx 減法操作

__mul = function : xxx 乘法操作

...

}


[1] = 20

[2] = 40

...

}


(1)算術運算

+ --> __add(a, b)

- --> __sub(a, b)

* --> __mul(a, b)

/ --> __div(a, b)

-負 --> __unm(a)

^ --> __pow(a, b)

(2)關係運算

== --> __eq(a, b)

< --> __lt(a, b)

<= --> __le(a, b)

只要定義上面幾個就行了,不用再定義 ~=, >=, >

(3)庫定義

__tostring = function(tb)

在print(t)的時候,print會調用元素中的tostring(t)來進行轉換,tostring(t)就查t中的metatable中是否有__tostring域,如果有就調用默認是有的。

(4)表相關

__index = function(tb, key)

__index = tb

訪問索引,如果執行讀取一個表中的操作,而這個表裏又沒有這個域,那麼Lua就去查詢metatable中的__index域。如果__index是表,那麼就去查__index表中有沒有這個域,如果有就從__index這個表裏的這個域裏去取值。如果__index是函數,那麼就調用__index(tb, key)函數。

__newindex = function(tb, key)

更新索引,在更改表域的時候,如果這個表中沒有這個域,那麼就會從元表的__index裏去找。與__index同理。

這兩個東西,在類繼承裏用到。

環境變量

Lua環境放在全局變量 _G 中。可以用以下語句打印全局變量。


for n in pairs(_G) do print(n) end
</pre></div></div></div><div id="highlighter_474421" class="syntaxhighlighter  lua"><div class="bar"><div class="toolbar"><pre code_snippet_id="358723" snippet_file_name="blog_20140522_27_6186527" name="code" class="cpp">a = 5

print(_G["a"]) --> 5
其實,所有的全局變量都是放在__G表裏。可以對_G表裏加metatable,來控制全局變量的形爲。如__newindex, __index, 限制直接定義與訪問一個變量。

在非全局域,防止訪問到全局域

do

    a = 'Hi'

    setfenv(1, {_G = _G})

    _G.print(_G.a) --> Hi

end


在局部Chunk中,將_G放入一個空表中,將這個表作爲全局表。  
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章