C++菱形继承问题解析:从数据冗余到虚继承解决方案
1. 项目概述为什么菱形继承是C面试的“常客”如果你在准备C面试或者正在学习C面向对象编程那么“菱形继承”这个词你大概率绕不过去。它就像一个经典的“面试官快乐题”既能考察你对继承机制的理解深度又能检验你对内存布局、虚函数表等底层概念的掌握程度。我第一次在项目里踩到这个坑是在设计一个图形编辑器的类层次结构时当时为了复用代码让一个Circle类同时继承了Shape图形和Drawable可绘制两个基类而这两个基类又都继承自同一个Object对象基类。编译没问题但运行时对象的内存大小和虚函数调用出现了诡异的行为调试了半天才定位到是菱形继承导致的数据冗余和二义性问题。简单来说菱形继承描述的是这样一种类关系一个派生类通过两条或以上的路径最终继承自同一个基类形成了一个像钻石菱形一样的继承图谱。这听起来像是代码复用的“捷径”但实际上它引入了C对象模型中两个最棘手的问题数据冗余和二义性。理解并解决这两个问题是掌握C多重继承和虚继承的关键。本文将从实际案例出发掰开揉碎地讲解菱形继承的原理、问题、以及C提供的解决方案——虚继承并分享一些在大型项目中处理复杂继承关系的实战心得。2. 菱形继承的核心问题与原理拆解2.1 一个典型的菱形继承案例让我们用一个更贴近业务的例子来具象化这个问题。假设我们在开发一个游戏里面有各种角色。我们设计了一个Character角色基类它有一个name_成员和一个printInfo()函数。class Character { public: std::string name_; Character(const std::string name) : name_(name) {} void printInfo() { std::cout Character: name_ std::endl; } };接着我们派生出两个中间类FlyingCharacter飞行角色和FightingCharacter战斗角色它们分别添加了飞行高度和战斗力的属性。class FlyingCharacter : public Character { public: int flyHeight_; FlyingCharacter(const std::string name, int height) : Character(name), flyHeight_(height) {} }; class FightingCharacter : public Character { public: int combatPower_; FightingCharacter(const std::string name, int power) : Character(name), combatPower_(power) {} };现在我们想创建一个既会飞行又会战斗的角色比如“龙”很自然地会让Dragon类同时继承FlyingCharacter和FightingCharacter。class Dragon : public FlyingCharacter, public FightingCharacter { public: Dragon(const std::string name, int height, int power) : FlyingCharacter(name, height), FightingCharacter(name, power) {} };至此一个标准的菱形继承结构就形成了Dragon-FlyingCharacter-Character 同时Dragon-FightingCharacter-Character。Character基类在Dragon对象中出现了两次。2.2 问题一数据冗余与内存浪费这是最直接的问题。由于Character在继承路径上出现了两次Dragon对象中将包含两份Character子对象的副本也就是两份name_成员。这不仅浪费内存更重要的是逻辑错误一条龙不应该有两个名字。我们可以用sizeof运算符和内存地址来验证Dragon dragon(Smaug, 1000, 999); std::cout Size of Dragon: sizeof(dragon) std::endl; // 输出会比预期大 // 尝试打印两个基类子对象的地址 FlyingCharacter* flyPtr dragon; FightingCharacter* fightPtr dragon; std::cout FlyingCharacter subobject address: static_castvoid*(flyPtr) std::endl; std::cout FightingCharacter subobject address: static_castvoid*(fightPtr) std::endl; // 你会发现这两个地址不同它们各自指向自己继承路径上的那个Character副本。注意在涉及多重继承时将派生类指针转换为不同基类指针编译器可能会进行地址偏移调整this指针。上面代码中flyPtr和fightPtr的值不同正是因为它们指向的是Dragon对象中不同的子对象起始位置。2.3 问题二访问的二义性当你想通过Dragon对象访问Character的成员时编译器会“懵掉”因为它不知道你指的是从FlyingCharacter这条线下来的name_还是从FightingCharacter那条线下来的name_。Dragon dragon(Smaug, 1000, 999); // dragon.name_ “NewName”; // 编译错误对成员‘name_’的请求不明确 dragon.printInfo(); // 编译错误对成员‘printInfo’的请求不明确编译器报错信息类似于“request for member ‘name_’ is ambiguous”。为了解决这个编译错误你必须显式地指定访问路径dragon.FlyingCharacter::name_ “NewName_Fly”; dragon.FightingCharacter::name_ “NewName_Fight”; dragon.FlyingCharacter::printInfo(); // 输出 Character: NewName_Fly dragon.FightingCharacter::printInfo(); // 输出 Character: NewName_Fight这显然不是我们想要的。我们只希望Dragon有一个统一的name_。这种二义性在函数调用、赋值等任何涉及基类成员的操作中都会出现使得代码冗长且极易出错。3. 解决方案虚继承与虚基类C为了解决菱形继承带来的数据冗余和二义性问题引入了虚继承的机制。其核心思想是让最终派生类如Dragon只包含一份共享的基类子对象无论这个基类在继承路径上被声明了多少次。3.1 语法与声明虚继承的语法是在继承时使用virtual关键字。我们需要在可能成为“菱形腰部”的中间类即直接继承公共基类的类的继承声明中做出修改。修改我们的例子class Character { /* 保持不变 */ }; // 使用虚继承 class FlyingCharacter : virtual public Character { // 注意 virtual 关键字 public: int flyHeight_; FlyingCharacter(const std::string name, int height) : Character(name), flyHeight_(height) {} }; class FightingCharacter : virtual public Character { // 注意 virtual 关键字 public: int combatPower_; FightingCharacter(const std::string name, int power) : Character(name), combatPower_(power) {} }; class Dragon : public FlyingCharacter, public FightingCharacter { public: Dragon(const std::string name, int height, int power) : Character(name), // 关键点虚基类由最终派生类直接初始化 FlyingCharacter(name, height), FightingCharacter(name, power) {} };这里有几个至关重要的变化FlyingCharacter和FightingCharacter现在是以virtual public的方式继承Character。Character因此被称为虚基类。在最终派生类Dragon的构造函数初始化列表中必须直接调用虚基类Character的构造函数。即使FlyingCharacter和FightingCharacter的构造函数也尝试初始化Character但在虚继承体系下这些对虚基类的初始化调用在最终派生类的构造函数中被忽略。这确保了Character只被构造一次。如果Dragon不显式初始化Character那么Character将使用其默认构造函数。如果Character没有默认构造函数编译器会报错。3.2 虚继承如何解决问题应用虚继承后之前的问题迎刃而解数据冗余消失Dragon对象中现在只包含一份Character子对象。FlyingCharacter和FightingCharacter子对象内部会包含一个指针通常是虚基类表指针vbptr指向共享的Character部分。二义性消失因为name_和printInfo()在内存中只有一份实体通过Dragon对象访问它们不再有任何歧义。Dragon dragon(Smaug, 1000, 999); dragon.name_ “Glaurung”; // 正确无二义性 dragon.printInfo(); // 正确输出 Character: Glaurung std::cout “Size with virtual inheritance: “ sizeof(dragon) std::endl; // 大小会比非虚继承时小省去了一份name_的开销但可能会多出虚基类表指针的开销。3.3 底层原理浅析虚基类表指针理解虚继承有必要稍微深入一下其实现机制这对面试和调试很有帮助。编译器通常会为每个含有虚基类的类生成一个虚基类表。该类的对象中会包含一个额外的指针——虚基类表指针它指向这个表。这个表里存储了什么主要是该对象的各个虚基类子对象相对于该指针所在位置的偏移量。当我们需要访问虚基类的成员如dragon.name_时首先通过对象的虚基类表指针找到虚基类表。然后从表中查到Character子对象相对于当前Dragon对象this指针的偏移量。最后通过this指针加上这个偏移量定位到唯一的Character子对象进而访问其成员。这也是为什么虚继承会带来一定的运行时开销多一次间接寻址和空间开销每个对象多一个指针。在内存布局上共享的虚基类子对象通常被放置在派生类对象的末尾。实操心得不要滥用虚继承。它解决了菱形继承问题但增加了对象模型复杂性和运行时开销。在设计类体系时优先考虑使用组合has-a而非继承is-a来复用代码。如果必须使用继承应仔细审视是否真的需要多重继承以及是否形成了菱形结构。很多情况下通过重新设计类层次例如将公共部分抽离成另一个类然后让需要它的类包含一个实例或指针可以完全避免菱形继承。4. 虚继承的陷阱与实战注意事项虚继承并非银弹它引入了一些新的复杂性和容易踩坑的地方。4.1 构造与析构顺序在含有虚继承的复杂层次中构造和析构顺序有严格规定且与普通多重继承不同虚基类优先所有虚基类按照它们在继承图中出现的深度优先、从左到右的顺序被构造。而且它们只被最终派生类构造一次。非虚基类其次然后非虚基类按照声明的顺序被构造。成员对象接着类自身的成员对象按照声明的顺序被构造。最终派生类自身最后执行最终派生类构造函数的函数体。析构顺序完全相反。在我们的Dragon例子中构造顺序是Character虚基类 -FlyingCharacter非虚基类 -FightingCharacter非虚基类 -Dragon的成员本例无 -Dragon构造函数体。常见坑点如果虚基类的构造函数依赖于某个非虚基类或成员对象的状态那么程序将出现未定义行为因为虚基类构造时那些依赖对象尚未被初始化。这种设计本身就是有问题的需要从架构上避免。4.2 类型转换与指针偏移由于虚继承改变了对象的内存布局指针转换变得更加微妙。使用static_cast或dynamic_cast在菱形继承层次中进行向上、向下或横向转换时编译器可能需要做复杂的指针偏移计算。Dragon dragon; Character* charPtr dragon; // 正确向上转换到虚基类 FlyingCharacter* flyPtr charPtr; // 错误不能直接从虚基类指针向下转换到派生类除非使用dynamic_cast且基类有多态性 FlyingCharacter* flyPtr2 static_castFlyingCharacter*(dragon); // 正确编译器知道如何计算偏移注意dynamic_cast在涉及虚继承的跨继承分支转换即“横向”转换如从FlyingCharacter*转FightingCharacter*时非常有用但它要求基类至少有一个虚函数以拥有RTTI信息。4.3 与虚函数的交互虚继承和虚函数是两个独立的概念但经常一起使用。当一个类同时拥有虚函数和虚基类时其对象可能包含两个指针虚函数表指针和虚基类表指针。这进一步增加了对象的开销。如果虚基类本身含有虚函数那么情况会怎样这通常是安全的并且是常见的设计。最终派生类会覆盖虚基类中的虚函数并且由于虚基类只有一份所以通过任何路径调用该虚函数都会解析到最终派生类的覆盖版本行为是一致的。class Character { public: virtual void attack() { std::cout “Character attacks!” std::endl; } virtual ~Character() {} // 虚析构函数重要 }; class Dragon : public FlyingCharacter, public FightingCharacter { public: void attack() override { std::cout “Dragon breathes fire!” std::endl; } }; Dragon dragon; Character* c dragon; FlyingCharacter* f dragon; c-attack(); // 输出Dragon breathes fire! f-Character::attack(); // 仍然输出Dragon breathes fire! (通过FlyingCharacter路径调用虚函数)重要提示在有多态性的继承体系中即基类有虚函数基类的析构函数必须声明为虚函数。这在菱形虚继承中同样至关重要以确保通过基类指针删除派生类对象时整个对象能被正确且完整地析构。5. 替代方案与设计模式探讨虽然虚继承提供了解决方案但在现代C设计和大型项目中工程师们往往倾向于寻找更清晰、耦合度更低的替代方案以避免菱形继承的复杂性。5.1 组合优于继承这是最根本的准则。重新审视“龙”的例子Flying飞行能力和Fighting战斗能力是否一定要用“是一个is-a”的关系来表达用“有一个has-a”可能更合适。class FlyCapability { /* 封装飞行相关属性和行为 */ }; class FightCapability { /* 封装战斗相关属性和行为 */ }; class Dragon : public Character { private: FlyCapability flyAbility_; FightCapability fightAbility_; public: // ... 通过成员对象调用相关功能 };这种方式完全避免了继承的菱形问题提高了代码的模块化和可测试性。FlyCapability和FightCapability可以独立开发、测试并被其他类复用。5.2 使用接口纯虚类另一种常见做法是使用只包含纯虚函数的抽象类作为接口。C中没有直接的interface关键字但可以通过包含所有纯虚函数和虚析构函数的类来模拟。类可以实现多个接口而接口通常不包含数据成员从而避免了数据冗余和二义性。class IFlyable { // 飞行接口 public: virtual void fly() 0; virtual ~IFlyable() default; }; class IFightable { // 战斗接口 public: virtual void fight() 0; virtual ~IFightable() default; }; class Dragon : public Character, public IFlyable, public IFightable { public: void fly() override { /* 实现飞行 */ } void fight() override { /* 实现战斗 */ } };这种方式下Dragon实现了多个接口但接口本身没有状态数据成员因此即使这些接口都继承自某个共同的基接口形成菱形只要那个基接口也是纯虚的、无状态的问题就不大。但若公共基接口有状态则又可能回到需要虚继承的老路。5.3 重新设计类层次有时菱形继承的出现意味着类层次设计可以优化。例如是否可以将FlyingCharacter和FightingCharacter中的公共部分进一步上移或下移或者将Character中的某些属性拆分到更细粒度的组件中一个思考方向是Flying和Fighting是不是Character的一种属性或技能而非一种“是”的关系如果是那么使用组件化设计类似于游戏开发中的ECS架构思想会更灵活。6. 面试常见问题与排查技巧实录菱形继承是C八股文中的经典题目。下面整理了几个高频问题和实战中排查相关Bug的技巧。6.1 面试高频问题速查什么是菱形继承它会导致什么问题答菱形继承指一个类通过多条路径继承自同一个基类。会导致数据冗余多份基类子对象和访问二义性编译器无法确定使用哪份数据。C如何解决菱形继承问题答使用虚继承。在中间类的继承声明中使用virtual关键字使得最终派生类只包含一份共享的虚基类子对象。虚继承下构造函数的调用顺序有什么特别之处答虚基类的构造函数由最终派生类直接调用且优先于所有非虚基类的构造函数执行。这确保了虚基类只被初始化一次。虚继承有什么缺点答增加了对象模型复杂度引入了额外的空间开销虚基类表指针和时间开销通过指针间接访问虚基类成员。同时构造函数初始化规则变得更复杂。如何避免使用菱形继承答遵循“组合优于继承”的原则使用接口纯虚类来定义行为重新审视类设计看是否能用更扁平化的层次结构或组件化设计来替代。6.2 实战调试与问题排查当项目中疑似出现菱形继承相关Bug时可以按以下步骤排查确认继承结构使用IDE的类图工具或手动画出继承关系图检查是否存在菱形路径。检查编译错误如果遇到“request for member ‘xxx’ is ambiguous”错误这几乎是菱形继承的典型标志。立即检查相关类的继承关系。使用调试器查看内存布局在GDB或LLDB中对于可疑对象使用p /x object以十六进制打印或x /[长度]xb object查看内存命令。观察对象起始部分看是否有重复的相似内存模式可能是重复的基类成员。对于有虚继承的类观察是否存在多个vptr/vbptr。验证对象大小在代码中打印sizeof(MyClass)。如果对象大小远大于你根据成员变量估算的大小很可能包含了多余的基类子对象。审查构造函数初始化列表在虚继承体系中如果最终派生类没有正确初始化虚基类或者非虚基类试图初始化虚基类都可能引发问题。确保只有最终派生类直接初始化虚基类。谨慎使用类型转换在调试与多重继承、虚继承相关的指针问题时记录下不同视图不同基类指针下的地址值。理解它们之间的偏移量。使用dynamic_cast在支持RTTI的情况下进行安全的跨分支转换并检查返回值是否为nullptr。我个人在调试一个大型遗留代码库的崩溃问题时曾遇到一个棘手的案例。崩溃发生在通过一个Character*指针调用虚函数时。最终发现在一个复杂的菱形继承层次中有一个中间类的构造函数没有正确传递参数给虚基类导致共享的虚基类子对象处于部分初始化状态。当另一个完全不相关的派生类通过另一条路径转换到该虚基类并调用虚函数时访问了未初始化的vptr导致段错误。这个问题的教训是在虚继承体系中必须严格保证最终派生类对虚基类的初始化是完备和正确的因为所有中间类对此的初始化尝试都会被忽略。同时对于复杂的对象层次在调试时画出完整的内存布局图和构造函数调用序列图是理清思路的最有效方法。

