Lua函數(shù)

函數(shù)有兩種用途:

  1. 完成指定任務(wù),此時(shí)函數(shù)作為調(diào)用語(yǔ)句使用刊棕。
  2. 計(jì)算并返回值,此時(shí)函數(shù)作為賦值語(yǔ)句的表達(dá)式使用待逞。
function fn(arguments-list)
  statements-list
end

調(diào)用函數(shù)時(shí)甥角,如果參數(shù)列表為空,必須使用()表明是函數(shù)調(diào)用识樱。

print(os.date())

當(dāng)函數(shù)只有一個(gè)參數(shù)且參數(shù)是字符串或表構(gòu)造式時(shí),()是可選的嗤无。

print "hello world"

dofile "main.lua"

print [[a multi-line message]]

fn{x=10, y=20}

type{}

在Lua中函數(shù)都是function類型的對(duì)象

  • 可被比較
  • 可賦值給一個(gè)變量
  • 可傳遞給函數(shù)
  • 可從函數(shù)中返回
  • 可作為table表中的鍵

函數(shù)定義

Lua使用關(guān)鍵字function定義函數(shù)

function fn(arg)
  -- function body...
end

函數(shù)定義的語(yǔ)法會(huì)定義一個(gè)全局函數(shù),名為fn怜庸,全局函數(shù)本質(zhì)上是函數(shù)類型的值賦給全局變量当犯。

函數(shù)變量式

fn = function(arg)
  -- function body...
end

由于函數(shù)定義本質(zhì)上是變量賦值,變量的定義總是應(yīng)放置在變量使用之前割疾,所以函數(shù)的定義也需要放置在函數(shù)調(diào)用之前嚎卫。

local function fn(arg)
  -- function body...
end

local fn = function(arg)
  -- function body...
end

如果參數(shù)列表為空,必須使用()表明函數(shù)調(diào)用宏榕。

定義函數(shù)并調(diào)用

-- 定義函數(shù)
function fn()
  print("hello function")
end
-- 調(diào)用函數(shù)
fn() 

在定義函數(shù)時(shí)要注意

  • 利用名字來(lái)解釋函數(shù)拓诸,變量的目的使人通過(guò)名字就能看出函數(shù)、變量的作用麻昼。
  • 每個(gè)函數(shù)的長(zhǎng)度要盡量控制在一個(gè)屏幕內(nèi)奠支,一樣就能看明白。
  • 讓代碼自己說(shuō)話抚芦,最好是不需要注釋倍谜。

由于函數(shù)定義等價(jià)于變量賦值,因此也可以將函數(shù)名替換為L(zhǎng)ua表中的某個(gè)字段叉抡。

-- 這種形式的函數(shù)定義不能使用local修飾符尔崔,因?yàn)椴淮嬖诙x新的局部變量了。
foo.bar = function()
  -- function body...
end

function foo.bar()
  -- function body ...
end

案例:接收兩個(gè)參數(shù)褥民,計(jì)算加減乘除的結(jié)果季春,并輸出到屏幕。

function fn(i,j)
    return i+j, i-j, i*j, i/j, i%j;
end

a,b,c,d,e = fn(10,5)
print(a,b,c,d,e) -- 15  5   50  2.0 0

函數(shù)參數(shù)

按值傳遞

Lua函數(shù)的參數(shù)大部分是按值傳遞的轴捎,值傳遞就是調(diào)用函數(shù)時(shí)鹤盒,實(shí)參把它的值通過(guò)賦值運(yùn)算傳遞給形參,然后形參的改變和實(shí)參就沒(méi)有關(guān)系了侦副。在這個(gè)過(guò)程中侦锯,實(shí)參是通過(guò)它在參數(shù)表中的位置與形參匹配起來(lái)的。

local function swap(x,y)
  local tmp = x
  x = y
  y = tmp
  print(x,y)
end

在調(diào)用函數(shù)時(shí)秦驯,若形參個(gè)數(shù)和實(shí)參個(gè)數(shù)不同時(shí)尺碰,Lua會(huì)自動(dòng)調(diào)整實(shí)參個(gè)數(shù)。調(diào)整規(guī)則:
若實(shí)參個(gè)數(shù)大于形參個(gè)數(shù)译隘,從左向右亲桥,多余的實(shí)參被忽略。若實(shí)參個(gè)數(shù)小于形參個(gè)數(shù)固耘,從左向右题篷,沒(méi)有實(shí)參初始化的形參會(huì)被初始化為nil

變長(zhǎng)參數(shù)

https://moonbingbing.gitbooks.io/openresty-best-practices/content/lua/function_parameter.html

若定義一個(gè)函數(shù)厅目,參數(shù)個(gè)數(shù)不固定番枚,應(yīng)該怎么辦呢?這就涉及到Lua中函數(shù)的可變參數(shù)损敷。

-- Lua中三個(gè)點(diǎn)表示函數(shù)的參數(shù)個(gè)數(shù)不確定葫笼,可以改變,即可變參數(shù)拗馒。
function fn(...)

end

return關(guān)鍵字只能出現(xiàn)在語(yǔ)句塊的結(jié)尾路星,也就是說(shuō),在end之前诱桂,或者是else之前洋丐,或者是until之前。

function fn(x)
    return x*x*x;
end

n = fn(5)
print(n) -- 125
function fn(x)
    if x<10 then
        return x*x*x
    else
        return x*x
    end
end

n = fn(5)
print(n)-- 125

函數(shù)基礎(chǔ)

Lua中函數(shù)是一種對(duì)語(yǔ)句和表達(dá)式進(jìn)行抽象的主要機(jī)制挥等。

Lua中函數(shù)即可完成某項(xiàng)特定的任務(wù)垫挨,一條函數(shù)調(diào)用可視為一條語(yǔ)句。

$ lua
>  a = match.sin(3) + math.cos(10)
> print(a)
-0.69795152101659

> print(os.date())
02/04/18 17:42:52

Lua函數(shù)也可以只做一些計(jì)算并返回結(jié)果触菜,可視為一句表達(dá)式九榔。

$ lua
> print(8*9, 9/8)
72      1.125

無(wú)論哪種用法都需將參數(shù)放入一對(duì)圓括號(hào)中。即使調(diào)用函數(shù)時(shí)沒(méi)有參數(shù)涡相,也必須寫(xiě)出一對(duì)空括號(hào)哲泊。對(duì)此規(guī)則只有一種特殊的例外情況:一個(gè)函數(shù)若只有一個(gè)參數(shù),并且此參數(shù)是一個(gè)字面量字符串或table構(gòu)造式催蝗,那么圓括號(hào)便是可有可無(wú)的切威。

