【LUA基础一本通】第四章:面向对象

0. 导入

通过tablefunction和元表模拟面向对象。简单对象创建时,冒号隐式传递self,点号显式传递。类通过new方法创建实例,元表_index实现继承。实例修改不影响基类。继承时,子类调用父类方法需注意self和参数传递,不同调用方式影响继承链和属性查找。对象间通过引用共享数据,修改会相互影响。
在这里插入图片描述

1. 简单对象的创建

情景引入:

animal = {name = "Tom", age = 5}
animal.bark = function (type)
    print(animal.name .. " is a " .. type)    
end

animal.bark("dog")    -- Tom is a dog

创建一个animal_1“对象”,使其等于animal,此时它们指向同一个内存空间,实际就是一个对象:

animal_1 = animal

animal_1.name = "Jerry"
animal_1.bark("cat")  -- Jerry is a cat

如果删除animalanimal_1还会继续存在,但是此时调用会报异常:

animal = nil
animal_1.bark("cat")  -- error: attempt to index a nil value (global 'animal')

因为此时animal.bark里传的是固定参数animal,这时候是nil报错,可以如下修改:

animal.bark = function (self, type)
    print(self.name .. " is a " .. type)    
end
-- 在调用时增加参数:
animal_1.bark(animal_1, "cat")  -- Jerry is a cat

此时animal成为了我们熟知的“对象”,self是显式的呈现出来,也可以使用冒号(lua的语法糖):

function animal:bark(type)
    print(self.name .. " is a " .. type)    
end
-- 在调用时无需加参数,但是要使用冒号:
animal_1:bark("cat")  -- Jerry is a cat
animal_1.bark(animal_1, "cat")  -- Jerry is a cat

总结:冒号是隐式self,点号是显式self,只在调用和定义时入参有区分,其他没变化。
但是,这种创建对象,始终都是同一个对象,如果需要创建不同对象如何处理呢?

2. 类的创建

Lua中使用tablefunction元表可以定义出类:

  1. 使用一个table作为基类,创建该基类的new()方法;
  2. new()方法中创建一个空表,并返回这个空表;
  3. 为该空表指定一个元表:该元表重写__index元方法,且将基类指定为重写的__index元方法.

至此,类即创建出来。

-- 创建一个基类
Animal = {name = "no_name", age = 5}
function Animal:bark(type)
    print(self.name .. " is a " .. type)  
end
-- 创建该基类的new()方法:
function Animal:new()
	-- 创建一个空表
    local a = {}
    -- 指定元表,将基类赋值给__index元方法
    setmetatable(a, {__index = self})
    -- 返回这个空表
    return a
end

还是那句话:__index元方法,只影响get,不影响set,所以在对象中找不到的,会去基类中找,在对象中set的动作不会影响基类。

思考:为啥会去基类中找?
因为找不到会去元表的__index元方法中找,因为赋值的就是基类,所以会去基类中找。

创建两个实例:

animal1 = Animal:new()
animal2 = Animal:new()

print(animal1)  -- table: 0000000000f898d0
print(animal2)  -- table: 0000000000f89ed0

打印这两个实例的地址,可以看到是不同的对象。

animal1.name = "Tom"
animal1.age = 6
animal1.type = "Cat"

animal1:bark(animal1.type) -- Tom is a Cat
print(Animal.name)  -- no_name
print(Animal.type)  -- nil

给实例animal1赋值,不会对基类有影响。

3. 类的继承

以下示例展示类的继承特性。

--  创建一个有参构造器
local people = {name = "unknow", age = 0}

function people:new(obj)
    local p = obj or {}
    setmetatable(p, {__index = self})
    return p
end

-- 创建一个入参
local Tom = {name = "Tom"}

-- 创建一个对象
local p_Tom = people:new(Tom)

p_Tom.gender = "male"

print("p_Tom.name = " .. p_Tom.name .. ", p_Tom.age = " .. p_Tom.age .. ", p_Tom.gender = " .. p_Tom.gender)

print("-------------------------")
print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name)
print("p_Tom.age = " .. p_Tom.age .. ", Tom.age = " .. Tom.age)

print("-------------------------")
p_Tom.name = "Jerry"
p_Tom.age = 5
print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name)
print("p_Tom.age = " .. p_Tom.age .. ", Tom.age = " .. Tom.age)

输出:

p_Tom.name = Tom, p_Tom.age = 0, p_Tom.gender = male
-------------------------
p_Tom.name = Tom, Tom.name = Tom
p_Tom.age = 0, Tom.age = 0
-------------------------
p_Tom.name = Jerry, Tom.name = Jerry
p_Tom.age = 5, Tom.age = 5

可以看到,p_Tom是继承peoplep_TomTom指向同一个内存,p_Tom变化,Tom也会变化:

print(p_Tom, Tom)  -- table: 0000000000e99d10 table: 0000000000e99d10

其实p_Tom是对象也是类,可以使用构造函数new()构造子类(对象)。

-- 创建一个子类继承p_Tom,这里p_Tom没有new方法,因此会去父类people中找
Tom1 = p_Tom.new()
Tom1.name = "Jack"

print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name .. ", Tom1.name = " .. Tom1.name)
-- print("p_Tom.age = " .. p_Tom.age .. ", Tom1.age = " .. Tom1.age)  -- attempt to concatenate a nil value (field 'age')

输出:

p_Tom.name = Jerry, Tom.name = Jerry, Tom1.name = Jack

但是如果传参呢?

Tom1 = p_Tom.new(p_Tom)
Tom1.name = "Jack"

print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name .. ", Tom1.name = " .. Tom1.name)
print("p_Tom.age = " .. p_Tom.age .. ", Tom1.age = " .. Tom1.age)

输出:

p_Tom.name = Jerry, Tom.name = Jerry, Tom1.name = Jack
p_Tom.age = 5, Tom1.age = 5

为什么会这样呢?
首先要搞清楚p_Tom.new()是如何调用的:p_Tom本身没有new()方法,由于p_Tom继承people,所以p_Tom会向上调用peoplenew()方法。
注意这里p_Tom.new()是点号不是冒号,调用p_Tomnew()方法相当于people.new(),即people.new(nil, nil),注意此时的self就是nil而不是people,因此,Tom1 = p_Tom.new()Tom1没有父类Tom1.age是未定义。

print(p_Tom, Tom1, people)  -- table: 0000000000f29f10 table: 0000000000f29bd0 table: 0000000000f29a90

可以看到三个对象地址均不同。
若是p_Tom.new(p_Tom),则调用关系如下:people.new(p_Tom, nil),,注意此时self就是p_Tom而不是peopleTom1父类是p_Tom,所以Tom1.age返回的是p_Tom.age的值5。
若是如下呢?

Tom1 = p_Tom.new(p_Tom, people)
Tom1.name = "Jack"

print("p_Tom.name = " .. p_Tom.name .. ", Tom.name = " .. Tom.name .. ", Tom1.name = " .. Tom1.name)
print("p_Tom.age = " .. p_Tom.age .. ", Tom1.age = " .. Tom1.age)

输出:

p_Tom.name = Jerry, Tom.name = Jerry, Tom1.name = Jack
p_Tom.age = 5, Tom1.age = 0

其实p_Tom.new(p_Tom, people)调用关系如下:people.new(p_Tom, people),此时self就是p_Tom,且objpeople,因此Tom1就是people,所以Tom1.age返回的是people.age的值0。

print(p_Tom, Tom1, people)  -- table: 0000000000f49990 table: 0000000000f49bd0 table: 0000000000f49bd0
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值