相关新闻

终极指南:如何用二维码在3分钟内完成跨设备文件传输

终极指南:如何用二维码在3分钟内完成跨设备文件传输

终极指南:如何用二维码在3分钟内完成跨设备文件传输 【免费下载链接】qr-filetransfer Transfer files over WiFi between your computer and your smartphone from the terminal 项目地址: https://gitcode.com/gh_mirrors/qr/qr-filetransfer 你是否曾因网…

2026/8/4 15:52:01 阅读更多 →
终极指南:如何彻底解决魔兽争霸III的现代化兼容问题

终极指南:如何彻底解决魔兽争霸III的现代化兼容问题

终极指南:如何彻底解决魔兽争霸III的现代化兼容问题 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为《魔兽争霸III》在现代系统上的…

2026/8/4 15:52:01 阅读更多 →
稀土元素在陶瓷工业中的关键应用与改性机制

稀土元素在陶瓷工业中的关键应用与改性机制

1. 稀土元素在陶瓷工业中的核心作用 稀土元素被称为现代工业的"维生素",在陶瓷制造领域扮演着关键角色。这17种特殊的金属元素(包括15种镧系元素加上钪和钇)具有独特的电子层结构,使其在陶瓷材料改性中展现出惊人的能力…

2026/8/4 15:52:01 阅读更多 →