$ lua
> print "hello world"
> dofile "test.lua"
> print [[a multi-line message]]

函數(shù)只有一個(gè)參數(shù),且參數(shù)是一個(gè)table構(gòu)造式丙号,則圓括號(hào)可有可無(wú)先朦。

> f{x=10, y=20}
> f({x=10, y=20})

> type{}
> type({})

Lua為面向?qū)ο笫降恼{(diào)用也提供了一種特殊的語(yǔ)法 - 冒號(hào)操作符缰冤。

> obj.foo(obj, arg)
-- 冒號(hào)操作符使調(diào)用obj.foo時(shí)將obj隱含地作為函數(shù)的第一個(gè)函數(shù)
> obj:foo(arg)

Lua程序即可使用以Lua編寫(xiě)的函數(shù),也可調(diào)用以C語(yǔ)言編寫(xiě)的函數(shù)喳魏。

function add(params)
    local sum=0;
    for k,v in ipairs(params) do
        sum=sum+v
    end
    return sum
end

print(add({1,2,3}))

在這種語(yǔ)法中棉浸,一個(gè)函數(shù)定義具有一個(gè)名詞(函數(shù)名)、一些列參數(shù)(參數(shù)表)刺彩、一個(gè)函數(shù)體(一系列語(yǔ)句)迷郑。

形式參數(shù)(parameter)的使用方式與局部變量非常類似,它們是由調(diào)用函數(shù)時(shí)的實(shí)際參數(shù)(argument)初始化的创倔。

調(diào)用函數(shù)時(shí)提供的實(shí)參數(shù)量可與形參數(shù)量不同嗡害,Lua會(huì)自動(dòng)調(diào)整實(shí)參數(shù)量,以匹配參數(shù)表的要求畦攘。這項(xiàng)調(diào)整與多重賦值(multiple assignment)很相似霸妹,即“若實(shí)參多于形參,則舍棄多于實(shí)參知押;若實(shí)參不足抑堡,則多余形參初始化為nil”。

function fn(a,b)
    print(a,b);
end

fn(1) -- 1 nil
fn(1,2) -- 1 2
fn(1,2,3) -- 1 2

雖然這種調(diào)整行為會(huì)導(dǎo)致一些編程錯(cuò)誤朗徊,但它也是很有用的首妖,尤其是對(duì)于默認(rèn)實(shí)參的應(yīng)用。

function incCount(n)
    n = n or 1 -- 函數(shù)以1作為默認(rèn)實(shí)參
    count = count + n
    print(n, count)
end

fn() -- attempt to perform arithmetic on a nil value (global 'count')

多重返回值

Lua函數(shù)具有一項(xiàng)非常與眾不同的特征爷恳,允許函數(shù)返回多個(gè)結(jié)果有缆。

Lua的幾個(gè)預(yù)定義函數(shù)就是返回多個(gè)值的,string.find()用于在字符串中定位一個(gè)模式(pattern)温亲,該函數(shù)允許在字符串中找到指定的模式棚壁,將返回匹配的起始字符和結(jié)尾字符的索引。

startstr,endstr = string.find("hello lua", "lua")
print(startstr, endstr) -- 7 9

以Lua編寫(xiě)的函數(shù)同樣可以返回多個(gè)結(jié)果栈虚,只需在return關(guān)鍵字后列出所有的返回值即可袖外。

例如:查找數(shù)組中最大元素并返回該元素的位置

function max(tbl)
    local index = 1
    local value = tbl[index]
    for i,v in ipairs(tbl) do
        if(v > value) then
            index = i
            value = v
        end
    end
    return index,value
end

print(max{9,2,12,8,3,9})-- 3 12

Lua會(huì)調(diào)整函數(shù)返回值數(shù)量以適應(yīng)不同的調(diào)用情況

  • 若將函數(shù)調(diào)用作為一條單獨(dú)語(yǔ)句時(shí),Lua會(huì)丟棄函數(shù)的所有返回值魂务。
  • 所將函數(shù)作為表達(dá)式的一部分來(lái)調(diào)用時(shí)曼验,Lua只保留函數(shù)的第一個(gè)返回值。只有當(dāng)一個(gè)函數(shù)調(diào)用是“一系列表達(dá)式”中最后一個(gè)元素或僅有一個(gè)元素時(shí)粘姜,才能獲得它的所有返回值鬓照。
-- test
local tbl = {9,2,12,8,3,9}

local a = max(tbl)
print(a) -- 3

local a,b = max(tbl)
print(a, b) -- 3 12

local a,b,c =  max(tbl) 
print(a,b,c)-- 3 12 nil

local a,b,c = 1, max(tbl) 
print(a,b,c)-- 1 3 12

local a,b,c,d = 1,max(tbl),0
print(a,b,c,d) -- 1 3 0 nil

這里所謂的“一系列表達(dá)式”,在Lua中表現(xiàn)為4種情況

  • 多重賦值
  • 函數(shù)調(diào)用時(shí)傳入的實(shí)參列表
  • table的構(gòu)造式
  • return語(yǔ)句

在多重賦值中孤紧,若函數(shù)調(diào)用是最后的或僅有的一個(gè)表達(dá)式豺裆,那么Lua會(huì)保留其盡可能多的返回值,用于匹配賦值變量号显。若函數(shù)沒(méi)有返回值或沒(méi)有足夠多的返回值臭猜,那么Lua會(huì)用nil來(lái)補(bǔ)充缺失的值躺酒。若函數(shù)調(diào)用不是一些列表達(dá)式的最后一個(gè)元素,那么將只產(chǎn)生一個(gè)值蔑歌。

當(dāng)函數(shù)調(diào)用作為另一個(gè)函數(shù)調(diào)用的最后一個(gè)或僅有的實(shí)參時(shí)羹应,第一個(gè)函數(shù)所有返回值都將作為實(shí)參傳入第二個(gè)函數(shù)。

function fn1()
end

function fn2()
    return 1
end

function fn3()
    return 1,2
end

print(fn1())
print(fn2()) -- 1
print(fn3()) -- 1 2

print(fn2(),2) -- 1 2
print(fn2()..'x') -- 1x

table構(gòu)造式可以完整地接收一個(gè)函數(shù)調(diào)用的所有結(jié)果丐膝,即不會(huì)有任何數(shù)量方面的調(diào)整。

function fn1()
end

function fn2()
    return 1
end

function fn3()
    return 1,2
end

tbl1 = {fn1()} -- 相當(dāng)于 tbl1={}
tbl2 = {fn2()} -- 相當(dāng)于 tbl2={1}
tbl3 = {fn3()} -- 相當(dāng)于 tbl3={1,2}

不過(guò)這種行為只有當(dāng)一個(gè)函數(shù)調(diào)用作為最后一個(gè)元素時(shí)才會(huì)發(fā)生钾菊,而在其他位置上的函數(shù)調(diào)用總是只纏身給一個(gè)結(jié)果值帅矗。

function fn1()
end

function fn2()
    return 1
end

function fn3()
    return 1,2
end

tbl = {fn1(), fn2(), fn3(), 4}
print(tbl[1], tbl[2], tbl[3], tbl[4]) -- nil 1 1 4

最后一種情況是return語(yǔ)句,諸如return fn()這樣的語(yǔ)句將返回fn的所有返回值煞烫。

function fn1()

end
function fn2()
    return 1
end
function fn3()
    return 1,2
end
function fn(i)
    if i==1 then return fn1()
    elseif i==2 then return fn2()
    elseif i==3 then return fn3()
    end
end

print(fn(1), fn(2), fn(3)) -- nil 1 1 2

-- 將函數(shù)調(diào)用放入一對(duì)圓括號(hào)中浑此,從而迫使它只返回一個(gè)結(jié)果。
print((fn(1)), (fn(2)), (fn(3))) -- nil 1 2

注意return語(yǔ)句后面的內(nèi)容不需要圓括號(hào)滞详,在該位置上書(shū)寫(xiě)圓括號(hào)會(huì)導(dǎo)致不同的行為凛俱。

return (fn(3)) -- 只返回一個(gè)值,而無(wú)關(guān)于fn()返回幾個(gè)值

關(guān)于多重返回值還要介紹一個(gè)特殊函數(shù) unpack料饥,unpack接收一個(gè)數(shù)組作為參數(shù)蒲犬,并從下標(biāo)1開(kāi)始返回該數(shù)組的所有元素。

-- lua5.2+中全局unpack函數(shù)已被移除岸啡,并被table.unpack替代原叮。
local unpack = unpack or table.unpack

x,y,z = unpack{1,2,3}
print(x,y,z) -- 1 2 3

unpack的一項(xiàng)重要用戶體現(xiàn)在“泛型調(diào)用(generic call)”機(jī)制中,泛型調(diào)用機(jī)制可動(dòng)態(tài)地以任何實(shí)參來(lái)調(diào)用任何函數(shù)巡蘸。

-- lua5.2+中全局unpack函數(shù)已被移除奋隶,并被table.unpack替代。
local unpack = unpack or table.unpack

fn = string.find
tbl = {'hello lua', 'lua'}

print(fn(unpack(tbl))) -- 7 9

變長(zhǎng)參數(shù)

Lua函數(shù)可接受可變數(shù)量的參數(shù)悦荒,和C語(yǔ)言類似唯欣。在函數(shù)參數(shù)列表中使用...表示函數(shù)有可變的參數(shù)。

Lua將函數(shù)的參數(shù)放在一個(gè)叫做arg的表中搬味,除了參數(shù)外境氢,arg表中還有一個(gè)域n表示參數(shù)的個(gè)數(shù)。

function dump(...)
  local str = ""
  for i,v in ipairs(arg) do
    str = str .. tostring(v).."\t"
  end
  return str.."\n"
end

例如:只想要string.find返回的第二個(gè)值碰纬,典型的方法是使用虛變量_产还。

local _, x = string.find(str, pattern)

Lua中的函數(shù)還可以接受不同數(shù)量的實(shí)參

例如:返回所有參數(shù)的總和

function sum(...)
    local sum=0
    for i,v in ipairs{...} do
        sum = sum + v
    end
    return sum
end

print(sum(1,2,3,4)) -- 10

參數(shù)中3個(gè)點(diǎn)...表示函數(shù)可接收不同數(shù)量的實(shí)參,當(dāng)函數(shù)被調(diào)用時(shí)嘀趟,它的所有參數(shù)都會(huì)被收集到一起脐区。這部分收集起來(lái)的實(shí)參稱為函數(shù)的“變長(zhǎng)參數(shù)(variable arguments)”。函數(shù)要訪問(wèn)它的變長(zhǎng)參數(shù)時(shí)她按,仍需要3個(gè)點(diǎn)牛隅。但不同的是炕柔,此時(shí)這個(gè)3個(gè)點(diǎn)是作為一個(gè)表達(dá)式來(lái)使用的。

function fn(...)
    local x,y,z = ...
    print(x,y,z)
end
fn(1,2) -- 1 2 nil
fn(1,2,3) -- 1 2 3

表達(dá)式...的行為類似于一個(gè)具有多重返回值的函數(shù)媒佣,它返回的是當(dāng)前函數(shù)的所有變量參數(shù)匕累。

function fwrite(fmt, ...)
    return io.write(string.format(fmt, ...))
end
fwrite("%s %s %s", 1, 2, 3) -- 1 2 3

Lua中迭代函數(shù)參數(shù)時(shí),可使用...參數(shù)收集到一個(gè)table中默伍,但變參中函數(shù)非法的nil時(shí)欢嘿,可使用select函數(shù)將其過(guò)濾掉。

function filter(...)
    for i=1, select("#", ...) do
        local arg = select(i, ...)
        if arg ~= nil then
            print(arg)
        end
    end
end
-- test
test(1,nil,2,3)-- 1 2 3

具名實(shí)參

Lua的函數(shù)參數(shù)是和位置相關(guān)的也糊,調(diào)用時(shí)會(huì)按順序依次傳遞給形式參數(shù)炼蹦。有時(shí)候,使用名字制定參數(shù)是很有用的(命名參數(shù))狸剃。

Lua中的參數(shù)傳遞機(jī)制和是具有“位置性”的掐隐,也就是說(shuō)在調(diào)用函數(shù)時(shí),實(shí)參時(shí)通過(guò)它在參數(shù)表中的位置與形參匹配起來(lái)钞馁。

function rename(tbl)
    print(tbl, tbl.oriname, tbl.newname)
end
rename{oriname="ori.lua", newname="new.lua"}

Lua中特殊的函數(shù)調(diào)用語(yǔ)法虑省,當(dāng)實(shí)參只有一個(gè)table構(gòu)造式時(shí),函數(shù)調(diào)用中的圓括號(hào)是可有可無(wú)的僧凰。

function rename(arg)
  return os.rename(arg.old, arg.new)
end

第一類值

Lua中的函數(shù)是帶有詞法定界(lexical scoping)的第一類值(first-class values)探颈。

"第一類值"是指在Lua中函數(shù)和其他值(數(shù)值、字符串...)一樣训措,函數(shù)可以被存儲(chǔ)在變量中膝擂,可以存放在表中,可以作為函數(shù)的參數(shù)隙弛,也可以作為函數(shù)的返回值架馋。

“詞法界定”是指被嵌套的函數(shù)可以訪問(wèn)其他外部函數(shù)中的變量,這一特性給Lua提供了強(qiáng)大的編程能力全闷。

Lua中關(guān)于函數(shù)難以理解的是“函數(shù)是可以沒(méi)有名字的叉寂,也就是匿名的∽苤椋”屏鳍,但提到函數(shù)名時(shí),實(shí)際上是說(shuō)以一個(gè)指向函數(shù)的變量局服,和其他類型值得變量是一樣的钓瞭。

Lua中函數(shù)作為“第一類值”,表示函數(shù)可以存儲(chǔ)在變量中淫奔,可通過(guò)參數(shù)傳遞給其他函數(shù)山涡,還可作為其他函數(shù)的返回值。由于函數(shù)在Lua中是一種“第一類值”,所以不僅可將其存儲(chǔ)在全局變量中鸭丛,還可存儲(chǔ)在局部變量甚至table的字段中竞穷。

Lua中函數(shù)是一種“第一類值(First-Class Value)”,它們具有特定的詞法域(Lexical Scoping)鳞溉。

“第一類值”是什么意思呢瘾带?這表示在Lua中函數(shù)與其他傳統(tǒng)類型的值具有相同的權(quán)利。函數(shù)可存儲(chǔ)到變量中或table中熟菲,可作為實(shí)參傳遞給其他函數(shù)看政,還可作為其他函數(shù)的返回值。

“詞法域”是什么意思呢抄罕?這是指一個(gè)函數(shù)可以嵌套在另一個(gè)函數(shù)中允蚣,內(nèi)部的函數(shù)可訪問(wèn)外部函數(shù)的變量。這項(xiàng)聽(tīng)起來(lái)平凡的特性將給語(yǔ)言帶來(lái)極大的能力贞绵,因?yàn)樗试S在Lua中應(yīng)用各種函數(shù)式語(yǔ)言(functional-language)中強(qiáng)大的編程技術(shù)厉萝。

