前言 還有個迭代器,基礎語法基本已經說完了,後面想到啥再補充,之後的教程會從以下方面來講: 基礎庫的使用,比如string、table等 基礎控制項的使用,比如listview、tab等 aardio和Python交互,比如給Python寫個界面 自帶的範常式序 我寫的一些小程式 當然,我的理解也是很 ...
前言
還有個迭代器,基礎語法基本已經說完了,後面想到啥再補充,之後的教程會從以下方面來講:
- 基礎庫的使用,比如string、table等
- 基礎控制項的使用,比如listview、tab等
- aardio和Python交互,比如給Python寫個界面
- 自帶的範常式序
- 我寫的一些小程式
當然,我的理解也是很基礎的,特別是在界面設計上,我都是用的預設控制項的預設設置,不會去自定義控制項內容。要想做出特別炫酷的程式,你還得依賴其他語言和工具的基礎。例如用HTML和CSS來實現界面。
元表、元方法
參考文檔:
https://bbs.aardio.com/doc/reference/libraries/kernel/table/meta.html
https://bbs.aardio.com/doc/reference/the%20language/operator/overloading.html
主要是用於重載運算符和內置函數的行為。
表可以定義另一個表作為元表,然後在元表裡定義元方法來定義操作符或內置函數操作表的一些行為。這種類似於Python的魔法方法,在Python中使用__eq__
定義==
的行為,而在aardio中用_eq
元方法來定義==
的行為。
初級使用例子
舉個例子,python中print會調用對象的__str__
或__repr__
來列印一個對象,而aardio也是調用tostring來列印一個對象,但表預設並沒有定義_tostring
元方法,導致列印出來的內容是table: 03B2E3A8
的格式
我們可以通過給表定義_tostring
元方法,來使io.print
或者console.log
正常顯示表
import console;
io.open()
var tab = {
a=1;
b=2;
@{
_tostring = function(...) {
// 元方法里不能調用觸發元方法的函數,比如_tostring里不能調用tostring
// _get元方法可以通過[[k]]運算符來避開元方法,通過.和[]會觸發_get,而[[]]不會
return table.tostring(owner);
}
}
}
io.print("沒有定義元方法" , {});
io.print("定義了元方法" , tab);
console.pause(true);
輸出如下:
沒有定義元方法 table: 03A8E2F8
定義了元方法 {
a=1;
b=2
}
運算符重載
這個就不細說了,應該很容易理解。
io.open(); tab = { x=10 ; y=20 };
tab2 = { x=12 ; y=22 }
//c = tab + tab2; //這樣肯定會出錯,因為 table預設是不能相加的
//創建一個元素,元表中的__add函數重載加運算符。
tab@ = {
_add = function(b) {
return owner.x + b.x
};
}
c = tab + tab2; //這時候會調用重載的操作符 tab@._add(tab2)
io.print( c ) //顯示22
入門使用例子
還有一個很常用的元方法是_get
和_set
,是定義訪問對象屬性時觸發的。利用這個可以讓代碼量少很多,看起來邏輯也更清晰。
這裡舉個實際例子,我在封裝sunny的時候,遇到個很累人的事。sunny的dll導出函數,返回值有些是指針,你需要手動給他轉成字元串,而且還需要手動釋放這個指針指向的記憶體,也就是說你調用一次導出函數,就得寫至少三行代碼(調用、轉字元串和釋放)。
那麼,有沒有一種方法,定義完這個導出函數,在使用的時候就調用函數釋放記憶體,並轉成字元串返回,而不用我每次都手動釋放和轉字元串。
先定義request類,現在只需要給它定義一個messageId屬性和_meta元方法:
namespace sunny;
class request{
ctor(messageId){
this.messageId = messageId;
}
@_meta;
}
@後面跟的是元表的名稱,你可以將元表定義在名字空間里,這樣看起來代碼更舒服。下麵在類的名字空間里定義dll方法和元表.
namespace request{
//釋放指針的函數
Free = ::SunnyDLL.api("Free","void(pointer p)");
// 下麵的函數第一個參數都是messageId
DelRequestHeader = ::SunnyDLL.api("DelRequestHeader","void(int id,str h)");
GetRequestBodyLen = ::SunnyDLL.api("GetRequestBodyLen","int(int id)");
GetRequestBody = ::SunnyDLL.api("GetRequestBody","pointer(int id)");
// 定義一個中間方法
// name是要調用的導出函數,messageId則是導出函數的第一個參數
xcall = function(name, messageId, len){
var func = self[name];
if(!func) error("不支持的函數!");
function proxyFunc(...){
var v = func(messageId, ...);
var result;
if(type(v) == type.pointer){
if(len) result = ..raw.tostring(v,1,len);
else result = ..raw.tostring(v);
Free(v);
}else{
result = v;
}
return result;
}
return proxyFunc;
}
// 定義元表
_meta = {
_get = function(k){
return xcall(k, owner.messageId);
}
}
}
這個代碼初看可能有點費勁,我們拆解著來看。
首先前面幾行只是定義了四個dll的導出函數,然後下麵定義了_meta
這個表。
而_meta里只定義了一個元方法_get
,它的作用是當你訪問對象的屬性時會觸發這個方法,然後給你返回值。比如我先實例化一個request對象
r = request(111111);
// 當訪問r.GetRequestBody時,這個對象沒有GetRequestBody屬性,所以會觸發_get元方法
// 得到的返回值就是 返回它的返回值也就是`xcall("GetRequestBody", owner.messageId)`.
console.log(r.GetRequestBody)
這裡的owner就是指r這個對象。然後定義了xcall這個函數,它裡面又定義了一個函數proxyFunc,並將它作為返回值,這種被稱為閉包。先分析下xcall方法
// 這裡的self指的是當前名字空間,也就是request,name則是需要調用的方法名,例如是GetRequestBody
// 這裡func的值就等於GetRequestBody,也就是::SunnyDLL.api("GetRequestBody","pointer(int id)");
var func = self[name];
// 如果func是null的話,說明當前名字空間下沒有這個函數,也就不是我們定義的sunny導出函數
if(!func) error("不支持的函數!");
// 定義了proxyFunc函數,`xcall(k, owner.messageId)`返回的值就是proxyFunc函數,這裡的三個點表示傳入任意個參數,類似於Python中的*args
function proxyFunc(...){
// 調用GetRequestBody(messageId, ...)
var v = func(messageId, ...);
// 定義返回結果
var result;
// 如果結果是指針的話
if(type(v) == type.pointer){
// 就把它轉為字元串,二進位數據需要指定長度,不然就是到\0結束
if(len) result = ..raw.tostring(v,1,len);
else result = ..raw.tostring(v);
// 調用導出函數釋放記憶體
Free(v);
}else{
// 如果是其他類型數據就直接返回,比如數值或null
result = v;
}
return result;
}
這樣一番折騰,起了什麼效果呢,看一下下麵兩段代碼,如果不利用元方法的話,你使用dll導出函數得這麼寫
// 導入request名字空間
improt request;
// 調用名字空間下的函數
var messageId = 111111
var pResult = request.GetRequestBody(messageId);
// 將指針轉為字元串
var result = raw.tostring(pResult,1);
// 釋放記憶體
request.Free(pResult);
// 再使用其他導出函數也需要重覆寫這幾行代碼
看著就幾行代碼,但是你想想調用一個函數都得寫好幾行,如果調用多次呢。而定義了xcall和_meta之後,只需要這樣寫代碼:
improt request;
var messageId = 111111;
var req = request(messageId);
var result = req.GetRequestBody();
// 後面調用都只需要用req.方法名()調用,不需要管raw.tostring和Free了
因為req是可以復用的,所以我調用任何導出函數都只需要寫一行代碼,使用sunny庫的代碼也變得更簡潔易懂了。
官方例子
給表創建一個代理,監控表屬性的訪問和設置:
// 創建一個代理,為另一個table對象創建一個替身以監控對這個對象的訪問
function table.createProxy(tab) {
var real = tab;//在閉包中保存被代理的數據表tab
var _meta = {
_get = function(k){
io.print(k+"被讀了");
return real[k];
};
_set = function (k,v){
io.print(k+"被修改值為"+v)
real[k]=v; //刪除這句代碼就創建了一個只讀表
}
}
var proxy = {@_meta};//創建一個代理表
return proxy; //你要訪問真正的表?先問過我吧,我是他的經紀人!!!
}
//下麵是使用示例
tab = {x=12;y=15};
proxy = table.createProxy(tab);//創建一個代理表,以管理對tab的存取訪問
io.open();
c = proxy.x; //顯示 "x被讀了"
proxy.y = 19; //顯示 "y被修改值為19"
io.print(proxy.y); //顯示 "y被讀了" 然後顯示19
所有的元方法
元方法/屬性 | 函數定義 | Python中的魔法方法 | 說明 |
---|---|---|---|
_weak |
用不到 | ||
_type |
屬性 | type(obj)函數的行為 | |
_readonly |
屬性 | 等於false,_ 開頭的成員也不是只讀屬性 |
|
_defined |
感覺沒啥用 | ||
_keys |
屬性 | 可用於table.keys等函數動態獲取對象的鍵名列表(例如動態生成鍵值對的外部JS對象可使用這個元方法返回成員名字列表 | |
_startIndex |
屬性 | 用於table.eachIndex等函數動態指定數組的開始下標。 | |
_get |
function(k,ownerCall) |
__getattr__ 和__getitem__ |
如果讀取表中不存在的鍵會觸發_get元方法並返回值 |
_set |
function(k,v) |
__setattr__ 和__setitem__ |
當你給表的一個缺少的鍵賦值時會觸發_set元方法 |
_tostring |
function(...) |
__str__ 和__repr__ |
tostring(obj, ...) |
_tonumber |
function() |
tonumber(obj) | |
_json |
function() |
web.json.stringify(obj),可返回一個可被轉化為json的值。或者返回一個字元串和true | |
_toComObject |
用於自定義一個表對象如何轉換為 COM 對象,可定義為函數,也可以直接定義為對象 | ||
_eq |
function(b) |
__eq__ 和__ne__ |
== 和!= ,比較對象時,兩個對象的_eq必須是同一個 |
_le |
function(b) |
__le__ 和__ge__ |
<= 和>= |
_lt |
function(b) |
__lt__ 和__gt__ |
< 和> |
_add |
function(b) |
__add__ |
+ |
_sub |
function(b) |
__sub__ |
- |
_mul |
function(b) |
__mul__ |
* |
_div |
function(b) |
__truediv__ |
/ |
_lshift |
function(b) |
__lshift__ |
<< 左移 |
_rshift |
function(b) |
__rshift__ |
>> 右移 |
_mod |
function(b) |
__mod__ |
% 取模 |
_pow |
function(b) |
__pow__ |
** 冪運算 |
_unm |
function() |
__neg__ |
- 負號 |
_len |
function() |
__len__ |
# 取長運算符,Python中則為len函數 |
_concat |
function(b) |
++ 連接運算符 |
|
_call |
function(...) |
__call__ |
對象當函數來調用 |
屬性元表
不僅可以給對象定義元表,也可以給對象的屬性定義一個元表,有點類似於Python中的property,可以控制屬性修改和獲取的行為。
如果要看例子的話,可以在aardio的目錄全局搜下@_metaProperty
以使用最多的屬性text
為例,基本每個控制項都有一個text屬性,你可以很方便的通過.text
獲取和修改空間顯示的文字。
其實不用屬性元表也能實現這個效果,代碼如下:
import console;
class staticText{
getText = function(){
..console.log("獲取到界面文本內容")
};
setText = function(v){
..console.log("將文本("+v+")顯示到界面控制項上")
}
@_meta;
}
namespace staticText{
_meta = {
_get = function(k){
if(k == "text"){
return owner.getText();
}
};
_set = function(k,v){
if(k == "text"){
return owner.setText(v);
}
}
}
}
s = staticText()
console.log(s.text);
s.text = "修改文本";
console.pause(true);
但是如果屬性多了的話,就需要一堆的if來判斷屬性,所以aardio作者就引入了metaProperty
這個功能。這樣寫的代碼看起來更簡潔和清晰,用法如下:
import console;
import util.metaProperty;
class staticText{
getText = function(){
..console.log("獲取到界面文本內容")
};
setText = function(v){
..console.log("將文本("+v+")顯示到界面控制項上")
}
@_metaProperty;
}
namespace staticText{
_metaProperty = ..util.metaProperty(
text = {
_get = function(){
return owner.getText();
};
_set = function(v){
return owner.setText(v);
}
};
// 可以寫其他屬性
);
// 可以列印下_metaProperty看看
..console.dump(_metaProperty);
}
s = staticText()
console.log(s.text);
s.text = "修改文本";
console.pause(true);
本文由博客一文多發平臺 OpenWrite 發佈!