#
一、函數:
在Lua中函數的調用方式和C語言基本相同,如:print("Hello World")和a = add(x, y)。唯一的差別是,如果函數只有一個參數,并且該參數的類型為字符串常量或table的構造器,那么圓括號可以省略,如print "Hello World"和f {x = 20, y = 20}。 Lua為面對對象式的調用也提供了一種特殊的語法--冒號操作符。表達式o.foo(o,x)的另一種寫法是o:foo(x)。冒號操作符使調用o.foo時將o隱含的作為函數的第一個參數。 Lua中函數的聲明方式如下: function add(a) local sum = 0 for i, v in ipairs(a) do sum = sum + v end return sum end 在以上聲明中,包含了函數名(add),參數列表(a),以及函數體。需要說明的是,Lua中實參和形參的數量可以不一致,一旦出現這種情況,Lua的處理規則等同于多重賦值,即實參多于形參,多出的部分被忽略,如果相反,沒有被初始化的形參的缺省值為nil。
1. 多重返回值: Lua支持返回多個結果值。如: 1 s,e = string.find("Hello Lua users","Lua") 2 print("The begin index is " .. s .. ", the end index is " .. e .. "."); 3 -- The begin index is 7, the end index is 9. 以上的代碼示例只是演示了如何獲取Lua函數的多個返回值,下面的示例將給出如何聲明返回多個值的Lua函數。如: 1 function maximum(a) 2 local mi = 1 3 local m = a[mi] 4 for i, val in ipairs(a) do 5 if val > m then 6 mi,m = i,val 7 end 8 end 9 return m,mi 10 end 11 print(maximum{8,10,23,12,5}) 12 --23 3 Lua會調整一個函數的返回值數量以適應不同的調用情況。若將函數調用作為一條單獨語句時,Lua會丟棄函數的所有返回值。若將函數作為表達式的一部分來調用時,Lua只保留函數的第一個返回值。只有當一個函數調用是一系列表達式中的最后一個元素時,才能獲得所有返回值。這里先給出三個樣例函數,如: function foo0() end function foo1() return "a" end function foo2() return "a","b" end 示例代碼 | 結果 | 注釋 | x,y = foo2() | x = "a", y = "b" | 函數調用時最后的(或僅有的)一個表達式,Lua會保留其盡可能多的返回值,用于匹配賦值變量。 | x = foo2() | x = "a", 返回值"b"被忽略 | x,y,z = 10,foo2() | x = 10, y = "a", z = "b" | x,y = foo0() | x = nil, y = nil | 如果一個函數沒有返回值或者沒有足夠多的返回值,那么Lua會用nil來填補。 | x,y = foo1() | x = "a", y = nil | x,y,z = foo2() | x = "a", y = "b", z = nil | x,y = foo2(),20 | x = "a", y = 20 | 如果一個函數調用不是一系列表達式的最后一個元素,那么將只產生一個值。 | x,y = foo0(),20,30 | x = nil, y = 20, 30被忽略。 | print(foo0()) | | 當一個函數調用左右另一個函數調用的最后一個實參時,第一個函數的所有返回值都將作為實參傳入第二個函數。 | print(foo1()) | a | print(foo2()) | a b | print(foo2(),1) | a 1 | t = {foo0()} | t = {} --空table | table構造器可以完整的接收一個函數調用的所有結果,即不會有任何數量方面的調整。 | t = {foo1()} | t = {"a"} | t = {foo2()} | t = {"a", "b"} | t = { foo0(), foo2(), 4} | t[1] = nil, t[2] = "a", t[3] = 4 | 如果函數調用不是作為最后一個元素,那么只返回函數的第一個結果值。 | print((foo2())) | a | 如果函數調用放入圓括號中,那么Lua將只返回該函數的第一個結果值。 |
最后一個需要介紹的是Lua中unpack函數,該函數將接收數組作為參數,并從下標1開始返回該數組的所有元素。如: /> lua > print(unpack{10,20,30}) 10 20 30 > a,b = unpack{10,20,30} > print(a,b) 10 20 > string.find(unpack{"hello","ll"}) --等同于string.find("hello","ll") 在Lua中unpack函數是用C語言實現的。為了便于理解,下面給出在Lua中通過遞歸實現一樣的效果,如: 1 function unpack(t,i) 2 i = i or 1 3 if t[i] then 4 return t[i], unpack(t,i + 1) 5 end 6 end 2. 變長參數: Lua中的函數可以接受不同數量的實參,其聲明和使用方式如下:
1 function add(...) 2 local s = 0 3 for i, v in ipairs{...} do 4 s = s + v 5 end 6 return s 7 end 8 print(add(3,4,5,6,7)) 9 --輸出結果為:25 解釋一下,函數聲明中的(...)表示該函數可以接受不同數量的參數。當這個函數被調用時,所有的參數都被匯聚在一起,函數中訪問它時,仍需用3個點(...)。但不同的是,此時這3個點將作為表達式來使用,如{...}表示一個由所有變參構成的數組。在含有變長參數的函數中個,同樣可以帶有固定參數,但是固定參數一定要在變長參數之前聲明,如: function test(arg1,arg2,...) ... end 關于Lua的變長參數最后需要說明的是,由于變長參數中可能包含nil值,因此再使用類似獲取table元素數量(#)的方式獲取變參的數量就會出現問題。如果要想始終獲得正確的參數數量,可以使用Lua提供的select函數,如: 1 for i = 1, select('#',...) do --這里'#'值表示讓select返回變參的數量(其中包括nil)。 2 local arg = select(i, ...) --這里的i表示獲取第i個變參,1為第一個。 3 --do something 4 end 3. 具名實參: 在函數調用時,Lua的傳參規則和C語言相同,并不真正支持具名實參。但是我們可以通過table來模擬,比如: function rename(old,new) ... end 這里我們可以讓上面的rename函數只接收一個參數,即table類型的參數,與此同時,該table對象將含有old和new兩個key。如: function rename(arg) local old = arg.old local new = arg.new ... end 這種修改方式有些類似于JavaBean,即將多個參數合并為一個JavaBean。然而在使用時,Lua的table存在一個天然的優勢,即如果函數只有一個參數且為string或table類型,在調用該函數時,可以不用加圓括號,如: rename {old = "oldfile.txt", new = "newfile.txt"}
二、深入函數:
在Lua中函數和所有其它值一樣都是匿名的,即它們都沒有名稱。在使用時都是操作持有該函數的變量,如: a = { p = print } a.p("Hello World") b = print b("Hello World") 在聲明Lua函數時,可以直接給出所謂的函數名,如: function foo(x) return 2 * x end 我們同樣可以使用下面這種更為簡化的方式聲明Lua中的函數,如: foo = function(x) return 2 * x end 因此,我們可以將函數理解為由語句構成的類型值,同時將這個值賦值給一個變量。由此我們可以將表達式"function(x) <body> end"視為一種函數的構造式,就想table的{}一樣。我們將這種函數構造式的結果稱為一個"匿名函數"。下面的示例顯示了匿名函數的方便性,它的使用方式有些類似于Java中的匿名類,如: table.sort(test_table,function(a,b) return (a.name > b.name) end)
1. closure(閉合函數): 若將一個函數寫在另一個函數之內,那么這個位于內部的函數便可以訪問外部函數中的局部變量,見如下示例:
1 function newCounter() 2 local i = 0 3 return function() --匿名函數 4 i = i + 1 5 return i 6 end 7 end 8 c1 = newCounter() 9 print("The return value of first call is " .. c1()) 10 print("The return value of second call is " .. c1()) 11 --輸出結果為: 12 --The return value of first call is 1 13 --The return value of second call is 2 在上面的示例中,我們將newCounter()函數稱為閉包函數。其函數體內的局部變量i被稱為"非局部變量",和普通局部變量不同的是該變量被newCounter函數體內的匿名函數訪問并操作。再有就是在函數newCounter返回后,其值仍然被保留并可用于下一次計算。再看一下下面的調用方式。 1 function newCounter() 2 local i = 0 3 return function() --匿名函數 4 i = i + 1 5 return i 6 end 7 end 8 c1 = newCounter() 9 c2 = newCounter() 10 print("The return value of first call with c1 is " .. c1()) 11 print("The return value of first call with c2 is " .. c2()) 12 print("The return value of second call with c1 is " .. c1()) 13 --輸出結果為: 14 --The return value of first call with c1 is 1 15 --The return value of first call with c2 is 1 16 --The return value of second call with c1 is 2 由此可以推出,Lua每次在給新的閉包變量賦值時,都會讓不同的閉包變量擁有獨立的"非局部變量"。下面的示例將給出基于閉包的更為通用性的用法: 1 do 2 --這里將原有的文件打開函數賦值給"私有變量"oldOpen,該變量在塊外無法訪問。 3 local oldOpen = io.open 4 --新增一個匿名函數,用于判斷本次文件打開操作的合法性。 5 local access_OK = function(filename,mode) <檢查訪問權限> end 6 --將原有的io.open函數變量指向新的函數,同時在新函數中調用老函數以完成真正的打開操作。 7 io.open = function(filename,mode) 8 if access_OK(filename,mode) then 9 return oldOpen(filename,mode) 10 else 11 return nil,"Access denied" 12 end 13 end 14 end 上面的這個例子有些類似于設計模式中裝飾者模式。
2. 非全局函數: 從上一小節中可以看出,Lua中的函數不僅可以直接賦值給全局變量,同時也可以賦值給其他類型的變量,如局部變量和table中的字段等。事實上,Lua庫中大多數table都帶有函數,如io.read、math.sin等。這種寫法有些類似于C++中的結構體。如: Lib = {} Lib.add = function(x,y) return x + y end Lib.sub = function(x,y) return x - y end 或者是在table的構造式中直接初始化,如: Lib = { add = function(x,y) return x + y end, sub = function(x,y) return x - y end } 除此之外,Lua還提供另外一種語法來定義此類函數,如: Lib = {} function Lib.add(x,y) return x + y end function Lib.sub(x,y) return x - y end 對于Lua中的局部函數,其語義在理解上也是非常簡單的。由于Lua中都是以程序塊作為執行單元,因此程序塊內的局部函數在程序塊外是無法訪問的,如: 1 do 2 local f = function(x,y) return x + y end 3 --do something with f. 4 f(4,5) 5 end 對于這種局部函數,Lua還提供另外一種更為簡潔的定義方式,如: local function f(x,y) return x + y end 該寫法等價于: local f f = function(x,y) return x + y end
3. 正確的尾調用: 在Lua中支持這樣一種函數調用的優化,即“尾調用消除”。我們可以將這種函數調用方式視為goto語句,如: function f(x) return g(x) end 由于g(x)函數是f(x)函數的最后一條語句,在函數g返回之后,f()函數將沒有任何指令需要被執行,因此在函數g()返回時,可以直接返回到f()函數的調用點。由此可見,Lua解釋器一旦發現g()函數是f()函數的尾調用,那么在調用g()時將不會產生因函數調用而引起的棧開銷。這里需要強調的是,尾調用函數一定是其調用函數的最后一條語句,否則Lua不會進行優化。然而事實上,我們在很多看似是尾調用的場景中,實際上并不是真正的尾調用,如: function f(x) g(x) end --沒有return語句的明確提示 function f(x) return g(x) + 1 --在g()函數返回之后仍需執行一次加一的指令。 function f(x) return x or g(x) --如果g()函數返回多個值,該操作會強制要求g()函數只返回一個值。 function f(x) return (g(x)) --原因同上。 在Lua中,只有"return <func>(<args>)"形式才是標準的尾調用,至于參數中(args)是否包含表達式,由于表達式的執行是在函數調用之前完成的,因此不會影響該函數成為尾調用函數。
一、表達式:
1. 算術操作符: Lua支持常規算術操作符有:二元的“+”、“-”、“*”、“/”、“^”(指數)、“%”(取模),一元的“-”(負號)。所有這些操作符都可用于實數。然而需要特別說明的是取模操作符(%),Lua中對該操作符的定義為: a % b == a - floor(a / b) * b 由此可以推演出x % 1的結果為x的小數部分,而x - x % 1的結果則為x的整數部分。類似的,x - x % 0.01則是x精確到小數點后兩位的結果。 2. 關系操作符: Lua支持的關系操作符有:>、<、>=、<=、==、~=,所有這些操作符的結果均為true或false。 操作符==用于相等性測試,操作符~=用于不等性測試。這兩個操作符可以應用于任意兩個值。如果兩個值的類型不同,Lua就認為他們不等。nil值與其自身相等。對于table、userdata和函數,Lua是通過引用進行比較的。也就是說,只有當他們引用同一個對象時,才視為相等。如: 1 a = {} 2 a.x = 1 3 a.y = 0 4 b = {} 5 b.x = 1 6 b.y = 1 7 c = a 其結果是a == c,但a ~= b。 對于字符串的比較,Lua是按照字符次序比較的。 3. 邏輯操作符: Lua支持的邏輯操作符有:and、or和not。與條件控制語句一樣,所有的邏輯操作符都將false和nil視為假,其他的結果均為真。和其他大多數語言一樣,Lua中的and和or都使用“短路原則”。在Lua中有一種慣用寫法"x = x or v",它等價于:if not x then x = v end。這里還有一種基于“短路原則”的慣用寫法,如: max = (x > y) and x or y 這等價于C語言中max = (x > y) ? x : y。由于x和y均為數值,因此它們的結果將始終為true。 4. 字符串連接: 前一篇Blog已經提到了字符串連接操作符(..),這里再給出一些簡單的示例。 /> lua > print("Hello " .. "World) Hello World > print(0 .. 1) --即使連接操作符的操作數為數值類型,在執行時Lua仍會將其自動轉換為字符串。 01
5. table構造器: 構造器用于構建和初始化table的表達式。這是Lua特有的表達式,也是Lua中最有用、最通用的機制之一。其中最簡單的構造器是空構造器{},用于創建空table。我們通過構造器還可以初始化數組,如: 1 days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"} 2 for i = 1,#days do 3 print(days[i]) 4 end 5 --輸出結果為 6 --Sunday 7 --Monday 8 --Tuesday 9 --Wednesday 10 --Thursday 11 --Friday 12 --Saturday 從輸出結果可以看出,days在構造后會將自動初始化,其中days[1]被初始化為"Sunday",days[2]為"Monday",以此類推。 Lua中還提供了另外一種特殊的語法用于初始化記錄風格的table。如:a = { x = 10, y = 20 },其等價于:a = {}; a.x = 10; a.y = 20 在實際編程時我們也可以將這兩種初始化方式組合在一起使用,如: polyline = {color = "blue", thickness = 2, npoints = 4, {x = 0, y = 0}, {x = 10, y = 0}, {x = -10, y = 1}, {x = 0, y = 1} } print(polyline["color"]); print(polyline[2].x) print(polyline[4].y) --輸出結果如下: --blue --10 --1 除了以上兩種構造初始化方式之外,Lua還提供另外一種更為通用的方式,如: 1 opnames = { ["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div"} 2 print(opnames["+"]) 3 i = 20; s = "-" 4 a = { [i + 0] = s, [i + 1] = s .. s, [i + 2] = s..s..s } 5 print(a[22]) 對于table的構造器,還有兩個需要了解的語法規則,如: a = { [1] = "red", [2] = "green", [3] = "blue", } 這里需要注意最后一個元素的后面仍然可以保留逗號(,),這一點類似于C語言中的枚舉。 a = {x = 10, y = 45; "one", "two", "three" } 可以看到上面的聲明中同時存在逗號(,)和分號(;)兩種元素分隔符,這種寫法在Lua中是允許的。我們通常會將分號(;)用于分隔不同初始化類型的元素,如上例中分號之前的初始化方式為記錄初始化方式,而后面則是數組初始化方式。
二、語句:
1. 賦值語句: Lua中的賦值語句和其它編程語言基本相同,唯一的差別是Lua支持“多重賦值”,如:a, b = 10, 2 * x,其等價于a = 10; b = 2 * x。然而需要說明的是,Lua在賦值之前需要先計算等號右邊的表達式,在每一個表達式都得到結果之后再進行賦值。因此,我們可以這樣寫變量交互:x,y = y,x。如果等號右側的表達式數量少于左側變量的數量,Lua會將左側多出的變量的值置為nil,如果相反,Lua將忽略右側多出的表達式。
2. 局部變量與塊: Lua中的局部變量定義語法為:local i = 1,其中local關鍵字表示該變量為局部變量。和全局變量不同的是,局部變量的作用范圍僅限于其所在的程序塊。Lua中的程序可以為控制結構的執行體、函數執行體或者是一個程序塊,如: 下面的x變量僅在while循環內有效。 1 while i <= x do 2 local x = i * 2 3 print(x) 4 i = i + 1 5 end 如果是在交互模式下,當執行local x = 0之后,該變量x所在的程序即以結束,后面的Lua語句將被視為新的程序塊。如果想避免此類問題,我們可以顯式的聲明程序塊,這樣即便是在交互模式下,局部變量仍然能保持其塊內有效性,如: 1 do 2 local a2 = 2 * a 3 local d = (b ^ 2 - 4 * a) ^ (1 / 2) 4 x1 = (-b + d) / a2 5 x2 = (-b - d) / a2 6 end --a2和d的作用域至此結束。 和其它編程語言一樣,如果有可能盡量使用局部變量,以免造成全局環境的變量名污染。同時由于局部變量的有效期更短,這樣垃圾收集器可以及時對其進行清理,從而得到更多的可用內存。
3. 控制結構: Lua中提供的控制語句和其它大多數開發語言所提供的基本相同,因此這里僅僅是進行簡單的列舉。然后再給出差異部分的詳細介紹。如: 1). if then else if a < 0 then b = 0 else b = 1 end 2). if elseif else then if a < 0 then b = 0 elseif a == 0 then b = 1 else b = 2 end 3). while local i= 1 while a[i] do print(a[i]) i = i + 1 end 4). repeat repeat line = io.read() until line ~= "" --直到until的條件為真時結束。 print(line) 5). for for var = begin, end, step do --如果沒有step變量,begin的缺省步長為1。 i = i + 1 end 需要說明的是,for循環開始處的三個變量begin、end和step,如果它們使表達式的返回值,那么該表達式將僅執行一次。再有就是不要在for的循環體內修改變量var的值,否則會導致不可預知的結果。 6). foreach for i, v in ipairs(a) do --ipairs是Lua自帶的系統函數,返回遍歷數組的迭代器。 print(v) end for k in pairs(t) do --打印table t中的所有key。 print(k) end 見如下示例代碼: 1 days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" } 2 revDays = {} 3 for k, v in ipairs(days) do 4 revDays[v] = k 5 end 6 7 for k in pairs(revDays) do 8 print(k .. " = " .. revDays[k]) 9 end 10 11 --輸出結果為: 12 --Saturday = 7 13 --Tuesday = 3 14 --Wednesday = 4 15 --Friday = 6 16 --Sunday = 1 17 --Thursday = 5 18 --Monday = 2 7). break 和C語言中的break語義完全相同,即跳出最內層循環。
一、基礎知識:
1. 第一個程序和函數: 在目前這個學習階段,運行Lua程序最好的方式就是通過Lua自帶的解釋器程序,如: /> lua > print("Hello World") Hello World 這樣我們就可以以交互性的方式輸入lua代碼,并立即得到執行結果了。對于代碼塊較少的測試程序來說,這種方式確實是非常方便的,然而對于相對復雜的程序而言,這種方式就不是很合適了。如果是這樣,我們可以將Lua代碼保存到一個獨立的Lua程序文件中,之后再通過Lua解釋器程序以命令行參數的形式執行文件中的Lua代碼。如我們將下面的Lua代碼保存到test.lua的文件中: 1 function fact(n) 2 if n == 0 then 3 return 1 4 else 5 return n * fact(n - 1) 6 end 7 end 8 print("Enter a number:") 9 a = io.read("*number") 10 print(fact(a)) /> lua D:/test.lua Enter a number: 4 24
2. 代碼規范: 1). Lua的多條語句之間并不要求任何分隔符,如C語言的分號(;),其中換行符也同樣不能起到語句分隔的作用。因此下面的寫法均是合法的。如:
1 a = 1 2 b = a * 2 3 4 a = 1; 5 b = a * 2; 6 7 a = 1; b = a * 2; 8 a = 1 b = a * 2 2). 通過dofile()方法引用其他Lua文件中的函數,如:
1 function fact(n) 2 if n == 0 then 3 return 1 4 else 5 return n * fact(n - 1) 6 end 7 end 將上面的函數保存到test2.lua文件中。 /> lua > dofile("d:/test2.lua") > print(fact(4)) 24 3). 詞法規范。 和大多數其它語言一樣,在聲明變量時,變量名可以由任意字母、數字和下劃線構成,但是不能以數字開頭。在Lua中還有一個特殊的規則,即以下劃線(_)開頭,后面緊隨多個大寫字母(_VERSION),這些變量一般被Lua保留并用于特殊用途,因此我們在聲明變量時需要盡量避免這樣的聲明方式,以免給后期的維護帶來不必要的麻煩。 Lua是大小寫敏感的,因此對于一些Lua保留關鍵字的使用要特別小心,如and。但是And和AND則不是Lua的保留字。 4). Lua中的注釋分為兩種,一種是單行注釋,如: --This is a single line comment. 另外一種是多行注釋,如: --[[ This is a multi-lines comment. --]]
3. 全局變量: 在Lua中全局變量不需要聲明,直接賦值即可。如果直接訪問未初始化的全局變量,Lua也不會報錯,直接返回nil。如果不想再使用該全局變量,可直接將其置為nil。如: /> lua > print(b) nil > b = 10 > print(b) 10 > b = nil > print(b) nil 4. 解釋器程序: 命令行用法如下: lua [options] [lua-script [arguments] ] 該工具的命令行選項主要有以下3個: -e: 可以直接執行命令行中Lua代碼,如:lua -e "print(\"Hello World\")" -l: 加載該選項后的Lua庫文件,如:lua -l mylib -e "x = 10",該命令在執行之前先將mylib中的Lua代碼加載到內存中,在后面的命令中就可以直接使用該文件中定義的Lua函數了。 -i: 在執行完指定的Lua程序文件之后,并不退出解釋器程序,而是直接進入該程序的交互模式。 在解釋器程序的交互模式下,我們可以通過在表達式前加等號(=)標識符的方式直接輸出表達式的執行結果。通過該方式,我們可以將該程序用于計算器,如: /> lua > = 3 + 1 + 4 8 該小節最后需要介紹的是lua腳本的命令行參數訪問規則。如: /> lua lua-script.lua a b c 在該腳本的程序入口,lua解釋器會將所有命令行參數創建一個名為arg的table。其中腳本名(lua-script.lua)位于table索引的0位置上。它的第一個參數(a)則位于索引1,其它的參數以此類推。這種索引方式和C語言中讀取命令行參數的規則相同。但是不同的是,Lua提供了負數索引,用以訪問腳本名稱之前的命令行參數,如: arg[-1] = lua arg[0] = lua-script.lua arg[1] = a arg[2] = b arg[3] = c
二、類型與值:
Lua是一種動態類型的語言。其語言本身沒有提供類型定義的語法,每個值都“攜帶”了它自身的類型信息。在Lua中有8中基礎類型,分別是:nil、boolean、number、string、userdata、function、thread和table。我們可以通過type函數獲得變量的類型信息,該類型信息將以字符串的形式返回。如: > print(type("hello world")) string > print(type(10.4)) number > print(type(print)) function > print(type(true)) boolean > print(type(nil)) nil > print(type(type(X))) string
1. nil(空): nil是一種類型,它只有一個值nil,它的主要功能是由于區別其他任何值。就像之前所說的,一個全局變量在第一次賦值前的默認值的默認值就是nil,將nil賦予一個全局變量等同于刪除它。Lua將nil用于表示一種“無效值”的情況。 2. boolean(布爾): 該類型有兩個可選值:false和true。在Lua中只有當值是false和nil時才視為“假”,其它值均視為真,如數字零和空字符串,這一點和C語言是不同的。 3. number(數字): Lua中的number用于表示實數。Lua中沒有專門的類型表示整數。 4. string(字符串): Lua中的字符串通常表示“一個字符序列”。字符串類型的變量是不可變的,因此不能像C語言中那樣直接修改字符串的某一個字符,而是在修改的同時創建了新的字符串。如: 1 a = "one string" 2 b = string.gsub(a,"one","another") 3 print(a) 4 print(b) /> lua d:/test.lua one string anotner string Lua支持和C語言類似的字符轉義序列,見下表: 轉義符 | 描述 | \a | 響鈴 | \b | 退格 | \n | 換行 | \r | 回車 | \t | 水平Tab | \\ | 反斜杠 | \" | 雙引號 | \' | 單引號 |
在Lua中還可以通過[[ all strings ]]的方式來禁用[[ ]]中轉義字符,如: page = [[ <html> <head> <title> An Html Page </title> </head> ]] 如果兩個方括號中包含這樣的內容:a = b[c[i]],這樣將會導致Lua的誤解析,因此在這種情況下,我們可以將其改為[===[ 和 ]===]的形式,從而避免了誤解析的發生。 Lua提供了運行時的數字與字符串的自動轉換。如: > print("10" + 1) 11 > print("10 + 1") 10 + 1 如果在實際編程中,不希望兩個數字字符串被自動轉換,而是實現字符串之間的連接,可以通過" .. "操作符來完成。如: > print(10 .. 20) 1020 注意..和兩邊的數字之間必須留有空格,否則就會被Lua誤解析為小數點兒。 盡管Lua提供了這種自動轉換的功能,為了避免一些不可預測的行為發生,特別是因為Lua版本升級而導致的行為不一致現象。鑒于此,還是應該盡可能使用顯示的轉換,如字符串轉數字的函數tonumber(),或者是數字轉字符串的函數tostring()。對于前者,如果函數參數不能轉換為數字,該函數返回nil。如: 1 line = io.read() 2 n = tonumber(line) 3 if n == nil then 4 error(line .. " is not a valid number") 5 else 6 print(n * 2) 7 end 關于Lua的字符串最后需要介紹的是"#"標識符,該標識符在字符串變量的前面將返回其后字符串的長度,如: /> lua d:/test.lua 5 5. table(表): 我們可以將Lua中table類型視為“關聯數組”,如C++標準庫中的map,差別是Lua中table的鍵(key)可以為任意類型(nil除外),而map中的鍵只能為模參類型。此外,table沒有固定的大小,可以動態的添加任意數量的元素到一個table中。table是Lua中最主要數據結構,其功能非常強大,可用于實現數組、集合、記錄和隊列數據結構。以下為table的變量聲明,以及關聯數據的初始化方式: 1 a = {} -- 創建一個table對象,并將它的引用存儲到a 2 k = "x" 3 a[k] = 10 -- 創建了新條目,key = "x", value = 10 4 a[20] = "great" -- 新條目,key = 20, value = "great" 5 print(a["x"]) 6 k = 20 7 print(a[k]) -- 打印great 8 a["x"] = a["x"] + 1 9 print(a["x"]) -- 打印11 所有的table都可以用不同類型的索引來訪問value,當需要容納新條目時,table會自動增長。 1 a = {} 2 for i = 1, 100 do 3 a[i] = i * 2 4 end 5 print(a[9]) 6 a["x"] = 10 7 print(a["x"]) 8 print(a["y"]) --table中的變量和全局變量一樣,沒有賦值之前均為nil。 9 10 --輸出結果為 11 --18 12 --10 13 --nil 在Lua中還提供了另外一種方法用于訪問table中的值,見如下示例: 1 a.x = 10 --等同于a["x"] = 10 2 print(a.x) --等同于print(a["x"]) 3 print(a.y) --等同于print(a["y"]) 對于Lua來說,這兩種方式是等價的。但是對于開發者而言,點的寫法隱式的將table表示為記錄,既C語言中的結構體。而之前講述的字符串表示法則意味著任何字符串均可作為table的key。 如果需要將table表示為傳統的數組,只需將整數作為table的key即可。如: 1 a = {} 2 for i = 1,10 do 3 a[i] = i * 2 4 end 5 6 for i = 1,10 do 7 print(a[i]) 8 end 在Lua中,我通常習慣以1作為數組索引的起始值。而且還有不少內部機制依賴于這個慣例。如: 1 a = {} 2 for i = 1,10 do 3 a[i] = i * 2 4 end 5 6 for i = 1,#a do 7 print(a[i]) 8 end 由于數組實際上仍為一個table,所以對于數組大小的計算需要留意某些特殊的場景,如: a = {} a[1000] = 1 在上面的示例中,數組a中索引值為1--999的元素的值均為nil。而Lua則將nil作為界定數據結尾的標志。當一個數組含有“空隙”時,即中間含有nil值,長度操作符#會認為這些nil元素就是結尾標志。當然這肯定不是我們想要的結果。因此對于這些含有“空隙”的數組,我們可以通過函數table.maxn()返回table的最大正數索引值。如: 1 a = {} 2 a[1000] = 1 3 print(table.maxn(a)) 4 5 -- 輸出1000 6. function(函數): 在Lua中,函數可以存儲在變量中,可以通過參數傳遞其它函數,還可以作為其它函數的返回值。這種特性使語言具有了極大的靈活性。
7. userdata(自定義類型): 由于userdata類型可以將任意C語言數據存儲到Lua變量中。在Lua中,這種類型沒有太多預定義的操作,只能進行賦值和相等性測試。userdata用于表示一種由應用程序或C語言庫所創建的新類型。
摘要: 概述——什么是makefile?或許很多Winodws的程序員都不知道這個東西,因為那些Windows的IDE都為你做了這個工作,但我覺得要作一個好的和professional的程序員,makefile還是要懂。這就好像現在有這么多的HTML的編輯器,但如果你想成為一個專業人士,你還是要了解HTML的標識的含義。特別在Unix下的軟件編譯,你就不能不自己寫makefile了... 閱讀全文
上午一個師弟在QQ上問我一道筆試題,是他前兩天去KONAMI面試時做的,這道題大致是這樣的: 解釋以下語句的含義: 1、new A; 2、new A(); 也許很多人包括我自己,都可以馬上給出第一種情況的答案:在堆上為A類分配內存,然后調用A的構造函數。這種說法被大家所熟知,因為包括《STL源碼剖析》等大作在內也都是這么寫的(但是你認為這種說法完全正確嗎?其實不盡然,答案后面揭曉) 第二種情況,對象構造的時候初始化列表為空會和第一種有什么不同呢?對于這種在實際工程中很少使用的情況,我一時還真給不出確切的答案。 網上搜了一下,看到CSDN里面還有專門針對這個問題的一個帖子(原帖鏈接 http://bbs.csdn.net/topics/320161716)。 好像最終也沒有可以信服的答案,認同度比較高的是這樣的說法:“加括號調用沒有參數的構造函數,不加括號調用默認構造函數或唯一的構造函數,看需求” (peakflys注:這種說法是錯誤的,答案后面揭曉) 既然沒有特別靠譜的答案,不如自己動手找出答案。 構造以下示例:
/** *\brief example1 difference between new and new() *\author peakflys *\data 12:10:24 Monday, April 08, 2013 */
class A { public: int a; };
int main() { A *pa = new A; A *paa = new A(); return 0; } 查看main函數的匯編代碼(編譯器:gcc (GCC) 4.4.6 20120305 (Red Hat 4.4.6-4) )
int main() { 4005c4: 55 push %rbp 4005c5: 48 89 e5 mov %rsp,%rbp 4005c8: 48 83 ec 10 sub $0x10,%rsp A *pa = new A; 4005cc: bf 04 00 00 00 mov $0x4,%edi 4005d1: e8 f2 fe ff ff callq 4004c8 <_Znwm@plt> //調用new 4005d6: 48 89 45 f0 mov %rax,-0x10(%rbp) //rax寄存器內容賦給指針pa(rax寄存器里是new調用產生的A對象堆內存地址) A *paa = new A(); 4005da: bf 04 00 00 00 mov $0x4,%edi 4005df: e8 e4 fe ff ff callq 4004c8 <_Znwm@plt> //調用new 4005e4: 48 89 c2 mov %rax,%rdx //rax的內容放入rdx,執行之后,rdx里存放的即是通過new A()產生的內存地址 4005e7: c7 02 00 00 00 00 movl $0x0,(%rdx) //把rdx內存指向的內容賦為0值,即把A::a賦值為0 4005ed: 48 89 45 f8 mov %rax,-0x8(%rbp) //rax寄存器內容賦給指針paa(rax寄存器里是new()調用產生的A對象堆內存地址) return 0; 4005f1: b8 00 00 00 00 mov $0x0,%eax } 4005f6: c9 leaveq 4005f7: c3 retq 通過上面產生的匯編代碼(對AT&T匯編不熟悉的可以看注釋)可以很容易看出,new A()的執行,在調用完operator new分配內存后,馬上對新分配內存中的對象使用0值初始化,而new A 僅僅是調用了operator new分配內存! 是不是這樣就可以下結論 new A()比new A多了一步,即初始化對象的步驟呢? 我們再看看下面這種情況:
/** *\brief example2 difference between new and new() *\author peakflys *\data 12:23:20 Monday, April 08, 2013 */
class A { public: A(){a = 10;} int a; };
int main() { A *pa = new A; A *paa = new A(); return 0; } 這種情況是類顯示提供含默認值的構造函數。 查看匯編實現如下:
int main() { 4005c4: 55 push %rbp 4005c5: 48 89 e5 mov %rsp,%rbp 4005c8: 53 push %rbx 4005c9: 48 83 ec 18 sub $0x18,%rsp A *pa = new A; 4005cd: bf 04 00 00 00 mov $0x4,%edi 4005d2: e8 f1 fe ff ff callq 4004c8 <_Znwm@plt> 4005d7: 48 89 c3 mov %rax,%rbx 4005da: 48 89 d8 mov %rbx,%rax 4005dd: 48 89 c7 mov %rax,%rdi 4005e0: e8 2d 00 00 00 callq 400612 <_ZN1AC1Ev> 4005e5: 48 89 5d e0 mov %rbx,-0x20(%rbp) A *paa = new A(); 4005e9: bf 04 00 00 00 mov $0x4,%edi 4005ee: e8 d5 fe ff ff callq 4004c8 <_Znwm@plt> 4005f3: 48 89 c3 mov %rax,%rbx 4005f6: 48 89 d8 mov %rbx,%rax 4005f9: 48 89 c7 mov %rax,%rdi 4005fc: e8 11 00 00 00 callq 400612 <_ZN1AC1Ev> 400601: 48 89 5d e8 mov %rbx,-0x18(%rbp) return 0; 400605: b8 00 00 00 00 mov $0x0,%eax } 40060a: 48 83 c4 18 add $0x18,%rsp 40060e: 5b pop %rbx 40060f: c9 leaveq 400610: c3 retq 上面的匯編代碼就不在添加注釋了,因為兩種操作產生的匯編代碼是一樣的,都是先調用operator new分配內存,然后調用構造函數。 上面的情況在VS2010下驗證是一樣的情況,有興趣的朋友可以自己去看,這里就不再貼出VS2010下的匯編代碼了。 通過上面的分析,對于new A和 new A() 的區別,我們可以得出下面的結論: 1、類體含有顯示適合地默認構造函數時,new A和new A()的作用一致,都是首先調用operator new分配內存,然后調用默認構造函數初始化對象。 2、類體無顯示構造函數時,new A()首先調用operator new來為對象分配內存,然后使用空值初始化對象成員變量,而new A僅僅是調用operator new分配內存,對象的成員變量是無意義的隨機值! (peakflys注:對于基本數據類型,如int等 適用此條) 注意到,現在很多書籍對new操作符的說明都存在紕漏,例如《STL源碼剖析》中2.2.2節中有以下的描述: 事實證明,new Foo的操作是否有構造函數的調用是不確定的,具體要看Foo類體里是否有顯示構造函數的出現。 by peakflys 13:40:00 Monday, April 08, 2013/*****************************************華麗分割線**************************************補充:剛才發現,在C++Primer第四版5.11節中,已經有了對于new A()的說明:
We indicate that we want to value-initialize the newly allocated object by following the type nameby a pair of empty parentheses. The empty parentheses signal that we want initialization but arenot supplying a specific initial value. In the case of class types (such as string) that define their own constructors, requesting value-initialization is of no consequence: The object is initialized by running the default constructor whether we leave it apparently uninitialized orask for value-initialization. In the case of built-in types or types that do not define any constructors, the difference is significant:
int *pi = new int; // pi points to an uninitialized int
int *pi = new int(); // pi points to an int value-initialized to 0
In the first case, the int is uninitialized; in the second case, the int is initialized to zero. 這里給出的解釋和上面自己分析的new A()的行為是一致的。 /***************************************再次華麗分割線************************************ 鑒于上面的結論是通過GCC和VS2010得出的,而且有朋友也提出同樣的質疑,為了確定這種結果是否是編譯器相關的,剛才特意查看了一下C++的標準化文檔。 摘自:ISO/IEC 14882:2003(E) 5.3.4 - 15 — If the new-initializer is omitted: — If T is a (possibly cv-qualified) non-POD class type (or array thereof), the object is default-initialized(8.5). If T is a const-qualified type, the underlying class type shall have a user-declared default constructor. — Otherwise, the object created has indeterminate value. If T is a const-qualified type, or a (possibly cv-qualified) POD class type (or array thereof) containing (directly or indirectly) a member of const-qualified type, the program is ill-formed; — If the new-initializer is of the form (), the item is value-initialized (8.5); 所以可以確定,這種情況完全是編譯器無關的(當然那些不完全按照標準實現的編譯器除外)。 但是通過上面標準化文檔的描述,我們可以看出文中對new A在無顯示構造函數時的總結并不是特別準確,鑒于很多公司都有這道面試題(撇去這些題目的實際考察意義不說),我們有必要再補充一下: 對于new A: 這樣的語句,再調用完operator new分配內存之后,如果A類體內含有POD類型,則POD類型的成員變量處于未定義狀態,如果含有非POD類型則調用該類型的默認構造函數。而 new A()在這些情況下都會初始化。
摘要: 作者:fengge8ylf 博客:http://blog.csdn.net/fengge8ylf
對于基于TCP開發的通訊程序,有個很重要的問題需要解決,就是封包和拆包.自從我從事網絡通訊編程工作以來(大概有三年的時間了),我一直在思索和改進封包和拆包的方法.下面就針對這個問題談談我的想法,拋磚引玉.若有不對,不妥之處,懇求大家指正.在此先謝過大家了.
一.為什么基于TCP的通訊程... 閱讀全文
關于低耦合的消息傳遞,實現的方式有很多,哪種方法更好與具體的使用環境有關,本文使用試錯的方法,逐步探索達成這一目的具體方式,并理解實現方式背后的原因。
面向對象的系統當中,不可避免的有大量的類間消息傳遞的需求:一個類需要通知另一個或幾個類做些什么。
這種類間消息傳遞,簡單的說,就是調用其他類的方法。
如下:
1 void A::OnMessageXX() 2  { 3 B::GetInstance()->DoSomething(); 4 5 } 6 7
在這里,類A需要通知類B做些事情。這種調用在所有的面向對象程序中都是極其常見的。
但是如果類A需要調用類B,就不可避免的產生了耦合性。雖然耦合性終歸是不可能完全避免的,但是在一定程度上降低耦合性是完全可能的。
(至于為什么在設計中應該盡可能降低耦合性,不在本文的探討范圍之內)
上面的例子,我們使用了Singleton的模式,從全局作用域中獲取了B的實例,并調用了B的相關方法。使用Singleton的一個缺點是,假若我們希望對類A編寫測試代碼,我們需要做一些額外的解耦合工作。(關于編寫測試與解耦合,可以參考Robert C. Martin Series 的Working Effectively with Legacy Code一書,該書的中譯版在這 )
我們也可以通過將B參數化的方法降低A與B間的耦合程度,像下面這樣:
1 void A::OnMessageXX(B* pBInstance) 2 { 3 pBInstance->DoSomething(); 4 5 } 6 7
現在的寫法要比之前的做法耦合性低,通過使用多態的方法,現在傳入函數的類B指針可能是另一個實現了B的相應接口的派生類,A并不關心B接口背后的具體實現。
但是等等,你說,現在對類B的耦合性雖然在A中被降低了,但是依舊存在于調用A::OnMessageXX的地方。在那里我們還是需要取得B的實例,然后傳遞給A。
沒錯,是這樣。
通過參數化類A的方法,我們把類A與類B間的耦合轉移了一部分到A的調用者那里。實際上總的耦合并沒有消除,只是被分解了。但是程序設計中不可能完全不存在耦合,我們需要做的是”正確”,而不是”完美”。類A的耦合性降低了,使得我們在未來需求變更的時候,類A有更大的可能性不需要被修改,并且對功能的擴展更加友好,這就達成了我們的目標了。
基于上述做法,如果我們在未來擴展是派生出一個B的子類,override相關的方法,那么類A的代碼基本是不需要修改的。
不過,問題是,假若A::OnMessageXX中,并不僅僅需要對類B發出消息,還需要對一系列相關的類B1,B2,B3等等發出消息呢?
哦,或許我們可以這樣做:
void A::OnMessageXX(const std::list<B*>& lstBInstances)
  {
for (std::list<B*>::const_iterator itr = lstBInstances.begin();
itr != lstBInstances.end();
++itr)
 {
(*itr)->DoSomething();

}
}


是的,上面這是一種做法,有一系列B的對象需要被通知到,所以我們可以用一個列表把他們串起來,然后在循環中通知他們去干活。不過這樣做的前提是,這一系列B對象都是派生自一個公共基類B,有共通的接口;此外,我們需要在A的OnMessageXX被調用之前構造一個需要接受通知的B對象列表。
當A需要通知B,C,D等一系列沒有公共接口的對象的時候,上面的這種做法就無法處理了。
對于B、C、D等需要由A來調用的類來說,它們需要在A通知它們的時候,做一些特定的事情。而又A則是在某些特定的時刻需要通知B、C、D。這樣,我們可以把問題看成一個消息響應機制。
B、C、D可以在A的某些事件上注冊一些回調函數,當事件發生時,A確保注冊該事件的函數被調用到。
如下:
typedef void(callback*)();
class A {
public:
enum EventIds {
EVENT_MSG1,
EVENT_MSG2,
};
void RegisterEvent(int nEventId, callback pfn);
private:
callback m_pfnCallback;
};
現在,B可以調用A::RegisterEvent注冊一個事件,并傳遞一個函數指針給A。
當A中發生了注冊的事件時,這個函數指針會被回調到。
不過這種簡單的做法適應性很差:
1、 不能支持單個事件的多個callback (可能有很多類都需要注冊該事件,并在事件發生時依次被回調)
2、 不能支持多個事件的同時存在
3、 回調函數沒有參數’
針對問題1,2,我們可以使用一個事件映射解決問題,做法如下:
typedef int EventId;
typedef void (callback*)();
typedef std::list<callback> CallbackList;
typedef std::map<EventId, CallbackList> CallbackMap;
現在這個數據結構就能夠支持多個event同時存在,且每個event都可以支持多個回調函數了。
但是這種用法依舊很不方便,如果類B想要注冊A上的一個事件,他需要定義一個 callback類型的函數,并把這個函數的地址傳遞給A。問題是,往往我們希望類B的回調函數在被調用到的時候,對類B中的數據和狀態進行修改,而一個單獨的函數,若想獲得/修改B中的狀態,則必須要與類B緊密耦合。(通過獲取全局對象,或者Singleton的方式)
這種緊密耦合引發我們的思考,能否在Callback中同時包含類B的指針與類B的成員函數。
答案是肯定的:泛型回調 就可以做到這一點。關于泛型回調(Generic callback)的信息,在Herb Sutter的Exceptional C++ Style 的35條中有詳細介紹。
一下比較簡單的泛型回調的定義如下:
class callbackbase {
public:
virtual void operator()() const {};
virtual ~callbackbase() = 0 {};
};
template <class T>
class callback : public callbackbase {
public:
typedef void (T::*Func)();
callback(T& t, Func func) : object(t), f(func) {} // 綁定到實際對象
void operator() () const { (object->*f)(); } // 調用回調函數
private:
T* object;
Func f;
};
有了這種泛型回調類,我們就可以將類B的實例與B的成員回調函數綁定在一起注冊到容器當中了,而不必再被如何在普通函數中修改B對象狀態的問題所困擾了。不過回調函數的參數問題依舊。如果想支持參數,我們不得不對每一種參數類型做一個不同的typedef,像上面定義的這樣 typedef void (T::*Func)();(如:typedef void (T::*Func)(int);)
一種解決方案是借助于Any(一種任意類型類)進行參數傳遞。
但是還有更完善的解決方案,不需要id號,也不需要泛型回調,Ogre采用Listener的方式實現的類間消息傳遞不僅可以支持單個類B對類A中某個事件的單次/多次注冊,也可以支持類B、C、D對同一個事件的注冊。而且可以完美的解決參數傳遞問題。
具體的方案如下:
1 class A { 2 public: 3 class Listener 4 { 5 public: 6 7 virtual void OnMessageXX(int param1, float param2) = 0; 8 9 virtual void OnMessageYY(int param1, const std::string& param2) = 0; 10 11 }; 12 13 void registerListener(Listener* obj) 14  { 15 m_lstListener.push_back(obj); 16 } 17 18 void removeListener(Listener* obj) 19  { 20 ListenerList::iterator itr = std::find(m_lstListener.begin(), m_lstListener.end(), obj); 21 22 if (itr != m_lstListener.end()) 23 m_lstListener.erase(itr); 24 } 25 26 private: 27 typedef std::list<Listener*> ListenerList; 28 29 ListenerList m_lstListeners; 30 }; 31 32
有了以上定義,當類A收到某個消息XX之后,只需遍歷m_lstListeners列表,調用所有列表成員的OnMessageXX即可。
而所有注冊A的消息的類,都必須從A::Listener派生一個類,在它感興趣的消息處理函數中做出相應處理,而對不感興趣的消息,只需設為空函數即可。
一個簡單的類B的定義如下:
class B {
public:
friend class BListener;
class BListener : public A::Listener {
public:
BListener(B* pBInstance) : m_pBInstance(pBInstance) {}
virtual void OnMessageXX(int param1, float param2)
{ m_pBInstance->DoSomething(); }
virtual void OnMessageYY(int param1, const std::string& param2) {}
private:
B* m_pBInstance;
};
explicit B(A* pAInstance) : m_pAInstance(pAInstance)
{
m_pListener(new BListener(this));
m_pAInstance->registerListener(m_pListener);
}
~B() { m_pAInstance->removeListener(m_pListener); delete m_pListener; }
void DoSomething();
private:
BListener* m_pListener;
}
類B在創建自身實例時,接受一個A的指針(這是合理的,因為類B需要監聽類A的消息,理應知道A的存在),并創建一個派生自A::Listener 的監聽者對象,并把自身的指針傳遞給該對象,以使得該監聽者改變類B的狀態,而后類B將創建好的監聽者對象加入到A的監聽者列表中。
在B進行析構的時候,需要從A中刪除自己注冊的監聽者。而后將該對象釋放。
這種做法的好處:
1、 類B(以及類C等)對類A實現了信息隱藏,類A不再關注任何需要監聽它自身消息的其他類,只需關注其自身的狀態。從而減低了類A與其他與之關聯的類之間的耦合。(類A不必再費盡心機的去獲取B的指針,不管是通過全局變量,還是Singleton,還是參數,還是類成員變量,都不再需要了,A只關心在 Listener中定義好的一組接口即可)而且,如果有必要類B可以對同一個消息注冊多次,且可以對同一消息有不同的反應(通過定義不同的 BListener實現達到這一目的),只需在B不再需要監聽相關消息時將所注冊過的對象注銷掉即可。
2、 由于1中所述,類A的實現無需關心類B的實現,因此類A的邏輯中不需要包含任何類B的方法調用,從而,類A的cpp文件中,無需包含類B的頭文件,(可能還包括類C,D等等,此處類B指代需要根據類A狀態而做出動作的類)從而降低編譯時間,這是解耦合所帶來的附加好處。
3、 同樣是解耦合帶來的好處:因為無需關注類B等等其他類的實現,類A的代碼邏輯變得更加清晰,并且減少未來邏輯需求變更的改動所需要付出的代價(邏輯變更可能需要更改接口,需要增加狀態判斷,無論是調試時間還是編譯時間都是不可忽視的代價)。
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/zougangx/archive/2009/07/30/4395775.aspx
在編寫程序的時候,我們經常要用到#pragma指令來設定編譯器的狀態或者是指示編譯器完成一些特定的動作. 下面介紹了一下該指令的一些常用參數,希望對大家有所幫助!
一. message 參數。 message 它能夠在編譯信息輸出窗口中輸出相應的信息,這對于源代碼信息的控制是非常重要的。
其使用方法為: #pragma message("消息文本")
當編譯器遇到這條指令時就在編譯輸出窗口中將消息文本打印出來。 當我們在程序中定義了許多宏來控制源代碼版本的時候,我們自己有可能都會忘記有沒有正確的設置這些宏,此時我們可以用這條指令在編譯的時候就進行檢查。假設我們希望判斷自己有沒有在源代碼的什么地方定義了_X86這個宏可以用下面的方法: #ifdef _X86 #pragma message("_X86 macro activated!") #endif 當我們定義了_X86這個宏以后,應用程序在編譯時就會在編譯輸出窗口里顯示 "_X86 macro activated!" 這樣,我們就不會因為不記得自己定義的一些特定的宏而抓耳撓腮了。
二. 另一個使用得比較多的#pragma參數是code_seg。 格式如: #pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] ) 該指令用來指定函數在.obj文件中存放的節,觀察OBJ文件可以使用VC自帶的dumpbin命令行程序,函數在.obj文件中默認的存放節為.text節,如果code_seg沒有帶參數的話,則函數存放在.text節中。
push (可選參數) 將一個記錄放到內部編譯器的堆棧中,可選參數可以為一個標識符或者節名 pop(可選參數) 將一個記錄從堆棧頂端彈出,該記錄可以為一個標識符或者節名 identifier (可選參數) 當使用push指令時,為壓入堆棧的記錄指派的一個標識符,當該標識符被刪除的時候和其相關的堆棧中的記錄將被彈出堆棧 "segment-name" (可選參數) 表示函數存放的節名 例如: //默認情況下,函數被存放在.text節中 void func1() { // stored in .text }
//將函數存放在.my_data1節中 #pragma code_seg(".my_data1") void func2() { // stored in my_data1 }
//r1為標識符,將函數放入.my_data2節中 #pragma code_seg(push, r1, ".my_data2") void func3() { // stored in my_data2 }
int main() { }
三. #pragma once (比較常用) 這是一個比較常用的指令,只要在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次
四. #pragma hdrstop表示預編譯頭文件到此為止,后面的頭文件不進行預編譯。 BCB可以預編譯頭文件以加快鏈接的速度,但如果所有頭文件都進行預編譯又可能占太多磁盤空間,所以使用這個選項排除一些頭文件。 有時單元之間有依賴關系,比如單元A依賴單元B,所以單元B要先于單元A編譯。你可以用#pragma startup指定編譯優先級,如果使用了#pragma package(smart_init) ,BCB就會根據優先級的大小先后編譯。
五. #pragma warning指令 該指令允許有選擇性的修改編譯器的警告消息的行為 指令格式如下: #pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...] #pragma warning( push[ ,n ] ) #pragma warning( pop )
主要用到的警告表示有如下幾個:
once:只顯示一次(警告/錯誤等)消息 default:重置編譯器的警告行為到默認狀態 1,2,3,4:四個警告級別 disable:禁止指定的警告信息 error:將指定的警告信息作為錯誤報告
如果大家對上面的解釋不是很理解,可以參考一下下面的例子及說明
#pragma warning( disable : 4507 34; once : 4385; error : 164 ) 等價于: #pragma warning(disable:4507 34) // 不顯示4507和34號警告信息 #pragma warning(once:4385) // 4385號警告信息僅報告一次 #pragma warning(error:164) // 把164號警告信息作為一個錯誤。 同時這個pragma warning 也支持如下格式: #pragma warning( push [ ,n ] ) #pragma warning( pop ) 這里n代表一個警告等級(1---4)。 #pragma warning( push )保存所有警告信息的現有的警告狀態。 #pragma warning( push, n)保存所有警告信息的現有的警告狀態,并且把全局警告等級設定為n。 #pragma warning( pop )向棧中彈出最后一個警告信息,在入棧和出棧之間所作的一切改動取消。例如: #pragma warning( push ) #pragma warning( disable : 4705 ) #pragma warning( disable : 4706 ) #pragma warning( disable : 4707 ) #pragma warning( pop )
在這段代碼的最后,重新保存所有的警告信息(包括4705,4706和4707)
在使用標準C++進行編程的時候經常會得到很多的警告信息,而這些警告信息都是不必要的提示,所以我們可以使用#pragma warning(disable:4786)來禁止該類型的警告在vc中使用ADO的時候也會得到不必要的警告信息,這個時候我們可以通過#pragma warning(disable:4146)來消除該類型的警告信息
六. pragma comment(...) 該指令的格式為: #pragma comment( "comment-type" [, commentstring] ) 該指令將一個注釋記錄放入一個對象文件或可執行文件中,comment-type(注釋類型):可以指定為五種預定義的標識符的其中一種。 五種預定義的標識符為:
1、compiler:將編譯器的版本號和名稱放入目標文件中,本條注釋記錄將被編譯器忽略 如果你為該記錄類型提供了commentstring參數,編譯器將會產生一個警告 例如:#pragma comment( compiler )
2、exestr:將commentstring參數放入目標文件中,在鏈接的時候這個字符串將被放入到可執行文件中,當操作系統加載可執行文件的時候,該參數字符串不會被加載到內存中.但是,該字符串可以被dumpbin之類的程序查找出并打印出來,你可以用這個標識符將版本號碼之類的信息嵌入到可執行文件中!
3、lib:這是一個非常常用的關鍵字,用來將一個庫文件鏈接到目標文件中常用的lib關鍵字,可以幫我們連入一個庫文件。 例如: #pragma comment(lib, "user32.lib") 該指令用來將user32.lib庫文件加入到本工程中
4、linker:將一個鏈接選項放入目標文件中,你可以使用這個指令來代替由命令行傳入的或者在開發環境中設置的鏈接選項,你可以指定/include選項來強制包含某個對象,例如: #pragma comment(linker, "/include:__mySymbol") 你可以在程序中設置下列鏈接選項 /DEFAULTLIB /EXPORT /INCLUDE /MERGE /SECTION
這些選項在這里就不一一說明了,詳細信息請看msdn!
5、user:將一般的注釋信息放入目標文件中commentstring參數包含注釋的文本信息,這個注釋記錄將被鏈接器忽略 例如: #pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )
1、讀取當前錯誤值:每次發生錯誤時,如果要對具體問題進行處理,那么就應該調用這個函數取得錯誤代碼。
int WSAGetLastError(void );
#define h_errno WSAGetLastError()
錯誤值請自己閱讀Winsock2.h。 2、將主機的unsigned long值轉換為網絡字節順序(32位):為什么要這樣做呢?因為不同的計算機使用不同的字節順序存儲數據。因此任何從Winsock函數對IP地址和端口號的引用和傳給Winsock函數的IP地址和端口號均時按照網絡順序組織的。
u_long htonl(u_long hostlong);
舉例:htonl(0)=0
htonl(80)= 1342177280
3、將unsigned long數從網絡字節順序轉換位主機字節順序,是上面函數的逆函數。
u_long ntohl(u_long netlong);
舉例:ntohl(0)=0
ntohl(1342177280)= 80
4、將主機的unsigned short值轉換為網絡字節順序(16位):原因同2:
u_short htons(u_short hostshort);
舉例:htonl(0)=0
htonl(80)= 20480
5、將unsigned short數從網絡字節順序轉換位主機字節順序,是上面函數的逆函數。
u_short ntohs(u_short netshort);
舉例:ntohs(0)=0
ntohsl(20480)= 80
6、將用點分割的IP地址轉換位一個in_addr結構的地址,這個結構的定義見筆記(一),實際上就是一個unsigned long值。計算機內部處理IP地址可是不認識如192.1.8.84之類的數據。
unsigned long inet_addr( const char FAR * cp );
舉例:inet_addr("192.1.8.84")=1409810880
inet_addr("127.0.0.1")= 16777343
如果發生錯誤,函數返回INADDR_NONE值。 7、將網絡地址轉換位用點分割的IP地址,是上面函數的逆函數。
char FAR * inet_ntoa( struct in_addr in );
舉例:char * ipaddr=NULL;
char addr[20];
in_addr inaddr;
inaddr. s_addr=16777343;
ipaddr= inet_ntoa(inaddr);
strcpy(addr,ipaddr);
這樣addr的值就變為127.0.0.1。 注意意不要修改返回值或者進行釋放動作。如果函數失敗就會返回NULL值。 8、獲取套接字的本地地址結構:
int getsockname(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數調用后獲得的地址值
namelen為緩沖區的大小。
9、獲取與套接字相連的端地址結構:
int getpeername(SOCKET s, struct sockaddr FAR * name, int FAR * namelen );
s為套接字
name為函數調用后獲得的端地址值
namelen為緩沖區的大小。
10、獲取計算機名:
int gethostname( char FAR * name, int namelen );
name是存放計算機名的緩沖區
namelen是緩沖區的大小
用法:
char szName[255];
memset(szName,0,255);
if(gethostname(szName,255)==SOCKET_ERROR)
{
//錯誤處理
}
返回值為:szNmae="xiaojin"
11、根據計算機名獲取主機地址:
struct hostent FAR * gethostbyname( const char FAR * name );
name為計算機名。
用法:
hostent * host;
char* ip;
host= gethostbyname("xiaojin");
if(host->h_addr_list[0])
{
struct in_addr addr;
memmove(&addr, host->h_addr_list[0],4);
//獲得標準IP地址
ip=inet_ ntoa (addr);
}
返回值為:hostent->h_name="xiaojin"
hostent->h_addrtype=2 //AF_INET
hostent->length=4
ip="127.0.0.1"
Winsock 的I/O操作:1、 兩種I/O模式
- 阻塞模式:執行I/O操作完成前會一直進行等待,不會將控制權交給程序。套接字 默認為阻塞模式。可以通過多線程技術進行處理。
- 非阻塞模式:執行I/O操作時,Winsock函數會返回并交出控制權。這種模式使用 起來比較復雜,因為函數在沒有運行完成就進行返回,會不斷地返回 WSAEWOULDBLOCK錯誤。但功能強大。
為了解決這個問題,提出了進行I/O操作的一些I/O模型,下面介紹最常見的三種: 2、select模型: 通過調用select函數可以確定一個或多個套接字的狀態,判斷套接字上是否有數據,或 者能否向一個套接字寫入數據。
int select( int nfds, fd_set FAR * readfds, fd_set FAR * writefds,
fd_set FAR *exceptfds, const struct timeval FAR * timeout );
◆先來看看涉及到的結構的定義: a、 d_set結構:
#define FD_SETSIZE 64?
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
fd_count為已設定socket的數量 fd_array為socket列表,FD_SETSIZE為最大socket數量,建議不小于64。這是微軟建 議的。 B、timeval結構:
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
tv_sec為時間的秒值。 tv_usec為時間的毫秒值。 這個結構主要是設置select()函數的等待值,如果將該結構設置為(0,0),則select()函數 會立即返回。 ◆再來看看select函數各參數的作用:
- nfds:沒有任何用處,主要用來進行系統兼容用,一般設置為0。
- readfds:等待可讀性檢查的套接字組。
- writefds;等待可寫性檢查的套接字組。
- exceptfds:等待錯誤檢查的套接字組。
- timeout:超時時間。
- 函數失敗的返回值:調用失敗返回SOCKET_ERROR,超時返回0。
readfds、writefds、exceptfds三個變量至少有一個不為空,同時這個不為空的套接字組 種至少有一個socket,道理很簡單,否則要select干什么呢。 舉例:測試一個套接字是否可讀:
fd_set fdread;
//FD_ZERO定義
// #define FD_ZERO(set) (((fd_set FAR *)(set))->fd_count=0)
FD_ZERO(&fdread);
FD_SET(s,&fdread); //加入套接字,詳細定義請看winsock2.h
if(select(0,%fdread,NULL,NULL,NULL)>0
{
//成功
if(FD_ISSET(s,&fread) //是否存在fread中,詳細定義請看winsock2.h
{
//是可讀的
}
}
◆I/O操作函數:主要用于獲取與套接字相關的操作參數。
int ioctlsocket(SOCKET s, long cmd, u_long FAR * argp );
s為I/O操作的套接字。 cmd為對套接字的操作命令。 argp為命令所帶參數的指針。 常見的命令:
//確定套接字自動讀入的數據量
#define FIONREAD _IOR(''''f'''', 127, u_long) /* get # bytes to read */
//允許或禁止套接字的非阻塞模式,允許為非0,禁止為0
#define FIONBIO _IOW(''''f'''', 126, u_long) /* set/clear non-blocking i/o */
//確定是否所有帶外數據都已被讀入
#define SIOCATMARK _IOR(''''s'''', 7, u_long) /* at oob mark? */
3、WSAAsynSelect模型: WSAAsynSelect模型也是一個常用的異步I/O模型。應用程序可以在一個套接字上接收以 WINDOWS消息為基礎的網絡事件通知。該模型的實現方法是通過調用WSAAsynSelect函 數 自動將套接字設置為非阻塞模式,并向WINDOWS注冊一個或多個網絡時間,并提供一 個通知時使用的窗口句柄。當注冊的事件發生時,對應的窗口將收到一個基于消息的通知。
int WSAAsyncSelect( SOCKET s, HWND hWnd, u_int wMsg, long lEvent);
s為需要事件通知的套接字 hWnd為接收消息的窗口句柄 wMsg為要接收的消息 lEvent為掩碼,指定應用程序感興趣的網絡事件組合,主要如下:
#define FD_READ_BIT 0
#define FD_READ (1 << FD_READ_BIT)
#define FD_WRITE_BIT 1
#define FD_WRITE (1 << FD_WRITE_BIT)
#define FD_OOB_BIT 2
#define FD_OOB (1 << FD_OOB_BIT)
#define FD_ACCEPT_BIT 3
#define FD_ACCEPT (1 << FD_ACCEPT_BIT)
#define FD_CONNECT_BIT 4
#define FD_CONNECT (1 << FD_CONNECT_BIT)
#define FD_CLOSE_BIT 5
#define FD_CLOSE (1 << FD_CLOSE_BIT)
用法:要接收讀寫通知:
int nResult= WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);
if(nResult==SOCKET_ERROR)
{
//錯誤處理
}
取消通知:
int nResult= WSAAsyncSelect(s,hWnd,0,0);
當應用程序窗口hWnd收到消息時,wMsg.wParam參數標識了套接字,lParam的低字標明 了網絡事件,高字則包含錯誤代碼。 4、WSAEventSelect模型 WSAEventSelect模型類似WSAAsynSelect模型,但最主要的區別是網絡事件發生時會被發 送到一個事件對象句柄,而不是發送到一個窗口。 使用步驟如下: a、 創建事件對象來接收網絡事件:
#define WSAEVENT HANDLE
#define LPWSAEVENT LPHANDLE
WSAEVENT WSACreateEvent( void );
該函數的返回值為一個事件對象句柄,它具有兩種工作狀態:已傳信(signaled)和未傳信 (nonsignaled)以及兩種工作模式:人工重設(manual reset)和自動重設(auto reset)。默認未 未傳信的工作狀態和人工重設模式。 b、將事件對象與套接字關聯,同時注冊事件,使事件對象的工作狀態從未傳信轉變未 已傳信。
int WSAEventSelect( SOCKET s,WSAEVENT hEventObject,long lNetworkEvents );
s為套接字 hEventObject為剛才創建的事件對象句柄 lNetworkEvents為掩碼,定義如上面所述 c、I/O處理后,設置事件對象為未傳信
BOOL WSAResetEvent( WSAEVENT hEvent );
Hevent為事件對象
成功返回TRUE,失敗返回FALSE。
d、等待網絡事件來觸發事件句柄的工作狀態:
DWORD WSAWaitForMultipleEvents( DWORD cEvents,
const WSAEVENT FAR * lphEvents, BOOL fWaitAll,
DWORD dwTimeout, BOOL fAlertable );
lpEvent為事件句柄數組的指針 cEvent為為事件句柄的數目,其最大值為WSA_MAXIMUM_WAIT_EVENTS fWaitAll指定等待類型:TRUE:當lphEvent數組重所有事件對象同時有信號時返回; FALSE:任一事件有信號就返回。 dwTimeout為等待超時(毫秒) fAlertable為指定函數返回時是否執行完成例程
對事件數組中的事件進行引用時,應該用WSAWaitForMultipleEvents的返回值,減去 預聲明值WSA_WAIT_EVENT_0,得到具體的引用值。例如:
nIndex=WSAWaitForMultipleEvents(…);
MyEvent=EventArray[Index- WSA_WAIT_EVENT_0];
e、判斷網絡事件類型:
int WSAEnumNetworkEvents( SOCKET s,
WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents );
s為套接字 hEventObject為需要重設的事件對象 lpNetworkEvents為記錄網絡事件和錯誤代碼,其結構定義如下:
typedef struct _WSANETWORKEVENTS {
long lNetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
f、關閉事件對象句柄:
BOOL WSACloseEvent(WSAEVENT hEvent);
調用成功返回TRUE,否則返回FALSE。
1、C++各大有名庫的介紹——C++標準庫 2、C++各大有名庫的介紹——準標準庫Boost 3、C++各大有名庫的介紹——GUI 4、C++各大有名庫的介紹——網絡通信 5、C++各大有名庫的介紹——XML 6、C++各大有名庫的介紹——科學計算 7、C++各大有名庫的介紹——游戲開發 8、C++各大有名庫的介紹——線程 9、C++各大有名庫的介紹——序列化 10、C++各大有名庫的介紹——字符串 11、C++各大有名庫的介紹——綜合 12、C++各大有名庫的介紹——其他庫 13、C++名人的網站 在 C++中,庫的地位是非常高的。C++之父 Bjarne Stroustrup先生多次表示了設計庫來擴充功能要好過設計更多的語法的言論。現實中,C++的庫門類繁多,解決的問題也是極其廣泛,庫從輕量級到重量級的都有。不少都是讓人眼界大開,亦或是望而生嘆的思維杰作。由于庫的數量非常龐大,而且限于筆者水平,其中很多并不了解。所以文中所提的一些庫都是比較著名的大型庫。 1、C++各大有名庫的介紹——C++標準庫
標準庫中提供了C++程序的基本設施。雖然C++標準庫隨著C++標準折騰了許多年,直到標準的出臺才正式定型,但是在標準庫的實現上卻很令人欣慰得看到多種實現,并且已被實踐證明為有工業級別強度的佳作。
1.1、Dinkumware C++ Library
參考站點:http://www.dinkumware.com/
P.J. Plauger編寫的高品質的標準庫。P.J. Plauger博士是Dr. Dobb's程序設計杰出獎的獲得者。其編寫的庫長期被Microsoft采用,并且最近Borland也取得了其OEM的license,在其 C/C++的產品中采用Dinkumware的庫。
1.2、RogueWave Standard C++ Library
參考站點:http://www.roguewave.com/
這個庫在Borland C++ Builder的早期版本中曾經被采用,后來被其他的庫給替換了。筆者不推薦使用。
1.3、SGI STL
參考站點:http://www.roguewave.com/
SGI公司的C++標準模版庫。
1.4、STLport
參考站點:http://www.stlport.org/
SGI STL庫的跨平臺可移植版本。
|
2、C++各大有名庫的介紹——準標準庫Boost
Boost庫是一個經過千錘百煉、可移植、提供源代碼的C++庫,作為標準庫的后備,是C++標準化進程的發動機之一。 Boost庫由C++標準委員會庫工作組成員發起,在C++社區中影響甚大,其成員已近2000人。 Boost庫為我們帶來了最新、最酷、最實用的技術,是不折不扣的“準”標準庫。
Boost中比較有名氣的有這么幾個庫:
2.1 Regex 正則表達式庫
2.2 Spirit LL parser framework,用C++代碼直接表達EBNF
2.3 Graph 圖組件和算法
2.4 Lambda 在調用的地方定義短小匿名的函數對象,很實用的functional功能
2.5 concept check 檢查泛型編程中的concept
2.6 Mpl 用模板實現的元編程框架
2.7 Thread 可移植的C++多線程庫
2.8 Python 把C++類和函數映射到Python之中
2.9 Pool 內存池管理
2.10 smart_ptr 5個智能指針,學習智能指針必讀,一份不錯的參考是來自CUJ的文章:
Smart Pointers in Boost,哦,這篇文章可以查到,CUJ是提供在線瀏覽的。中文版見筆者在《Dr.Dobb's Journal軟件研發雜志》第7輯上的譯文。
Boost總體來說是實用價值很高,質量很高的庫。并且由于其對跨平臺的強調,對標準C++的強調,是編寫平臺無關,現代C++的開發者必備的 工具。但是Boost中也有很多是實驗性質的東西,在實際的開發中實用需要謹慎。并且很多Boost中的庫功能堪稱對語言功能的擴展,其構造用盡精巧的手 法,不要貿然的花費時間研讀。Boost另外一面,比如Graph這樣的庫則是具有工業強度,結構良好,非常值得研讀的精品代碼,并且也可以放心的在產品 代碼中多多利用。
參考站點:http://www.boost.org
|
3、C++各大有名庫的介紹——GUI
在眾多C++的庫中,GUI部分的庫算是比較繁榮,也比較引人注目的。在實際開發中,GUI庫的選擇也是非常重要的一件事情,下面我們綜述一下可選擇的GUI庫,各自的特點以及相關工具的支持。
3.1、MFC
大名鼎鼎的微軟基礎類庫(Microsoft Foundation Class)。大凡學過VC++的人都應該知道這個庫。雖然從技術角度講,MFC是不大漂亮的,但是它構建于Windows API 之上,能夠使程序員的工作更容易,編程效率高,減少了大量在建立 Windows 程序時必須編寫的代碼,同時它還提供了所有一般 C++ 編程的優點,例如繼承和封裝。MFC 編寫的程序在各個版本的Windows操作系統上是可移植的,例如,在Windows 3.1下編寫的代碼可以很容易地移植到 Windows NT 或 Windows 95 上。但是在最近發展以及官方支持上日漸勢微。
3.2、QT
參考網站:http://www.trolltech.com
Qt是Trolltech公司的一個多平臺的C++圖形用戶界面應用程序框架。它提供給應用程序開發者建立藝術級的圖形用戶界面所需的所用功 能。Qt是完全面向對象的很容易擴展,并且允許真正地組件編程。自從1996年早些時候,Qt進入商業領域,它已經成為全世界范圍內數千種成功的應用程序 的基礎。Qt也是流行的Linux桌面環境KDE 的基礎,同時它還支持Windows、Macintosh、Unix/X11等多種平臺。[wangxinus注:QT目前已經是Nokia旗下的產品,原官方網站已經失效,目前為http://qt.nokia.com.2009年初發布的Qt4.5版本開始使用LGPL協議,諾基亞希望以此來吸引更多的開發人員使用Qt庫]
3.3、WxWindows
參考網站:http://www.wxwindows.org
跨平臺的GUI庫。因為其類層次極像MFC,所以有文章介紹從MFC到WxWindows的代碼移植以實現跨平臺的功能。通過多年的開發也是一個日趨完善的GUI庫,支持同樣不弱于前面兩個庫。并且是完全開放源代碼的。新近的C++ Builder X的GUI設計器就是基于這個庫的。[wangxinus注:迫于微軟的施壓,已經由WxWindows更名為wxWidgets]
3.4、Fox
參考網站:http://www.fox-toolkit.org/
開放源代碼的GUI庫。作者從自己親身的開發經驗中得出了一個理想的GUI庫應該是什么樣子的感受出發,從而開始了對這個庫的開發。有興趣的可以嘗試一下。
3.5、WTL
基于ATL的一個庫。因為使用了大量ATL的輕量級手法,模板等技術,在代碼尺寸,以及速度優化方面做得非常到位。主要面向的使用群體是開發COM輕量級供網絡下載的可視化控件的開發者。
3.6、GTK
參考網站:http://gtkmm.sourceforge.net/
GTK是一個大名鼎鼎的C的開源GUI庫。在Linux世界中有Gnome這樣的殺手應用。而Qt就是這個庫的C++封裝版本。[wangxinus注:“Qt 就是這個庫的C++封裝版本”是錯誤的。Qt早于GTK,最初Qt由于協議的原因引起社區的不滿,另外開發了一個基于C語言的GTK庫,后面的擴展版本為 GTK+。GTK+的Gnome和Qt的KDE是目前linux桌面的兩大陣營,曾有水火不容之勢。目前雙方都以及開源社區的精神,已經和解。]
|
4、C++各大有名庫的介紹——網絡通信
5、C++各大有名庫的介紹——XML
6、C++各大有名庫的介紹——科學計算
6.1、Blitz++
參考網站:http://www.oonumerics.org/blitz
Blitz++ 是一個高效率的數值計算函數庫,它的設計目的是希望建立一套既具像C++ 一樣方便,同時又比Fortran速度更快的數值計算環境。通常,用C++所寫出的數值程序,比 Fortran慢20%左右,因此Blitz++正是要改掉這個缺點。方法是利用C++的template技術,程序執行甚至可以比Fortran更快。
Blitz++目前仍在發展中,對于常見的SVD,FFTs,QMRES等常見的線性代數方法并不提供,不過使用者可以很容易地利用Blitz++所提供的函數來構建。
6.2、POOMA
參考網站:http://www.codesourcery.com/pooma/pooma
POOMA是一個免費的高性能的C++庫,用于處理并行式科學計算。POOMA的面向對象設計方便了快速的程序開發,對并行機器進行了優化以達到最高的效率,方便在工業和研究環境中使用。
6.3、MTL
參考網站:http://www.osl.iu.edu/research/mtl
Matrix Template Library(MTL)是一個高性能的泛型組件庫,提供了各種格式矩陣的大量線性代數方面的功能。在某些應用使用高性能編譯器的情況下,比如Intel的編譯器,從產生的匯編代碼可以看出其與手寫幾乎沒有兩樣的效能。
6.4、CGAL
參考網站:www.cgal.org
Computational Geometry Algorithms Library的目的是把在計算幾何方面的大部分重要的解決方案和方法以C++庫的形式提供給工業和學術界的用戶。
|
7、C++各大有名庫的介紹——游戲開發
8、C++各大有名庫的介紹——線程
9、C++各大有名庫的介紹——序列化
10、C++各大有名庫的介紹——字符串
11、C++各大有名庫的介紹——綜合
12、C++各大有名庫的介紹——其他庫
12.1、Loki
參考網站:http://www.moderncppdesign.com/
哦,你可能抱怨我早該和Boost一起介紹它,一個實驗性質的庫。作者在loki中把C++模板的功能發揮到了極致。并且嘗試把類似設計模式這樣思想層面的東西通過庫來提供。同時還提供了智能指針這樣比較實用的功能。
12.2、ATL
ATL(Active Template Library)是一組小巧、高效、靈活的類,這些類為創建可互操作的COM組件提供了基本的設施。
12.3、FC++: The Functional C++ Library
這個庫提供了一些函數式語言中才有的要素。屬于用庫來擴充語言的一個代表作。如果想要在OOP之外尋找另一分的樂趣,可以去看看函數式程序設計的世界。大師Peter Norvig在 “Teach Yourself Programming in Ten Years”一文中就將函數式語言列為至少應當學習的6類編程語言之一。
12.4、FACT!
參考網站:http://www.kfa-juelich.de/zam/FACT/start/index.html
另外一個實現函數式語言特性的庫
12.5、Crypto++
提供處理密碼,消息驗證,單向hash,公匙加密系統等功能的免費庫。
還有很多非常激動人心或者是極其實用的C++庫,限于我們的水平以及文章的篇幅不能包括進來。在對于這些已經包含近來的庫的介紹中,由于并不是每一個我們都使用過,所以難免有偏頗之處,請讀者見諒。
|
13、C++名人的網站 正如我們可以通過計算機歷史上的重要人物了解計算機史的發展,C++相關人物的網站也可以使我們得到最有價值的參考與借鑒,下面的人物我們認為沒有介紹的必要,只因下面的人物在C++領域的地位眾所周知,我們只將相關的資源進行羅列以供讀者學習,他們有的工作于貝爾實驗室,有的工作于知名編譯器廠商,有的在不斷推進語言的標準化,有的為讀者撰寫了多部千古奇作……
|