a = {p = print}
a.p('hello') -- hello

print = math.sin
a.p(print(1)) -- 0.8414709848079

sin = a.p
sin(10,20) -- 10 20

Lua對(duì)函數(shù)式編程(functional programming)提供了良好的支持

在Lua中有一個(gè)很容易混淆概念是恍飘,函數(shù)與所有其他值一樣都是匿名的榨崩,即它們都沒(méi)有名稱。當(dāng)討論一個(gè)函數(shù)名時(shí)章母,實(shí)際上是在討論一個(gè)持有某函數(shù)的變量母蛛。這與其他變量持有各種值的一個(gè)道理,可以以多種方式來(lái)操作這些變量乳怎。

如果說(shuō)函數(shù)是值的話彩郊,那是否可以說(shuō)函數(shù)就是由一些表達(dá)式創(chuàng)建的呢?是的蚪缀,事實(shí)上在Lua中最常見(jiàn)的是函數(shù)編寫(xiě)方式秫逝。

function fn(x)
    return 2*x
end

-- 簡(jiǎn)化書(shū)寫(xiě)“語(yǔ)法糖”
fn = function(x) return 2*x end

一個(gè)函數(shù)定義實(shí)際就是一條語(yǔ)句,更準(zhǔn)確地說(shuō)是一條賦值語(yǔ)句询枚,這條語(yǔ)句創(chuàng)建了一種類型為“函數(shù)”的值违帆。

