一团乱麻?带你厘清 Rust 中的函数指针、函数项、fn 类型、Fn Trait
文章目录1. 知识图谱2. 什么是函数“类型”和“值”的分离3. 函数指针与 fn 类型4. 函数项及其类型5. 闭包及其类型6. 函数类型大一统Fn / FnMut / FnOnce7. 重新思考Rust 如何实现了函数“类型”和“值”的分离对于 Rust 新手来说一次性搞清楚函数指针、函数项、Fn trait、fn type 这些概念并不是一件容易的事情。很多人仔细研究了每一种机制却依旧梳理不出“头绪”对它们之间错综复杂的关系感到困惑和迷茫。本文我们将带领大家沿着一条清晰的逻辑主线逐一梳理这些概念相信一定能带给你“豁然开朗”的感觉。1. 知识图谱函数指针、函数项、fn 类型、Fn Trait 这四种机制放之间的关系非常紧密通常会在代码中“结伴”出现它们往往出现在回调、动态切换函数策略模式、高阶函数等一些典型场景中在这些场景里对函数的使用形式是一致的即需要将一个函数 A 以参数形式传给另一个函数 B这时我们会遇到一个现实问题函数 A 的“类型”应该怎样描述因为在定义函数 B 时需要指明它需要的是一个什么样的函数参数以及函数 A 的“值”或者“引用”、“指针”又该如何描述因为在调用函数 B 时需要传入函数 A 的“值”或者“引用”、“指针”我们今天要讨论的函数指针、函数项、fn 类型、Fn Trait 这四种机制可以说就是为了解决这个现实问题而引入的。为了让大家能清楚引入这些机制的“前因后果”我们先简短地回顾一下函数的“进化史”最初以 C/C 为代表的编程语言率先引入了函数指针实现了回调、运行时的函数切换策略模式等重要功能后来进入到以 Java/C# 为代表的基于虚拟机的纯面向对象编程语言时代函数指针被“抛弃”了通过接口、设计模式、匿名内部类等机制这些纯面向对象语言实现了与函数指针类似的功能在这一时期由于函数是“委身”在类下面的“成员函数”所以函数其实是被“弱化”了再后来随着函数式编程的兴起函数在很多语言中的地位被抬升到了前所未有的高度成为了语言中的“一等公民”这一时期函数已经实现了类型和值的分离定义和使用方式发生了质的变化。作为一门后发的编程语言Rust 可以说是“博采众长”在对“函数”的设计上体现得淋漓尽致。一方面作为一门系统系编程语言Rust 沿用了 C/C 中的一些经典机制“函数指针”就是其中之一另一方面Rust 大量引入了函数式编程的理念和机制将函数“类型化”使用特定的 trait 进行描述再加上闭包的参与形成了一套 FP 风格的函数机制最后为使传统的“函数指针”和“FP 风格的函数闭包”两种机制能有机地融合在一起不至于形成两种割裂的风格Rust 又做了一些“打通”工作。以上三方面基本概括了 Rust 语言在函数及其关联机制上的“布局”这个布局可以用下面这张图清晰地表述出来这张图同时也是函数指针、函数项、Fn Trait、fn Type 这些概念和机制的“知识图谱”它揭示了它们之间真正的内在联系帮助大家从全局视角观察和理解这些概念和机制所处的位置以及相互之间的关系。本文我们也是围绕这张图进行介绍的其中主要涉及这样几个关键话题什么是函数“类型”和“值”的分离函数指针与 fn 类型函数项及其类型闭包及其类型函数类型大一统Fn / FnMut / FnOnce重新思考Rust 如何实现了函数“类型”和“值”的分离特别声明本博客长期遭受 51CTO 无休止的盗文、剽窃再次声讨其可耻行径文章多处已嵌入此声明盗用本文即传播了这份声明2. 什么是函数“类型”和“值”的分离在写作本文时我们始终用“函数式编程”的视角来观察和审视 Rust 对函数的处理机制因为函数式编程是函数进化到现在的最高形态以这种“最高”视角观察可以让我们看清 Rust 函数及其关联机制的“全貌”也更能理解引入这些机制的“动机”。这个观察视角的“出发点”其实是在观察什么是函数的“类型”什么是函数的“值”它们有没有被语言明确地“分离”开让我们看一个标准的“类型”和“值”分离后的函数定义你就能深刻地体会到函数式编程所处的“境界”这是一个用 Scala 实现的示例不用担心看不懂 Scala 代码示例其实非常简单。为什么不用 Rust 来写呢因为 Rust 做不到这里没有“贬低” Rust 的意思实际上在本文的最后我们会看到 Rust 是如何用自己的方式实现了函数“类型”和“值”的分离但在这里我觉得有必要让大家知道到底什么才是真正意义上的函数“类型”和“值”的分离valplusOne:IntInt{(num)num1}在这个极其简单的示例中我们定义一个变量plusOne但同时它又是一个函数val是用于定义变量的关键字类似于 Rust 中的letplusOne是变量名当然也是函数名IntInt是变量的类型当然也是函数的类型{ (num)num1 }则是函数的值也就是函数的实现在这个示例中我们实际上演示了完全不同于fn func_name()这种传统形式的函数定义方式这种像定义普通变量一样定义函数的形式可以归纳为函数名: 函数类型 函数值函数的实现部分这种形式就是函数“类型”和“值”完全分离的标志因为如此一来在传递函数参数时我们就可以在形参上书写出所需函数的“类型”在调用时将函数的“值”作为实参传给函数了。关于函数“类型”和“值”分离的深层意义可以参考《从函数字面量发现函数式编程》。不过如前文所述在 Rust 中我们写不出这种形式的函数定义1这里我们是想让大家明白函数“类型”和“值”的分离应该是什么样的这是我们的“愿景”当一一介绍完全部机制重新回顾 Rust 是如何实现函数参数的类型定义和传递时我们会看到 Rust “异曲同工”的结果。3. 函数指针与 fn 类型让我们回归到 Rust 语言看一下继承自 C/C 的传统函数指针是怎样工作的。看下面这个示例/// 示例 (1)fnplus_one(num:i32)-i32{num1}fnhandle_vec(v:Veci32,f:implFn(i32)-i32)-Veci32{v.into_iter().map(f).collect()}fnmain(){letnumsvec![1,2,3];letplus_one_fn:fn(i32)-i32plus_one;letresulthandle_vec(nums,plus_one_fn);println!({:?},result);}示例中的plus_one_fn是一个“指针”与 C/C 中的“函数指针”完全一致打印它的值输出的就是一个内存地址。这个plus_one_fn的类型是fn(i32) - i32它以函数参数列表为“骨架”抽取出类型声明并在前面辅以fn关键字修饰在 Rust 中函数指针的类型就是“fn类型”。“fn类型”是 Rust 语言的内置类型Primitive Type并且是一种类型构造器Type Constructor。也就是说它不是一种具体的类型而是一类类型示例中的fn(i32) - i32就是fn类型的一种具体类型。此外Rust 编译器会为fn类型自动实现Fn/FnMut/FnOnce三个 trait 中的一个或多个这就是为什么plus_one_fn能匹配参数f: impl Fn(i32) - i32并传给handle_vec的关键原因。关于Fn(i32) - i32下文会专门讨论。4. 函数项及其类型上述介绍对于熟悉 C/C 函数指针的读者来说还是很容易理解的尽管函数类型使用了“fn类型”这种称谓但整体上是熟悉的语法和机制。比较独特的是出现在第 13 行等号后面的plus_one了它是 Rust 中特有的一个概念叫“函数项”Function Item。“函数项”是编译器为了表示某个具体函数而生成的一种零大小Zero Sized的类型。直白地说只要把一个定义好的函数的“名字”拿出来用于赋值或传参时这个被拿出来的“函数名”就是“函数项”它就代表那个函数本身。函数项和函数指针看上去非常像实际上函数项可以看成是编译期”对一个函数的“引用”而函数指针则是运行期”的函数地址。正因为函数项是编译期”对一个函数的引用所以它才是零大小Zero-Sized的因为函数代码存放在程序内存的 text 段里而函数项在编译期仅仅是一个“名字”。就如同一个变量let a 1变量a只是程序员和编译器约定的一个“符号”栈上不会保存一个内容是a的字符和关联的内存地址变量名a只存在于编译阶段在运行阶段根本没有所谓的“变量名”程序完全靠 “内存地址偏移” 而非 “名字” 来定位栈上的变量函数项也是如此所以它才可以没有大小。函数项的另一个重要问题是函数项的“类型”是什么样的例如示例中的plus_one它是什么类型呢首先它的类型绝不是fn(i32) - i32我们说函数指针和函数项是两种不同的类型所以函数项的类型必不可能是 fn 类型实际上编译器会为每一个函数项自动生成一个匿名类型也就是说每一个函数项都确确实实有一个自己的类型但这个类型是匿名的所以我们写不出也不需要显式地写出来因为在使用函数项的地方已经合适它的“另外的”类型候在那里了。有两种具体的情形一种是当我们把一个“函数项”赋给一个“函数指针”时Rust 编译器会帮我们把函数项类型自动转换为函数指针在我们的示例代码中作为函数项的plus_one之所以能赋给plus_one_fn这个函数指针是因为这里其实发生了一次自动类型转换而另一种更常见的情形是我们也可以把函数项作为参数传给使用 Fn 描述的类型参数。这是因为编译器也为函数项也自动实现了 Fn、FnMut、FnOne。看这个示例/// 示例 (2)fnplus_one(x:i32)-i32{x1}fnhandle_vec(v:Veci32,f:implFn(i32)-i32)-Veci32{v.into_iter().map(f).collect()}fnmain(){letnumsvec![1,2,3];letresulthandle_vec(nums,plus_one);println!({:?},result);}示例 (2) 与示例 (1) 的差别仅在于第 13 行我们没有通过函数指针而是直接把函数项也就是plus_one传给了handle_vec之所以可以这样做就是因为函数项也自动实现了 Fn、FnMut、FnOne准确地说是编译器在为函数项生成它的匿名类型时一并自动实现了 Fn、FnMut、FnOne因此plus_one也能匹配参数f: impl Fn(i32) - i32可以直接传给handle_vec这和上一节函数指针的情况类似。如果不是在运行期需要动态切换执行函数函数项要远比函数指针来得方便仅仅是一个“函数名”就可以作为参数传递了这确实令人极为“舒适”并且函数项还是一个编译期机制具有零成本抽象优势。5. 闭包及其类型关于闭包我们已经在《Rust 语言特性闭包》 一文介绍过了但是并没有介绍闭包的类型这是闭包能作为参数传递给一个函数的关键。还是先看一个示例/// 示例 (3)fnhandle_vec(v:Veci32,f:implFn(i32)-i32)-Veci32{v.into_iter().map(f).collect()}fnmain(){letnumsvec![1,2,3];letresulthandle_vec(nums,|x|x1);// let result hof(nums, |x| x 1 );println!({:?},result);}在示例 (3) 中我们不再使用函数去实现加 1 功能而是直接改用闭包去实现|x| x 1那这个闭包的类型是什么呢是Fn(i32) - i32吗不是闭包的类型也是由编译器自动生成的匿名的且自动实现了 Fn / FnMut / FnOnce 三个 trait 中的一个或多个这就是为什么|x| x 1可以匹配Fn(i32) - i32类型的原因。如此一来函数指针、函数项和闭包都可以匹配 Fn 类型的函数参数三者在使用形式上就可以完全统一了。但要注意的是函数指针、函数项、闭包三者的类型始终是不同的是因为编译器自动为它们实现了 Fn / FnMut / FnOnce 三个 trait 中的一个或多个才使得它们都可以作为 Fn 类型的参数传给高阶函数。6. 函数类型大一统Fn / FnMut / FnOnce最终我们来到了 Fn / FnMut / FnOnce。前面三个章节都提到过一个关键信息函数指针、函数项、闭包三种类型全部由编译器自动为其实现了 Fn / FnMut / FnOnce这使得它们都可以传给由 Fn / FnMut / FnOnce 描述的函数参数从而实现了函数的“指针”、“引用”、“值”等不同指代形式在类型上的“统一”彼此可以无缝替换使用了。为了更形象地说明这一点我们把前面三个示例“打包”到一起在不修改handle_vec函数签名的情况下分别使用函数指针、函数项、闭包三种形式作为实参传给handle_vec全部可以成功运行/// 示例 (4)fnplus_one(num:i32)-i32{num1}fnhandle_vec(v:Veci32,f:implFn(i32)-i32)-Veci32{v.into_iter().map(f).collect()}fnmain(){// 1. 函数指针letplus_one_fn:fn(i32)-i32plus_one;letresult_1handle_vec(vec![1,2,3],plus_one_fn);println!({:?},result_1);// 2. 函数项letresult_2handle_vec(vec![1,2,3],plus_one);println!({:?},result_2);// 3. 闭包letresult_3handle_vec(vec![1,2,3],|x|x1);println!({:?},result_3);}最后我们简单的介绍一下 Fn / FnMut / FnOnce 三者的区别对于普通函数项和函数指针它们都会实现 Fn、FnMut、FnOnce因为普通函数没有所谓的“捕获环境”不存在调用过程修改或消耗自用变量的问题所以在作为参数约束时通常都是使用 Fn。只有闭包由于会捕获环境才需要根据它对捕获环境的使用方式区分 Fn / FnMut / FnOnce它们对应三种情形如果闭包对自己捕获的状态是只读的无论执行多少次结果都一样对于这样的闭包编译器会自动为其实现 Fn如果闭包需要修改自已捕获的状态则编译器会自动为其实现 FnMut比如闭包捕获了一个mut变量并在闭包内修改了它的值如果闭包需要消耗掉自已捕获的状态则编译器会自动为其实现 FnOnce意味者闭包只能执行一次比如闭包捕获了一个 String 值并在闭包内将它消耗了则这个闭包只能执行这一次。以上归纳起来可以表述为如下的关系可调用对象 │ ┌────────────┼────────────┐ │ │ │ ▼ ▼ ▼ 函数项 函数指针 闭包 │ │ │ │ │ │ ▼ ▼ ▼ Fn Fn 根据捕获方式是否捕获了可变引用 FnMut FnMut 或消耗了自由变量的值来决定使用 FnOnce FnOnce Fn / FnMut / FnOnce还有一点以Fn(i32) - i32这种书写形式就可以看出Fn 绝不是一个普通的、完全编写代码就能定义出来的 traitRust 显然对这种 trait 做了特殊的支持特别声明本博客长期遭受 51CTO 无休止的盗文、剽窃再次声讨其可耻行径文章多处已嵌入此声明盗用本文即传播了这份声明7. 重新思考Rust 如何实现了函数“类型”和“值”的分离重新回顾一下前面几个章节的核心内容函数指针是指向函数的“指针”其“类型”是fn类型同时编译器自动为其实现了Fn/FnMut/FnOnce函数项可以视作是编译期对函数的“引用”它的“类型”是由编译器自动生成的且是匿名的没有类型名同时编译器也自动为它实现了Fn / FnMut / FnOnce闭包没有指针没有函数项其自身可以视作自身的“值”它的“类型”也是由编译器自动生成的且是匿名的没有类型名同时编译器也自动为它实现了Fn/FnMut/FnOnce三个 trait 中的一个或多个结合前文的示例来梳理的话是这样的plus_one_fn │ └── 函数指针 │ └── 类型fn(i32) - i32 │ └── 其类型实现 Fn(i32) - i32 plus_one │ └── 函数项 │ └── 由编译器生成匿名类型 │ ├── 可以转换成 fn(i32) - i32 │ └── 其类型实现 Fn(i32) - i32 |x| x 1 │ └── 闭包 │ └── 由编译器生成匿名类型 │ └── 其类型实现 Fn(i32) - i32在 Rust 中函数并没有在语言层面上实现“类型”和“值”的分离函数的函数指针和函数项可以视作是对函数“值”函数本身的指针与引用而闭包的“值”是闭包自身在类型方面函数指针、函数项和闭包都有自己的类型但它们的类型又都由编译器自动为它们实现了Fn/FnMut/FnOnce三个 trait 中的一个或多个使得三者在类型上得到了“统一”Rust 是通过这样的语言机制布局实现了实际意义上的函数“类型”和“值”的分离为传递函数型参数铺平了道路。参考资料https://www.yijiecc.com/public/spec/49https://www.yijiecc.com/public/spec/47https://juejin.cn/post/6991378217701474334https://doc.rust-lang.org/reference/types/function-item.htmlhttps://doc.rust-lang.org/std/primitive.fn.htmlhttps://doc.rust-lang.org/reference/types/function-pointer.html可能最接近的一种形式是let plus_one: dyn Fn(i32) - i32 |x| x 1但对于 Rust 来说这是一种很扭曲的函数书写形式关键 Rust 并不是用我们所说的函数式的风格来处理这段代码的这也是为什么说它是“扭曲”的原因。 ↩︎