最新新闻

不装SSH客户端也能连服务器?极空间部署WebSSH实战

不装SSH客户端也能连服务器?极空间部署WebSSH实战

前言 远程维护Linux服务器时,通常需要在电脑中安装SSH客户端,并提前准备主机地址、端口和登录凭据。临时更换设备,或者只能使用平板和公共电脑时,重新配置客户端并不方便。 WebSSH是一套将SSH连接界面放进浏览器中的开源工具。部…

2026/8/4 16:38:33 阅读更多 →
Spring Boot自习室预约系统设计与实现

Spring Boot自习室预约系统设计与实现

1. 项目概述自习室预约系统是高校和公共图书馆场景下的刚需应用。这个基于Spring Boot实现的系统,核心解决了传统纸质登记方式存在的三大痛点:座位资源利用率低、预约过程不透明、管理统计困难。我在实际开发中发现,一个合格的预约系统需要同…

2026/8/4 16:38:33 阅读更多 →
5分钟掌握AI音频修复:让模糊语音重获新生

5分钟掌握AI音频修复:让模糊语音重获新生

5分钟掌握AI音频修复:让模糊语音重获新生 【免费下载链接】voicefixer General Speech Restoration 项目地址: https://gitcode.com/gh_mirrors/vo/voicefixer 你是否曾为嘈杂的会议录音而烦恼?是否曾为模糊不清的采访音频而遗憾?在数…