可將表達(dá)式function(x) <body> end視為一種函數(shù)的構(gòu)造式,類似table的構(gòu)造式{}一樣金蜀。將這種函數(shù)構(gòu)造式的結(jié)果稱為一個(gè)“匿名函數(shù)”刷后,雖然一般情況下,會(huì)將函數(shù)賦予全局變量渊抄,即給予其一個(gè)名稱尝胆。但在某些特殊情況下,仍會(huì)需要用到匿名函數(shù)护桦。

Lua即可以調(diào)用以自身Lua語(yǔ)言編寫(xiě)的函數(shù)含衔,又可以調(diào)用以C語(yǔ)言編寫(xiě)的函數(shù)。

table庫(kù)提供了一個(gè)函數(shù)table.sort,它接收一個(gè)table并對(duì)其中的元素排序抱慌。向這種函數(shù)就必須支持各種各樣可能的排序規(guī)則逊桦。sort函數(shù)并沒(méi)有提供所有排序準(zhǔn)則,而是提供了一個(gè)可選的參數(shù)抑进,所謂“次序函數(shù)(order function)”强经。這個(gè)函數(shù)接收兩個(gè)元素,并返回在有序情況下第一個(gè)元素是否已排在第二個(gè)元素之前寺渗。

local tbl = {
    {name="ip1", ip="210.26,30,34"},
    {name="ip2", ip="210.26,30,33"},
    {name="ip3", ip="210.26,30,12"},
}
table.sort(tbl, function(x,y)
    return x.name > y.name
end)
for k,v in pairs(tbl) do
    print(v.name) --ip3 ip2 ip1
end

sort這樣的函數(shù)匿情,接收另一個(gè)函數(shù)作為實(shí)參,稱其為“高階函數(shù)(higher-order function)”信殊。高階函數(shù)是一種強(qiáng)大的編程機(jī)制炬称,應(yīng)用匿名函數(shù)來(lái)創(chuàng)建高階函數(shù)所需的實(shí)參則可以帶來(lái)更大的靈活性。但請(qǐng)記住涡拘,高階函數(shù)并沒(méi)有什么特權(quán)玲躯。Lua強(qiáng)調(diào)將函數(shù)視為“第一類值”,所以高階函數(shù)只是一種基于該觀點(diǎn)的應(yīng)用體現(xiàn)而已鳄乏。

例如:關(guān)于導(dǎo)數(shù)的高階函數(shù)

function derivative(fn, delta)
    delta = delta or 1e-4
    return function(x)
        return (fn(x+delta)-fn(x))/delta
    end
end

fn = derivative(math.sin)
print(math.cos(10), fn(10)) // -0.83907152907645    -0.83904432662041

閉包函數(shù)

當(dāng)一個(gè)函數(shù)內(nèi)部嵌套另一個(gè)函數(shù)定義時(shí)跷车,內(nèi)部的函數(shù)體可以訪問(wèn)外部函數(shù)的局部變量,這種特征稱之為“詞法界定”橱野。詞法界定加上第一類函數(shù)在編程語(yǔ)言中是一個(gè)功能強(qiáng)大的工具朽缴。