相关新闻

MATLAB要凉了?TIOBE CEO直言“它的时代正在倒计时”,最新8月榜单发布

MATLAB要凉了?TIOBE CEO直言“它的时代正在倒计时”,最新8月榜单发布

整理 | 屠敏出品 | CSDN(ID:CSDNnews)编程语言世界正在经历一轮缓慢变化。近日,一月一更的 TIOBE 最新发布了 8 月编程语言榜单,这个月又发生了怎样的改变?我们不妨一起看看。MATLAB 十余年后跌出前二十整体…

2026/8/12 17:05:39 阅读更多 →
从心形线到动态爱心:C++、Python与HTML/JS多语言实现详解

从心形线到动态爱心:C++、Python与HTML/JS多语言实现详解

1. 项目概述:从“李峋同款”到编程入门的浪漫实践最近在社交媒体和编程社区里,“爱心代码”这个话题又火了起来,尤其是和“李峋”这个名字绑定的各种版本。很多刚入门编程的朋友,或者想用一点小创意表达心意的同学,都在…

2026/8/12 17:05:39 阅读更多 →
Chrome插件开发实战:提升开发者效率的必备工具与工作流

Chrome插件开发实战:提升开发者效率的必备工具与工作流

在日常开发和学习中,浏览器是我们最核心的工具之一。然而,原生的谷歌浏览器虽然强大,但在效率提升、信息管理、代码辅助等方面,仍有巨大的优化空间。你是否曾为反复查找某个API文档而烦恼?是否觉得在多个标签页间切换效…

