#
一、函数:
在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的通讯程... 阅读全文
关于低耦合的消息传递,实现的方式有很多,哪种方法更好与具体的使用环境有关,本文使用试错的方法,逐步探索达成这一目的具体方式,并理解实现方式背后的原因。
面向对象的系统当中,不可避免的有大量的类间消息传递的需求:一个类需要通知另一个或几个类做些什么。
这种类间消息传递,简单的说,就是调用其他类的方法。
如下:
1void 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对同一个事件的注册。而且可以完美的解决参数传递问题。
具体的方案如下:
1class A { 2public: 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 13void registerListener(Listener* obj) 14{ 15 m_lstListener.push_back(obj); 16} 17 18void 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 26private: 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++领域的地位众所周知,我们只将相关的资源进行罗列以供读者学习,他们有的工作于贝尔实验室,有的工作于知名编译器厂商,有的在不断推进语言的标准化,有的为读者撰写了多部千古奇作……
|