若將函數(shù)寫(xiě)在另一個(gè)函數(shù)何內(nèi),那么這個(gè)位于內(nèi)部的函數(shù)便可訪問(wèn)外部函數(shù)中的局部變量水援,這項(xiàng)特征稱之為“詞法域”密强。

例如:根據(jù)每個(gè)學(xué)生的年級(jí)來(lái)對(duì)它們姓名進(jìn)行由高到低的排序

local userlist = {
    {username="mary", score=81},
    {username="shiva", score=92},
    {username="seth", score=65}
}

table.sort(userlist, function(x, y)
    return x.score > y.score
end)

for k,v in pairs(userlist) do
    print(k, v.username, v.score)
end

--[[
1   shiva   92
2   mary    81
3   seth    65
--]]

創(chuàng)建函數(shù)完成操作

function sort_by_grade(names, grades)
    table.sort(names, 
        function(a,b)
            return grades[a] > grades[b]
        end
    );
end

names = {"alice", "peter", "paul", "mary"}
grades = {alice=10, peter=5, paul=9, mary=2}
sort_by_grade(names,grades)

for key,val in pairs(names) do
    print(key, val, grades[val])
end

有趣的是,傳遞給sort匿名函數(shù)可以訪問(wèn)參數(shù)grades蜗元,而grades是外部函數(shù)sort_by_grade的局部變量或渤。在這個(gè)匿名函數(shù)內(nèi)部,grades即不是全局變量也不是局部變量奕扣,將其稱為一個(gè)非局部的變量(non-local variable)upvalues或“外部的局部變量(external local variable)”薪鹦。為什么在Lua中允許這種訪問(wèn)呢?原因在于函數(shù)是“第一類值”成畦。

function count()
    local i = 0
    return function()
        i = i + 1
        return i
    end
end

cnt = count()
print(cnt()) -- 1
print(cnt()) -- 2
print(cnt()) -- 3

匿名函數(shù)訪問(wèn)了一個(gè)“非局部的變量i距芬,i變量用于保持一個(gè)計(jì)數(shù)器。Lua會(huì)以closure的概念來(lái)正確地處理這種情況循帐。簡(jiǎn)單來(lái)說(shuō)框仔,一個(gè)closure就是一個(gè)函數(shù)加上該函數(shù)所需訪問(wèn)的所有“非局部的變量”。如果再次調(diào)用count()拄养,那么它會(huì)創(chuàng)建一個(gè)新的局部變量i离斩,從而也將得到一個(gè)新的closure银舱。

技術(shù)上來(lái)講,閉包指的是值而不是函數(shù)跛梗,函數(shù)僅僅是閉包的原型聲明寻馏,盡管如此,在不會(huì)導(dǎo)致混淆的情況下核偿,使用術(shù)語(yǔ)函數(shù)代指閉包诚欠。

從技術(shù)上講,Lua中只有closure漾岳,而不存在函數(shù)轰绵。因此,函數(shù)本身就是一種特殊的closure尼荆。不過(guò)只要不會(huì)引起混淆左腔,仍將采用術(shù)語(yǔ)函數(shù)來(lái)指代closure

在許多場(chǎng)合中closure都是一種很有價(jià)值的工具捅儒,例如:可作為sort類高階函數(shù)的參數(shù)液样。closure對(duì)于創(chuàng)建其他函數(shù)也很有價(jià)值。這種機(jī)制使Lua可混合在函數(shù)式編程世界中久經(jīng)考驗(yàn)的編程技術(shù)巧还。另外closure對(duì)于回調(diào)函數(shù)也很有用鞭莽。

例如,假設(shè)有一個(gè)傳統(tǒng)的GUI工具包可以創(chuàng)建按鈕狞悲,每個(gè)按鈕都有一個(gè)回調(diào)函數(shù)撮抓,每當(dāng)用戶按下按鈕是GUI工具包都會(huì)調(diào)用這些回調(diào)函數(shù)妇斤。再假設(shè)摇锋,基于此要做一個(gè)十進(jìn)制計(jì)算器,其中需要10個(gè)數(shù)字按鈕站超,你會(huì)發(fā)現(xiàn)這些按鈕之間的區(qū)別其實(shí)并不大荸恕,僅需在按下不同按鈕時(shí)稍微不同的操作就可以了。

-- 創(chuàng)建按鈕
function digitButton(digit)
    return Button{label=tostring(digit), action=function() add_to_display(digit) end}
end

假設(shè)Button是工具包中一個(gè)用于創(chuàng)建新按鈕的函數(shù)死相,label是按鈕的標(biāo)簽融求,action是回調(diào)closure,每當(dāng)按鈕按下時(shí)就會(huì)調(diào)用它算撮∩穑回調(diào)一般發(fā)生在digitButton函數(shù)執(zhí)行完后,那時(shí)局部變量digit已經(jīng)超出了作用范圍肮柜,但closure仍可以訪問(wèn)到這個(gè)變量陷舅。

closure在另一種情況中也非常有用,例如在Lua中函數(shù)是存儲(chǔ)在普通變量中的审洞,因此可以輕易地重新定義某些函數(shù)莱睁,甚至是重新定義那些預(yù)定義的函數(shù)。這也是Lua相當(dāng)靈活的原因之一。

通常當(dāng)重新定義一個(gè)函數(shù)時(shí)仰剿,需要在新的實(shí)現(xiàn)中調(diào)用原來(lái)的那個(gè)函數(shù)创淡。舉例來(lái)說(shuō),假設(shè)要重新定義函數(shù)sin南吮,使其參數(shù)能使用角度來(lái)替代原先的弧度琳彩。那么這個(gè)新函數(shù)就必須得轉(zhuǎn)換它的實(shí)參,并調(diào)用原來(lái)的sin函數(shù)完成真正的計(jì)算部凑。

oldSin = math.oldSin

math.sin = function(x)
    return oldSin(x*math.pi/180)
end

還有一種更徹底的做法

do
    local oldSin = math.sin
    local k = math.pi/180
    math.sin = function(x)
        return oldSin(x*k)
    end
end

將老版本的sin保存到一個(gè)私有變量汁针,現(xiàn)在只有通過(guò)新版本的sin才能訪問(wèn)到它。

可以使用同樣的技術(shù)來(lái)創(chuàng)建一個(gè)安全的運(yùn)行環(huán)境砚尽,即所謂的沙盒(sandbox)施无。當(dāng)執(zhí)行一些未受信任的代碼時(shí)就需要一個(gè)安全的運(yùn)行環(huán)境,例如在服務(wù)器中執(zhí)行那些從Internet上接收到的代碼必孤。

