热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

lua语言闭包、模式匹配、日期、编译、模块的特性及应用

本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。

lua中的闭包

在lua语言中,函数是严格遵循词法定界(lexical scoping)的第一类值(first-class value)。

第一类值意味着lua语言中的函数与其它常见类型的值(例如数值和字符串)具有同等权限:一个程序可以将某个函数保存到变量、或表中,也可以将函数作为参数传递给另外一个函数,还可以将函数作为某个函数的返回值返回。

词法定界意味着,内层函数可以访问外层函数中的变量。

上面两个特性组合起来,为lua语言带来了极大的灵活性。




函数是第一类值


我们来看看lua中是如何体现函数是第一类值的

a = print
a("hello world") -- hello world
-- 函数可以作为一个变量自由传递
function f(func, str, substr)
print(func(str, substr))
end
f(string.find, "mashiro", "shi") -- 3 5
function f(func, str, start, end_)
print(func(str, start, end_))
end
f(string.sub, "satori", 2, -2) -- ator
-- 函数可以作为参数传递给另一个函数
function f()
return string.gsub
end
-- 函数也可以返回一个函数
print(f()("hello mashiro", "mashiro", "satori")) -- hello satori 1
-- 对于表也是一样的
t = {print, std = io.write }
t[1]("mashiro") -- mashiro
t["std"]("satori\n") -- satori
-- 两个函数也能替换
math.type = string.sub
print(math.type("xxx", 2, -2)) -- x

标准库table提供了一个sort函数,专门用来对表里面的内容进行排序的,但是怎么排,lua没有提供具体的方法,而是需要我们通过一个函数来指定

t = {
{name = "mashiro", age = 16},
{name = "satori", age = 17},
{name = "nagisa", age = 20},
{name = "kurisu", age = 18},
}
-- 会对元素进行比较,因为a.age > b.age的话,结果为true
-- 所以年龄大的,会排在前面。在python中,满足条件的排在后面
table.sort(t, function(a, b) return a.age > b.age end)
print(t[1].name, t[1].age) -- nagisa 20

像这种没有函数名的函数,我们也可以称之为匿名函数。匿名函数不能单独定义,需要作为值进行传递,比如放到表中、作为参数、或者使用变量进行接收等等。


我们看到table.sort它需要接收一个函数作为参数,因此像table.sort这样的函数我们也称之为是高阶函数




非全局函数


由于函数是第一类值,因此一个显而易见的结果就是: 函数不仅可以被存储在全局变量中,还可以存储在表字段和局部变量中。

lib = {}
function add(a, b)
return a + b
end
lib.a = add
print(lib.a(1, 22)) -- 23

上面是将函数存储在表中,我们在上面已经见识了,下面看看将函数存储在局部变量中是什么样子的。

function outer(a, b)
function inner(c)
return a + b + c
end
-- 这里必须要返回inner,否则是无法调用inner的
return inner
end
-- 上面我们就实现了一个闭包
print(outer(1, 2)(33)) -- 36
-- 但是问题来了,我们看到inner居然可以直接调用
-- 因为我们说不管在什么地方,只要没有local关键字,定义的都是全局的
-- 所以尽管不能上来就调用inner,但是当outer调用之后,inner就会被加入到全局变量中
-- 可问题是,结果为啥是6,这是因为inner里面需要a和b,所以当outer调用之后,inner就记住了a和b的值
-- 而我们上面给outer的a和b传递的值是1和2,因此再调用inner的时候,a和b还是1和2
print(inner(3)) -- 6
print(inner(8)) -- 11
-- 重新执行outer,那么inner会被重新定义
-- 此时inner记住的a和b就变成了11和22
print(outer(11, 22)(33)) -- 66
print(inner(1)) -- 34

那么如何将函数定义成局部的呢?

function outer(a, b)
local function inner(c)
return a + b + c
end
-- 这里必须要返回inner,否则是无法调用inner的
return inner
end
-- 此时就将inner定义成局部的了,因为我们使用了local关键字
-- 所以我们看到local也可以作用于函数
print(outer(1, 2)(3)) -- 6
print(inner) -- nil
-- 那如何将函数保存起来呢
function f1(a)
local f2 = function(b) return a + b end
return f2
end
print(f1(1)(22)) -- 23
print(f2) -- nil

将函数保存在局部变量中有一个坑,我们来看看