2026/8/12 17:05:39 阅读更多 →

最新新闻

北京会场|8-9月学术会议征稿通知

北京会场|8-9月学术会议征稿通知

8-9月,北京会场会议主要覆盖 计算机技术、生物医学、机械工程、航空航天、材料应用、人工智能、神经网络、自然语言处理、电子信息工程、大数据、社会科学、计算社会科学 等方向。对准备投稿、参会或进行学术交流的老师和同学来说,可以结合研究方向、截稿…

2026/8/12 17:54:12 阅读更多 →
【建模渲染】3ds Max 2027 安装教程

【建模渲染】3ds Max 2027 安装教程

软件介绍3D Studio Max简称【3ds Max】,是Discreet公司开发的(后被Autodesk公司合并)基于PC系统的三维动画渲染和制作软件,前身是基于DOS操作系统的3D Studio系列软件,目前最新版本是3ds Max 2027,广泛应用…

2026/8/12 17:54:12 阅读更多 →
笔记 21-6 :彭老师 14章,系统移植,Uboot变量

笔记 21-6 :彭老师 14章,系统移植,Uboot变量

(178)彻底理解 uboot 的引导启动,还需要学很多知识。 以上是 boot 2017,以下是 bootcmd 变量里的内容( copy 各种镜像以及启动内核的命令)。 if mmc rescan; then echo SD/MMC found on device ${mmcdev};i…