舉例來(lái)說(shuō)猾骡,如果要限制一個(gè)程序訪問(wèn)文件的話,只需要使用closure來(lái)重新定義函數(shù)io.open()即可敷搪。

do
    local oldOpen = io.open
    local accessOK = function(filename,mode)
        -- 檢查訪問(wèn)權(quán)限
    end
    io.open =function(filename, mode)
        if accessOK(filename, mode) then
            return oldOpen(filename,mode)
        else
            return nil,"access denied"
        end
    end
end

經(jīng)過(guò)重新定義后兴想,一個(gè)函數(shù)就只能通過(guò)受限版本來(lái)調(diào)用原來(lái)那個(gè)未受限的open()函數(shù)了。將原來(lái)不安全的版本保存到closure的一個(gè)私有變量中赡勘,從而使得外部再也無(wú)法直接訪問(wèn)到原來(lái)的版本了嫂便。

通過(guò)這種技術(shù),可以再Lua的語(yǔ)言層面上就構(gòu)建出一個(gè)安全的運(yùn)行環(huán)境闸与,且不失建議性和靈活性毙替。相對(duì)于提供一套大而全的解決方案,Lua提供的則是一套“元機(jī)制(meta-mechanism)”践樱,因此可以根據(jù)特定的安全需要來(lái)創(chuàng)建一個(gè)安全的運(yùn)行環(huán)境厂画。

非全局的函數(shù)

由于函數(shù)是一種“第一類值”,因此一個(gè)顯而易見(jiàn)的推論是拷邢,函數(shù)不僅可以存儲(chǔ)在全局變量中袱院,也可以存儲(chǔ)在table的字段中和局部變量中

Lua中大部分庫(kù)采用將函數(shù)存儲(chǔ)在table字段中這種機(jī)制瞭稼。若要在Lua中創(chuàng)建這種函數(shù)忽洛,只需將常規(guī)的函數(shù)語(yǔ)法和table語(yǔ)法結(jié)合即可。

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

當(dāng)然环肘,也可使用構(gòu)造式

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

只要將一個(gè)函數(shù)存儲(chǔ)到一個(gè)局部變量中欲虚,即得到一個(gè)局部函數(shù)(local function),也就是說(shuō)該函數(shù)只能在某個(gè)特定的作用域中使用廷臼。

對(duì)于程序包(package)而言苍在,這種函數(shù)定義是非常有用的绝页,因?yàn)長(zhǎng)ua是將每個(gè)特定程序塊(chunk)作為一個(gè)函數(shù)來(lái)處理的,所以在一個(gè)程序塊中聲明的函數(shù)就是局部函數(shù)寂恬,這些局部函數(shù)只在該程序塊中可見(jiàn)续誉。詞法域確保了程序包中的其他函數(shù)可以使用這些局部函數(shù)。

local fn = function(arg)
    -- function body
end

local func = function(arg)
    fn()
end

對(duì)于局部函數(shù)的定義初肉,Lua支持一種特殊的語(yǔ)法糖:

local function fn(arg)
    -- function body
end

在定義遞歸的局部函數(shù)中酷鸦,有一個(gè)特別之處需要注意罐孝。

local face = function(n)
    if n==0 then
        return 1
    else
        -- 錯(cuò)誤的遞歸調(diào)用
        -- 當(dāng)Lua編譯到函數(shù)體中調(diào)用fact(n-1)的地方時(shí)机隙,由于局部的fact尚未定義完畢。
        -- 因此這句表達(dá)式其實(shí)調(diào)用了一個(gè)全局的fact盲泛,而非此函數(shù)本身妄壶。
        return n*face(n-1)
    end
end

--[[結(jié)局方案:可以先定義一個(gè)局部變量摔握,然后再定義函數(shù)本身。--]]
local fact
fact = function(n)
    if n==0 then return 1
    else return n*fact(n-1)
    end
end
--[[
現(xiàn)在函數(shù)中的fact調(diào)用就表示了局部變量丁寄,即使在函數(shù)定義時(shí)氨淌,這個(gè)局部變量的值尚未完成定義,但之后在函數(shù)執(zhí)行時(shí)伊磺,fact則肯定擁有了正確的值盛正。
--]]

當(dāng)Lua展開(kāi)局部函數(shù)定義的“語(yǔ)法糖”時(shí),并不是使用基礎(chǔ)函數(shù)定義語(yǔ)法屑埋。而是對(duì)于局部函數(shù)定義:

local function fn(<args>) <function body> end

Lua將其展開(kāi)為

local fn

fn = function(<args>) <function body> end

因此豪筝,使用此種語(yǔ)法定義遞歸函數(shù)不會(huì)產(chǎn)生錯(cuò)誤:

local function fact(n)
  if n==0 then return 1
  else return n*fact(n-1)
  end
end

當(dāng)然,這個(gè)技巧對(duì)于間接遞歸的函數(shù)而言是無(wú)效的摘能。對(duì)于間接遞歸的情況下续崖,必須使用一個(gè)明確的向前聲明(Forward Declaration):

local fn,func -- 向前聲明

function func()
  fn()
end

function fn()
  func()
end

--[[
注意,別把第二個(gè)函數(shù)定義為"local function fn"徊哑。
如果那樣的話袜刷,Lua會(huì)創(chuàng)建一個(gè)全新的局部變量fn聪富。
而將原來(lái)聲明的fn(func函數(shù)中所引用的那個(gè))置于未定義狀態(tài)莺丑。
--]]

尾調(diào)用

Lua函數(shù)有一個(gè)有趣的特征,那就是Lua支持“尾調(diào)用消除(tail-call elimination)”墩蔓。

所謂“尾調(diào)用(tail call)”就是一種類似于goto()的函數(shù)調(diào)用梢莽,當(dāng)一個(gè)函數(shù)調(diào)用是另一個(gè)函數(shù)的最后一個(gè)動(dòng)作時(shí),該調(diào)用才算是一條“尾調(diào)用”奸披。

function fn(x)
  return func(x)
end

--[[
當(dāng)fn()函數(shù)調(diào)用完func()函數(shù)之后就再無(wú)其他事情可做了
因此在這種情況下昏名,程序就不需要返回那個(gè)“尾調(diào)用”所在的函數(shù)了。
所以在“尾調(diào)用”之后阵面,程序也不需要保存任何關(guān)于該函數(shù)的棧(stack)信息轻局。
當(dāng)func()函數(shù)返回時(shí)洪鸭,執(zhí)行控制權(quán)可以直接返回給調(diào)用fn()函數(shù)的那個(gè)點(diǎn)上。
有些語(yǔ)言實(shí)現(xiàn)(例如Lua解釋器)可以得益于這個(gè)特點(diǎn)仑扑,使得在進(jìn)行尾調(diào)用時(shí)不耗費(fèi)任何椑谰簦空間。
將這種實(shí)現(xiàn)稱之為支持“尾調(diào)用消除”镇饮。
--]]

