目錄
· 一些廢話
· 測(cè)試代碼
· 代碼說(shuō)明
· 基類(lèi)(父類(lèi)) A
· 派生類(lèi)(子類(lèi)) B
· 子類(lèi)對(duì)象操作私有變量
· 繼續(xù)往下繼承
別人的經(jīng)驗(yàn),我們的階梯!
一些廢話
Lua語(yǔ)言是一個(gè)小而美的語(yǔ)言,使用者不多。
估計(jì)閱讀這篇文章的人也不會(huì)多,姑且當(dāng)做一篇筆記吧。
這篇文章主要描述:在Lua語(yǔ)言中,如何通過(guò)table結(jié)構(gòu)來(lái)實(shí)現(xiàn)面向?qū)ο缶幊獭?/P>
主要是看到某鳥(niǎo)教程上錯(cuò)誤百出,估計(jì)示例代碼自己都沒(méi)有測(cè)試過(guò);
關(guān)于Lua語(yǔ)言中的table以及metatable的基本知識(shí),這里就不贅述了,官方手冊(cè)中描述的很清楚。
測(cè)試代碼


執(zhí)行結(jié)果如下:

代碼說(shuō)明
基類(lèi)(父類(lèi)) A
首先來(lái)分析下4-25行的代碼。
4-9行:定義父類(lèi)A的成員變量和函數(shù)(按照C++中的習(xí)慣,可以叫做方法),可以看出Lua語(yǔ)言中的函數(shù)是“一等公民”,是可以賦值給一個(gè)變量的。
11-16行:相當(dāng)于是構(gòu)造函數(shù),用來(lái)創(chuàng)建一個(gè)父類(lèi)A的對(duì)象。
18-20行:給父類(lèi)A增加一個(gè)函數(shù),待會(huì)在分析子類(lèi)B的時(shí)候再說(shuō)。
22行:調(diào)用A:new()函數(shù),創(chuàng)建一個(gè)類(lèi)A的對(duì)象,賦值給變量objA。
在A:new()函數(shù)中,關(guān)鍵是第13行代碼:此時(shí)self等于A,就相當(dāng)于是A.__index = A,這是合法的。
因?yàn)楹瘮?shù)的調(diào)用方式是A:new(),Lua的語(yǔ)法糖會(huì)把A作為第一個(gè)參數(shù)傳遞給new()函數(shù)的第一個(gè)隱藏參數(shù)self。
然后執(zhí)行14行的setmetatable(t, self),相當(dāng)于把表t的元表設(shè)置為A。
以上兩行搞明白之后,23-24行的打印語(yǔ)句就簡(jiǎn)單了:
23行:因?yàn)楸韔bjA中沒(méi)有成員a,但是objA被設(shè)置了元表A,而且該元表A帶有__index屬性,該屬性的值是表A自己,于是就到A中查找是否有成員a,于是就打印出:

__index 屬性的值,可以是一個(gè)表,可以是一個(gè)函數(shù);
只不過(guò)這里特殊一點(diǎn):__index 設(shè)置為 A 自己;
24行:查找函數(shù)的過(guò)程是一樣的,找到元表A的__index屬性的值,也就是表A自己中的funcA函數(shù),然后調(diào)用,打印出:

派生類(lèi)(子類(lèi)) B
28-33行:定義了子類(lèi)B,其實(shí)它也是一個(gè)對(duì)象。
在創(chuàng)建函數(shù)A:new(t)中,參數(shù)t的值是:

