UnLua实现多态

一、什么是多态
多态其实就是子类重写父类的函数,在运行时,如果通过父类对象调用函数,实际上会根据子类的实际类型调用子类中重写的函数。
二、在父类中定义函数
BP_WeaponBase_C.lua

--父类构造函数
function BP_WeaponBase_C:UserConstructionScript()
	print("BP_WeaponBase_C:UserConstructionScript")
	print(tostring(self))
    self:SpawnBall()	--直接模拟外部调用父类的SpawnBall函数
end
--父类生成球函数	
function BP_WeaponBase_C:SpawnBall()
 	print("BP_WeaponBase_C:SpawnBall")
	print(tostring(self))
	return nil
end

三、子类如何实现多态?
看下面3种情况,但只有一种情况下是对的!

BP_DefaultWeapon_C.lua

1、情况一:子类构造函数不调用父类构造函数。

--子类继承父类
local BP_DefaultWeapon_C = UnLua.Class("BP_WeaponBase")
--子类构造函数
function BP_DefaultWeapon_C:UserConstructionScript()
    print("BP_DefaultWeapon_C:UserConstructionScript")
    print(tostring(self))
end   
--重写父类生成球函数
function BP_DefaultWeapon_C:SpawnBall()
    print("BP_DefaultWeapon_C:SpawnBall")
    print(tostring(self))
end

打印
在这里插入图片描述
只调用子类的构造函数,并没有调用父类构造函数,所以SpawnBall也没有调用

很明显,连父类构造函数都没有调用,并没有实现多态!

2、情况二:子类构造函数用冒号调用父类构造函数。
在子类的构造函数中,添加self.Super:UserConstructionScript()代码

--子类构造函数
function BP_DefaultWeapon_C:UserConstructionScript()
    print("BP_DefaultWeapon_C:UserConstructionScript")
    print(tostring(self))
    self.Super:UserConstructionScript()
end   

打印
在这里插入图片描述
先调用子类的构造函数,子类的self地址是00000BADB6066700
再调用父类的构造函数,父类的self地址是00000BADB6068600,与子类的self不一样
最后调用父类的SpawnBall,传进去的self是00000BADB6068600,即父类的self

虽然调用了父类构造函数,但是当执行self:SpawnBall()时,这里的self是父类的self,所以调用的是父类的函数而不是子类所重写的函数,也没有实现多态!

3、情况三:子类构造函数用点号调用父类构造函数,并将子类的self作为第一个参数传进去。
在子类的构造函数中,修改代码如下:

function BP_DefaultWeapon_C:UserConstructionScript()
    print("BP_DefaultWeapon_C:UserConstructionScript")
    print(tostring(self))
    self.Super.UserConstructionScript(self)
end   

打印
在这里插入图片描述
先调用子类的构造函数,子类的self地址是00000BAD9C969F80
再调用父类的构造函数,父类的self地址是00000BAD9C969F80,即子类的self
最后调用子类的SpawnBall,传进去的self是00000BAD9C969F80,也是子类的self

显而易见,这种调用方式才是UnLua实现多态的正确姿势,在子类中通过点号调用父类构造函数,并把子类的self当作参数传给父类,这样才能在执行self:SpawnBall()时,确保self是子类的self,执行的也是子类的函数!