2026/8/12 17:54:12 阅读更多 →
计算机毕业设计最新题目帮助

计算机毕业设计最新题目帮助

1 引言 毕业设计是大家学习生涯的最重要的里程碑,它不仅是对四年所学知识的综合运用,更是展示个人技术能力和创新思维的重要过程。选择一个合适的毕业设计题目至关重要,它应该既能体现你的专业能力,又能满足实际应用需求&#xf…

2026/8/12 17:54:12 阅读更多 →
成品网站建设哪家好?2024年避坑指南:别再花冤枉钱买“垃圾”模板了

成品网站建设哪家好?2024年避坑指南:别再花冤枉钱买“垃圾”模板了

做企业的都知道,网站就像是你在互联网上的第二张名片,甚至第一张。以前咱们做生意,靠的是口口相传,只要酒香不怕巷子深,客户自然会找上门。但现在不一样了,大家买东西前第一件事就是上网搜一搜。如果你连个像样的网页都打不开,或者打开速度比龟爬还慢,客户的第一反应通…

2026/8/12 17:54:12 阅读更多 →
华强北AirPods Pro选购指南:芯片方案、主板与调校全解析

华强北AirPods Pro选购指南:芯片方案、主板与调校全解析

1. 华强北耳机选购迷局:从“小白”到“行家”的避坑指南 每次路过华强北的档口,或者打开电商平台,看到琳琅满目、价格从几十到几百不等的“华强北三代”耳机,你是不是都一头雾水?什么“洛达1562AE”、“慧联A6Pro”、“…