function f1()
local function f2(n)
if n == 1 then
return 1
else
return n + f2(n - 1)
end
end
return f2
end
-- 上面是一个简单的求和,通过递归来实现
print(f1()(100)) -- 5050
-- 如果是下面这种形式呢
function f1()
local f2 = function(n)
if n == 1 then
return 1
else
return n + f2(n - 1)
end
end
return f2
end
-- 我们代码和上面的版本没有太大区别,只不过将函数使用局部变量保存了
-- 执行的时候会出现什么结果呢?
f1()(100)

上面递归求和的第二个版本是否存在问题呢?答案是存在的,当调用内部的n + f2(n - 1)的时候,你会发现f2不存在。为什么是这个结果,也很好理解,我们用右边那一大坨函数赋值给f2,但是函数里面在赋值给f2的同时,里面已经就引用了f2。所以此时的f2是一个nil,或者说会去外部去找这个f2。

function f1()
local f2
f2 = function(n)
if n == 1 then
return 1
else
return n + f2(n - 1)
end
end
return f2
end
-- 我们代码和上面的版本没有太大区别,只不过将函数使用局部变量保存了
-- 执行的时候会出现什么结果呢?
print(f1()(100)) -- 5050

此时就没有问题了,因为我们先定义了一个局部变量f2,尽管定义的时候f2还没有正确的值,但是一旦当执行函数的时候,f2就已经有了正确的值。




词法定界


关于词法定界,其实我们上面已经见识到了, 就是内层函数访问外层函数的变量

a = "xxx"
function f1()
local a = 123
local function f2()
return a
end
return f2
end
-- 此时返回的是f1中的a,内层函数可以访问外层函数的变量
print(f1()()) -- 123
a = "xxx"
function f1()
a = 123
local function f2()
return a
end
return f2
end
-- f1中的a和全局的是一个a
print(f1()(), a) -- 123 123
a = "xxx"
function f1()
local a = 123
local function f2()
local a = ">>>"
return a
end
return f2
end
-- 自身有a这个变量,就不会到外界去找了
print(f1()()) -- >>>

来个小栗子:

function f1(str)
local function f2(substr)
return string.find(str, substr)
end
return f2
end
f2 = f1("abcde")
print(f2("ab")) -- 1 2
print(f2("bcd")) -- 2 4
print(f2("abcde")) -- 1 5

lua中的模式匹配

下面来介绍一下lua中的模式匹配,这个很明显是针对字符串的,但是与其它脚本语言不同,lua中并没有正则表达式来进行模式匹配(pattern matching),lua没有提供POSIX风格的正则、也没有提供Perl风格的正则。


之所以这么做的主要原因是大小问题,一个典型的POXIS正则表达式实现需要超过4000行代码,这比lua标准库所有代码的一半还要多。

而lua提供的模式匹配的实现,代码只有不到600行。尽管无法实现正则的所有功能,但也是非常强大的,并且同时还具有和POXIS不同但又可与之匹配的功能。





模式匹配的相关函数


函数 string.find

这个我们在介绍string库的时候,已经说过了,功能就是查找子串在字符串中出现的开始位置和结束位置,但是我们当时介绍的比较简单,而string.find还支持更复杂的功能。