<think>嗯,用户深入询问C++多态实现机制和使用场景,特别是关注指针(*)和引用(&)在多态中的应用区别。从对话历史看,用户已经掌握了基础概念,现在需要更技术性的解析。用户提供的站内引用很有价值,其中引用[1]和[2]展示了多态的核心代码范例,引用[3]解释了多态的本质,引用[4]提到了模板实现的编译时多态,引用[5]则以生动案例说明虚函数机制。这些材料正好构成回答的骨架。关于指针和引用的多态应用差异,需要突出三个技术层面:一是语法访问方式的区别(ptr->vsobj.),二是空值安全性的不同(nullptr风险vs引用安全),三是使用场景的分野(动态内存必须用指针,函数参数优先用引用)。引用[1]的Func(Person&p)和引用[2]的Func1(Person*p)并排对比特别能说明问题。注意到用户对实现机制的关注,准备着重解释虚函数表原理。这里计划用两个层级说明:首先通过代码片段展示基类与派生类的虚表结构差异,然后用数学公式表达动态绑定的地址计算过程$$\text{函数地址}=\text{vptr}[n]$$,这样既符合用户提供的数学格式要求,又能直观展现运行时决策机制。多态的类型分类也需要系统化整理。准备把运行时多态细分为接口多态(纯虚函数)、实现多态(虚函数),编译时多态则区分为函数重载、模板特化和操作符重载,每种都匹配引用中的实例代码。其中引用[4]的模板多态案例很典型,会作为重点说明。最后的安全警告很重要:用户引用[5]的示例暴露了指针可能为null的风险,需要强调用引用替代指针的场景。设计在总结部分用对比表格清晰呈现指针引用适用场景,并标注危险项(如"可选参数+动态绑定"场景允许指针但必须null检查)。</think> 在C++中实现多态时,**指针(`*`)**和**引用(`&`)**的选择取决于具体场景,二者在实现运行时多态时功能相似但存在关键差异。以下是核心区别和使用场景分析: --- ### 一、多态实现机制 多态的核心是**虚函数(virtual function)**机制: 1. 基类通过`virtual`声明虚函数,派生类重写该函数。 2. **运行时绑定**:当基类的指针或引用调用虚函数时,根据实际指向的对象类型决定调用哪个版本的函数[^2]。 ```cpp class Animal { public: virtual void speak() { cout << "Animal sound" << endl; // 基类虚函数[^2] } }; class Dog : public Animal { public: void speak() override { cout << "Woof!" << endl; // 派生类重写[^5] } }; ``` --- ### 二、指针(`*`)和引用(`&`)的使用场景与区别 #### 1. **使用指针(`*`)的场景** - **动态内存分配**:需操作堆内存中的对象 ```cpp Animal* animal = new Dog(); // 基类指针指向派生类对象 animal->speak(); // 输出 "Woof!" delete animal; // 释放内存 ``` - **可选参数传递**:参数可能为`nullptr` ```cpp void makeSound(Animal* animal) { if (animal) animal->speak(); // 安全检查[^2] } ``` - **重新绑定对象**:需修改指针指向 ```cpp Animal* ptr = &dog1; ptr->speak(); // 输出dog1的声音 ptr = &dog2; // 重新绑定 ptr->speak(); // 输出dog2的声音 ``` #### 2. **使用引用(`&`)的场景** - **高效安全的参数传递**:保证对象有效且避免拷贝 ```cpp void makeSound(Animal& animal) { animal.speak(); // 无需空指针检查[^1] } Dog myDog; makeSound(myDog); // 输出 "Woof!" ``` - **运算符重载和返回值**:需直接修改原对象 ```cpp Vector3D& operator+=(const Vector3D& other) { x += other.x; return *this; // 返回自身引用 } ``` - **面向对象接口设计**:强制传递有效对象 ```cpp class Zoo { public: void feedAnimal(Animal& animal); // 动物必须有效 }; ``` --- ### 三、关键区别总结 | 特性 | 指针(`*`) | 引用(`&`) | |--------------|-------------------------------------|------------------------------------| | **空值** | 允许`nullptr`,需安全检查 | 必须绑定有效对象,无空值风险 | | **重绑定** | 可重新指向不同对象 | 初始化后不可更改绑定 | | **语法** | 需`->`访问成员,显式解引用`*ptr` | 直接使用`.`操作,无额外符号 | | **内存管理** | 需手动`delete`(动态分配时) | 自动管理生命周期 | | **安全性** | 有野指针、内存泄漏风险 | 更安全,减少低级错误 | --- ### 四、技术原理:虚函数表(vTable) 运行时多态通过**虚函数表**实现[^2]: 1. 含虚函数的类隐式生成虚表(存储函数指针) 2. 对象包含隐藏指针`vptr`指向虚表 3. 调用过程: $$ \text{函数地址} = \text{vptr}[n] \quad (n=\text{虚函数索引}) $$ ```cpp Dog dog; Animal* animal = &dog; animal->speak(); // 等价于 (*(animal->vptr[0]))() // vptr[0]指向Dog::speak() ``` --- ### 五、多态的其他实现方式(补充) 1. **编译时多态(静态绑定)** - **函数重载**:相同函数名,不同参数 - **模板**:泛型编程(引用[4]) ```cpp template<typename T> void draw(const T& shape) { shape.draw(); // 编译时实例化 } ``` 2. **接口多态(纯虚函数)** ```cpp class IDrawable { public: virtual void draw() = 0; // 纯虚函数 }; ``` --- ### 六、总结建议 - **优先引用**:函数参数传递、运算符重载等场景(安全高效) - **必须指针**:动态内存管理、可选参数、重新绑定需求 - **禁用场景**: - 引用不可用于存储动态分配的对象(`int& ref = *new int`危险!) - 指针需避免野指针(初始化后使用) > 多态的本质是 **“相同接口,不同行为”**,通过虚函数机制实现运行时的动态绑定[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值