บทความก่อนหน้า
บทสรุป
เขียนมาได้ 13 ตอนแล้วเป็นบทความชุดยาวมากสำหรับคนขี้เกียจแบบผมลากมาข้ามปีเลยไม่ค่อยได้เขียนอะไรวิชาการแบบนี้อยากจะให้เป็นบทความอ่านง่ายสำหรับมือใหม่หัดเขียนโปรแกรมแต่ไม่รู้จะอ่านกันรู้เรื่องหรือเปล่า 555 หัวข้อก็โดดไปมาบางเนื้อหาก็ข้ามไปเพราะไม่อยากเขียนซ้ำกับบทความเก่า บางส่วนก็อาจมีตกหล่นหรืออธิบายไม่ละเอียดไปบ้างใครที่หลงเข้ามาอ่านอยากแนะนำหรือสอบถามก็ลงในคอมเมนท์กันได้ครับ ( แต่อาจไม่ได้ตอบทันที ) สำหรับตอนนี้ก็จะสรุปเนื้อหาที่ผ่านมา trick และ tip ที่พอจะนึกออก และเนื้อหาเพิ่มเติมที่ตกหล่นไปครับ
ประเภทข้อมูล
nil ข้อมูลประเภท nil ใช้แสดงว่าไม่มีข้อมูลและใช้ในการลบข้อมูล boolean ข้อมูลประเภทตรรกะประกอบด้วย true, false number ข้อมูลประเภทตัวเลข integer ข้อมูลจำนวนเต็ม เป็นประเภทย่อย ( sub type ) ของตัวเลข มีในรุ่น 5.3 ขึ้นไป float ข้อมูลจำนวนทศนิยม เป็นประเภทย่อย ( sub type ) ของตัวเลข มีในรุ่น 5.3 ขึ้นไป string ข้อมูลข้อความตัวอักษร table ข้อมูลประเภทโครงสร้างแบบคู่ลำดับ key, value ที่ประกอบด้วยข้อมูลประเภทอื่นๆ หรือประเภทเดียวกัน function ข้อมูลที่เป็นกลุ่มคำสั่งที่สามารถเรียกใช้ได้ thread ข้อมูลที่สร้างขึ้นมาจากการใช้ฟังก์ชัน coroutine.create() userdata ข้อมูลที่สร้างมาจากมอดูลภาษา C ( หรือฟังก์ชันภายในอย่าง io.open() เป็นต้น )
ว่าด้วยเรื่องของ Type system
มีคำศัพท์ที่อาจทำให้คนสับสนระหว่าง dynamic/static type กับ strong/weak type สองตัวแรกเป็นเรื่องของ type checking คือการตรวจประเภทข้อมูลภาษาที่เป็น dynamic จะไม่ต้องกำหนดประเภทข้อมูลให้กับตัวแปรและสามารถกำหนดค่าใดๆ ให้ตัวแปรก็ได้ในขณะที่ภาษาแบบ static จะต้องมีการกำหนดประเภทข้อมูลที่ชัดเจน ( type declaration ) หรืออาจกำหนดจากข้อมูลตั้งต้นที่ใส่ในตัวแปรหรืออนุมานจากกฏของภาษาและกำหนดประเภทให้อัตโนมัติก็ได้ ( type inference ) และตัวแปรนั้นจะไม่สามารถใส่ค่าที่มีประเภทข้อมูลต่างกันได้อีก
ตัวอย่าง dinamic type-- ภาษา Lua a = 10 -- type(a)==number a = "hello" -- type(a)==string
ตัวอย่าง static type// ภาษา Go package main import "fmt" func main() { var a string // ประกาศตัวแปร "a" เป็นประเภท string a = "a is string.\n" b := 10 // "b" เป็นตัวแปรประเภท integer มีค่าเท่ากับ 10 fmt.Printf(a) b = "aaa" // บรรทัดนี้ error "cannot use "aaa" (type string) as type int in assignment" fmt.Printf(a) }
ส่วนอีกสองอย่างหลังเป็นเรื่องของ type safety คือช่องโหว่หรือการเปลี่ยนแปลงประเภทของค่าในตัวแปร strong คือจะไม่มีการเปลี่ยนแปลงในขณะที่ weak จะยอมให้มีการเปลี่ยนแปลงได้เช่นมี implicit type conversion ( coercion ) คือการเปลี่ยนประเภทข้อมูลอัตโนมัติโดยที่เราไม่ได้เป็นผู้กำหนดเป็นต้น แต่การแบ่งประเภท strong/weak นั้นไม่เป็นทางการชัดเจนเหมือน dynamic/static เช่น JavaScript นั้นแข็งแรงกว่า PHP แต่อ่อนกว่า Lua ยิ่งภาษาที่ safety น้อยก็มีโอกาสที่เราจะเขียนโปรแกรมผิดพลาดได้ง่ายและหาจุดผิดยากจึงควรรู้ถึงลักษณะการทำงานของภาษาที่ใช้และระมัดระวังในการเขียน และควรทดสอบการรับส่งค่าต่างๆ ให้ดี
-- ภาษา Lua a = "10" b = 10 print(a == b) -- ได้ false print(1 == true) -- ได้ false print(not 0) -- ได้ false ( 0 มีค่าเทียบเท่า true ) print(not "") -- ได้ false ( "" มีค่าเทียบเท่า true ) print(not {}) -- ได้ false ( {} มีค่าเทียบเท่า true ) print(not nil) -- ได้ true ( nil มีค่าเทียบเท่า false ) print(nil == false) -- ได้ false ( nil เทียบเท่า false แต่ nil ไม่ใช่ false ) -- แต่ Lua ก็มี coercion เล็กน้อยเช่น print(a+b) -- ได้ 20.0 print(a..b) -- ได้ "1010"
# ภาษา Python a = "10" b = 10 print(a == b) # ได้ False print(1 == True) # ได้ True แทน True ด้วย 1 ได้ print(0 == False) # ได้ True แทน False ด้วย 0 ได้ print(not []) # ได้ True ( [] มีค่าเทียบเท่า False ) print([] == False) # ได้ False ( [] เทียบเท่า False แต่ [] ไม่ใช่ False ) print(not "") # ได้ True ( "" มีค่าเทียบเท่า False ) print("" == False) # ได้ False ( "" เทียบเท่า False แต่ "" ไม่ใช่ False ) print(a + b) # เอาเลข 10 มา่ต่อกับข้อความ "10" ไม่ได้ TypeError: cannot concatenate 'str' and 'int' objects print(b + a) # เอาข้อความ "10" มาบวกกับเลข 10 ไม่ได้ TypeError: unsupported operand type(s) for +: 'int' and 'str'
// ภาษา PHP $a = "10abc"; $b = 10 if ($a == $b) { // แปลง $a เป็นตัวเลขได้ 10 echo $a." == ". $b; // ได้ "10abc == 10" แปลงเลข 10 เป็นข้อความ "10" มาต่อกับข้อความ "10abc ==" } if ($a === $b) { // === จะตรวจสอบ type ด้วย echo $a." === ". $b; // บรรทัดนี้ไม่ทำงานเพราะคนละ type กัน } echo "10abc" + "10"; // ได้ 20 แปลง "10abc" เป็น 10 และแปลง "10" เป็น 10 แล้วมาบวกกัน echo "abc"==true ? "T" : "F"; // ได้ "T" แปลงข้อความเป็น boolean ได้ true echo "abc"==0 ? "T" : "F"; // ได้ "T" แปลงข้อความเป็นตัวเลขได้ 0 echo 0==true ? "T" : "F"; // ได้ "F" แปลง 0 เป็น boolean ได้ false echo ""==false ? "T" : "F";// ได้ "T" แปลง "" เป็น boolean ได้ false echo []==false ? "T" : "F";// ได้ "T" แปลง [] เป็น boolean ได้ false
// ภาษา C bool b = true; // "b" เป็นตัวแปรประเภท boolean มีค่าเท่ากับ 1 ( true ) int i = b; // "i" เป็นตัวแปรประเภท integer มีค่าเท่ากับ 1 char c = 'A'; // "c" เป็นตัวแปรประเภท character มีค่าเท่ากับ 65 ( 'A' ) i = c; // "i" มีค่าเท่ากับ 65 ( รหัสตัวอักษรของ 'A' ) b = c; // "b" มีค่าเท่ากับ 1 ( boolean เก็บค่าแค่ 0 กับ 1 ) c = 128; // "c" มีค่าเท่ากับ -128 ( char มีขนาด 1 byte 0 ถึง 255 หรือ -128 ถึง 127 ) c = i * 10; // "c" มีค่าเท่ากับ -118 ( i * 10 ได้ 650 เกิด overflow เกินขนาดที่กำหนด ) double d = 123.5 // "d" เป็นตัวแปรประเภท double floating point มีค่าเท่ากับ 123.5 i = d; // "i" มีค่าเท่ากับ 123
Short circuit ใน Lua
ความหมายก็ตามชื่อครับ คือโปรแกรมจะข้ามการทำงานบางขั้นตอนไปเช่นการตรวจสอบเงื่อนไขใน and กับ or เมื่อนิพจน์ทางซ้ายของ and เทียบเท่ากับ false ภาษา Lua จะข้ามการตรวจสอบในนิพจน์ทางขวาไป เช่นเดียวกันภาษา Lua จะข้ามการตรวจสอบในนิพจน์ทางขวาเมื่อนิพจน์ทางซ้ายของ or เทียบเท่ากับ true ข้อดีคือสามารถเขียนโค้ดได้สั้นลงโดยไม่ต้องกังวลในกรณีที่นิพจน์ทางขวาสามารถเกิด error ได้เช่น
if tonumber(a) then
if a + 1 == 9 then
....
end
end
-- เราสามารถเขียนใหม่ให้ง่ายขึ้นเป็น
if tonumber(a) and a + 1 == 9 then
....
end
จากตัวอย่างข้างต้นถ้าเป็นภาษาที่ไม่มี short circuit เมื่อ "a" ไม่เป็นตัวเลข "a + 1" จะทำให้ error จึงต้องเขียนแยกทีละเงื่อนไขแต่ Lua สามารถเขียนรวมในเงื่อนไขเดียวด้วยการใช้ and เชื่อมโดยเมื่อฝั่งซ้ายของ and เป็นเท็จจะข้ามการทำงานในฝั่งขวาของ and เพราะเท็จ and กับอะไรก็เป็นเท็จจึงไม่จำเป็นต้องตรวจฝั่งขวาอีก บางภาษามีให้ใช้ทั้งแบบปกติและแบบ short circuit เช่นภาษา VB.Net จะใช้ AndAlso กับ OrElse ที่เป็น short circuit แทน And กับ Or
นอกจากนี้ and และ or จะคืนผลลัพธ์เป็นค่าฝั่งใดฝั่งหนึ่งของ operator ตามการทำงานของ short circuit โดยไล่การทำงานจากซ้ายไปขวา
-- ภาษา Lua 1 ไม่เท่ากับ "1" และไม่เท่ากับ true เพราะเป็นข้อมูลคนละประเภทกันแต่สามารถนำมาใช้ในเงื่อนไขได้
-- เพราะมีค่าเทียบเท่ากับข้อมูลที่เป็นจริง ส่วนข้อมูลที่มีค่าเท่ากับเท็จมีสองตัวคือ nil กับ false เท่านั้น
C = A and B
-- กรณีที่ A เป็นเท็จจะไม่ไปทำงานในส่วน B จึงได้ค่า C = A
-- กรณีที่ A เป็นจริงจะไปทำงานต่อในส่วน B ไม่ว่า B เป็นจริงหรือเท็จก็จะได้ค่า C = B
C = A or B
-- กรณีที่ A เป็นเท็จจะไปทำงานต่อในส่วน B ไม่ว่า B เป็นจริงหรือเท็จก็จะได้ค่า C = B
-- กรณีที่ A เป็นจริงจะไม่ไปทำงานในส่วน B จึงได้ค่า C = A
-- สามารถใช้ or ในการทำค่า default ให้พารามิเตอร์ได้เช่น
function foo1(a, b)
local x
if b then x = b else x = 0 end
-- code...
end
-- สามารถเขียนแทนด้วย
function foo2(a, b)
local x = b or 0
-- code...
end
-- การใช้ and or แทน if else
D = A and B or C -- กรณีนี้ B และ C ต้องไม่ใช่ค่าเท็จ
-- จะทำงานทีละคู่จากซ้ายไปขวาเริ่มที่ A and B
-- กรณีที่ A จริงจะได้ B or C --> B เพราะ B เป็นจริง
-- กรณีที่ A เท็จจะได้ A or C --> C เพราะ A เป็นเท็จ
-- ดังนั้นในเงื่อนไข
local x
if a == b then
x = "Yes"
else
x = "No"
end
-- สามารถเขียนแทนด้วย
local x = a == b and "Yes" or "No"
-- กรณีที่มีเงื่อนไขซ้อนกันควรใส่วงเล็บเพื่อป้องกันการสับสนจะได้อ่านง่ายเช่น
local x = (a == b) and (a ~= c and "b" or "b/c") or "???"
local y = a == b and "a eq b" or (a == c and "a eq c" or "none")
การจัดการข้อผิดพลาด
ปกติในการทำงานของโปรแกรมเมื่อเจอข้อผิดพลาดโปรแกรมจะหยุดทำงานและแสดงข้อผิดพลาดขึ้น แต่บางกรณีเราอาจต้องการให้โปรแกรมทำงานต่อเนื่องจากความผิดพลาดนั้นอยู่ในขอบเขตที่อาจเกิดขึ้นและไม่กระทบกับการทำงานส่วนอื่นของโปรแกรม หรือเราอยากจะควบคุมจัดการข้อผิดพลาดที่เกิดขึ้นเอง
ในภาษา Lua เรามี pcall() กับ xpcall() สำหรับจัดการในส่วนนี้โดยฟังก์ชันแรกจะคืนค่าสองตัวคือ ["สถานะ"] กับ ["ผลลัพธ์" หรือ "ข้อความแสดงข้อผิดพลาด"] ส่วนฟังก์ชันที่สองจะคืนค่า ["สถานะ"] กับ ["ผลลัพธ์"] ส่วน ["ข้อความแสดงข้อผิดพลาด"] จะส่งกลับไปให้ฟังก์ชัน callback ที่เรากำหนด
-- Ex.1 การทำงานปกติ
function square(a)
return a * a -- 2) คืนค่า 2 * 2
end
print(square(2)) -- 1) ส่งค่า 2 ไปที่ฟังก์ชัน "square()"
-- 3) พิมพ์ค่า 4
print("Hello") -- 4) พิมพ์ค่า "Hello"
-- ผลลัพธ์
--[[
4
Hello
]]
-- Ex.2 กรณีที่เกิดข้อผิดพลาด
function square(a)
return a * a -- 2) "x" * "x" --> error ไม่ใช่ตัวเลข
-- 3) หยุดการทำงานและแสดงข้อผิดพลาด
end
print(square("x")) -- 1) ส่งค่า string "x" ไปที่ฟังก์ชัน "square()"
print("Hello") -- 4) บรรทัดนี้ไม่ทำงานเพราะโปรแกรมหยุดที่ 2)
-- ผลลัพธ์ โปรแกรมหยุดพร้อมแสดงข้อผิดพลาด
--[[
lua:2: attempt to perform arithmetic on a string value (local 'a')
Stack trace:
#0 [C]: in function __mul
...
...
]]
-- Ex.3 การใช้ pcall()
function square(a)
return a * a -- 2) "x" * "x" --> error ไม่ใช่ตัวเลข
end
local status, result = pcall(square, "x") -- 1) ส่งค่า string "x" ไปที่ฟังก์ชัน "square()"
-- 3) คืนค่า false กับ ข้อความแสดงข้อผิดพลาด ( ถ้าไม่ error จะได้ค่า true กับ ผลลัพธ์จาก "square()" )
if status then
print("result =", result)
else -- 4) status == false
print("error =", result) -- 5) พิมพ์ข้อผิดพลาด
end
print("Hello") -- 6) ทำงานคำสั่งถัดไป
-- ผลลัพธ์
--[[
error = lua:2: attempt to perform arithmetic on a string value (local 'a')
Hello
]]
-- Ex.4 การใช้ xpcall()
function square(a)
return a * a -- 2) "x" * "x" --> error ไม่ใช่ตัวเลข
end
function printErr(msg)
print("Error = "..msg) -- 3) ส่งข้อความผิดพลาดมาที่ฟังก์ชัน callback ทำงาน
end
local status, result = xpcall(square, printErr, "x") -- 1) ส่งค่า string "x"
-- 4) คืนค่า false กับ nil ( ถ้าไม่ error จะได้ค่า true กับ ผลลัพธ์จาก "square()" )
if status then
print("result =", result)
else -- 5) status == false
print("error =", result) -- 6) พิมพ์ผลลัพธ์ ค่า result เป็น nil เพราะเกิด error
end
print("Hello") -- 7) ทำงานคำสั่งถัดไป
-- ผลลัพธ์
--[[
Error = lua:2: attempt to perform arithmetic on a string value (local 'a')
error = nil
Hello
]]
Metatable และ Metamethod
จากตอนที่ผ่านมาเราใช้ metatable ในการผูกคุณสมบัติเพิ่มเติมให้กับ table เช่นการสร้าง object ใหม่ที่มีการสืบทอดคุณสมบัติจาก table อื่นให้สามารถเรียกใช้ method จากอีก table ได้เป็นต้น นอกจากนี้เรายังสามารถใช้ metamethod ในการเพิ่มคุณสมบัติให้กับ table ได้เช่น
-- Ex.5 สร้าง Vector
function Vector(x, y) -- function สร้าง vector
local vec = {x = x, y = y} -- object "vec" --> {["x"] = x, ["y"] = y}
local meta = { -- metatable "meta"
__add = function(self, v) -- metamethod "__add" สำหรับการบวก vector
return Vector(self.x + v.x, self.y + v.y)
end,
__sub = function(self, v) -- metamethod "__sub" สำหรับการลบ vector
return Vector(self.x - v.x, self.y - v.y)
end,
__tostring = function(self) -- metamethod "__tostring" สำหรับการใช้ฟังก์ชัน tostring(vector) หรือ print(vector)
return "vector {x:" .. self.x .. ", y:" .. self.y .."}"
end
}
return setmetatable(vec, meta) -- คืนค่า vector ใหม่
end
local v1 = Vector(5, 10)
print("v1", v1) -- เรียกใช้ metamethod "__tostring"
local v2 = Vector(3, 6)
print("v2", v2) -- เรียกใช้ metamethod "__tostring"
local v3 = v1 + v2 -- เรียกใช้ metamethod "__add"
print("v3", v3) -- เรียกใช้ metamethod "__tostring"
-- ผลลัพธ์
--[[
v1 vector {x:5, y:10}
v2 vector {x:3, y:6}
v3 vector {x:8, y:16}
]]
-- Ex.6 เรียกใช้คลาส Person ( จาก module ในตอนที่แล้ว )
local Person = require("person.person")
setmetatable(Person, {__call = Person.new}) -- ให้ไปเรียกฟังก์ชัน "new()" เมื่อมีการใช้ function call กับ table "Person"
local john = Person.new("John", "Hello")
local jane = Person("Jane", "Bye") -- เรียก metamethod "__call" --> Person.new()
print(john.say())
print(jane.say())
-- ผลลัพธ์
--[[
John say: Hello
Jane say: Bye
]]
จะเห็นได้ว่าเราสามารถดัดแปลง table ของเราให้ใช้งานได้หลากหลายมากขึ้น สามารถศึกษาเพิ่มเติมเกี่ยวกับ metamethod ว่ามีอะไรบ้างได้ที่นี่
การเพิ่มประสิทธิภาพ
เป็นทริกบางส่วนที่เอามาจาก Lua Programming Gems เช่นพยายามใช้ตัวแปร local แทน global เพราะ Lua เก็บตัวแปรแบบ local ใน register ที่เรียกใช้ได้เลยในขณะที่ global ต้องอ่านค่ามาเก็บที่ register เพื่อเรียกใช้และถ้ามีการแก้ไขค่าก็ต้องเก็บค่าจาก register ลงตัวแปรอีกดังนั้นในกรณีที่มีการเรียกใช้งานตัวแปรบ่อยๆ ก็จะยิ่งเห็นความแตกต่างเช่น
-- ไฟล์ tabtest1.lua local table1 = {) for i = 1, 1000000 do -- วนลูปหนึ่งล้านรอบ table.insert(table1, i ) -- ใส่ข้อมูลลง table end for i = 1, 1000000 do -- วนลูปหนึ่งล้านรอบ table.remove(table1) -- เอาข้อมูลออกจาก table end -- ไฟล์ tabtest2.lua local table1 = {) local insert, remove = table.insert, table.remove for i = 1, 1000000 do -- วนลูปหนึ่งล้านรอบ insert(table1, i ) -- ใส่ข้อมูลลง table end for i = 1, 1000000 do -- วนลูปหนึ่งล้านรอบ remove(table1) -- เอาข้อมูลออกจาก table end
ทดสอบโปรแกรม# time เป็นคำสั่งใน Linux ใช้แสดงเวลาที่โปรแกรมทำงาน ( time โปรแกรมที่เรียก ) $ time lua tabtest1.lua real 0m0.878s user 0m0.855s sys 0m0.008s $ time lua tabtest2.lua real 0m0.753s user 0m0.727s sys 0m0.017s $ _
เห็นได้ว่าการเรียกตัวแปร local นั้นเพิ่มความเร็วในการทำงานได้เล็กน้อย
พยายามประกาศค่าเริ่มต้นให้ table เท่าที่เป็นไปได้ ในภาษา C การประกาศตัวแปร array จะต้องกำหนดขนาดเพื่อจองพื้นที่หน่วยความจำไว้ล่วงหน้า แต่ใน Lua สามารถประกาศ table เปล่าแล้วเพิ่มค่าเข้าไปทีหลังได้แต่ถ้าเรารู้ขนาดที่แน่นอนของ table แล้วเรากำหนดค่าตั้งต้นให้ก่อนแล้ว การแก้ไขค่าทีหลังจะเร็วกว่าการเพิ่มค่าใหม่เล็กน้อย
-- ไฟล์ tabtest3.lua function create_table(tbl, v1, v2, v3, v4) tbl.a = v1 tbl.b = v2 tbl.c = v3 tbl.d = v4 end for i = 1, 1000000 do local table1 = {} -- ประกาศ table เปล่า create_table(table1, i, i+2, i+3, i+4) -- เพิ่มสมาชิกใน table end -- ไฟล์ tabtest4.lua function create_table(tbl, v1, v2, v3, v4) tbl.a = v1 tbl.b = v2 tbl.c = v3 tbl.d = v4 end for i = 1, 1000000 do local table1 = {a = 0, b = 0, c = 0, d = 0} -- กำหนดขนาด table ล่วงหน้า create_table(table1, i, i+2, i+3, i+4) -- แก้ไขค่าใน table end
ทดสอบโปรแกรม$ time lua tabtest3.lua real 0m1.168s user 0m1.156s sys 0m0.004s $ time lua tabtest4.lua real 0m0.725s user 0m0.718s sys 0m0.004s $ _
string ใน Lua เป็น imutable คือไม่สามารเปลี่ยนแปลงแก้ไขได้และ Lua จะเก็บค่า string ที่สร้างไว้ การกำหนดให้ str1 = str1 .. str2 จะเป็นการสร้าง string ใหม่จาก str1 ต่อกับ str2 เช่น str1 เก็บ "a" และ str2 เก็บ "b" แล้วจากนิพจน์ข้างต้นจะได้ string สามตัวคือ "a" "b" และ "ab" แล้วแทนที่ str1 ด้วย "ab" เทียบกับภาษา Python ที่ string ปกติจะเป็น imutable เหมือนกันแต่ Python มี operator ที่ไม่ต้องสร้าง string ใหม่แต่แทนที่ค่าใหม่ใน string เดิมได้เลยส่วน Lua ไม่มี operator สำหรับการทำงานแบบนี้โดยเฉพาะ ดังนั้นเมื่อเราใช้การต่อ string ในลูปจะทำให้ช้าและเปลืองหน่วยความจำเราสามารถใช้ table ที่เป็น mutable มาจัดการตรงนี้ได้และสร้าง string ใหม่ครั้งเดียว
# ภาษา Python
>>> a = "abc" # สร้าง string ใหม่เก็บใน "a"
>>> id(a) # ดู id ของ string
2903892928
>>> a = a + "d" # สร้าง string ใหม่เก็บใน "a"
>>> id(a) # ได้ id ใหม่
2903649408
>>> a += "e" # แก้ไข string เดิมที่เก็บใน "a"
>>> id(a) # ได้ id เดิม
2903649408
-- ไฟล์ strtast1.lua local str = "" for i = 1, 30000 do str = str .. tostring(i) end -- ไฟล์ strtest2.lua local tbl = {} for i = 1, 30000 do tbl[i] = tostring(i) end local str = table.concat(tbl)
ทดสอบโปรแกรม$ time lua strtest1.lua real 0m1.740s user 0m1.354s sys 0m0.366s $ time lua strtest2.lua real 0m0.043s user 0m0.037s sys 0m0.004s $ _
เวลาและวันที่
จากตอนที่ 5 Lua มีฟังก์ชัน os.date() จัดการแสดงเวลาและวันที่ซึ่งใช้งานได้ง่ายโดยการกำหนดรูปแบบข้อมูลที่ต้องการได้ดังนี้
"*t" แสดงค่าเป็น table ที่มีคีย์ดังนี้ yday เป็นวันที่เท่าไหร่ในปีนั้น ( นับจาก 1 มกราคมเป็น 1 ) wday เป็นวันที่เท่าไหร่ในสัปดาห์ 1-7 ( นับจากวันอาทิตย์เป็น 1 ) day เป็นวันที่ในเดือนนั้น month เดือนที่เท่าไหร่ ( นับจากเดือนมกราคมเป็น 1 ) year ปี ค.ศ. hour ชั่วโมงแบบ 24 ชั่วโมง ( 0 - 23 เริ่มจากเที่ยงคืน ) min นาที ( 0 - 59 ) sec วินาที ( 0 - 59 ) isdst ใช้ค่า Daylight savings time หรือไม่ ( เป็นค่า boolean ) "%a" แสดงชื่อวันแบบย่อเช่น "Mon" "Fri" "%A" แสดงชื่อวันแบบเต็มเช่น "Wednesday" "%b" หรือ "%h" แสดงชื่อเดือนแบบย่อเช่น "Feb" "Jun" "Dec" "%B" แสดงชื่อเดือนแบบเต็ม "November" "%C" แสดงเลขศตวรรษ "%j" แสดงวันที่ในปีนั้น ( นับจาก 1 มกราคม ) "%w" แสดงวันในสัปดาห์ 0-6 ( นับจากวันอาทิตย์เป็น 0 ) "%W" แสดงสัปดาห์ที่เท่าไหร่ในปีแบบมีศูนย์นำ "%d" แสดงวันที่แบบมีศูนย์นำ ( สองตัวอักษรเช่น "09" ) "%m" แสดงเดือนที่แบบมีศูนย์นำ ( "01" ถึง "12" ) "%y" แสดงเลขปี ค.ศ. แบบสองตัวหลัง "%Y" แสดงเลขปี ค.ศ. แบบสี่ตัว "%D" เทียบเท่ากับ "%m/%d/%y" ( แสดง เดือน/วัน/ปี แบบเลขสองตัว ) "%F" เทียบเท่ากับ "%Y-%m-%d" ( แสดง เลขปีสี่ตัว-เดือน-วัน ) "%H" แสดงชั่วโมงแบบ 24 ชั่วโมง ( "00" ถึง "23" เริ่มจากเที่ยงคืน ) "%I" แสดงชั่วโมงแบบ 12 ชั่วโมง ( "00" ถึง "10" ) "%p" แสดง "AM" หรือ "PM" "%M" แสดงนาทีแบบมีศูนย์นำ ( "00" ถึง "59" ) "%S" แสดงวินาทีแบบมีศูนย์นำ ( "00" ถึง "59" ) "%r" แสดงเวลาในระบบ 12 ชั่วโมงเช่น "12:00:00 PM" "%R" เทียบเท่ากับ "%H:%M" "%T" เทียบเท่ากับ "%H:%M:%S" "%c" แสดงวันเวลาแบบเต็ม ( รูปแบบขึ้นกับ locale ที่กำหนด ) เช่น "Tue Jun 1 12:00:00 2021" "%x" แสดงวันที่แบบย่อ ( รูปแบบขึ้นกับ locale ที่กำหนด ) เช่น "06/01/21" "%X" แสดงเวลา ( รูปแบบขึ้นกับ locale ที่กำหนด ) เช่น "12:00:00" "%z" แสดง GMT offset เช่นในไทยเป็น "+0700" "%Z" แสดง Time zone เช่นในไทยเป็น "+07" "%%" แสดง "%"
และยังสามารถกำหนดวันเวลาที่ต้องการด้วยฟังก์ชัน os.time() โดยใช้ table ที่มีโครงสร้างเหมือนกับข้อมูลที่ได้จากการใช้ "*t" ใน os.date() เป็นอาร์กิวเมนท์โดยต้องระบุคีย์อย่างน้อย 3 ตัวคือ day, month และ year ส่วน hour ถ้าไม่ระบุจะใช้ค่า 12 และ min กับ sec ถ้าไม่ระบุจะใช้ค่า 0
local d1 = os.date("*t", os.time{day=1, month=6, year=2021}) for k, v in pairs(d1) do print(k, v) end -- ผลลัพธ์ --[[ yday 152 sec 0 wday 3 isdst false month 6 hour 12 min 0 year 2021 day 1 ]]
ถ้าระบุข้อมูลผิดจะปรับวันเวลาให้เองlocal d1 = os.date("*t", os.time{day=29, month=2, year=2021}) for k, v in pairs(d1) do print(k, v) end -- ผลลัพธ์ --[[ yday 152 sec 0 wday 3 isdst false month 3 hour 12 min 0 year 2021 day 1 ]]
จบแล้วครับสำหรับบทความที่ยาวที่สุดที่เคยเขียนหากตกหล่นหรือผิดพลาดอะไรก็ขออภัยด้วยครับ หวังว่าจะเป็นประโยชน์สำหรับผู้ที่สนใจอยากหัดเขียนโปรแกรมหรือมีประสบการณ์กับภาษาอื่นแล้วอยากลองภาษาใหม่ดูบ้างนะครับ
ช่วยได้เยอะเลยครับ!
ตอบลบยินดีที่เป็นประโยชน์ครับ
ลบ