2026/8/4 16:38:33 阅读更多 →
2026年西藏智慧燃气安全监测管理系统的建设与服务商观察

2026年西藏智慧燃气安全监测管理系统的建设与服务商观察

高海拔地区的燃气安全监测标准,无法照搬平原方案。拉萨、日喀则、山南等城镇燃气管网建设起步较晚,管线里程持续扩展,高原施工条件艰苦,管道焊接和防腐质量比低海拔地区更难控制。昼夜近三十度温差、冬季零下二三十度持续低温&…

2026/8/4 16:38:33 阅读更多 →
ADP7118ARDZ-3.3-R7,带软启动的高精度线性稳压器

ADP7118ARDZ-3.3-R7,带软启动的高精度线性稳压器

简介ADP7118ARDZ-3.3-R7 是 ADI 推出 8 引脚带散热焊盘 SOIC 封装、固定 3.3V 输出的 CMOS 低压差线性稳压器,专为高精度低噪声模拟电路设计,输入兼容 2.7V 至 20V 宽电压,最大可输出 200mA 电流,适配工业、医疗、通信等多类设备的…

2026/8/4 16:38:33 阅读更多 →
Dart函数调用:fun与fun()的本质区别与应用场景

Dart函数调用:fun与fun()的本质区别与应用场景