由于“尾調(diào)用”不會(huì)耗費(fèi)楎阎瘢空間,所以一個(gè)程序可以擁有無(wú)數(shù)嵌套的“尾調(diào)用”储藐。

function fn(n)
  if n>0 then 
    return fn(n-1)
  end
end

--[[
在調(diào)用fn()函數(shù)時(shí)俱济,傳入任何數(shù)字n作為參數(shù)都不會(huì)造成棧溢出。
--]]

有一點(diǎn)需要注意的是钙勃,當(dāng)想要受益于“尾調(diào)用消除”時(shí)蛛碌,務(wù)必要確定當(dāng)前的調(diào)用時(shí)一條“尾調(diào)用”。判斷的準(zhǔn)則是“一個(gè)函數(shù)在調(diào)用完另一個(gè)函數(shù)之后辖源,是否就無(wú)其他事情需要做了”左医。有些看似“尾調(diào)用”的代碼,其實(shí)都違背了這條準(zhǔn)則同木。

function fn(x)
  func(x)
end

--[[
當(dāng)調(diào)用完func()函數(shù)后浮梢,fn()函數(shù)并不能立即返回,它還需丟棄func()函數(shù)返回的臨時(shí)結(jié)果彤路。
--]]

Lua中秕硝,只有return <func>(<args>)這樣的調(diào)用形式才算是一條“尾調(diào)用”。

return fn(x)+1 -- 必須做一次加法

return x or fn(x) -- 必須調(diào)整為一個(gè)返回值

return (g(x)) -- 必須調(diào)整為一個(gè)返回值

Lua在調(diào)用前對(duì)<func>及其參數(shù)求值洲尊,所以它們可以是任意復(fù)雜的表達(dá)式远豺。

return x[i].fn(x[j]+a*b, i+j)

一條“尾調(diào)用”就好比是一條goto語(yǔ)句。因此坞嘀,在Lua中“尾調(diào)用”的一大應(yīng)用就是編寫(xiě)“狀態(tài)機(jī)(state machine)”躯护。這種程序通常以一個(gè)函數(shù)來(lái)表示一個(gè)狀態(tài),改變狀態(tài)就是goto(或調(diào)用)到另一個(gè)特定的函數(shù)和丽涩。

例如:一個(gè)簡(jiǎn)單的迷宮游戲中棺滞,一個(gè)迷宮有幾間房間,每間房中最多有東南西北4扇門(mén)矢渊。用戶在每一步異動(dòng)中都需要輸入一個(gè)移動(dòng)的方向继准。如果在某個(gè)方向上有門(mén),那么用戶可以進(jìn)入相應(yīng)的房間矮男。不然移必,程序就打印一條警告。游戲的目標(biāo)就是讓用戶從最初的房間走到最終的房間毡鉴。

這個(gè)游戲就是典型的狀態(tài)機(jī)崔泵,其中當(dāng)前房間就是一個(gè)狀態(tài)秒赤。可以將迷宮的每間房實(shí)現(xiàn)為一個(gè)函數(shù)憎瘸,并使用“尾調(diào)用”來(lái)實(shí)現(xiàn)從一間房移動(dòng)到另一件倒脓。

function room1()
    local move = io.read()
    if move=='south' then 
        return room3()
    elseif move=="east" then 
        return room2()
    else 
        print("invalid move")
        return room1()
    end
end

function room2()
    local move = io.read()
    if move=='south' then
        return room4()
    elseif move=='west' then
        return room1()
    else
        print('invalid move')
        return room2()
    end
end

function room3()
    local move=io.read()
    if move=='north' then
        return room1()
    elseif move=='ease' then
        return room4()
    else
        print("invalid move")
        return room3()
    end
end

function room4()
    print("congratulations")
end
-- 調(diào)用初始房間來(lái)開(kāi)始游戲
room1()

--[[
若沒(méi)有“尾調(diào)用消除”,每次用戶的異動(dòng)都會(huì)創(chuàng)建一個(gè)新的棧層(stack level)
異動(dòng)若干步后就有可能會(huì)導(dǎo)致棧溢出
“尾調(diào)用消除”則對(duì)用戶異動(dòng)的次數(shù)沒(méi)有任何限制
因?yàn)槊看萎悇?dòng)實(shí)際上都只是完成一條goto語(yǔ)句到另一個(gè)函數(shù)含思,而非傳統(tǒng)的函數(shù)調(diào)用崎弃。
--]]

對(duì)于簡(jiǎn)單的游戲而言,或許覺(jué)得將程序設(shè)計(jì)為數(shù)據(jù)驅(qū)動(dòng)的會(huì)更好一些含潘,其中將房間和異動(dòng)記錄在table中饲做。不過(guò),如果游戲中每間房間都有各自特殊的情況的話遏弱,采用這種狀態(tài)機(jī)的設(shè)計(jì)則更為合適盆均。

Lua迭代器與閉包

迭代器是一種可以遍歷(iterate over)集合中所有元素的機(jī)制。在Lua中通常將迭代器表示為函數(shù)漱逸,每次調(diào)用函數(shù)即返回集合中的“下一個(gè)”元素泪姨。

每個(gè)迭代器都需要在每次成功調(diào)用之間保持一些狀態(tài),這樣才能知道它所在的位置及如何步進(jìn)到下一個(gè)位置饰抒。閉包(closure)對(duì)于這類任務(wù)提供極佳的支持肮砾,一個(gè)閉包就是一種可以訪問(wèn)其外部嵌套環(huán)境中的局部變量的函數(shù)。

對(duì)于閉包而言袋坑,這些變量就可用于在成功調(diào)用之間保持狀態(tài)值仗处,從而使閉包可以記住它在一次遍歷中所在的位置。

當(dāng)然枣宫,為了創(chuàng)建一個(gè)新的閉包婆誓,還必須創(chuàng)建它的“非局部變量(non-local variable)”。因此也颤,一個(gè)閉包結(jié)構(gòu)通常涉及到兩個(gè)函數(shù):閉包本身和一個(gè)用于創(chuàng)建該閉包的工廠函數(shù)洋幻。

