<为什么>要有virtual,及如何理解晚捆绑

本文探讨了C++中的虚函数机制与多态性的实现原理,解释了虚拟机制的作用及晚绑定的概念,通过示例说明了如何使用虚函数实现多态。

下边要说的是我在语言学习过程中遇到一些不理解的问题,为什么要有virtual机制,还有什么是晚捆绑。我当时学到这的时候可能困惑了一段时间,后来经过继续学习测试等,得出了自己的结论,仅在这记录一下。没有太多的代码实例,更像是我在啰嗦一些东西。

先说virtual

说道virtual肯定就想到多态,virtual是c++ 实现多态的一种方法,为什么要有这样一套虚机制? 还是那些原则,代码复用 ”想用不变的代码去实现不同算法“,大概就是 懒吧。

举个简单的例子,(可能我现在在站的高度不够,理解范围有限,但这个例子至少是虚机制想要解决的问题中的一种)

有一个方法void test() 来测试不同国家的人说的语言,比如说有 Chinese  Japanese French 等几个类,类中都有一个speak() 方法

 然后可以考虑这样写

 void test(Chinese *p)

{

p->speak();

 void test(Japanese*p)

{

p->speak();

 void test(French *p)

{

p->speak();

这样可以达到测试每个对象所说语种的目的。 但这样看起来很不好,这几个函数,只有参数类型不一样,内部实现都一样。那么想  能不能把参数类型设置成某个特殊的类型,这个特殊的类型可以接收 Chinese  Japanese French 等这些类的对象,然后在调用speak时,根据我实际传进去的参数类型,再调用每个类中的speak方法。 如果可以的话就能只写一个函数,实现不同的测试了

void test (special_class *p)

{

p->speak();

}

virtual机制就解决了上述问题,可以弄一个human的基类  然后让Chinese  Japanese French这几个类继承它,  然后语法上允许父类指针指向子类对象

void test(human * p)

{

p->speak();

}

然后问题是 test既然是能处理不同子类对象,那它如何确定该调用哪个子类中的speak()?

这就需要 晚捆绑啊,函数覆盖啊  虚表啊什么的那一套来实现,这个不是我困惑的重点,关于虚表可以看看这个

点击打开链接



第二个  关于晚捆绑

学到这的时候我很是困惑,有早捆绑 或者叫 静态绑定,比如说上边的test 的参数 只是一个类类型  而不是指针或者引用的话,也就是说它没有多态,只能处理一种类型的对象,

这时候调用speak 就是静态绑定,在编译阶段就能完成。   如果有多态  那就叫晚捆绑(动态绑定)  绑定只能在运行时。困惑就在这了,

什么叫运行时才能绑定啊????



在我的程序运行之前,我的所有代码不是已经的写定了么,编译之后 已经生成了 那些固定的汇编语句了啊,还有什么是非得运行之后才确定么????

说是不知道函数要处理的是哪个具体的子类对象,所以确定不了调用哪个函数, 那既然我调用test的代码已经写了

比如     

Chinese  a;

test(a);

那函数要处理的对象不就是Chinese 类型的么   那就能确定调用的是Chinese 中的speak()了

编译时就把p->speak(); 换成 call(Chinese中speak的地址) 不就行了么,还有什么是在运行之前不能确定的呢?????

?????????????????????????????????????????????????????????????????????????????

当时确实很困惑啊,后来想想 too young too naive 啊

按照上边的想法,如果只调用test(a);

那是可以 把test函数中的speak调用改成call(Chinese中speak的地址),当程序执行到test(a);这句时 跳到改好的 test()函数的代码部分  能往下执行,并调用了正确的函数。只要再稍微往后想一点, test()都被改成了 针对对象a的特定版本了, 那还能调用它 来处理其他子类对象么? 不能处理了 ,那多态跑哪去了?

可见照上述我所想的这种静态绑定是实现不了多态的。 那多态是如何实现的,是靠动态绑定实现的,在c++编程思想中讲的比较明白了,为了实现多态,编译器在处理时会隐藏的插入一些内容


简单说,就是因为它不知道要处理的对象类型,不能直接去call某个函数的地址,它插入这段代码,如果是有虚机制的对象,那在对象存储空间里能找到v_ptr  也就是虚表指针,这个不管哪个子类的对象只要有virtual   都会有这个指针,然后通过这个指针的偏移处理,在虚函数表中找到要调用的具体版本的函数的地址,再 call     就能实现不管处理什么类型的子对象,都能找到v_ptr都能通过偏移 找到想要的函数,这就实现了 通用的处理。而这种处理方式,需要在程序运行是,找到传进来的那个具体对象的存储空间,然后在里边找到v_ptr 然后计算等等, 然后才能得到想要的函数, 也就可以理解 这是发生在运行时。



如果对这一块有疑惑,推荐去看c++编程思想第二版  第十五章



`virtual` 是 C# 中的一个关键字,用于**标记一个方法、属性、索引器或事件为“可被派生类重写(override)”**。 --- ### ✅ 你问的这行代码: ```csharp public virtual async Task<List<InventoryLog>> BatchRecordOperationsAsync(...) ``` 其中的 `virtual` 表示: > 这个异步方法是 **可以被子类重写(override)的**。如果不加 `virtual`,子类就不能用 `override` 来修改它的实现。 --- ### 🧩 举个例子说明 假设你有一个基类: ```csharp public class InventoryAppService : IInventoryAppService { public virtual async Task<List<InventoryLog>> BatchRecordOperationsAsync( BatchOperationInput input) { // 默认实现:批量记录操作日志 var logs = new List<InventoryLog>(); foreach (var op in input.Operations) { var log = new InventoryLog { Action = op.Action, Timestamp = DateTime.Now }; logs.Add(log); } return logs; } } ``` 现在你想在某个特殊场景下扩展这个行为,比如增加发送通知的功能: ```csharp public class SpecialInventoryAppService : InventoryAppService { public override async Task<List<InventoryLog>> BatchRecordOperationsAsync( BatchOperationInput input) { // 先调用父类逻辑 var logs = await base.BatchRecordOperationsAsync(input); // 再添加额外逻辑:发送邮件通知 Console.WriteLine("已批量处理操作,共 " + logs.Count + " 条记录"); return logs; } } ``` ✅ 只有父类方法是 `virtual`,子类才能使用 `override` 来重写它。 --- ### ❌ 如果没有 `virtual` 会怎样? 如果你把父类的方法写成: ```csharp public async Task<List<InventoryLog>> BatchRecordOperationsAsync(...) // 没有 virtual ``` 那么你在子类中写 `override` 就会报错: ``` 'SpecialInventoryAppService.BatchRecordOperationsAsync' : cannot override inherited member 'InventoryAppService.BatchRecordOperationsAsync' because it is not marked virtual, abstract, or override ``` --- ### ⚠️ 注意:`async` 不影响 `virtual` 虽然这是一个 `async` 方法,但 `virtual` 仍然有效。C# 支持异步方法的虚调用和重写: ```csharp public virtual async Task<SomeType> DoWork() { ... } ``` 是可以被 `override` 的,没问题。 --- ### ✅ 什么候该用 `virtual`? | 场景 | 是否建议加 `virtual` | |------|------------------| | 你想让别人继承并修改你的方法行为 | ✅ 是 | | 你在写框架/基类服务,允许插件化扩展 | ✅ 是 | | 方法是密封的、核心逻辑不允许改动 | ❌ 不要加 | | 使用依赖注入 + 接口编程(更推荐) | 🔁 更推荐用接口而不是继承 | --- ### 💡 ABP 框架中的常见用途 在 ASP.NET Boilerplate (ABP) 或 Volo.Abp 中,应用服务(Application Service)经常使用 `virtual`: - 因为它们可能被继承以进行定制 - 或者被 AOP 拦截器(如审计日志、权限检查)动态代理增强 例如:ABP 的 `CrudAppService` 中很多方法都是 `virtual`,方便你继承后重写 `Create`, `Update`, `Delete` 等行为。 --- ### ✅ 总结 | 关键词 | 含义 | |--------|------| | `virtual` | 标记方法为“可被重写”,允许子类通过 `override` 修改其实现 | | `override` | 子类提供新的实现方式 | | `base.Method()` | 调用父类原始实现 | | `async + virtual` | 完全支持,常用于可扩展的服务层方法 | ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值