C++可变参数模板:从基础语法到实战应用全解析
1. 从“硬编码”到“无限可能”为什么我们需要可变参数模板在C的日常开发里我们经常会遇到一个头疼的问题如何优雅地处理参数数量不确定的函数或类比如你想写一个日志打印函数它最好能像printf那样既能打印单个字符串也能格式化输出多个变量。在C11之前我们有什么选择呢无非是几种“笨办法”要么写一堆重载函数为1个、2个、3个参数各写一个版本代码冗余且维护困难要么使用C风格的可变参数函数va_list但这玩意儿类型不安全编译器没法帮你检查类型运行时一不留神就core dump了调试起来简直是噩梦。直到C11引入了可变参数模板这个问题才迎来了一个既安全又优雅的解决方案。简单来说它允许你定义一个可以接受任意数量、任意类型参数的模板。这不仅仅是语法糖它彻底改变了我们编写泛型代码的方式是构建现代C库如标准库中的tuple、make_shared、emplace系列函数的基石。今天我们就来彻底拆解这个强大的特性看看它如何工作以及如何在实际项目中安全、高效地使用它。2. 可变参数模板的核心语法包展开与递归推导理解可变参数模板关键在于掌握两个概念模板参数包和函数参数包以及如何对它们进行展开。2.1 基本定义参数包是什么一个可变参数模板在声明时使用省略号...来表示一个“包”。这个包可以出现在两个地方模板参数包在模板参数列表中。例如templatetypename... Args中的Args就是一个模板参数包它代表零个或多个类型参数。函数参数包在函数参数列表中。例如void foo(Args... args)中的args就是一个函数参数包它代表零个或多个函数参数。这里的Args和args是程序员自己起的名字你可以用Ts、params等任何合法的标识符。一个最简单的可变参数函数模板看起来是这样的templatetypename... Args void print(Args... args) { // 这里需要对args...进行处理 }这个print函数现在可以接受任意数量和类型的参数了print(1, “hello”, 3.14);。但光有声明没用我们得在函数体里能访问到这些参数。这就是“包展开”要解决的问题。2.2 包展开的四种模式让参数包“现形”包展开是可变参数模板的灵魂操作。它的核心语法是在参数包名字后面加上省略号...但展开必须发生在特定的“上下文”中。主要有四种模式模式一表达式展开这是最常用的一种。你可以对一个包含参数包的表达式进行展开编译器会生成一个由逗号分隔的表达式列表。templatetypename... Args void print(Args... args) { (std::cout ... args) std::endl; // C17折叠表达式后面会讲 // 在C17之前通常需要借助递归或初始化列表技巧 }更经典的例子是调用一个函数并将参数包中的每个元素作为该函数的参数templatetypename... Args void forward_to_another(Args... args) { another_function(args...); // 展开为 another_function(arg1, arg2, arg3, ...) }模式二类型展开用于生成一个类型列表常见于声明中使用。templatetypename... Args class MyTuple { std::tupleArgs... data; // 展开为 std::tupleint, double, char }; templatetypename... Args using MyPointerTuple std::tupleArgs*...; // 展开为 std::tupleint*, double*, char*模式三初始化列表展开利用花括号初始化列表的上下文进行展开常用于执行一系列有副作用的操作。这是一个非常实用的技巧可以避免单独写递归终止函数。templatetypename... Args void print_all(Args... args) { // 利用初始化列表和逗号运算符依次展开对每个参数调用print_single // 逗号运算符会依次执行其两边的表达式并返回右边表达式的结果 // 整个初始化列表最终的值是最后一个0int类型 int dummy[] { (print_single(args), 0)... }; // 展开过程假设args为1, “a”, 3.14 // 等价于int dummy[] { (print_single(1), 0), (print_single(“a”), 0), (print_single(3.14), 0) }; } void print_single(const auto x) { std::cout x ” “; }这个技巧的精妙之处在于它利用了一个匿名数组的初始化来保证print_single会按照参数包的顺序被依次调用。(print_single(args), 0)是一个逗号表达式它先执行print_single(args)然后返回0。整个初始化列表最终是一组0用于初始化dummy数组。我们根本不关心这个数组本身只关心其初始化过程中产生的副作用即函数调用。模式四折叠表达式C17这是C17引入的语法糖专门用于简化对参数包进行二元运算的代码让代码变得异常简洁。折叠表达式有四种形式(pack op ...)一元右折叠。(args ...)等价于arg1 (arg2 (arg3 ...))。(... op pack)一元左折叠。(... args)等价于((arg1 arg2) arg3) ...。(pack op ... op init)二元右折叠。(init op ... op pack)二元左折叠。用折叠表达式重写上面的print函数会变得非常直观templatetypename... Args void print(Args... args) { // 左折叠将输出运算符在参数包上从左到右依次应用 (std::cout ... args) std::endl; }如果参数包args是(1, “hello”, 3.14)那么展开后就是std::cout 1 “hello” 3.14 std::endl;。折叠表达式极大地提升了代码的可读性和编写效率。注意包展开的上下文是严格定义的。你不能在任意地方使用args...。合法的上下文包括函数调用实参列表、初始化列表、基类列表、类成员初始化列表、模板实参列表等。如果你在错误的地方展开编译器会报错。3. 实战递归展开与完美转发虽然折叠表达式很强大但在C17之前以及处理一些更复杂的、非二元运算的逻辑时递归展开是最经典、最根本的方法。同时为了保持参数的原始值类别左值/右值和常量性我们必须结合完美转发。3.1 递归展开模式处理参数包的通用范式递归展开的核心思想是每次从参数包中“剥”出一个参数进行处理然后将剩下的参数包递归地传递给自身直到参数包为空。这需要两个函数模板递归函数模板处理一个参数和剩余的参数包。递归终止函数或特化处理参数包为空的情况。让我们实现一个sum函数它可以将所有参数相加假设参数类型都支持运算符// 1. 递归终止函数当参数包为空时返回0 int sum() { return 0; } // 2. 递归函数模板处理第一个参数Head和剩余的参数包Tail templatetypename T, typename... Args auto sum(T head, Args... tail) - decltype(head sum(tail...)) { return head sum(tail...); // 将head与剩余包的和相加 }调用sum(1, 2, 3, 4)时展开过程如下sum(1, 2, 3, 4) 1 sum(2, 3, 4) 1 (2 sum(3, 4)) 1 (2 (3 sum(4))) 1 (2 (3 (4 sum()))) 1 (2 (3 (4 0))) 10这个模式非常通用你可以把换成任何操作比如打印、构造对象、插入容器等。3.2 结合完美转发保持参数的值类别上面的sum函数有一个问题它按值传递参数。如果参数是移动语义的比如一个std::unique_ptr或者我们想避免不必要的拷贝按值传递就不合适了。这时我们需要使用万能引用和std::forward来实现完美转发。假设我们要写一个emplace_back_all函数将任意数量的参数依次emplace_back到一个容器中templatetypename Container, typename... Args void emplace_back_all(Container c, Args... args) { // 错误做法直接循环或递归调用c.emplace_back(args)... // 这会导致所有参数都以左值形式传递丢失了可能的右值引用优化机会。 // 正确做法使用初始化列表展开 完美转发 // 这里利用了逗号运算符和初始化列表确保按顺序调用 (c.emplace_back(std::forwardArgs(args)), ...); // C17 折叠表达式写法 }如果使用C17之前的递归模式写法会复杂一些但原理相同// 递归终止 templatetypename Container void emplace_back_all(Container) {} // 递归展开 templatetypename Container, typename T, typename... Args void emplace_back_all(Container c, T first, Args... rest) { c.emplace_back(std::forwardT(first)); // 完美转发第一个参数 emplace_back_all(c, std::forwardArgs(rest)...); // 完美转发剩余参数包 }关键点Args... args中的是万能引用它既能绑定左值也能绑定右值。std::forwardArgs(args)...在展开参数包的同时对每个参数进行完美转发。如果原始实参是右值转发后仍是右值引用可以触发移动构造如果是左值则转发后仍是左值引用。这是实现诸如std::make_shared,std::make_unique,std::vector::emplace_back等函数的关键技术。实操心得在编写可变参数模板时除非确定参数类型简单如内置类型否则应习惯性地使用Args...和std::forward。这能最大程度地提升代码的效率避免不必要的拷贝尤其是在模板库的开发中你无法预知用户会传入什么类型的参数。4. 进阶应用实现自己的tuple与printf理解了递归和完美转发我们就可以挑战一些更高级的应用这能帮你深刻理解标准库中类似组件的实现原理。4.1 实现一个简易的MyTuplestd::tuple是可变参数模板的经典应用。一个极简的、不可变的tuple实现思路如下// 前向声明 templatetypename... Types class MyTuple; // 递归终止特化空元组 template class MyTuple {}; // 递归定义一个元素 剩余元素组成的子元组 templatetypename Head, typename... Tail class MyTupleHead, Tail... : private MyTupleTail... { // 私有继承实现“包含” private: Head value; // 存储当前元素 public: MyTuple() default; // 完美转发构造函数 MyTuple(Head h, Tail... t) : MyTupleTail...(std::forwardTail(t)...) // 初始化基类子元组 , value(std::forwardHead(h)) { // 初始化当前成员 } // 获取第N个元素简化版需要借助另一个模板类这里省略索引逻辑 // ... };这个实现利用了“递归继承”MyTupleint, double, std::string继承自MyTupledouble, std::stringMyTupledouble, std::string继承自MyTuplestd::stringMyTuplestd::string继承自MyTuple每个层级都保存着自己对应类型的那个值value。 这样一个三元组在内存中大致是[int value][double value][string data]的布局。获取第N个元素std::getN则需要通过复杂的模板元编程在编译期计算偏移这涉及到另一个强大的工具可变参数模板与std::index_sequence的结合。4.2 实现一个类型安全的printfformat函数雏形C语言的printf不安全因为它依赖运行时解析格式字符串和可变参数列表。我们可以用可变参数模板在编译期进行类型检查。// 基础case当参数包为空时直接输出格式字符串剩余部分 void safe_printf(const char* s) { while (*s) { if (*s ‘%’ *(s) ! ‘%’) { throw std::runtime_error(“格式字符串参数不足”); } std::cout *s; } } // 递归case处理格式字符串和第一个参数 templatetypename T, typename... Args void safe_printf(const char* s, T value, Args... args) { while (*s) { if (*s ‘%’ *(s) ! ‘%’) { // 遇到一个格式说明符消耗一个参数 std::cout std::forwardT(value); // 递归处理剩余格式字符串和参数 return safe_printf(s, std::forwardArgs(args)...); } std::cout *s; } throw std::runtime_error(“参数过多”); }这个safe_printf虽然简单但实现了核心的安全特性参数数量在编译期检查。如果你调用safe_printf(“%d %s”, 42);编译器会报错因为格式字符串需要两个参数而你只提供了一个。这比C的printf在运行时出现未定义行为要安全得多。C20的std::format库正是基于类似的理念但功能要强大和复杂得多。5. 避坑指南与性能考量可变参数模板功能强大但使用不当也会带来问题。下面是一些常见的“坑”和注意事项。5.1 递归深度与编译性能递归展开是编译期行为。如果一个参数包有1000个参数就会实例化1001个函数模板包括终止函数。这可能导致编译时间显著增加每个模板实例化都需要编译器进行工作。可能的递归深度限制某些编译器对模板实例化深度有限制如MSVC默认500GCC/Clang默认900。虽然1000个参数在实际中很少见但在元编程中可能触及。优化建议对于特别长的参数列表考虑使用折叠表达式C17它通常能生成更高效的代码且不涉及递归实例化。如果必须用递归并且参数可能很多可以尝试“二分递归”模式每次处理一半参数减少递归深度。在项目编译设置中如果确实需要可以调整编译器的模板实例化深度限制如GCC的-ftemplate-depth。5.2 空参数包的处理空参数包是一个有效的边界情况。你必须确保你的可变参数模板能正确处理它。在递归模式下你需要一个单独的、无参数的终止函数或特化。在折叠表达式中空包的行为是定义良好的但需要注意运算符的“单位元”。例如对于二元左折叠(... args)如果args为空包通常是非法的除非在运算符后提供一个初始值如(0 ... args)。在初始化列表展开技巧中int dummy[] { (print_single(args), 0)... };当args为空时会展开为int dummy[] {};这是合法的一个空数组但某些编译器可能发出警告。可以改为int dummy[] { 0, (print_single(args), 0)... };来避免空数组。5.3 完美转发的陷阱std::forward的误用记住std::forward必须用在推导出的模板参数类型上。templatetypename... Args void bad_forward(Args... args) { some_function(std::forwardArgs(args)...); // 正确 // some_function(std::forwardArgs...(args...)); // 错误语法错误 // some_function(std::forward(args)...); // 错误无法推导 }std::forward是一个有条件转换的函数模板它依赖于其模板参数来判定原始类型。Args是从函数实参推导出来的参数包std::forwardArgs(args)...会将每个arg与其对应的推导类型Arg配对进行转发。5.4 调试困难可变参数模板相关的错误信息往往又长又晦涩。例如一个类型不匹配的错误编译器可能会打印出涉及几十层模板实例化的信息。这给调试带来了挑战。调试技巧从简到繁先用少量、简单类型的参数测试你的模板。使用static_assert在模板内部使用static_assert进行编译期检查可以提前给出清晰的错误信息。例如可以用static_assert检查参数包中是否都是某种类型。分而治之将复杂的可变参数模板逻辑拆分成多个小函数或小模板分别测试。借助IDE和工具现代IDE如CLion Visual Studio的模板错误高亮和简化功能越来越强大。6. 现代C中的增强折叠表达式与if constexprC17引入的两个特性让可变参数模板的编写变得更加简洁和安全。6.1 折叠表达式的威力前面我们已经看到了折叠表达式在打印和调用函数时的简洁性。它几乎可以用于任何二元运算符。// 判断所有参数是否都为true templatetypename... Args bool all_true(Args... args) { return (... args); // 二元左折叠等价于 ((arg1 arg2) arg3) ... } // 连接字符串要求参数支持运算符 templatetypename... Args auto concatenate(Args... args) { return (std::string{} ... args); // 二元左折叠需要一个初始空字符串 }折叠表达式极大地减少了模板实例化的数量提升了编译效率也让代码意图一目了然。6.2 使用if constexpr简化递归终止在C17之前我们需要写一个单独的递归终止函数。现在我们可以用if constexpr在同一个函数模板内处理终止条件。templatetypename T, typename... Args void print_with_space(const T first, const Args... rest) { std::cout first; if constexpr (sizeof...(rest) 0) { // 编译期if判断 std::cout ” “; print_with_space(rest...); // 递归调用参数包数量减少 } else { std::cout std::endl; } }if constexpr在编译期判断条件如果条件为false那么它对应的分支包括其中的递归调用根本不会被实例化。这就避免了我们需要为sizeof...(rest) 0的情况单独写一个函数重载。这让递归逻辑全部集中在一个函数里代码结构更清晰。7. 总结与最佳实践可变参数模板是C迈向现代泛型编程的关键一步。它从语言层面提供了类型安全的可变参数处理能力。回顾一下核心要点和最佳实践理解核心机制掌握模板参数包、函数参数包以及包展开的几种模式表达式、类型、初始化列表、折叠表达式是基础。优先使用折叠表达式如果你的编译器支持C17对于二元操作优先考虑使用折叠表达式。它代码简洁编译高效。递归是通用范式对于复杂的、非线性的操作递归展开仍然是标准解法。结合if constexpr可以让代码更整洁。始终考虑完美转发在编写接受参数的函数模板时养成使用Args...和std::forwardArgs(args)...的习惯以保持值类别实现最优效率。小心边界情况务必处理好空参数包的情况并注意递归深度可能带来的编译期开销。从标准库汲取灵感std::tuple,std::make_shared,std::index_sequence,std::apply等都是可变参数模板的绝佳范例。阅读它们的实现或类似实现是深入学习的最佳途径。我个人在大型项目中使用可变参数模板最多的场景是编写工厂函数、日志封装、以及自定义的容器emplace操作。它让接口变得极其灵活同时借助编译期的类型检查将很多潜在的错误扼杀在摇篮里。刚开始接触时那些...符号和复杂的展开规则确实让人望而生畏但一旦理解了其模式你就会发现它是一把打开泛型编程新世界的钥匙。下次当你需要处理不确定数量的参数时别再想着用va_list或者写一堆重载了试试可变参数模板你会爱上这种编译期带来的安全感与优雅。

相关新闻

GRAIL框架:基于深度粒度与混合共振的实时智能体精准发现

GRAIL框架:基于深度粒度与混合共振的实时智能体精准发现

1. 从“大海捞针”到“精准定位”:实时智能体发现为何如此之难在当今这个由海量智能体(Agent)构成的复杂数字生态中,找到一个能精准解决你当前特定问题的智能体,其难度不亚于在信息爆炸的汪洋中捞起一根特定的针。无论…

2026/8/22 3:17:27 阅读更多 →
2026 年编码遇瓶颈?Huzzah 实验性编辑器带来全新 AI 编程范式!

2026 年编码遇瓶颈?Huzzah 实验性编辑器带来全新 AI 编程范式!

2026 年编码瓶颈咋破?Huzzah 实验性编辑器带来全新 AI 编程范式! 2026 年 8 月 在软件工程师的日常工作中,2026 年的头几个月堪称不可思议。编码代理工具突然强大到让我们无需手动编写代码,但一段时间后,大家可能会遇…

2026/8/22 3:17:27 阅读更多 →
Windows 10服务管理终极指南:从原理到实战精准优化

Windows 10服务管理终极指南:从原理到实战精准优化

1. 从“服务”说起:为什么Win10后台总有东西在跑?每次打开任务管理器,看着那一长串的“服务”列表,是不是总有种莫名的烦躁感?尤其是当电脑风扇突然狂转,或者开机后要等上好几分钟才能流畅操作时&#xff0…

2026/8/22 3:17:27 阅读更多 →

最新新闻

PCSD:让AI智能体通过自我对话与持续反思实现稳定强化学习

PCSD:让AI智能体通过自我对话与持续反思实现稳定强化学习

1. 项目概述:当智能体学会“自我对话”与“持续反思”在强化学习的世界里,我们一直在教智能体如何通过试错变得更好。传统的路径是让智能体与环境互动,收集奖励信号,然后更新策略。但这个过程往往效率低下,尤其是在复杂…

2026/8/22 4:00:40 阅读更多 →
简历机筛系统(ATS)解析与赛力斯技术岗突破策略

简历机筛系统(ATS)解析与赛力斯技术岗突破策略

1. 简历机筛的核心逻辑与行业现状简历机筛系统(ATS)已经成为企业招聘的第一道门槛,根据行业调研数据显示,超过75%的500强企业和90%的互联网公司都在使用各类机筛系统。以赛力斯这类智能驾驶领域头部企业为例,每天收到的…

2026/8/22 4:00:40 阅读更多 →
AI Agent可观测性实践:构建思维链可视化调试工具

AI Agent可观测性实践:构建思维链可视化调试工具

1. 项目缘起:从“黑盒”对话到“解剖”Agent最近在折腾各种AI Agent和Skill开发,不知道你有没有这种感觉:很多时候,Agent就像一个“黑盒”。你给它一个指令,它返回一段结果,中间到底发生了什么?…

2026/8/22 4:00:40 阅读更多 →
快速搭一个属于自己的 WebDAV 文件服务器:完整实战指南

快速搭一个属于自己的 WebDAV 文件服务器:完整实战指南

快速搭一个属于自己的 WebDAV 文件服务器:完整实战指南 【免费下载链接】webdav A simple and standalone WebDAV server. 项目地址: https://gitcode.com/gh_mirrors/we/webdav 手机拍的照片想存到电脑,电脑里的资料又想在平板上读,来…

2026/8/22 4:00:40 阅读更多 →
phone2qq 教程:3 分钟用手机号查回绑定的 QQ 号,一次讲透

phone2qq 教程:3 分钟用手机号查回绑定的 QQ 号,一次讲透

phone2qq 教程:3 分钟用手机号查回绑定的 QQ 号,一次讲透 【免费下载链接】phone2qq 项目地址: https://gitcode.com/gh_mirrors/ph/phone2qq 换了手机、换了电脑,那个用了十几年的 QQ 号一下子想不起来——密码重置了、邮箱也丢了&a…

2026/8/22 4:00:40 阅读更多 →
CentOS 7最小化安装:打造高效安全服务器环境的完整指南

CentOS 7最小化安装:打造高效安全服务器环境的完整指南

1. 为什么选择最小化安装CentOS 7?如果你正在搭建一个服务器,无论是用于学习、开发测试,还是作为生产环境的基础,一个纯净、高效、安全的起点至关重要。很多朋友在初次接触Linux服务器时,可能会直接选择带有图形界面的…

2026/8/22 3:59:40 阅读更多 →

日新闻

沉金PCB工艺实战指南:从设计到SMT焊接的可靠性保障

沉金PCB工艺实战指南:从设计到SMT焊接的可靠性保障

在电子硬件开发领域,PCB(印制电路板)的沉金工艺是提升产品可靠性和焊接质量的关键环节。对于需要高密度互连、长期稳定运行或高频信号传输的板卡,如“黍姐仿通行证”这类可能涉及身份识别、数据交互的硬件项目,选择正确…

2026/8/22 0:00:11 阅读更多 →
电气考研电路八月强化四步法:从知识体系到真题实战的闭环攻略

电气考研电路八月强化四步法:从知识体系到真题实战的闭环攻略

这次我们来看一个针对电气考研电路科目的学习规划项目。它不是软件工具,而是一套聚焦于8月份关键节点的备考策略。对于电气工程考研的同学来说,电路分析是专业课的重中之重,也是拉开分差的关键。进入8月,复习进入强化阶段&#xf…

2026/8/22 0:00:11 阅读更多 →
消除AI代码的“AI味”:Claude Code设计优化技能配置与实战指南

消除AI代码的“AI味”:Claude Code设计优化技能配置与实战指南

大家好,我是专注于前端开发与AI工具实践的技术博主。在日常使用 Claude Code 等AI编程助手时,你是否也遇到过这样的困扰:生成的代码功能上没问题,但代码风格、组件设计、交互逻辑总透着一股“AI味”——布局单调、样式简陋、交互生…

2026/8/22 0:00:11 阅读更多 →

周新闻

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

基于阿里云与通义千问(Qwen)构建AI应用:从模型调用到生产部署的完整实践指南

如果你是一名开发者,最近可能已经感受到了AI大模型正在从“玩具”变成“生产力工具”的强烈信号。从代码补全到智能Agent,从本地部署到云端API,我们正处在一个技术栈快速重构的节点。然而,面对层出不穷的模型、框架和工具&#xf…

2026/8/21 3:21:33 阅读更多 →
工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

工业通信系统底层逻辑:04 反射——高频能量撞墙之后会发生什么?

第四篇:反射——高频能量撞墙之后会发生什么? —— 你以为信号已经过去了,其实它正在回来打你 老Q的现场笔记 第五季,我们正式进入工业神经系统层。这里不再是单个设备的战斗,而是整个工厂“经脉”层面的秩序之战。从这一篇开始,你将第一次看清:看似简单的信号传播,背…

2026/8/21 0:02:09 阅读更多 →
【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

【文章复现】非线性值迭代自适应动态规划(ADP):离散时间非线性系统的策略迭代自适应动态规划算法研究附Matlab代码

✅作者简介:热爱科研的Matlab仿真开发者,擅长毕业设计辅导、数学建模、数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。🍎 往期回顾关注个人主页:Matlab科研工作室👇 关注我领取海量matlab电子书和…

2026/8/21 6:07:56 阅读更多 →

月新闻

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

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

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

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

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

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

2026/8/20 21:46:49 阅读更多 →
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/22 3:22:48 阅读更多 →