此時(shí),self仍然是父類(lèi)A,B的創(chuàng)建過(guò)程與objA的創(chuàng)建過(guò)程是一樣的,只不過(guò)給參數(shù)t設(shè)置了子類(lèi)B自己的成員變量和函數(shù)。
所以,B的元表被設(shè)置為A(14行代碼的功勞),當(dāng)然了A的__index仍然被設(shè)置為A自己。
關(guān)鍵是35行:objB = B:new(),得仔細(xì)嘮嘮。
子類(lèi)B并沒(méi)有自己的new函數(shù),但是類(lèi)B(也是一個(gè) table) 的元表被設(shè)置為A,并且A.__index = A,所以最終就找到了A中的new函數(shù),也就是11-16行代碼。
進(jìn)入這個(gè)函數(shù)中時(shí),第一個(gè)隱藏參數(shù)self被設(shè)置為 B 了,因?yàn)楹瘮?shù)調(diào)用形式是:B:new()。
所以:
13 行 self.__index = self 相當(dāng)于設(shè)置 B.__index = B
14 行 etmetatable(t, self) 相當(dāng)于把表 t 的元表設(shè)置為 B
new()函數(shù)返回之后,就把t賦值給objB。
下面再看一下36-39行的打印語(yǔ)句:

36行:objB中并沒(méi)有成員a,但是objB的元表是B,而且B.__index = B,所以就到B中去查找a。
雖然B中也沒(méi)有a,但是B的元表是A,而且A.__index = A,所以就在A中找到了成員a,打印出:

37行:objB中并沒(méi)有成員b,但是objB的元表是B,而且B.__index = B,所以在B中找到了成員b,因此打印出:

37和38行的查找過(guò)程是類(lèi)似的,只不過(guò)換成了函數(shù)而已。
子類(lèi)對(duì)象操作自己的變量
41行:objB:myadd(10)。
查找myadd函數(shù)的過(guò)程與查找obj.a(chǎn)的過(guò)程是一樣的,這里再?lài)Z叨一遍:
1. objB 中并沒(méi)有函數(shù) myadd,但是 objB 的元表是 B,而且 B.__index = B,所以就到 B 中去查找 myadd;
2. 雖然 B 中也沒(méi)有 myadd,但是 B 的元表是 A,而且 A.__index = A,所以就在 A 中找到了函數(shù) myadd;
于是就調(diào)用了函數(shù):

而且self等于objB,因此函數(shù)體中就等于是:

加法表達(dá)式中的objB.a(chǎn)的讀取過(guò)程,上面已經(jīng)描述過(guò)了,最終定位到的是父類(lèi)A中的a,即:1。
1 + 10 = 11,然后把11賦值給objB.a(chǎn)。
在賦值操作中,被賦值的objB.a(chǎn)就不再是父類(lèi)A中的那個(gè)a了!
因?yàn)閛bjB本質(zhì)是一個(gè)table,給objB設(shè)置鍵值對(duì)的時(shí)候:
1. 如果鍵已經(jīng)存在了,那么就直接設(shè)置該鍵的值;
2. 如果鍵不存在,那么 lua 會(huì)看它的元表中是否有 __newindex 字段(可以是一個(gè)table,也可以是一個(gè)函數(shù));
2-1. 如果有 __newindex 字段,那么就是調(diào)用 __newindex (如果是一個(gè)函數(shù)),或者在 __newindex 中添加鍵值對(duì)(如果是一個(gè)table);
2-2. 如果沒(méi)有 __newindex 字段,那么就直接在 objB 中存儲(chǔ)該鍵值對(duì);
根據(jù)上面這個(gè)規(guī)則,就會(huì)設(shè)置objB.a(chǎn) = 11。
明白以上這些之后,42和43行的打印語(yǔ)句就不復(fù)雜了。
42行:objA最終找到的a是父類(lèi)A中的成員a,打印出:objA.a(chǎn) = 1。
43行:objB中自己已經(jīng)有了成員a,所以打印出:objB.a(chǎn) = 11。
繼續(xù)往下繼承
有了上面的基礎(chǔ),再?gòu)淖宇?lèi)B中派生出類(lèi)C,C派生出類(lèi)D... 都不是什么問(wèn)題了,如下所示:

感興趣的讀者可以自己測(cè)試一下。