1. Dart函数调用基础概念在Dart语言中,函数作为一等公民,其调用方式直接影响程序执行流程。初学者最容易混淆的就是fun和fun()这两种写法,它们看似相似却有着本质区别。1.1 函数标识符与函数调用fun代表的是函数对象本身,相当于获…

2026/8/4 16:37:33 阅读更多 →

日新闻

AI Agent白手起家26: 使用标准事件驱动大模型实践

AI Agent白手起家26: 使用标准事件驱动大模型实践

纲要 练习目标:掌握大模型标准事件的调用回顾 LangChain 中的核心标准事件 invokestreambatchastream_eventswith_structured_output 环境准备实战代码:多种事件调用对比 同步调用与流式输出批量处理异步事件流监听结构化输出 运行说明与预期结果总结与扩…

2026/8/4 0:00:40 阅读更多 →
dealsea是什么?跨境卖家必知的美国deal站入门指南

dealsea是什么?跨境卖家必知的美国deal站入门指南

说实话,第一次听说美国这个老牌折扣网站的跨境卖家,十个有八个会问同一个问题:这个平台到底是干嘛的?我见过一个做家居出口的朋友,他在亚马逊上月销二十万美金,却从来没用过它。我给他看了首页——一屏一屏…

2026/8/4 0:01:40 阅读更多 →
清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