-- 查找第一个子串出现的位置
print(string.find("abc abc", "abc")) -- 1 3
-- 可以指定第三个参数,表示起始位置
-- 从第2个开始,那么就只能找第二个abc了
print(string.find("abc abc", "abc", 2)) -- 5 7
-- 除了第三个参数,还可以有第四个参数
-- 一个布尔值,表示是否进行简单搜索,默认是false
-- print(string.find("abc [abc", "[abc")),这行代码是会报错的
-- 因为[在lua中具有特殊意义,子串中不可以出现一个单独的[
-- 如果指定为true的话,表示单纯的字符串匹配,子串中的[就没有特殊含义了
print(string.find("abc [abc", "[abc", 0, true)) -- 5 8

函数 string.match

string.match和string.find类似,但是它返回的是一个匹配上的子串,而不是位置。

print(string.match("hello world", "world")) -- world
-- 虽然能匹配上,但是貌似没啥卵用
-- 别急,继续看
print(string.match("2020-01-01", "%d+-%d+-%d+")) -- 2020-01-01
-- 这样是不是就有用了呢?关于%d、以及string.match更高级的用法,我们后面会说

函数 string.gsub

这个函数我们也见过,接收三个参数:字符串、要替换的部分、替换成指定字符串,但是gsub还可以接收第4个参数,表示最多替换多少个。

print(string.gsub("aaa", ‘a‘, ‘b‘)) -- bbb 3
print(string.gsub("aaa", ‘a‘, ‘b‘, 2)) -- bba 2



模式


lua中也提供了其它语言中,类似于\d、\s之类的功能,但是lua中用的不是反斜杠,而是%,并且%(在模式匹配中)也是转义字符。



  • . 匹配 任意字符

  • %a 匹配 字母

  • %c 匹配 控制字符

  • %d 匹配 数字

  • %g 匹配 处空格之外的可打印字符

  • %l 匹配 小写字母

  • %u 匹配 大写字母

  • %p 匹配 标点符号

  • %s 匹配 空白字符

  • %w 匹配 字母和数字

  • %x 匹配 十六进制数字

除此之外,也提供了+、-、()、[]之类的具有特殊意义的字符,这个有兴趣可以自己查看,这里不说了。


lua中的日期和时间

lua中有两个函数,用于操作日期和时间,我们来看一下。

-- 调用os.time会返回当前时间的时间戳
print(os.time()) -- 1592124891
-- 也可以指定年月日时分秒
print(os.time({year=2020, mOnth=5, day=3, hour=14, min=33, sec=51})) -- 1588487631
-- 还有os.date
-- 自动打印当前的日期,除此之外还可以传入一个时间戳,来打印指定格式的日期
-- 返回的是一个string类型
print(os.date("%Y-%m-%d")) -- 2020-06-14
print(os.date("%Y-%m-%d", os.time({year=2020, mOnth=4, day=3}))) -- 2020-04-03

lua中关于日期的符号如下:

技术图片


编译、执行和错误

虽然我们把 Lua 语言称为解释型语言( interpreted language ),但 Lua 语言总是在运行代码前先预编译( precompile )源码为中间代码(这没什么大不了的,很多解释型语言也这样 做)。编译( compilation )阶段的存在听上去超出了解释型语言的范畴,但解释型语言的区分并不在于源码是否被编译,而在于是否有能力(且轻易地)执行动态生成的代码。

下面我们会详细学习lua语言运行代码的过程、编译究竟是什么意思和做了什 么、 Lua 语言是如何运行编译后代码的、以及在编译过程中如何处理错误。




编译


lua中提供了一个load函数,我们来看看是做什么的。

f = load("i = 1 + 1")
-- 当调用f的时候,load里面的字符串就会当成代码来执行
-- 有点类似于python中的exec
print(i) -- nil
f()
print(i) -- 2

尽管load的功能很强大,但还是要谨慎使用,因为相比其他的可选函数而言,load的开销比较大。另外,我们当前将load函数的返回值保存了下来,但是也可以不保存。

-- 直接调用
load("s = ‘这是字符串‘")()
print(s) -- 这是字符串
-- load里面还可以放入一个字符串形式的函数
-- 多行的话,我们可以通过[[ ]]来代替引号
load(
[[
function f()
return "hello mashiro"
end
]]
)()
print(f()) -- hello mashiro

如果load中的字符串不符合lua的语法规则,那么会得到什么呢?

print(load("s = ‘这是字符串‘")) -- function: 00000000001bd010
-- 我们看到如果符合语法规则,那么会返回一个函数
-- 不符合的话,会返回nil和错误信息
print(load("s === ‘这是字符串‘")) -- nil [string "s === ‘这是字符串‘"]:1: syntax error near ‘==‘
-- 因此最好的办法是使用assert函数
-- 如果里面的值为nil或者false,直接把异常抛出来
-- 比如assert(2 == 2)正常通过,assert(2!=2)就会报错,因为里面的值为false
-- 而如果为false或者nil,那么assert就会将抛出异常
-- 如果是返回了多个值,那么就看第一个值是否为nil或者false
print(load("s = ‘这是字符串‘")) -- function: 00000000001bd010
-- 我们看到如果符合语法规则,那么会返回一个函数
-- 不符合的话,会返回nil和错误信息
print(load("s === ‘这是字符串‘")) -- nil [string "s === ‘这是字符串‘"]:1: syntax error near ‘==‘
assert(load("s === ‘这是字符串‘"))
-- 返回值的第一个为nil,所以报错了,会把异常值抛出来
--[[
C:\lua\lua.exe: lua/5.lua:9: [string "s === ‘这是字符串‘"]:1: syntax error near ‘==‘
stack traceback:
[C]: in function ‘assert‘
lua/5.lua:9: in main chunk
[C]: in ?
]]

关于assert,可能有人不是很清楚,这里来说一下。

-- assert是用于断言的
assert(2 == 2)
print("正常通过")
-- 如果是assert(2 != 2),就通不过了
function f1()
return 123, nil
end
assert(f1())
-- 上述f1函数返回了两个值,而第一个值为真的,所以正常通过
function f2()
return nil, 123
end
-- 这里就通不过了,因为f2的返回值的第一个为nil
-- 如果报错了,那么会将返回值的第二个作为异常信息
assert(f2())
--[[
C:\lua\lua.exe: 123
stack traceback:
[C]: in function ‘assert‘
lua/5.lua:23: in main chunk
[C]: in ?
]]
-- assert一旦断言失败,那么程序就终止了,不会再往下走了

回到load函数,load函数里面如果是字符串,那么其实没有什么意义。f = load("i = 1")和function f() i = 1 end两者是完全等价的。

但是,由于后者、也就是定义函数的方式,代码会与其外层的函数一起被编译,所以其执行速度要快得多。与之对比,调用函数 load 时会进行一次独立编译

不过由于涉及到词法定界,上述示例的两段代码不一定完全等价


i = 123
function f1()
local i = 0
f = load("i = i + 1; print(i)")
g = function () i = i + 1;print(i) end
f()
g()
end
f1()
--[[
124
1
]]
print(i) -- 124
-- 我们看到全局中有一个i,函数f1中也有一个局部变量i
-- 对于load来说,会直接去找全局变量i,因为load总是在全局环境中编译代码段
-- 而内层函数在找i的时候,会现在自身中找、然后是外层函数、最后是全局变量
-- 所以f()执行打印124,此时外部的i也被修改了,内层函数由于找的是局部变量i,所以打印的是1

函数 load 最典型的用法是执行外部代码,比如我们可以在控制台中输入代码,因为收到的是字符串,所以正好可以通过load将其当成代码来执行。

但是这样能够处理的内容有限,所以在lua中还提供了一个loadfile,可以将一个文件里面的字符串当成代码来执行。

function add(a, b)
return a + b
end
a = 123
b = "xxx"

上面是一个文件1.txt,我们通过loadfile进行加载,加载的时候不需要文件名一定以lua结尾,只是将文件里面的内容读取出来,只要内容符合lua的语法规范即可。

loadfile("1.txt")()
print(add(11, 22)) -- 33
print(a) -- 123
print(b) -- xxx



预编译的代码


正如之前所说,lua在运行源代码之前会对源代码进行预编译,lua也可以允许我们手动编译源代码。

编译的方式可以通过luac进行编译,编译的文件已lc结尾。比如将5.lua编译成5.lc,就可以通过:"luac -o 5.lc 5.lua",编译之后lua解释器会像执行普通源代码一样执行编译之后的文件,完成与原来代码一致的动作。

可以自己尝试一下




错误


"人非圣贤孰能无过",因此我们必须尽可能的处理错误。并且由于lua是一种经常被嵌入到应用程序中的扩展语言,所以当错误发生时不能简单的崩溃或者退出。相反只要错误发生,lua必须提供处理错误的方式。

lua语言在遇到非预期情况时会引发错误,例如:将两个非数值类型的值相加,对不是函数的值进行调用,对不是表类型的值进行索引等等(当然我们后面会学习使用原表:metatable来改变上述行为)。我们也可以显示地调用函数error并传入一个错误信息作为参数来引发一个错误。通常,这个函数就是在代码中提示出错的合理方式

error("程序崩溃了")
--[[
C:\lua\lua.exe: lua/5.lua:1: 程序崩溃了
stack traceback:
[C]: in function ‘error‘
lua/5.lua:1: in main chunk
[C]: in ?
]]

由于针对某些情况调用函数error太常见了,所以lua还提供了内置函数assert来完成这类工作。assert不需要多说,会检测第一个参数是否为真,如果为真,那么正常通过;否则报错,同时会将第二个参数作为异常值。


assert总是在执行之前先对参数求值


assert(nil, "hello" .. " " .. "satori")
--[[
C:\lua\lua.exe: lua/5.lua:1: hello satori
stack traceback:
[C]: in function ‘assert‘
lua/5.lua:1: in main chunk
[C]: in ?
]]

这里是先将后面的字符串拼接起来之后,再执行assert,事实上,这算是废话,那个函数在执行前不是先确定参数的。

但是问题来了,我们的目的是希望在异常发生时,能够让其沉默,说人话就是异常捕获。

在lua中实现异常捕获的话,需要使用函数pcall,假设要执行一段lua代码并捕获里面出现的所有错误,那么需要首先将这段代码封装到一个函数中,然后通过pcall来调用这个函数。

function f()
a = "xxx"
return 1, 2, 3
end
print(pcall(f)) -- true 1 2 3
print(a) -- xxx
-- pcall里面需要传入一个函数,然后pcall来调用
-- 如果正确执行,那么pcall的返回值为:true 加上 pcall里面的函数的返回值
-- 如果执行失败,那么返回值为:false 加上 错误信息
-- 也可以直接传入一个匿名函数
print(pcall(function() print("" > 1) end)) -- false lua/5.lua:15: attempt to compare number with string
-- 如果报错,我们可以手动写入错误信息,通过函数error实现
status, err = pcall(function() error({code=666, err_msg="出错啦"}) end)
print(status, err.code, err.err_msg) -- false 666 出错啦

lua中异常捕获显然没有传统的try catch方便,但是对于一个小巧的脚本语言也足够了。




错误信息和栈回溯


-- 我们看到pcall里面传入的是一个函数名
-- 这就要求函数不能有参数,或者说pcall在调用的时候不会传递参数
-- 那么函数参数就全部为nil,因此如果函数需要参数该怎么办?
function f(arg)
if type(arg) ~= "string" then
error("需要一个字符串")
end
end
-- 答案是把调用放在一个函数里
pcall(function() f("xxx") end)
-- 另外,如果我们在这里传递的不是一个字符串的话
print(pcall(function()f(123) end)) -- false lua/5.lua:6: 需要一个字符串
-- 告诉我们需要一个字符串,但是报错提示在第6行,也就是函数f中
-- 但这明显不是f的问题,而是我们在匿名函数中值传递错误了
function f(arg)
if type(arg) ~= "string" then
-- 可以指定一个层级,告诉error实际发生错误的地方是第2层
-- 第一层为f本身
error("需要一个字符串", 2)
end
end
print(pcall(function()f(123) end)) -- false lua/5.lua:25: 需要一个字符串
-- 我们看到报错信息变成了25行,也就是上面这一行

模块和包

我们目前所有的代码都写在一个文件里面,但是内容多了之后,肯定不能都写在一个文件里面,而是需要分文件了。这个时候就要设计到导入模块,在lua中导入模块通过require?"模块名"即可。

-- 我们看到模块在lua中是一个table类型
print(type(math)) -- table
-- 我们调用math.sin相当于从math这张表里面检索sin
-- 在lua5.3中,模块是内嵌在解释器里面的
-- 但是我们也可以按照导入模块的模式进行加载
m = require "math"
-- 我们看到得到的结果是一样
print(type(m), m.sin(m.pi / 2)) -- table 1.0
-- 模块不会重复导入,所以m和math是同一个表
print(m == math) -- true



自己编写一个模块


-- m.lua
-- 创建一个模块,我们说模块本质上一张表
m = {}
function add(a, b)
return a + b
end
-- 将add函数加入到模块m中
-- 当然也可以是m.xxx = add, 只不过我们后面在导入这个模块的时候就不能调用m.add了,而是m.xxx
m.add = add
-- 或者
function m.sub(a, b)
return a - b
end
-- 对于变量也是可以ide
m.a = 123
b = 456
m.b = b
-- 最后将模块返回出去
return m

导入模块

-- 注意:如果直接去找的话,是找不到的,因为只会到默认的路径中去找
-- python的话会把当前模块所在的路径也算进去,但是lua不会
-- 我们需要使用package.path增加搜索路径, 多个搜索路径的话使用;分隔即可
package.path = "D:\\satori\\lua\\?.lua"
m = require "m"
print(m.add(1, 2)) -- 3
print(m.sub(1, 2)) -- -1
print(m.a) -- 123
print(m.b) -- 456

lua语言(2):闭包、模式匹配、日期、编译、模块



推荐阅读
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • 本文介绍了一种解析GRE报文长度的方法,通过分析GRE报文头中的标志位来计算报文长度。具体实现步骤包括获取GRE报文头指针、提取标志位、计算报文长度等。该方法可以帮助用户准确地获取GRE报文的长度信息。 ... [详细]
  • PDF内容编辑的两种小方法,你知道怎么操作吗?
    本文介绍了两种PDF内容编辑的方法:迅捷PDF编辑器和Adobe Acrobat DC。使用迅捷PDF编辑器,用户可以通过选择需要更改的文字内容并设置字体形式、大小和颜色来编辑PDF文件。而使用Adobe Acrobat DC,则可以通过在软件中点击编辑来编辑PDF文件。PDF文件的编辑可以帮助办公人员进行文件内容的修改和定制。 ... [详细]
  • CentOS 6.5安装VMware Tools及共享文件夹显示问题解决方法
    本文介绍了在CentOS 6.5上安装VMware Tools及解决共享文件夹显示问题的方法。包括清空CD/DVD使用的ISO镜像文件、创建挂载目录、改变光驱设备的读写权限等步骤。最后给出了拷贝解压VMware Tools的操作。 ... [详细]
author-avatar
逗趣游戏
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有