2026/8/12 17:53:12 阅读更多 →

日新闻

Ubuntu 22.04安装与使用tree命令:高效管理Linux目录结构

Ubuntu 22.04安装与使用tree命令:高效管理Linux目录结构

1. 为什么需要一个“目录树”工具?在Linux世界里,尤其是Ubuntu这样的发行版,命令行是很多人的主战场。我们每天都要和文件、目录打交道。ls命令是查看目录内容的首选,它简洁、高效,能列出文件名、权限、大小等关键信息…

2026/8/12 9:33:34 阅读更多 →
博思AI智能体:意图识别、思考链与性能优化的工程实践

博思AI智能体:意图识别、思考链与性能优化的工程实践

在AI应用从“能用”走向“好用”的进程中,系统的响应速度、决策透明度与高并发稳定性是决定用户体验的关键。博思AI智能体近期完成了一次重要的专项优化,聚焦于意图识别、思考链展示与全链路压测三大核心领域,将系统从功能实现推向了工程卓越…

2026/8/12 9:33:34 阅读更多 →
子代理架构:AI智能体任务分解与协同执行的核心原理与实践

子代理架构:AI智能体任务分解与协同执行的核心原理与实践

1. 项目概述:为什么我们需要“子代理”?最近在折腾各种AI应用和自动化流程时,我越来越频繁地遇到一个瓶颈:单个AI智能体(Agent)的能力边界。无论是处理复杂的多步骤任务,还是需要同时调用多个专…

2026/8/12 9:33:34 阅读更多 →

周新闻

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁

5分钟告别提取码焦虑:baidupankey如何智能破解百度网盘资源锁 【免费下载链接】baidupankey 在线查询网盘提取码(维护中 rm repo) 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 你是否曾经在深夜寻找一份重要资料&#x…

2026/8/12 1:11:09 阅读更多 →
如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/12 1:11:09 阅读更多 →
收藏!小白程序员轻松入门大模型,从Harness工程开始实践

收藏!小白程序员轻松入门大模型,从Harness工程开始实践

文章强调学习大模型不应只关注模型本身,而应重视模型外的系统搭建,即Harness。提出AgentModelHarness的实用公式,详细介绍Harness的四个层次:持久化层、执行层、控制层和观察与验证层。文章还探讨了上下文工程、工具设计、AGENTS.…

2026/8/12 1:11:08 阅读更多 →

月新闻

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

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

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

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

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

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

2026/8/12 1:11:10 阅读更多 →
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/11 17:09:45 阅读更多 →