-- 為列表編寫(xiě)簡(jiǎn)單的迭代器,與ipaires不同的是該迭代器并不是返回每個(gè)元素的索引翅娶,而是返回元素的值文留。
-- values是一個(gè)工廠,每當(dāng)調(diào)用這個(gè)工廠時(shí)故觅,就創(chuàng)建一個(gè)新的閉包(迭代器本身)厂庇。這個(gè)閉包將它的狀態(tài)保存在其外部變量tbl和i中。
function values(tbl)
    local i=0
    return function()
        i = i+1
        return tbl[i]
    end
end

-- 每當(dāng)調(diào)用這個(gè)迭代器時(shí)输吏,它就從列表tbl中返回下一個(gè)值。
-- 直到最后一個(gè)元素返回后替蛉,迭代器就會(huì)返回nil贯溅,以此表示迭代器的結(jié)束拄氯。
tbl={1,2,3,4}
iter = values(tbl) -- 創(chuàng)建迭代器
while true do
    local el = iter() -- 調(diào)用迭代器
    if el==nil then
        break
    end
    print(el)
end

然而,使用泛型for則更為簡(jiǎn)單它浅,你會(huì)發(fā)現(xiàn)泛型for正是為這種迭代而設(shè)計(jì)的译柏。

function values(tbl)
    local i=0
    return function()
        i = i+1
        return tbl[i]
    end
end

-- 泛型for為一次迭代循環(huán)做了所有的薄記工作。
-- 它在內(nèi)部保存了迭代器函數(shù)姐霍,因此不再需要iter變量鄙麦。
-- 它在每次新迭代時(shí)調(diào)用迭代器,并在迭代器返回nil時(shí)結(jié)束循環(huán)镊折。
tbl={1,2,3,4}
for el in values(tbl) do
    print(el)
end

需求:遍歷當(dāng)前輸入文件中所有單詞的迭代器
為完成這樣的遍歷胯府,需要保持兩個(gè)值:當(dāng)前行的內(nèi)容、當(dāng)前行所處的位置恨胚。有了這些信息就可以不斷產(chǎn)生下一個(gè)單詞骂因。迭代器函數(shù)的主要部分使用 string.find在當(dāng)前行中以當(dāng)前位置作為起始來(lái)所搜索一個(gè)單詞。使用模式%w+來(lái)描述一個(gè)單詞赃泡,它用于匹配一個(gè)或多個(gè)的文字/數(shù)字字符寒波。如果string.find找到了一個(gè)單詞,迭代器就會(huì)將當(dāng)前位置更新為該單詞之后的第一個(gè)字符升熊,并返回該單詞俄烁。否認(rèn)則,它就讀取新的一行并反復(fù)這個(gè)搜索過(guò)程级野。若沒(méi)有剩余的行猴娩,則返回nil表示迭代的結(jié)束。

function allwords()
    local line = io.read()
    local pos = 1

    return function()
        while line do
            local s,e = string.find(line, "%w+", pos)
            if s then
                pos = e+1
                return string.sub(line,s,e)
            else
                line = io.read()
                pos = 1
            end
        end
        return nil
    end
end

for word in allwords() do
    print(word)
end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末勺阐,一起剝皮案震驚了整個(gè)濱河市卷中,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌渊抽,老刑警劉巖蟆豫,帶你破解...
    沈念sama閱讀 221,430評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異懒闷,居然都是意外死亡十减,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)愤估,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)帮辟,“玉大人,你說(shuō)我怎么就攤上這事玩焰∮删裕” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,834評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵昔园,是天一觀的道長(zhǎng)蔓榄。 經(jīng)常有香客問(wèn)我并炮,道長(zhǎng),這世上最難降的妖魔是什么甥郑? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,543評(píng)論 1 296
  • 正文 為了忘掉前任逃魄,我火速辦了婚禮,結(jié)果婚禮上澜搅,老公的妹妹穿的比我還像新娘伍俘。我一直安慰自己,他們只是感情好勉躺,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,547評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布癌瘾。 她就那樣靜靜地躺著,像睡著了一般赂蕴。 火紅的嫁衣襯著肌膚如雪柳弄。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,196評(píng)論 1 308
  • 那天概说,我揣著相機(jī)與錄音碧注,去河邊找鬼。 笑死糖赔,一個(gè)胖子當(dāng)著我的面吹牛萍丐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播放典,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼逝变,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了奋构?” 一聲冷哼從身側(cè)響起壳影,我...
    開(kāi)封第一講書(shū)人閱讀 39,671評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弥臼,沒(méi)想到半個(gè)月后宴咧,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,221評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡径缅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,303評(píng)論 3 340
  • 正文 我和宋清朗相戀三年掺栅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纳猪。...
    茶點(diǎn)故事閱讀 40,444評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氧卧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氏堤,到底是詐尸還是另有隱情沙绝,我是刑警寧澤,帶...
    沈念sama閱讀 36,134評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站宿饱,受9級(jí)特大地震影響熏瞄,放射性物質(zhì)發(fā)生泄漏脚祟。R本人自食惡果不足惜谬以,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,810評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望由桌。 院中可真熱鬧为黎,春花似錦、人聲如沸行您。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,285評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)娃循。三九已至炕檩,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捌斧,已是汗流浹背笛质。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,399評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留捞蚂,地道東北人妇押。 一個(gè)月前我還...
    沈念sama閱讀 48,837評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像姓迅,于是被迫代替她去往敵國(guó)和親敲霍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,455評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Lua 5.1 參考手冊(cè) by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,831評(píng)論 0 38
  • 雖然計(jì)算機(jī)可以做很多事情丁存,但它不會(huì)思考肩杈,它需要接受系統(tǒng)化的指令來(lái)工作。大部分用戶通過(guò)應(yīng)用程序?yàn)橛?jì)算機(jī)指派任務(wù)解寝,軟件...
    JunChow520閱讀 7,160評(píng)論 0 4
  • 《超級(jí)個(gè)體-伽藍(lán)214》85/100,1.31日打卡嘉抓,天氣多云 【三件事】 1. 聽(tīng)每日得到音頻 2. 閱讀+筆記...
    伽藍(lán)214閱讀 101評(píng)論 0 0
  • 2018年04月26日 星期四 親子日記第110天 今天孩子進(jìn)行了期中考試索守,于是下午我早早回到家,給孩子做...
    夢(mèng)_0ba6閱讀 199評(píng)論 0 1
  • 23歲之前從未到過(guò)我現(xiàn)在所在的城市抑片,畢業(yè)之前“奉命”來(lái)到佛山卵佛,格外陌生…… 天下著大雨,我從學(xué)校出...
    D034雨愛(ài)雨_佛山閱讀 75評(píng)論 1 6