Lua字符串


    字符串用於文本。Lua語言中的字符串即可以表示單個字符,也可以表示一整本書籍。在Lua語言中,操作100K或者1M個字母組成的字符串的程序也很常見。
    Lua語言中的字符串是不可變值。我們不能像在C語言中那樣直接改變某個字符串中的某個字符,但是我們可以通過創建一個新的字符串的方式來達到修改的目的。例如:

a = "one string"
b = string.gsub(a,"one","another") 	--改變字符串中的某些部分
print(a)				--one string
print(b)				--another string

    像Lua語言中的其他對象一樣,Lua語言中的字符串也是自動內存管理的對象之一。這意味着Lua語言會負責字符串的分配和釋放,開發人員無須關注。
    可以使用長度操作符(#)獲取字符串的長度:

a = "hello"
print(#a)			--5
print(#"good bye")  --8

該操作符返回字符串佔用的字節數,在某些編碼中,這個值可能與字符串中字符的個數不同。
    我們可以使用連接操作符..(兩個點)來進行字符串連接。如果操作數中存在數值,那麼Lua語言會先把數值轉換成字符串:

> "Hello" .. "World" 		-- Hello World
> "result is " .. 3			-- result is 3

在某些語言中,字符串連接使用的是加號,但實際上3+5和3…5是不一樣的。
    應該注意,在Lua語言中,字符串是不可變量。字符串連接總是創建一個新字符串,而不會改變原來作爲操作數的字符串:

> a = "Hello"
> a .. "World"      -- Hello World
> a 				-- Hello

字符串常量

    我們可以使用一對雙引號或單引號來聲明字符串常量:

a = " a line "
b = ' another line'

使用雙引號和單引號聲明字符串是等價的。它們兩者唯一的區別在於,使用雙引號聲明的字符串中出現單引號時,單引號可以不用轉義;使用單引號聲明的字符串中出現雙引號時,雙引號可以不用轉義。
    Lua語言中的字符串支持下列C語言風格的轉義字符:

\a			響鈴(bell)
\b			退格(back space)
\f			換頁(form feed)
\n			換行(newline)
\r			回車(carriage return)
\t			水平製表符(horizontal tab)
\v			垂直製表符(vertical tab)
\\			反斜槓(backslash)
\"			雙引號(double quote)
\'			單引號(single quote)

下述示例展示了轉義字符的使用方法:

>print("one line \n next line \n\"in quotes\",'in quotes' ")
one line
next line
"in quotes" , 'in quotes'
>print('a backslash inside quotes:\' \\\ '')
a backslash inside quotes: '\'
>print("a simpler way: '\\' ")
a simpler way: '\'

    在字符串中,還可以通過轉義\add和\xhh來聲明字符。其中,add是由最多3個十進制數字組成的序列,hh是由兩個且必須是兩個十六進制數字組成的序列。

長字符串/多行字符串

    像長註釋/多行註釋一樣,可以使用一對雙括號來聲明長度字符串/多行字符串常量。被方括號括起來的內容可以包含很多行,並且內容中的轉義序列不會被轉義。此外,如果多行字符串中的第一個字符是換行符,那麼這個換行符會被忽略。多行字符串在聲明包含大段代碼的字符串時非常方便,例如:

page = [[
<html>
<head>
	<title> An HTML Page</title>
</head>
<body>
	<a href = "http://www.lua.org">Lua<a>
</body>
</html>
]]

    有時字符串中可能有類似a = b[c[i]]這樣的內容,或者,字符串中可能有被註釋掉的代碼。爲了應對這些情況,可以在兩個左方括號之間加上任意數量的等號,如[===[。這樣,字符串常量只有在遇到了包含了相同數量等號的兩個右括號時纔會結束。Lua語言的語法掃描器會忽略所含等號數量不相同的方括號。通過選擇恰當數量的等號,就可以在無須修改原字符串的情況下聲明任意的字符串常量了。
    對註釋而言,這種機制也同樣有效。例如,我們可以使用–[=和]=]來進行長註釋,從而降低了對內部已經包含註釋的代碼進行註釋的難度。
    當代碼中需要使用常量文本時,使用長字符串是一種理想的選擇。但是,對於非文本的常量我們不應該濫用長字符串。雖然Lua語言中的字符串常量可以包含任意字節,但是濫用這個特行並不明智。同時,像"\r\n"一樣的EOF序列在被讀取的時候可能會被歸一化成"\n"。作爲替代方案,最好就是把這些可能引起歧義的二進制數據用十進制或十六進制的數值轉義系列進行表示,例如"\x13\x01\xA1\xBB"。不過,由於這種轉義表示行程的字符串往往很長,所以對於長字符串來說仍可能是個問題。針對這種情況,從Lua5.2開始引入了轉義序列\z,該轉義符會跳過其後的所有空白字符,直到遇到第一個非空白字符。下列中演示了該轉義符的使用方法:

data = "\x00\x01\x02\x03\x04\x05\x06\x07\z
		\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"

第一行最後的\z會跳過其後的EOF和第二行的製表符,因此最終得到的字符串中,\x08實際上是緊跟着\x07的。

強制類型轉換

    Lua語言在運行時提供了數值和字符串之間的自動轉換。針對字符串的所有算術操作會嘗試將字符串轉換爲數值。Lua語言不僅僅在算術操作時進行這種強制類型轉換,還會在任何需要數值的情況下進行,例如函數math.sin的參數。
    相反,當Lua語言發現在需要字符串的地方出現了數值時,它就會把數值轉換爲字符串:

print(10 .. 20 )    	--1020

當在數值後緊接着使用字符串連接時,必須使用空格將它們分開,否則Lua語言會把第一個點當成小數點。
    很多人認爲自動強制類型轉換算不上是Lua語言中的一項好設計。作爲原則之一,建議最好不要完全寄希望於自動強制類型轉換。雖然在某些場景下這種機制很便利,但同時也給語言和適用這種機制的程序帶來了複雜性。
    作爲這種"二類狀態"的表現之一,Lua5.3沒有實現強制類型轉換娛整型的集成,而是採用了另一種更簡單和快速的實現方式:算術運算的規則就是隻有在兩個操作數都是整型值時結果纔是整型。因此,由於字符串不是整型值,所以任何有字符串參與的算術運算都會被當做浮點運算處理:

>"10" + 1          --11.0

    如果需要顯示地將一個字符串轉換成數值,那麼可以使用函數tonumber。當這個字符串的內容不能表示爲有效數字時該函數返回nil;否則,該函數就按照Lua語法掃描器的規則返回對應的整型值或浮點類型值:

> tounmber(" -3 ")			-- -3
> tounmber(" 10e4 ")		-- 100000.0
> tounmber(" 10e ")         -- nil (not a valid number)
> tounmber(" 0x1.3p - 4")   -- 0.07421875

    默認情況下,函數tonumber使用的是十進制,但是也可以指明使用二進制到三十六進制之間的任意進制:

> tounmber("100101", 2)			-- 37
> tounmber("fff" ,16)			-- 4095
> tounmber("-ZZ",36)        	-- -1295
> tounmber("987",8)   			-- nli

在最後一行,對於制定的進制而言,傳入的字符串是一個無效值,因此函數tonumber返回nil。
    調用函數tonumber可以將數值轉換成字符串:

print(tostring(10) == "10")   --true

上述的這種轉換總是有效,但我們需要記住,使用這種轉換時並不能控制輸出字符串的格式。

字符串標準庫

    Lua語言解釋器本身處理字符串的功能是十分有限的。一個程序能夠創建字符串、連接字符串、比較字符串和獲取字符串的長度,但是,它並不能提取字符串的子串或檢視字符串的內容。Lua語言處理字符串的完整能力來自其字符串標準庫。
    字符串標準庫中的一些函數非常簡單:函數string.len(s)返回字符串s的長度,等價於#s。函數string.rep(s,n)返回將字符串s重複n遍的結果。可以通過調用string.rep(“a”,2202^{20})創建一個1MB大小的字符串。函數string.reverse用於字符串翻轉。函數string.lower(s)返回一份s的副本,其中所有的大寫字母都被轉換成小寫字母,而其他字符則保持不變。函數string.upper與之相反,該函數會將小寫字母轉換成大寫字母。

> string.rep("abc",3)			-- abcabcabc
> string.reverse("A Long Line!")	-- !eniL gnoL A
> string.lower("A Long Line!")		-- a long line!
> string.upper("A Long Line!")		-- A LONG LINE!

作爲一種典型的應用,我們可以使用如下代碼在忽略大小寫差異的原則下比較兩個字符串:

string.lower(a) < string.lower(b)

    函數string.sub(s,i,j)從字符串s中提取第i個到第j個字符。該函數也支持負數索引,負數索引從字符串的結尾開始計數:索引-1代表字符串的最後一個字符,索引-2代表倒數第二個字符,依此類推。這樣,對字符串s調用函數string.sub(s,1,j)得到的是字符串s中長度爲j的前綴,調用string.sub(s,j,-1)得到的是字符串s從第j個字符開始的後綴,調用string.sub(s,2,-2)返回的是去掉字符串s中第一個和最後一個字符的結果。
    請注意,Lua語言中的字符串是不可變的。和Lua語言中的所有其他函數一樣,函數string.sub不會改變原有字符串的值,它只會返回一個新字符串。一種常見的誤解是以爲string.sub(s,2,-2)返回的是修改後的s。如果需要修改原字符串,那麼必須把心的值賦值給它:

s = string.sub(s,2,-2)

    函數string.char 和string.byte用於轉換字符串及其內部數值表示。函數string.char接收零個或多個證書作爲參數,然後將每個整數轉換成對應的字符,最後返回由這些字符連接而成的字符串。函數string.byte(s,i)返回字符串s中第i 個字符的內部數值表示,該函數的第二個參數是可選的。調用string.byte(s)返回字符串s中第一個字符的內部數值表示。在下面例子中,假定字符串是用ASCII表示的:

print(string.char(97))			-- a
i = 99; print(string.char(i,i+7,i+2))		-- cde
print(string.byte("abc"))			-- 97
pring(string.byte("abc" , 2))		-- 98
pring(string.byte("abc" , -1))		-- 99

在最後一行中,使用負數索引來訪問字符串的最後一個字符。
    調用string.byte(s,i,j)返回索引i到j之間的所有字符的數字表示:

print(string.byte("abc",1,2)     -- 97 98

一種常見的寫法是{string.byte(s,1,-1)},該表達式會創建一個由字符串s中的所有字符代碼組成的表。
    函數string.format是用於進行字符串格式化和將數值輸出爲字符串的強大工具,該函數會返回一個參數的副本,其中的每一個指示符都會被替換爲使用對應格式化後的對應參數。格式化字符串中的指示符與C語言中函數printf的規則類似,一個指示符由一個百分號和一個代表格式化方式的字母組成:d代表一個十進制整數、x代表一個十六進制整數、f代表一個浮點數、s代表字符串等等。

> string.format("x = %d y = %d",10 ,20)			-- x = 10 y = 20
> string.format("x = %x", 200)					-- x = c8
> string.format("x = 0x%x", 200)				-- x = 0xC8
> string.format("x = %f", 200)					-- x = 200.000000
> tag, title = "h1", "a title"
> string.foramt("<%s>%s</%s>",tag,title,tag)	-- <h1> a title</h1>

在百分號和字母之間可以包含用於控制格式細節的其他選項。例如,可以指定一個浮點數中小數的位數:

print(string.foramt("pi = %.4f", math.pi))			-- pi = 3.1416
d = 5; m = 11; y = 1990
print(string.format("%02d/%02d/%04d", d, m, y))		-- 05/11/1990

在上例中,%.4f表示小數點後保留4位小數;%02d表示一個十進制數至少由兩個數字組成,不足兩個數字的用0補齊,而%2d則表示用空格來補齊。關於這些指示符的完整描述可以參考C語言printf函數的相關文檔。
    可以使用冒號操作符像調用字符串的一個方法那樣調用字符串中標準庫中的所有函數。例如,string.sub(s,i,j)可以重寫成s:sub(i,j),string.upper(s)可以重寫成s:supper()。
    字符串標準庫還包括了幾個基於模式匹配的函數。函數string.find用於在指定的字符串中進行模式搜索:

> string.find("hello world" , "wor")		-- 7 9
> string.find("hello world" , "war")		-- nil

如果該函數在指定的字符串中找到了匹配的模式,則返回模式的開始和結束位置,否則返回nil。函數string.gsub(Global SUBstitution)則把所有匹配的模式用另一個字符串替換:

> string.gsub("hello world", "l" , ".")			-- he..o wor.d 3
> string.gsub("hello world", "ll" , "..")       -- he..o world 1
> string.gsub("hello world", "a", ".")			-- hello world 0

該函數還會在第二個返回值中返回發生替換的次數。

Unicode編碼

    UTF-8是Web環境中用於Unicode的主要編碼之一。由於UTF-8編碼娛ASCII編碼部分兼容,所以UTF-8對於Lua來說是一種理想的編碼方式。這種兼容性保證了用於ASCII字符串的一些字符操作技巧無須修改就可以用於UTF-8字符串。
    UTF-8使用變長的多個字節來編碼一個Unicode字符。例如,UTF-8編碼使用一個字節的65來代表A,使用兩個字節的215-144代表希伯來語字符Aleph。UTF-8使用一個字節表示所有ASCII範圍內的字符(小於128)。對於其他字符,則使用字節序列表示,其中第一個字節的範圍時[194,244],而後續的字節範圍時[128,191]。更準確地說,對於兩個字節組成的序列,第一個字節的範圍是[194,223];對於三個字節組成的序列來說,第一個字節的範圍是[224,239];對於四個字節組成的序列來說,第一個字節的範圍是[240,224],這些範圍互相之間均不重疊。這種特點保證了任意字符對應的字節序列不會在其他字符對應的字節序列中出現。特別地,一個小於128的字節永遠不會在多字節序列中,它只會代表與之對應的ASCII字符。
    Lua語言中的一些機制對UTF-8字符串來說同樣“有效”。由於Lua語言使用8個字節來編碼字符,所以可以像操作其他字符串一樣讀寫和存儲UTF-8字符串。字符串常量也可以包含UTF-8數據。字符串連接UTF-8字符串同樣適用。對字符串比較會按照Unicode編碼中的字符代碼順序進行。
    Lua語言的操作系統庫和輸入輸出庫是與對應系統之間的主要接口,所以它們是否支持UTF-8取決於對應的操作系統。例如,在Linux操作系統下文件名要使用UTF-8編碼,而在Windows操作系統下文件名使用UTF-16編碼。因此,如果要在Windows操作系統中處理Unicode文件名,要麼使用額外的庫,要麼就修改Lua語言的標準庫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章