清华大学重磅EST:植物自导电闪蒸焦耳热600°C/2600°C两步法!稀土超积累植物秒级转化为CeO₂-石墨烯电催化剂!

通讯作者:邓兵、刘建国通讯单位:清华大学DOI:https://doi.org/10.1021/acs.est.6c00603研究背景稀土元素(REEs)是清洁能源技术与电子器件不可或缺的核心原料,然而传统提取方式依赖能耗高、排放大的采矿与强…

2026/8/4 0:01:40 阅读更多 →

周新闻

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

最大流算法详解:从水管网络到Ford-Fulkerson与Dinic实战

1. 从水管网络到最大流:一个核心问题的诞生想象一下,你是一个城市供水系统的总工程师。你的城市有多个水源(水库),需要通过一个复杂的地下管道网络,将水输送到各个居民区。每条管道都有其最大通水能力&…

2026/8/4 13:24:41 阅读更多 →
基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

基于Springboot的企业门户网站(源码+LW+调试文档+讲解)

温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:本人主页置顶文章(点我)开头有 CSDN 平台…

2026/8/4 11:41:39 阅读更多 →
MATLAB xcorr函数详解:从互相关原理到四大实战应用

MATLAB xcorr函数详解:从互相关原理到四大实战应用

1. 从一次信号“找茬”说起:为什么我们需要互相关几年前,我在处理一组声学传感器数据时遇到了一个棘手的问题。我有两个麦克风记录了一段相同的音频信号,理论上它们接收到的声音波形应该非常相似,只是由于麦克风位置不同&#xff…

2026/8/4 5:26:40 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/4 13:38:24 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/4 11:09:16 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/4 13:38:40 阅读更多 →