• Lua语法入门


    基础特性

    lua中不需要分号作为语句的结束点

    注释语句

    -- 单行注释
    
    --[[
     注释语句
    ]]--
    

    ..操作符可以用于拼接类型,拼接后的结果是stringnil不支持拼接)

    a = 123 .. 456
    -- data type is string
    -- data is 123456
    print("data type is " .. type(a) .. "\n" .. "data is " .. a)
    

    #代表获取长度,例如可以获取字符串或者数组等的长度。获取的结果是number类型

    name = "Jelly"
    -- 5
    print(#name)
    

    使用#获取长度的规则是:末尾的nil不列入长度计算

    a = {1, 2, nil, 3, 4, 5, nil, nil, nil}
    -- 6
    print(#a)
    

    常用函数

    输出语句

    print("Hello World")
    

    获取变量类型,将返回变量类型名的string

    type()
    

    类型系统

    lua中并不需要声明变量类型,它是弱类型的语言

    对于空数据而言,它们的类型是nil

    a = nil
    -- nil
    print(type(a))
    

    对于数值而言,它们的类型是number

    a = 20.21
    -- number
    print(type(a))
    

    对于字符串而言,它们的类型是string,不管是单引号或者是双引号

    a = 'Hello'
    -- string
    print(type(a))
    

    对于真假而言,它们的类型是boolean

    a = true
    -- boolean
    print(type(a))
    

    lua中使用没有声明过的变量,不会报错,默认是nil

    -- nil
    print(magicData)
    

    字符串常见操作

    字符串拼接

    • %d:代表与number拼接
    • %a:代表与任意字符拼接
    • %s:代表与string拼接
    -- I am No.1
    print(string.format("I am No.%d", 1))
    

    其他类型转化为字符串

    a = true
    b = tostring(a)
    

    其他常用方法

    大小写转换,以及倒序字符串

    -- HELLO WORLD
    print(string.upper("hello world"))
    -- hello world
    print(string.lower("HELLO WORLD"))
    -- OLLEH
    print(string.reverse("HELLO"))
    

    查找截取以及修改。lua中字符串从1开始计数,而不是0

    -- 查找字符串 a的值为2
    a = string.find("Hello", "ell")
    -- llo
    print(string.sub("Hello", 3, 5))
    
    a = string.gsub("Hello World", "o", "O")
    -- HellO WOrld
    print(a)
    

    ASCII码互转

    -- 将"L"转化为ASCII码
    a = string.byte("L")
    -- a的值为76 a的类型是number
    print(type(a))
    -- 将a从ASCII转换为字符串 结果是"L"
    print(string.char(a))
    

    运算符

    • lua中没有自增自减运算符,即--++

    • lua中没有符合运算符,即-=+=

    • string类型可以进行运算符操作,会自动转成number,运算的结果也是number

      a = "3.14" + 2
      
    • 由于lua中的数都是number类型,没有整形和浮点型之分,因此

      -- 0.5
      print(1 / 2)
      
    • lua中有幂运算符号

      -- 8.0
      print(2 ^ 3)
      
    • lua中的不等于运算符是~=

      -- true
      print(1 ~= 20)
      
    • 与,或和非的操作符是andornot。短路操作lua中仍然存在

    • lua中不支持位运算符,不支持三目运算符。但可以使用andor来实现三目运算符的功能

      i, j = 10, 30
      -- result的值是30
      result = (i > j) and i or j
      

    if语句

    a = 20
    if a > 10 and a < 30 then
        print("a > 10 and a < 30")
    end
    
    a = 100
    
    if a == 20 then
        print("a == 20")
    elseif a == 30 then
        print("a == 30")
    else
        print("a ~= 20")
        print("a ~= 30")
    end
    

    switch语句

    lua中没有switch语句

    循环语句

    while循环

    输出01234

    num = 0
    while num < 5 do
        print(num)
        num = num + 1
    end
    

    do-while循环

    输出012345

    num = 0
    repeat
        print(num)
        num = num + 1
    until num > 5
    

    for循环

    lua中的for循环语句会默认进行+1操作,且循环终止的条件是<=

    -- 输出 0 1 2 3 4 5
    for i = 0, 5 do
        print(i)
    end
    

    以上代码相当于

    for (int i = 0; i <= 5; i++)
        std::cout << i << std::endl;
    

    如果不想采用默认的+1操作,可以显式指明

    -- 输出 0 2 4
    for i = 0, 5, 2 do
        print(i)
    end
    

    以上代码相当于

    for (int i = 0; i <= 5; i += 2)
        std::cout << i << std::endl;
    

    如果想采用递减操作,也是显式指明的操作

    -- 输出 5 4 3 2 1 0
    for i = 5, 0, -1 do
        print(i)
    end
    

    函数

    无参数无返回值

    以下代码展示函数的声明和调用,与C++相同,声明和调用的顺序是固定的

    function func1()
        print("this is func1")
    end
    
    func1()
    

    除此之外还可以有类似“匿名函数”的写法

    func2 = function()
        print("this is func2")
    end
    
    func2()
    

    带参数函数

    同理,lua是弱类型的语言,因此函数参数不需要声明类型

    function func1(a)
        print(a)
    end
    
    func1(20)
    

    更重要的,如果参数数量不匹配,那么会自动补全或丢弃

    function func1(a, b)
        print(b)
    end
    
    -- 补全两个nil 输出nil
    func1()
    -- 丢弃末尾两个参数 输出30
    func1(20, 30, 430, 5)
    

    函数返回值

    由于是弱类型语言,因此不需要指明返回值类型

    function cal(a, b)
        return a * b
    end
    
    print(cal(2, 3))
    

    多返回值的处理情况

    function cal(a, b)
        return a + b, a - b, a * b
    end
    
    data1, data2, data3 = cal(2, 3)
    
    print(string.format("%d %d %d", data1, data2, data3))
    

    多返回值的情况可以参照C++17中的结构化绑定。同样的多余的返回参数如果没有变量接住,那么也会被丢弃;如果声明了多个变量但返回的元素个数不够,那么会默认置空

    template<typename T>
    std::tuple<T, T, T> cal(T a, T b) {
        return {a + b, a - b, a * b};
    }
    
    int main() {
        auto [data1, data2, data3] = cal(2, 3);
        std::cout << data1 << " " << data2 << " " << data3 << std::endl;
    }
    

    函数的隐藏

    由于Lua中不存在函数重载,因此函数将会出现重定义的情况(即隐藏先声明的函数)

    function func(a)
        print(a)
    end
    
    function func()
        print("empty func")
    end
    
    -- 参数100被丢弃 然后输出empty func
    func(100)
    

    变长参数

    function func(...)
        table = {...}
        for i = 1, #table, 1 do
            print(table[i])
        end
    end
    
    func(100, 200, "Hello", true, 3.14)
    

    换做C++的写法那就是

    template<typename... Ts>
    void func(Ts... args) {
        ((std::cout << args << std::endl), ...);
    }
    
    int main() {
        func(100, 200, "Hello", true, 3.14);
    }
    

    函数嵌套

    形成一个闭包,变量x的生命周期延长

    function func(x)
        return function(y)
            return x + y
        end
    end
    
    funcObj = func(5)
    -- 输出15
    print(funcObj(10))
    

    由于C++中需要手动管理对象的生命周期,按引用捕获并不会延长变量的生命周期,所以只能使用按值捕获,因此C++的写法是

    auto func(int x) {
        return [=](int y) { return x + y; };
    }
    
    int main() {
        // 存在捕获 所以使用auto
        auto funcObj = func(5);
        std::cout << funcObj(10) << std::endl;
    }
    

    table是一个数据类型,不论是数组,字典或者是类等等,它们的类型都是table

    数组

    如下图即声明了一个数组,lua中的数组并不要求数组内元素类型的一致

    a = {1, false, 3, "123"}
    

    lua中下表索引是从1开始的,通过[]对数组中元素进行访问。lua中数组越界并不会导致程序崩溃,它会返回nil

    -- false
    print(a[2])
    -- nil
    print(a[100])
    

    二维数组

    a = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }
    
    for i = 1, #a do
        for j = 1, #a[i] do
            print(a[i][j])
        end
    end
    

    自定义索引

    lua中可以自定义数组的索引,如下图代码,令索引1的值为1,索引2的值为2,索引3的值为3,索引4的值为4

    a = {[1] = 1, [2] = 2, [3] = 3, [4] = 4}
    

    lua中可以跳跃的指定索引,被忽略的索引在访问时值为nil

    a = {[1] = 1, [2] = 2, [3] = 3, [6] = 4}
    -- nil
    print(a[5])
    

    因此在此基础上就可以实现所谓“哈希表”

    a = {["name"] = "Jelly", ["age"] = 20, ["sex"] = 0}
    
    -- age     20
    -- name    Jelly
    -- sex     0
    for k, v in pairs(a) do
        print(k, v)
    end
    
    -- 直接新增元素
    a["score"] = 100
    -- 访问新增加的元素
    print(a["score"])
    

    ipairs和pairs迭代器遍历

    • ipairs适合访问连续的非空数组
    • pairs能够访问所有表,“哈希表”只能通过pairs访问
    a = {[0] = 0, 1, 2, [-1] = -1, [5] = 5}
    
    -- 使用ipairs迭代器只能访问到数组中的元素1和元素2
    -- ipairs遍历将会从1开始往后遍历 且只能访问连续索引的数据 遇到断序后无法继续往后访问
    for k, v in ipairs(a) do
        print("key:" .. k .. " value:" .. v)
    end
    
    print()
    
    -- 使用pairs迭代器能够访问数组中的所有元素
    for k, v in pairs(a) do
        print("key:" .. k .. " value:" .. v)
    end
    

    至此我们可以将其理解为是C++中的std::unordered_map,然后通过std::pair去对键值对进行访问

    使用表实现面向对象

    本质上还是“哈希表”,只是不同的语法体现形式

    Student = {
        age = 20,
        score = 100,
    
        sayHello = function()
            print("Hello")
        end
    }
    
    print(Student.score)
    Student.sayHello()
    

    与“哈希表”类似,可以在表的外部添加“类成员”和“类成员函数”

    Student = {
        sayHello = function()
            print("Hello")
        end
    }
    
    Student.score = 100;
    Student.sayGoodBye = function()
        print("GoodBye")
    end
    

    如果想要在表中的函数对象中访问表中的元素,有两种方法

    • 通过指定变量名进行访问

      Student = {
          age = 20,
          
          printAge = function()
              -- 需要指明是Student中的值 否则将会使用全局变量 若不存在此全局变量 则默认为nil
              print(string.format("Age is %d", Student.age))
          end
      }
      
      Student.printAge()
      
    • 通过传递函数参数进行访问

      Student = {
          score = 100,
          
          printScore = function(s)
              print(string.format("Score is %d", s.score))
          end
      }
      
      -- 调用时传递自己
      Student.printScore(Student)
      

      通过语法糖:进行优化

      Student = {
          score = 100
      }
      
      function Student:printScore()
          -- : 代表函数默认第一个参数会传递“self”
          -- self搭配:使用 代表使用传入的第一个参数
          print(string.format("Score is %d", self.score))
      end
      
      -- : 代表调用函数时 会默认传一个自己(Student)作为第一个参数
      Student:printScore()
      

      这与C++中的运作方式类似,C++在调用非静态成员函数时,编译器会默认添加this指针作为函数的第一个参数。只不过在Lua中我们需要显示指明传入一个self

    表的常见操作

    插入

    表的插入操作有两个版本的“重载”。分为指定位置与不指定位置

    -- 在第五号元素的前面插入值"Hello"
    a = { 20, 30, 40, 50, 90 }
    table.insert(a, 5, "Hello")
    
    -- 不指定位置 在表的末端插入
    a = { 20, 30, 40, 50, 90 }
    table.insert(a, 10)
    

    删除

    表的删除操作也有两个版本的“重载”。分为指定位置与不指定位置

    -- 删除表中第三个元素 即40
    a = { 20, 30, 40, 50, 90 }
    table.remove(a, 3)
    
    -- 不指定位置 删除最后一个元素
    a = { 20, 30, 40, 50, 90 }
    table.remove(a)
    

    排序

    -- 降序排序
    a = { 20, 30, 40, 50, 90 }
    table.sort(a, function(a, b)
        return a > b
    end)
    

    局部变量与全局变量

    默认声明的变量是全局变量,以下变量wordscoredata都是全局变量。全局变量的生命周期是整个程序的执行周期

    word = "Hello"
    
    for i = 1, 10, 1 do
        score = 10
    end
    
    function test_func()
        data = 300
    end
    test_func()
    

    所有的全局变量都存储在G表中,包括tablefunction等等

    自然也可以通过G表来访问其中的全局元素

    globalData = 20
    print(_G["globalData"])
    

    局部变量需要使用local修饰。局部变量的生命周期是当前作用域或当前文件,离开了作用域或离开了文件访问将得到nil

    function test_func()
        local data = 300
    end
    

    多脚本执行与卸载

    使用require关键字执行其他脚本文件中的代码

    -- HelloWorld.lua
    print("Hello World")
    require("AnotherCode")
    print("Hello World")
    
    -- AnotherCode.lua
    print("AnotherCode")
    

    在运行HelloWorld.lua

    • 输出HelloWorld
    • 执行AnotherCode.lua文件,输出AnotherCode
    • 输出HelloWorld

    一个脚本文件只能被加载一次,即多次require一个脚本文件没有效果。我们需要卸载脚本文件

    -- 输出脚本有没有被加载 返回一个boolean
    print(package.loaded["AnotherCode"])
    -- 卸载脚本
    package.loaded["AnotherCode"] = nil
    

    脚本文件可以有一个返回值,然后再另一个文件中通过require来获取

    -- HelloWorld.lua
    data = require("AnotherCode")
    print(data)
    
    -- AnotherCode.lua
    return 10 + 20
    

    协程

    创建协程

    • 创建协程对象并运行

      co = coroutine.create(function ()
          for i = 1, 10, 1 do
              print("Hello" .. i)
          end
      end)
      
      coroutine.resume(co)
      
    • 创建协程函数并运行

      coFunc = coroutine.wrap(function ()
          for i = 1, 10, 1 do
              print("Bye" .. i)
          end
      end)
      
      coFunc()
      

    在执行协程的时候,执行权转移到协程中,只有当协程执行完毕或主动挂起时,执行权才会转移回来

    挂起协程(yield)

    co = coroutine.create(function ()
        for i = 1, 10, 1 do
            print("Hello" .. i)
            coroutine.yield()
        end
    end)
    
    -- 共计会执行四次Hello 由于协程被挂起 因此需要重复调用以让协程从挂起点恢复执行
    coroutine.resume(co)
    coroutine.resume(co)
    coroutine.resume(co)
    coroutine.resume(co)
    

    协程返回值

    coroutine.yield()的函数参数可以作为返回值在外部获取

    • 协程对象的返回值

      可以看作是std::tuple<bool, Ts...>,第一个参数代表协程是否被成功执行

      co = coroutine.create(function ()
          for i = 1, 10, 1 do
              print("Hello")
              coroutine.yield(i, 10 - i)
          end
      end)
      
      -- true    1       9
      isWork, first, second = coroutine.resume(co)
      print(isWork, first, second)
      
    • 协程函数的返回值

      可以看作是std::tuple<Ts...>,没有默认参数

      coFunc = coroutine.wrap(function ()
          for i = 1, 10, 1 do
              coroutine.yield(i)
          end
      end)
      
      -- 1
      print(coFunc())
      

    协程传参

    • 当首次执行协程时,传递的参数是执行函数的参数
    • 当协程从挂起点恢复执行时,传递的参数是coroutine.yield()的返回值

    协程状态

    只能查看协程对象的状态,协程函数的状态无法查看

    • suspended:代表协程被挂起(当一个协程被创建但是没有被执行时,它是suspended的)
    • running:代表协程正在运行
    • dead:代表协程已经执行完毕
    • normal:当协程A中启动协程B后,在执行协程B的过程中,协程A的状态是normal
    co = coroutine.create(function ()
        for i = 1, 10, 1 do
            coroutine.yield()
        end
    end)
    
    coroutine.resume(co)
    -- suspended
    print(coroutine.status(co))
    

    八股文题目

    function foo (a)
        print("foo", a)
        return coroutine.yield(2 * a)
    end
    
    co = coroutine.create(function (a,b)
        print("co-body", a, b)
        local r = foo(a + 1)
        print("co-body", r)
        local r, s = coroutine.yield(a + b, a - b)
        print("co-body", r, s)
        return b, "end"
    end)
    
    print("main", coroutine.resume(co, 1, 10))
    print("main", coroutine.resume(co, "r"))
    print("main", coroutine.resume(co, "x", "y"))
    print("main", coroutine.resume(co, "x", "y"))
    

    执行结果为

    co-body 1       10
    foo     2
    main    true    4
    co-body r
    main    true    11      -9
    co-body x       y
    main    true    10      end
    main    false   cannot resume dead coroutine
    

    元表

    • 任何一个表都可以设置为另一个表的元表(类似父类的概念)
    • 对子表进行特定操作时,会执行元表中“重写”的操作。调用的前提是:它是一个表的元表 且 它“重写”了相应的操作
    base_student_table = {}
    student_table = {}
    -- 将base_student_table设置为student_table的元表
    setmetatable(student_table, base_student_table)
    

    __tostring

    当表需要当作string类型使用时,会默认调用元表中的__tostring函数。__tostring默认会传递一个调用者作为形参

    base_student = {
        __tostring = function(a)
            return a.name
        end
    }
    
    student = {    
        name = "Jelly",
    }
    
    setmetatable(student, base_student)
    -- Jelly
    print(student)
    

    这其实有一点类似C++中的类型转换重载。只是C++中的类型转换重载不允许有返回值与形参

    struct base_student {
        operator std::string() {
            return "Jelly";
        }
    };
    
    struct student : base_student {};
    

    __call

    当表需要当作function使用时,会默认调用元表中的__call函数。__call默认会传递一个调用者作为形参

    base_student = {
        __call = function(default, param)
            print(default.name .. param)
        end
    }
    
    student = {    
        name = "Jelly",
    }
    
    setmetatable(student, base_student)
    -- Jelly is here
    student(" is here")
    

    这其实相当于C++中的仿函数

    运算符重载

    • 加号运算符重载:__add;减号运算符重载:__sub;乘法运算符重载:_mul;除法运算符重载:__div
    • 取余运算符重载:__mod;求幂运算符重载:__pow
    • 比较运算符重载:__eq;小于运算符重载:__lt;小于等于运算符重载:__le
    • 没有大于和大于等于的运算符重载,lua会自动对小于等于和小于进行取反
    base_student = {
        __add = function(left, right)
            return left.score + right.score
        end
    }
    
    student1 = {    
        score = 100
    }
    
    student2 = {
        score = 200
    }
    
    setmetatable(student1, base_student)
    setmetatable(student2, base_student)
    -- 300
    print(student1 + student2)
    

    __index与__newindex

    当表中找不到某个数据项时,会通过它的元表的__index所指定的表中去搜索

    base_student = {
    	base_student.__index = { name = "Jelly" }
    }
    
    student = {}
    setmetatable(student, base_student)
    
    print(student.name)
    

    额外注意,当元表的__index指向的是自己时,__index需要写在表外部

    base_student = {
        name = "Jelly"
    }
    base_student.__index = base_student
    

    当表需要修改某数据项时,且该数据项不存在于当前表中,会修改(添加)它元表的__newIndex所指定的表

    student_db = {}
    
    base_student = {}
    base_student.__newindex = student_db
    
    student = {}
    setmetatable(student, base_student)
    
    -- 添加到student_db中
    student.score = 200;
    print(student_db.score)
    

    使用rawgetrawset方法来限制只在当前表中查找或添加

    print(rawget(student, "name"))
    rawset(student, "name", "Jelly")
    

    Lua实现面向对象

    利用元表和__index查找索引来模拟面向对象

    利用表模拟一个Object基类

    Object = {
        id = 0
    }
    
    Object.printID = function(self)
        print(self.id)
    end
    
    Object.new = function(self)
        -- 创建一个局部变量
        local obj = {}
        -- 将调用者设置为obj的元表
        setmetatable(obj, self)
        -- “子类中找不到在基类中查找”
        self.__index = self
        return obj;
    end
    
    Object.createSubClass = function(self, subClassName)
        -- 在G表中创建全局对象 作为“类型”
        _G[subClassName] = {}
        local subClass = _G[subClassName]
        -- 该类型的“父类”是调用者
        setmetatable(subClass, self)
        subClass.base = self
        self.__index = self
    end
    

    Object的基础上创建子类GameObject,子类拥有属性scale,以及两个“成员函数”

    Object:createSubClass("GameObject")
    GameObject.scale = 1.0
    
    GameObject.addScale = function(self, multi)
        self.scale = self.scale * multi
    end
    
    GameObject.printScale = function(self)
        print(self.scale)
    end
    

    GameObject的基础上创建子类Person,“重写”了方法addScale

    GameObject:createSubClass("Person")
    
    Person.addScale = function(self, multi)
        self.base.addScale(self, multi)
        print("Person Add Scale")
    end
    

    测试代码,通过:new创建两个局部对象

    local p1 = Person:new()
    p1:addScale(20)
    -- 20.0
    p1:printScale()
    
    local p2 = Person:new()
    p2.scale = 200
    -- 200
    p2:printScale()
    

    垃圾回收

    Lua中有自动定时垃圾回收机制,也可以手动执行

    • print(collectgarbage("count")):打印Lua程序占用的内存空间大小,单位是KB
    • collectgarbage("collect"):手动进行垃圾回收
  • 相关阅读:
    poj 3415 后缀数组+单调栈
    hdu 3450 后缀数组
    hdu 2774 后缀数组
    后缀数组模板(倍增法)
    hdu 4405 概率dp
    zoj 3329 概率dp
    [日常摸鱼]bzoj2875[NOI2012]随机数生成器-矩阵快速幂
    [日常摸鱼]bzoj1038[ZJOI2008]瞭望塔-半平面交
    [日常摸鱼]bzoj1007[HNOI2008]水平可见直线-半平面交(对偶转凸包)
    [日常摸鱼]bzoj3083遥远的国度-树链剖分
  • 原文地址:https://www.cnblogs.com/tuapu/p/15758135.html
Copyright © 2020-2023  润新知